mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-21 16:38:23 +01:00
app is working again. new structure for blocks. new useWaveObjectValueWithSuspense hook
This commit is contained in:
parent
abedca2364
commit
e6d7a4e674
@ -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} />;
|
||||||
|
@ -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 };
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -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", () =>
|
||||||
|
@ -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 />
|
||||||
|
3
frontend/types/custom.d.ts
vendored
3
frontend/types/custom.d.ts
vendored
@ -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 };
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
2
main.go
2
main.go
@ -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())
|
||||||
})
|
})
|
||||||
|
@ -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 {
|
||||||
|
@ -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:
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user