app is working again. new structure for blocks. new useWaveObjectValueWithSuspense hook

This commit is contained in:
sawka 2024-05-27 15:44:57 -07:00
parent abedca2364
commit e6d7a4e674
14 changed files with 193 additions and 144 deletions

View File

@ -3,8 +3,7 @@
import * as React from "react"; import * as React from "react";
import * as jotai from "jotai"; import * as jotai from "jotai";
import { atoms, blockDataMap } from "@/store/global"; import * as WOS from "@/store/wos";
import { TerminalView } from "@/app/view/term"; import { TerminalView } from "@/app/view/term";
import { PreviewView } from "@/app/view/preview"; import { PreviewView } from "@/app/view/preview";
import { PlotView } from "@/app/view/plotview"; import { PlotView } from "@/app/view/plotview";
@ -33,9 +32,10 @@ const Block = ({ tabId, blockId }: { tabId: string; blockId: string }) => {
}, [blockRef.current]); }, [blockRef.current]);
let blockElem: JSX.Element = null; let blockElem: JSX.Element = null;
const blockAtom = blockDataMap.get(blockId); const [blockData, blockDataLoading] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", blockId));
const blockData = jotai.useAtomValue(blockAtom); if (blockDataLoading) {
if (blockData.view === "term") { blockElem = <CenteredDiv>Loading...</CenteredDiv>;
} else if (blockData.view === "term") {
blockElem = <TerminalView blockId={blockId} />; blockElem = <TerminalView blockId={blockId} />;
} else if (blockData.view === "preview") { } else if (blockData.view === "preview") {
blockElem = <PreviewView blockId={blockId} />; blockElem = <PreviewView blockId={blockId} />;

View File

@ -4,13 +4,9 @@
import * as jotai from "jotai"; import * as jotai from "jotai";
import * as rxjs from "rxjs"; import * as rxjs from "rxjs";
import { Events } from "@wailsio/runtime"; import { Events } from "@wailsio/runtime";
import { produce } from "immer";
import { BlockService } from "@/bindings/blockservice";
import * as wstore from "@/gopkg/wstore";
import * as WOS from "./wos"; import * as WOS from "./wos";
const globalStore = jotai.createStore(); const globalStore = jotai.createStore();
const blockDataMap = new Map<string, jotai.Atom<wstore.Block>>();
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
const globalWindowId = urlParams.get("windowid"); const globalWindowId = urlParams.get("windowid");
const globalClientId = urlParams.get("clientid"); const globalClientId = urlParams.get("clientid");
@ -19,8 +15,10 @@ const clientIdAtom = jotai.atom(null) as jotai.PrimitiveAtom<string>;
globalStore.set(windowIdAtom, globalWindowId); globalStore.set(windowIdAtom, globalWindowId);
globalStore.set(clientIdAtom, globalClientId); globalStore.set(clientIdAtom, globalClientId);
const uiContextAtom = jotai.atom((get) => { const uiContextAtom = jotai.atom((get) => {
const windowData = get(windowDataAtom);
const uiContext: UIContext = { const uiContext: UIContext = {
windowid: get(atoms.windowId), windowid: get(atoms.windowId),
activetabid: windowData.activetabid,
}; };
return uiContext; return uiContext;
}) as jotai.Atom<UIContext>; }) as jotai.Atom<UIContext>;
@ -54,7 +52,6 @@ const atoms = {
client: clientAtom, client: clientAtom,
waveWindow: windowDataAtom, waveWindow: windowDataAtom,
workspace: workspaceAtom, workspace: workspaceAtom,
blockDataMap: blockDataMap,
}; };
type SubjectWithRef<T> = rxjs.Subject<T> & { refCount: number; release: () => void }; type SubjectWithRef<T> = rxjs.Subject<T> & { refCount: number; release: () => void };
@ -93,6 +90,8 @@ Events.On("block:ptydata", (event: any) => {
subject.next(data); subject.next(data);
}); });
const blockAtomCache = new Map<string, Map<string, jotai.Atom<any>>>();
function useBlockAtom<T>(blockId: string, name: string, makeFn: () => jotai.Atom<T>): jotai.Atom<T> { function useBlockAtom<T>(blockId: string, name: string, makeFn: () => jotai.Atom<T>): jotai.Atom<T> {
let blockCache = blockAtomCache.get(blockId); let blockCache = blockAtomCache.get(blockId);
if (blockCache == null) { if (blockCache == null) {
@ -103,8 +102,9 @@ function useBlockAtom<T>(blockId: string, name: string, makeFn: () => jotai.Atom
if (atom == null) { if (atom == null) {
atom = makeFn(); atom = makeFn();
blockCache.set(name, atom); blockCache.set(name, atom);
console.log("New BlockAtom", blockId, name);
} }
return atom as jotai.Atom<T>; return atom as jotai.Atom<T>;
} }
export { globalStore, atoms, getBlockSubject, blockDataMap, useBlockAtom, WOS }; export { globalStore, atoms, getBlockSubject, useBlockAtom, WOS };

View File

@ -94,7 +94,7 @@ function createWaveValueObject<T extends WaveObj>(oref: string, shouldFetch: boo
} }
wov.pendingPromise = null; wov.pendingPromise = null;
globalStore.set(wov.dataAtom, { value: val, loading: false }); globalStore.set(wov.dataAtom, { value: val, loading: false });
console.log("GetObject resolved", oref, Date.now() - startTs + "ms"); console.log("WaveObj resolved", oref, Date.now() - startTs + "ms");
}); });
return wov; return wov;
} }
@ -113,6 +113,25 @@ function loadAndPinWaveObject<T>(oref: string): Promise<T> {
return wov.pendingPromise; return wov.pendingPromise;
} }
function useWaveObjectValueWithSuspense<T>(oref: string): T {
let wov = waveObjectValueCache.get(oref);
if (wov == null) {
wov = createWaveValueObject(oref, true);
waveObjectValueCache.set(oref, wov);
}
React.useEffect(() => {
wov.refCount++;
return () => {
wov.refCount--;
};
}, [oref]);
const dataValue = jotai.useAtomValue(wov.dataAtom);
if (dataValue.loading) {
throw wov.pendingPromise;
}
return dataValue.value;
}
function useWaveObjectValue<T>(oref: string): [T, boolean] { function useWaveObjectValue<T>(oref: string): [T, boolean] {
let wov = waveObjectValueCache.get(oref); let wov = waveObjectValueCache.get(oref);
if (wov == null) { if (wov == null) {
@ -214,7 +233,6 @@ function wrapObjectServiceCall<T>(fnName: string, ...args: any[]): Promise<T> {
); );
prtn = prtn.then((val) => { prtn = prtn.then((val) => {
if (val.updates) { if (val.updates) {
console.log(val.updates);
updateWaveObjects(val.updates); updateWaveObjects(val.updates);
} }
return val; return val;
@ -222,14 +240,6 @@ function wrapObjectServiceCall<T>(fnName: string, ...args: any[]): Promise<T> {
return prtn; return prtn;
} }
function AddTabToWorkspace(tabName: string, activateTab: boolean): Promise<{ tabId: string }> {
return wrapObjectServiceCall("AddTabToWorkspace", tabName, activateTab);
}
function SetActiveTab(tabId: string): Promise<void> {
return wrapObjectServiceCall("SetActiveTab", tabId);
}
function getStaticObjectValue<T>(oref: string, getFn: jotai.Getter): T { function getStaticObjectValue<T>(oref: string, getFn: jotai.Getter): T {
let wov = waveObjectValueCache.get(oref); let wov = waveObjectValueCache.get(oref);
if (wov == null) { if (wov == null) {
@ -239,10 +249,23 @@ function getStaticObjectValue<T>(oref: string, getFn: jotai.Getter): T {
return atomVal.value; return atomVal.value;
} }
function AddTabToWorkspace(tabName: string, activateTab: boolean): Promise<{ tabId: string }> {
return wrapObjectServiceCall("AddTabToWorkspace", tabName, activateTab);
}
function SetActiveTab(tabId: string): Promise<void> {
return wrapObjectServiceCall("SetActiveTab", tabId);
}
function CreateBlock(blockDef: BlockDef, rtOpts: RuntimeOpts): Promise<{ blockId: string }> {
return wrapObjectServiceCall("CreateBlock", blockDef, rtOpts);
}
export { export {
makeORef, makeORef,
useWaveObject, useWaveObject,
useWaveObjectValue, useWaveObjectValue,
useWaveObjectValueWithSuspense,
loadAndPinWaveObject, loadAndPinWaveObject,
clearWaveObjectCache, clearWaveObjectCache,
updateWaveObject, updateWaveObject,
@ -251,4 +274,5 @@ export {
getStaticObjectValue, getStaticObjectValue,
AddTabToWorkspace, AddTabToWorkspace,
SetActiveTab, SetActiveTab,
CreateBlock,
}; };

View File

@ -23,7 +23,7 @@ const TabContent = ({ tabId }: { tabId: string }) => {
{tabData.blockids.map((blockId: string) => { {tabData.blockids.map((blockId: string) => {
return ( return (
<div key={blockId} className="block-container"> <div key={blockId} className="block-container">
<Block tabId={tabId} blockId={blockId} /> <Block key={blockId} tabId={tabId} blockId={blockId} />
</div> </div>
); );
})} })}

View File

@ -3,15 +3,16 @@
import * as React from "react"; import * as React from "react";
import * as jotai from "jotai"; import * as jotai from "jotai";
import { atoms, blockDataMap, useBlockAtom } from "@/store/global"; import { atoms, useBlockAtom } from "@/store/global";
import { Markdown } from "@/element/markdown"; import { Markdown } from "@/element/markdown";
import { FileService, FileInfo, FullFile } from "@/bindings/fileservice"; import { FileService, FileInfo, FullFile } from "@/bindings/fileservice";
import * as util from "@/util/util"; import * as util from "@/util/util";
import { CenteredDiv } from "../element/quickelems"; import { CenteredDiv } from "../element/quickelems";
import { DirectoryTable } from "@/element/directorytable"; import { DirectoryTable } from "@/element/directorytable";
import * as wstore from "@/gopkg/wstore"; import * as WOS from "@/store/wos";
import "./view.less"; import "./view.less";
import { first } from "rxjs";
const MaxFileSize = 1024 * 1024 * 10; // 10MB const MaxFileSize = 1024 * 1024 * 10; // 10MB
@ -62,10 +63,17 @@ function DirectoryPreview({ contentAtom }: { contentAtom: jotai.Atom<Promise<str
} }
function PreviewView({ blockId }: { blockId: string }) { function PreviewView({ blockId }: { blockId: string }) {
const blockDataAtom: jotai.Atom<wstore.Block> = blockDataMap.get(blockId); const blockData = WOS.useWaveObjectValueWithSuspense<Block>(WOS.makeORef("block", blockId));
if (blockData == null) {
return (
<div className="view-preview">
<CenteredDiv>Block Not Found</CenteredDiv>
</div>
);
}
const fileNameAtom = useBlockAtom(blockId, "preview:filename", () => const fileNameAtom = useBlockAtom(blockId, "preview:filename", () =>
jotai.atom<string>((get) => { jotai.atom<string>((get) => {
return get(blockDataAtom)?.meta?.file; return blockData?.meta?.file;
}) })
); );
const statFileAtom = useBlockAtom(blockId, "preview:statfile", () => const statFileAtom = useBlockAtom(blockId, "preview:statfile", () =>

View File

@ -5,13 +5,7 @@ import * as React from "react";
import * as jotai from "jotai"; import * as jotai from "jotai";
import { TabContent } from "@/app/tab/tab"; import { TabContent } from "@/app/tab/tab";
import { clsx } from "clsx"; import { clsx } from "clsx";
import { atoms, blockDataMap } from "@/store/global"; import { atoms } from "@/store/global";
import { v4 as uuidv4 } from "uuid";
import { BlockService } from "@/bindings/blockservice";
import { ClientService } from "@/bindings/clientservice";
import { Workspace } from "@/gopkg/wstore";
import * as wstore from "@/gopkg/wstore";
import * as jotaiUtil from "jotai/utils";
import * as WOS from "@/store/wos"; import * as WOS from "@/store/wos";
import { CenteredLoadingDiv, CenteredDiv } from "../element/quickelems"; import { CenteredLoadingDiv, CenteredDiv } from "../element/quickelems";
@ -36,7 +30,7 @@ function Tab({ tabId }: { tabId: string }) {
); );
} }
function TabBar({ workspace, waveWindow }: { workspace: Workspace; waveWindow: WaveWindow }) { function TabBar({ workspace }: { workspace: Workspace }) {
function handleAddTab() { function handleAddTab() {
const newTabName = `Tab-${workspace.tabids.length + 1}`; const newTabName = `Tab-${workspace.tabids.length + 1}`;
WOS.AddTabToWorkspace(newTabName, true); WOS.AddTabToWorkspace(newTabName, true);
@ -58,34 +52,31 @@ function Widgets() {
const windowData = jotai.useAtomValue(atoms.waveWindow); const windowData = jotai.useAtomValue(atoms.waveWindow);
const activeTabId = windowData.activetabid; const activeTabId = windowData.activetabid;
async function createBlock(blockDef: wstore.BlockDef) { async function createBlock(blockDef: BlockDef) {
const rtOpts: wstore.RuntimeOpts = new wstore.RuntimeOpts({ termsize: { rows: 25, cols: 80 } }); const rtOpts: RuntimeOpts = { termsize: { rows: 25, cols: 80 } };
const rtnBlock: wstore.Block = await BlockService.CreateBlock(blockDef, rtOpts); await WOS.CreateBlock(blockDef, rtOpts);
const newBlockAtom = jotai.atom(rtnBlock);
blockDataMap.set(rtnBlock.blockid, newBlockAtom);
addBlockIdToTab(activeTabId, rtnBlock.blockid);
} }
async function clickTerminal() { async function clickTerminal() {
const termBlockDef = new wstore.BlockDef({ const termBlockDef = {
controller: "shell", controller: "shell",
view: "term", view: "term",
}); };
createBlock(termBlockDef); createBlock(termBlockDef);
} }
async function clickPreview(fileName: string) { async function clickPreview(fileName: string) {
const markdownDef = new wstore.BlockDef({ const markdownDef = {
view: "preview", view: "preview",
meta: { file: fileName }, meta: { file: fileName },
}); };
createBlock(markdownDef); createBlock(markdownDef);
} }
async function clickPlot() { async function clickPlot() {
const plotDef = new wstore.BlockDef({ const plotDef: BlockDef = {
view: "plot", view: "plot",
}); };
createBlock(plotDef); createBlock(plotDef);
} }
@ -122,7 +113,7 @@ function WorkspaceElem() {
const ws = jotai.useAtomValue(atoms.workspace); const ws = jotai.useAtomValue(atoms.workspace);
return ( return (
<div className="workspace"> <div className="workspace">
<TabBar workspace={ws} waveWindow={windowData} /> <TabBar workspace={ws} />
<div className="workspace-tabcontent"> <div className="workspace-tabcontent">
<TabContent key={windowData.workspaceid} tabId={activeTabId} /> <TabContent key={windowData.workspaceid} tabId={activeTabId} />
<Widgets /> <Widgets />

View File

@ -4,6 +4,7 @@
declare global { declare global {
type UIContext = { type UIContext = {
windowid: string; windowid: string;
activetabid: string;
}; };
type ORef = { type ORef = {
@ -33,7 +34,7 @@ declare global {
}; };
type BlockDef = { type BlockDef = {
controller: string; controller?: string;
view?: string; view?: string;
files?: { [key: string]: FileDef }; files?: { [key: string]: FileDef };
meta?: { [key: string]: any }; meta?: { [key: string]: any };

View File

@ -18,20 +18,12 @@ const urlParams = new URLSearchParams(window.location.search);
const windowId = urlParams.get("windowid"); const windowId = urlParams.get("windowid");
const clientId = urlParams.get("clientid"); const clientId = urlParams.get("clientid");
wstore.Block.prototype[immerable] = true;
wstore.Tab.prototype[immerable] = true;
wstore.Client.prototype[immerable] = true;
wstore.Window.prototype[immerable] = true;
wstore.Workspace.prototype[immerable] = true;
wstore.BlockDef.prototype[immerable] = true;
wstore.RuntimeOpts.prototype[immerable] = true;
wstore.FileDef.prototype[immerable] = true;
wstore.Point.prototype[immerable] = true;
wstore.WinSize.prototype[immerable] = true;
loadFonts(); loadFonts();
console.log("Wave Starting");
document.addEventListener("DOMContentLoaded", async () => { document.addEventListener("DOMContentLoaded", async () => {
console.log("DOMContentLoaded");
// ensures client/window are loaded into the cache before rendering // ensures client/window are loaded into the cache before rendering
await WOS.loadAndPinWaveObject<Client>(WOS.makeORef("client", clientId)); await WOS.loadAndPinWaveObject<Client>(WOS.makeORef("client", clientId));
const waveWindow = await WOS.loadAndPinWaveObject<WaveWindow>(WOS.makeORef("window", windowId)); const waveWindow = await WOS.loadAndPinWaveObject<WaveWindow>(WOS.makeORef("window", windowId));
@ -40,6 +32,7 @@ document.addEventListener("DOMContentLoaded", async () => {
let elem = document.getElementById("main"); let elem = document.getElementById("main");
let root = createRoot(elem); let root = createRoot(elem);
document.fonts.ready.then(() => { document.fonts.ready.then(() => {
console.log("Wave First Render");
root.render(reactElem); root.render(reactElem);
}); });
}); });

View File

@ -72,7 +72,7 @@ func createWindow(windowData *wstore.Window, app *application.App) {
Width: windowData.WinSize.Width, Width: windowData.WinSize.Width,
Height: windowData.WinSize.Height, Height: windowData.WinSize.Height,
}) })
eventbus.RegisterWailsWindow(window) eventbus.RegisterWailsWindow(window, windowData.OID)
window.On(events.Common.WindowClosing, func(event *application.WindowEvent) { window.On(events.Common.WindowClosing, func(event *application.WindowEvent) {
eventbus.UnregisterWailsWindow(window.ID()) eventbus.UnregisterWailsWindow(window.ID())
}) })

View File

@ -14,7 +14,6 @@ import (
"time" "time"
"github.com/creack/pty" "github.com/creack/pty"
"github.com/google/uuid"
"github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/application"
"github.com/wavetermdev/thenextwave/pkg/eventbus" "github.com/wavetermdev/thenextwave/pkg/eventbus"
"github.com/wavetermdev/thenextwave/pkg/shellexec" "github.com/wavetermdev/thenextwave/pkg/shellexec"
@ -61,41 +60,6 @@ func jsonDeepCopy(val map[string]any) (map[string]any, error) {
return rtn, nil return rtn, nil
} }
func CreateBlock(ctx context.Context, bdef *wstore.BlockDef, rtOpts *wstore.RuntimeOpts) (*wstore.Block, error) {
// TODO
blockId := uuid.New().String()
blockData := &wstore.Block{
OID: blockId,
BlockDef: bdef,
Controller: bdef.Controller,
View: bdef.View,
RuntimeOpts: rtOpts,
}
var err error
blockData.Meta, err = jsonDeepCopy(bdef.Meta)
if err != nil {
return nil, fmt.Errorf("error copying meta: %w", err)
}
err = wstore.DBInsert(ctx, blockData)
if err != nil {
return nil, fmt.Errorf("error inserting block: %w", err)
}
if blockData.Controller != "" {
StartBlockController(blockId, blockData)
}
return blockData, nil
}
func CloseBlock(blockId string) {
// TODO
bc := GetBlockController(blockId)
if bc == nil {
return
}
bc.Close()
close(bc.InputCh)
}
func (bc *BlockController) setShellProc(shellProc *shellexec.ShellProc) error { func (bc *BlockController) setShellProc(shellProc *shellexec.ShellProc) error {
bc.Lock.Lock() bc.Lock.Lock()
defer bc.Lock.Unlock() defer bc.Lock.Unlock()
@ -232,15 +196,23 @@ func (bc *BlockController) Run(bdata *wstore.Block) {
} }
} }
func StartBlockController(blockId string, bdata *wstore.Block) { func StartBlockController(ctx context.Context, blockId string) error {
if bdata.Controller != BlockController_Shell { blockData, err := wstore.DBMustGet[*wstore.Block](ctx, blockId)
log.Printf("unknown controller %q\n", bdata.Controller) if err != nil {
return return fmt.Errorf("error getting block: %w", err)
}
if blockData.Controller == "" {
// nothing to start
return nil
}
if blockData.Controller != BlockController_Shell {
return fmt.Errorf("unknown controller %q", blockData.Controller)
} }
globalLock.Lock() globalLock.Lock()
defer globalLock.Unlock() defer globalLock.Unlock()
if _, ok := blockControllerMap[blockId]; ok { if _, ok := blockControllerMap[blockId]; ok {
return // already running
return nil
} }
bc := &BlockController{ bc := &BlockController{
Lock: &sync.Mutex{}, Lock: &sync.Mutex{},
@ -249,7 +221,17 @@ func StartBlockController(blockId string, bdata *wstore.Block) {
InputCh: make(chan BlockCommand), InputCh: make(chan BlockCommand),
} }
blockControllerMap[blockId] = bc blockControllerMap[blockId] = bc
go bc.Run(bdata) go bc.Run(blockData)
return nil
}
func StopBlockController(blockId string) {
bc := GetBlockController(blockId)
if bc == nil {
return
}
bc.Close()
close(bc.InputCh)
} }
func GetBlockController(blockId string) *BlockController { func GetBlockController(blockId string) *BlockController {

View File

@ -5,11 +5,13 @@ package eventbus
import ( import (
"errors" "errors"
"fmt"
"log" "log"
"runtime/debug" "runtime/debug"
"sync" "sync"
"github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/application"
"github.com/wavetermdev/thenextwave/pkg/waveobj"
) )
const EventBufferSize = 50 const EventBufferSize = 50
@ -24,9 +26,16 @@ type WindowEvent struct {
Event application.WailsEvent Event application.WailsEvent
} }
type WindowWatchData struct {
Window *application.WebviewWindow
WaveWindowId string
WailsWindowId uint
WatchedORefs map[waveobj.ORef]bool
}
var globalLock = &sync.Mutex{} var globalLock = &sync.Mutex{}
var wailsApp *application.App var wailsApp *application.App
var wailsWindowMap = make(map[uint]*application.WebviewWindow) var wailsWindowMap = make(map[uint]*WindowWatchData)
func Start() { func Start() {
go processEvents() go processEvents()
@ -42,10 +51,18 @@ func RegisterWailsApp(app *application.App) {
wailsApp = app wailsApp = app
} }
func RegisterWailsWindow(window *application.WebviewWindow) { func RegisterWailsWindow(window *application.WebviewWindow, windowId string) {
globalLock.Lock() globalLock.Lock()
defer globalLock.Unlock() defer globalLock.Unlock()
wailsWindowMap[window.ID()] = window if _, found := wailsWindowMap[window.ID()]; found {
panic(fmt.Errorf("wails window already registered with eventbus: %d", window.ID()))
}
wailsWindowMap[window.ID()] = &WindowWatchData{
Window: window,
WailsWindowId: window.ID(),
WaveWindowId: "",
WatchedORefs: make(map[waveobj.ORef]bool),
}
} }
func UnregisterWailsWindow(windowId uint) { func UnregisterWailsWindow(windowId uint) {
@ -56,18 +73,18 @@ func UnregisterWailsWindow(windowId uint) {
func emitEventToWindow(event WindowEvent) { func emitEventToWindow(event WindowEvent) {
globalLock.Lock() globalLock.Lock()
window := wailsWindowMap[event.WindowId] wdata := wailsWindowMap[event.WindowId]
globalLock.Unlock() globalLock.Unlock()
if window != nil { if wdata != nil {
window.DispatchWailsEvent(&event.Event) wdata.Window.DispatchWailsEvent(&event.Event)
} }
} }
func emitEventToAllWindows(event *application.WailsEvent) { func emitEventToAllWindows(event *application.WailsEvent) {
globalLock.Lock() globalLock.Lock()
wins := make([]*application.WebviewWindow, 0, len(wailsWindowMap)) wins := make([]*application.WebviewWindow, 0, len(wailsWindowMap))
for _, window := range wailsWindowMap { for _, wdata := range wailsWindowMap {
wins = append(wins, window) wins = append(wins, wdata.Window)
} }
globalLock.Unlock() globalLock.Unlock()
for _, window := range wins { for _, window := range wins {
@ -79,6 +96,25 @@ func SendEvent(event application.WailsEvent) {
EventCh <- event EventCh <- event
} }
func findWindowIdsByORef(oref waveobj.ORef) []uint {
globalLock.Lock()
defer globalLock.Unlock()
var ids []uint
for _, wdata := range wailsWindowMap {
if wdata.WatchedORefs[oref] {
ids = append(ids, wdata.WailsWindowId)
}
}
return ids
}
func SendORefEvent(oref waveobj.ORef, event application.WailsEvent) {
wins := findWindowIdsByORef(oref)
for _, windowId := range wins {
SendWindowEvent(windowId, event)
}
}
func SendEventNonBlocking(event application.WailsEvent) error { func SendEventNonBlocking(event application.WailsEvent) error {
select { select {
case EventCh <- event: case EventCh <- event:

View File

@ -4,49 +4,17 @@
package blockservice package blockservice
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
"time" "time"
"github.com/wavetermdev/thenextwave/pkg/blockcontroller" "github.com/wavetermdev/thenextwave/pkg/blockcontroller"
"github.com/wavetermdev/thenextwave/pkg/wstore"
) )
type BlockService struct{} type BlockService struct{}
const DefaultTimeout = 2 * time.Second const DefaultTimeout = 2 * time.Second
func (bs *BlockService) CreateBlock(bdef *wstore.BlockDef, rtOpts *wstore.RuntimeOpts) (*wstore.Block, error) {
ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)
defer cancelFn()
if bdef == nil {
return nil, fmt.Errorf("block definition is nil")
}
if rtOpts == nil {
return nil, fmt.Errorf("runtime options is nil")
}
blockData, err := blockcontroller.CreateBlock(ctx, bdef, rtOpts)
if err != nil {
return nil, fmt.Errorf("error creating block: %w", err)
}
return blockData, nil
}
func (bs *BlockService) CloseBlock(blockId string) {
blockcontroller.CloseBlock(blockId)
}
func (bs *BlockService) GetBlockData(blockId string) (*wstore.Block, error) {
ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)
defer cancelFn()
blockData, err := wstore.DBGet[*wstore.Block](ctx, blockId)
if err != nil {
return nil, fmt.Errorf("error getting block data: %w", err)
}
return blockData, nil
}
func (bs *BlockService) SendCommand(blockId string, cmdMap map[string]any) error { func (bs *BlockService) SendCommand(blockId string, cmdMap map[string]any) error {
cmd, err := blockcontroller.ParseCmdMap(cmdMap) cmd, err := blockcontroller.ParseCmdMap(cmdMap)
if err != nil { if err != nil {

View File

@ -10,6 +10,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/wavetermdev/thenextwave/pkg/blockcontroller"
"github.com/wavetermdev/thenextwave/pkg/waveobj" "github.com/wavetermdev/thenextwave/pkg/waveobj"
"github.com/wavetermdev/thenextwave/pkg/wstore" "github.com/wavetermdev/thenextwave/pkg/wstore"
) )
@ -109,3 +110,25 @@ func (svc *ObjectService) SetActiveTab(uiContext wstore.UIContext, tabId string)
} }
return updatesRtn(ctx, nil) return updatesRtn(ctx, nil)
} }
func (svc *ObjectService) CreateBlock(uiContext wstore.UIContext, blockDef *wstore.BlockDef, rtOpts *wstore.RuntimeOpts) (any, error) {
if uiContext.ActiveTabId == "" {
return nil, fmt.Errorf("no active tab")
}
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancelFn()
ctx = wstore.ContextWithUpdates(ctx)
blockData, err := wstore.CreateBlock(ctx, uiContext.ActiveTabId, blockDef, rtOpts)
if err != nil {
return nil, fmt.Errorf("error creating block: %w", err)
}
if blockData.Controller != "" {
err = blockcontroller.StartBlockController(ctx, blockData.OID)
if err != nil {
return nil, fmt.Errorf("error starting block controller: %w", err)
}
}
rtn := make(map[string]any)
rtn["blockid"] = blockData.OID
return updatesRtn(ctx, rtn)
}

View File

@ -169,6 +169,7 @@ func (update WaveObjUpdate) MarshalJSON() ([]byte, error) {
type UIContext struct { type UIContext struct {
WindowId string `json:"windowid"` WindowId string `json:"windowid"`
ActiveTabId string `json:"activetabid"`
} }
type Client struct { type Client struct {
@ -239,7 +240,7 @@ type FileDef struct {
} }
type BlockDef struct { type BlockDef struct {
Controller string `json:"controller"` Controller string `json:"controller,omitempty"`
View string `json:"view,omitempty"` View string `json:"view,omitempty"`
Files map[string]*FileDef `json:"files,omitempty"` Files map[string]*FileDef `json:"files,omitempty"`
Meta map[string]any `json:"meta,omitempty"` Meta map[string]any `json:"meta,omitempty"`
@ -317,6 +318,28 @@ func SetActiveTab(ctx context.Context, windowId string, tabId string) error {
}) })
} }
func CreateBlock(ctx context.Context, tabId string, blockDef *BlockDef, rtOpts *RuntimeOpts) (*Block, error) {
return WithTxRtn(ctx, func(tx *TxWrap) (*Block, error) {
tab, _ := DBGet[*Tab](tx.Context(), tabId)
if tab == nil {
return nil, fmt.Errorf("tab not found: %q", tabId)
}
blockId := uuid.New().String()
blockData := &Block{
OID: blockId,
BlockDef: blockDef,
Controller: blockDef.Controller,
View: blockDef.View,
RuntimeOpts: rtOpts,
Meta: blockDef.Meta,
}
DBInsert(tx.Context(), blockData)
tab.BlockIds = append(tab.BlockIds, blockId)
DBUpdate(tx.Context(), tab)
return blockData, nil
})
}
func EnsureInitialData() error { func EnsureInitialData() error {
// does not need to run in a transaction since it is called on startup // does not need to run in a transaction since it is called on startup
ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second) ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)