mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-02 18:39:05 +01:00
fe now rendering workspace/tab from db. got useWaveObject working. need to work on updates
This commit is contained in:
parent
95ce1cc86d
commit
6d3f76cb74
@ -3,7 +3,7 @@
|
||||
|
||||
import * as React from "react";
|
||||
import * as jotai from "jotai";
|
||||
import { atoms, blockDataMap, removeBlockFromTab } from "@/store/global";
|
||||
import { atoms, blockDataMap } from "@/store/global";
|
||||
|
||||
import { TerminalView } from "@/app/view/term";
|
||||
import { PreviewView } from "@/app/view/preview";
|
||||
@ -17,7 +17,7 @@ const Block = ({ tabId, blockId }: { tabId: string; blockId: string }) => {
|
||||
const [dims, setDims] = React.useState({ width: 0, height: 0 });
|
||||
|
||||
function handleClose() {
|
||||
removeBlockFromTab(tabId, blockId);
|
||||
// TODO
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
|
@ -15,28 +15,40 @@ import * as wstore from "@/gopkg/wstore";
|
||||
import { Call as $Call } from "@wailsio/runtime";
|
||||
|
||||
const globalStore = jotai.createStore();
|
||||
|
||||
const tabId1 = uuidv4();
|
||||
|
||||
const tabArr: wstore.Tab[] = [new wstore.Tab({ name: "Tab 1", tabid: tabId1, blockids: [] })];
|
||||
const blockDataMap = new Map<string, jotai.Atom<wstore.Block>>();
|
||||
const blockAtomCache = new Map<string, Map<string, jotai.Atom<any>>>();
|
||||
|
||||
const atoms = {
|
||||
activeTabId: jotai.atom<string>(tabId1),
|
||||
tabsAtom: jotai.atom<wstore.Tab[]>(tabArr),
|
||||
blockDataMap: blockDataMap,
|
||||
clientAtom: jotai.atom(null) as jotai.PrimitiveAtom<wstore.Client>,
|
||||
|
||||
// initialized in wave.ts (will not be null inside of application)
|
||||
windowId: jotai.atom<string>(null) as jotai.PrimitiveAtom<string>,
|
||||
windowData: jotai.atom<wstore.Window>(null) as jotai.PrimitiveAtom<wstore.Window>,
|
||||
windowData: jotai.atom<WaveWindow>(null) as jotai.PrimitiveAtom<WaveWindow>,
|
||||
};
|
||||
|
||||
type SubjectWithRef<T> = rxjs.Subject<T> & { refCount: number; release: () => void };
|
||||
|
||||
const blockSubjects = new Map<string, SubjectWithRef<any>>();
|
||||
|
||||
function isBlank(str: string): boolean {
|
||||
return str == null || str == "";
|
||||
}
|
||||
|
||||
function makeORef(otype: string, oid: string): string {
|
||||
if (isBlank(otype) || isBlank(oid)) {
|
||||
return null;
|
||||
}
|
||||
return `${otype}:${oid}`;
|
||||
}
|
||||
|
||||
function splitORef(oref: string): [string, string] {
|
||||
let parts = oref.split(":");
|
||||
if (parts.length != 2) {
|
||||
throw new Error("invalid oref");
|
||||
}
|
||||
return [parts[0], parts[1]];
|
||||
}
|
||||
|
||||
function getBlockSubject(blockId: string): SubjectWithRef<any> {
|
||||
let subject = blockSubjects.get(blockId);
|
||||
if (subject == null) {
|
||||
@ -69,20 +81,6 @@ Events.On("block:ptydata", (event: any) => {
|
||||
subject.next(data);
|
||||
});
|
||||
|
||||
function addBlockIdToTab(tabId: string, blockId: string) {
|
||||
let tabArr = globalStore.get(atoms.tabsAtom);
|
||||
const newTabArr = produce(tabArr, (draft) => {
|
||||
const tab = draft.find((tab) => tab.tabid == tabId);
|
||||
tab.blockids.push(blockId);
|
||||
});
|
||||
globalStore.set(atoms.tabsAtom, newTabArr);
|
||||
}
|
||||
|
||||
function removeBlock(blockId: string) {
|
||||
blockDataMap.delete(blockId);
|
||||
blockAtomCache.delete(blockId);
|
||||
}
|
||||
|
||||
function useBlockAtom<T>(blockId: string, name: string, makeFn: () => jotai.Atom<T>): jotai.Atom<T> {
|
||||
let blockCache = blockAtomCache.get(blockId);
|
||||
if (blockCache == null) {
|
||||
@ -97,18 +95,7 @@ function useBlockAtom<T>(blockId: string, name: string, makeFn: () => jotai.Atom
|
||||
return atom as jotai.Atom<T>;
|
||||
}
|
||||
|
||||
function removeBlockFromTab(tabId: string, blockId: string) {
|
||||
let tabArr = globalStore.get(atoms.tabsAtom);
|
||||
const newTabArr = produce(tabArr, (draft) => {
|
||||
const tab = draft.find((tab) => tab.tabid == tabId);
|
||||
tab.blockids = tab.blockids.filter((id) => id !== blockId);
|
||||
});
|
||||
globalStore.set(atoms.tabsAtom, newTabArr);
|
||||
removeBlock(blockId);
|
||||
BlockService.CloseBlock(blockId);
|
||||
}
|
||||
|
||||
function GetObject(oref: string): Promise<any> {
|
||||
function GetObject<T>(oref: string): Promise<T> {
|
||||
let prtn = $Call.ByName(
|
||||
"github.com/wavetermdev/thenextwave/pkg/service/objectservice.ObjectService.GetObject",
|
||||
oref
|
||||
@ -116,84 +103,85 @@ function GetObject(oref: string): Promise<any> {
|
||||
return prtn;
|
||||
}
|
||||
|
||||
type WaveObjectHookData = {
|
||||
oref: string;
|
||||
};
|
||||
function GetClientObject(): Promise<Client> {
|
||||
let prtn = $Call.ByName(
|
||||
"github.com/wavetermdev/thenextwave/pkg/service/objectservice.ObjectService.GetClientObject"
|
||||
);
|
||||
return prtn;
|
||||
}
|
||||
|
||||
type WaveObjectValue<T> = {
|
||||
pendingPromise: Promise<any>;
|
||||
value: T;
|
||||
loading: boolean;
|
||||
dataAtom: jotai.PrimitiveAtom<{ value: T; loading: boolean }>;
|
||||
};
|
||||
|
||||
const waveObjectValueCache = new Map<string, WaveObjectValue<any>>();
|
||||
let waveObjectAtomCache = new WeakMap<WaveObjectHookData, jotai.Atom<any>>();
|
||||
|
||||
function clearWaveObjectCache() {
|
||||
waveObjectValueCache.clear();
|
||||
waveObjectAtomCache = new WeakMap<WaveObjectHookData, jotai.Atom<any>>();
|
||||
}
|
||||
|
||||
function createWaveObjectAtom<T>(oref: string): jotai.Atom<[T, boolean]> {
|
||||
let cacheVal: WaveObjectValue<T> = waveObjectValueCache.get(oref);
|
||||
if (cacheVal == null) {
|
||||
cacheVal = { pendingPromise: null, value: null, loading: true };
|
||||
cacheVal.pendingPromise = GetObject(oref).then((val) => {
|
||||
cacheVal.value = val;
|
||||
cacheVal.loading = false;
|
||||
cacheVal.pendingPromise = null;
|
||||
});
|
||||
waveObjectValueCache.set(oref, cacheVal);
|
||||
}
|
||||
return jotai.atom(
|
||||
(get) => {
|
||||
return [cacheVal.value, cacheVal.loading];
|
||||
},
|
||||
(get, set, newVal: T) => {
|
||||
cacheVal.value = newVal;
|
||||
function createWaveValueObject<T>(oref: string): WaveObjectValue<T> {
|
||||
const wov = { pendingPromise: null, dataAtom: null };
|
||||
wov.dataAtom = jotai.atom({ value: null, loading: true });
|
||||
let startTs = Date.now();
|
||||
let localPromise = GetObject<T>(oref);
|
||||
wov.pendingPromise = localPromise;
|
||||
localPromise.then((val) => {
|
||||
if (wov.pendingPromise != localPromise) {
|
||||
return;
|
||||
}
|
||||
);
|
||||
const [otype, oid] = splitORef(oref);
|
||||
if (val != null) {
|
||||
if (val["otype"] != otype) {
|
||||
throw new Error("GetObject returned wrong type");
|
||||
}
|
||||
if (val["oid"] != oid) {
|
||||
throw new Error("GetObject returned wrong id");
|
||||
}
|
||||
}
|
||||
wov.pendingPromise = null;
|
||||
globalStore.set(wov.dataAtom, { value: val, loading: false });
|
||||
console.log("GetObject resolved", oref, val, Date.now() - startTs + "ms");
|
||||
});
|
||||
return wov;
|
||||
}
|
||||
|
||||
function useWaveObjectValue<T>(oref: string): [T, boolean] {
|
||||
const objRef = React.useRef<WaveObjectHookData>(null);
|
||||
if (objRef.current == null) {
|
||||
objRef.current = { oref: oref };
|
||||
console.log("useWaveObjectValue", oref);
|
||||
let wov = waveObjectValueCache.get(oref);
|
||||
if (wov == null) {
|
||||
console.log("creating new wov", oref);
|
||||
wov = createWaveValueObject(oref);
|
||||
waveObjectValueCache.set(oref, wov);
|
||||
}
|
||||
const objHookData = objRef.current;
|
||||
let objAtom = waveObjectAtomCache.get(objHookData);
|
||||
if (objAtom == null) {
|
||||
objAtom = createWaveObjectAtom(oref);
|
||||
waveObjectAtomCache.set(objHookData, objAtom);
|
||||
}
|
||||
const atomVal = jotai.useAtomValue(objAtom);
|
||||
return [atomVal[0], atomVal[1]];
|
||||
const atomVal = jotai.useAtomValue(wov.dataAtom);
|
||||
return [atomVal.value, atomVal.loading];
|
||||
}
|
||||
|
||||
function useWaveObject<T>(oref: string): [T, boolean, (T) => void] {
|
||||
const objRef = React.useRef<WaveObjectHookData>(null);
|
||||
if (objRef.current == null) {
|
||||
objRef.current = { oref: oref };
|
||||
console.log("useWaveObject", oref);
|
||||
let wov = waveObjectValueCache.get(oref);
|
||||
if (wov == null) {
|
||||
wov = createWaveValueObject(oref);
|
||||
waveObjectValueCache.set(oref, wov);
|
||||
}
|
||||
const objHookData = objRef.current;
|
||||
let objAtom = waveObjectAtomCache.get(objHookData);
|
||||
if (objAtom == null) {
|
||||
objAtom = createWaveObjectAtom(oref);
|
||||
waveObjectAtomCache.set(objHookData, objAtom);
|
||||
}
|
||||
const [atomVal, setAtomVal] = jotai.useAtom(objAtom);
|
||||
return [atomVal[0], atomVal[1], setAtomVal];
|
||||
const [atomVal, setAtomVal] = jotai.useAtom(wov.dataAtom);
|
||||
const simpleSet = (val: T) => {
|
||||
setAtomVal({ value: val, loading: false });
|
||||
};
|
||||
return [atomVal.value, atomVal.loading, simpleSet];
|
||||
}
|
||||
|
||||
export {
|
||||
globalStore,
|
||||
makeORef,
|
||||
atoms,
|
||||
getBlockSubject,
|
||||
addBlockIdToTab,
|
||||
blockDataMap,
|
||||
useBlockAtom,
|
||||
removeBlockFromTab,
|
||||
GetObject,
|
||||
GetClientObject,
|
||||
useWaveObject,
|
||||
useWaveObjectValue,
|
||||
clearWaveObjectCache,
|
||||
|
@ -5,12 +5,16 @@ import * as React from "react";
|
||||
import * as jotai from "jotai";
|
||||
import { Block } from "@/app/block/block";
|
||||
import { atoms } from "@/store/global";
|
||||
import * as gdata from "@/store/global";
|
||||
|
||||
import "./tab.less";
|
||||
import { CenteredLoadingDiv } from "../element/quickelems";
|
||||
|
||||
const TabContent = ({ tabId }: { tabId: string }) => {
|
||||
const tabs = jotai.useAtomValue(atoms.tabsAtom);
|
||||
const tabData = tabs.find((tab) => tab.tabid === tabId);
|
||||
const [tabData, tabLoading] = gdata.useWaveObjectValue<Tab>(gdata.makeORef("tab", tabId));
|
||||
if (tabLoading) {
|
||||
return <CenteredLoadingDiv />;
|
||||
}
|
||||
if (!tabData) {
|
||||
return <div className="tabcontent">Tab not found</div>;
|
||||
}
|
||||
|
@ -5,32 +5,40 @@ import * as React from "react";
|
||||
import * as jotai from "jotai";
|
||||
import { TabContent } from "@/app/tab/tab";
|
||||
import { clsx } from "clsx";
|
||||
import { atoms, addBlockIdToTab, blockDataMap } from "@/store/global";
|
||||
import { atoms, blockDataMap } 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 gdata from "@/store/global";
|
||||
|
||||
import "./workspace.less";
|
||||
import { CenteredLoadingDiv, CenteredDiv } from "../element/quickelems";
|
||||
|
||||
function Tab({ tab }: { tab: wstore.Tab }) {
|
||||
const [activeTab, setActiveTab] = jotai.useAtom(atoms.activeTabId);
|
||||
function Tab({ tabId }: { tabId: string }) {
|
||||
const windowData = jotai.useAtomValue(atoms.windowData);
|
||||
const [tabData, tabLoading] = gdata.useWaveObjectValue<Tab>(gdata.makeORef("tab", tabId));
|
||||
|
||||
function setActiveTab(tabId: string) {
|
||||
if (tabId == null) {
|
||||
return;
|
||||
}
|
||||
// TODO
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clsx("tab", { active: activeTab === tab.tabid })} onClick={() => setActiveTab(tab.tabid)}>
|
||||
{tab.name}
|
||||
<div
|
||||
className={clsx("tab", { active: tabData != null && windowData.activetabid === tabData.oid })}
|
||||
onClick={() => setActiveTab(tabData?.oid)}
|
||||
>
|
||||
{tabData?.name ?? "..."}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TabBar() {
|
||||
const [tabData, setTabData] = jotai.useAtom(atoms.tabsAtom);
|
||||
const [activeTab, setActiveTab] = jotai.useAtom(atoms.activeTabId);
|
||||
const tabs = jotai.useAtomValue(atoms.tabsAtom);
|
||||
const client = jotai.useAtomValue(atoms.clientAtom);
|
||||
|
||||
function TabBar({ workspace, waveWindow }: { workspace: Workspace; waveWindow: WaveWindow }) {
|
||||
function handleAddTab() {
|
||||
const newTabId = uuidv4();
|
||||
const newTabName = "Tab " + (tabData.length + 1);
|
||||
@ -38,10 +46,11 @@ function TabBar() {
|
||||
setActiveTab(newTabId);
|
||||
}
|
||||
|
||||
const tabIds = workspace?.tabids ?? [];
|
||||
return (
|
||||
<div className="tab-bar">
|
||||
{tabs.map((tab, idx) => {
|
||||
return <Tab key={idx} tab={tab} />;
|
||||
{tabIds.map((tabid, idx) => {
|
||||
return <Tab key={idx} tabId={tabid} />;
|
||||
})}
|
||||
<div className="tab-add" onClick={() => handleAddTab()}>
|
||||
<i className="fa fa-solid fa-plus fa-fw" />
|
||||
@ -51,7 +60,8 @@ function TabBar() {
|
||||
}
|
||||
|
||||
function Widgets() {
|
||||
const activeTabId = jotai.useAtomValue(atoms.activeTabId);
|
||||
const windowData = jotai.useAtomValue(atoms.windowData);
|
||||
const activeTabId = windowData.activetabid;
|
||||
|
||||
async function createBlock(blockDef: wstore.BlockDef) {
|
||||
const rtOpts: wstore.RuntimeOpts = new wstore.RuntimeOpts({ termsize: { rows: 25, cols: 80 } });
|
||||
@ -113,27 +123,15 @@ function Widgets() {
|
||||
|
||||
function WorkspaceElem() {
|
||||
const windowData = jotai.useAtomValue(atoms.windowData);
|
||||
const activeTabId = jotai.useAtomValue(atoms.activeTabId);
|
||||
const workspaceId = windowData.workspaceid;
|
||||
const wsAtom = React.useMemo(() => {
|
||||
return jotaiUtil.loadable(
|
||||
jotai.atom(async (get) => {
|
||||
const ws = await ClientService.GetWorkspace(workspaceId);
|
||||
return ws;
|
||||
})
|
||||
);
|
||||
}, [workspaceId]);
|
||||
const wsLoadable = jotai.useAtomValue(wsAtom);
|
||||
if (wsLoadable.state === "loading") {
|
||||
const workspaceId = windowData?.workspaceid;
|
||||
const activeTabId = windowData?.activetabid;
|
||||
const [ws, wsLoading] = gdata.useWaveObjectValue<Workspace>(gdata.makeORef("workspace", workspaceId));
|
||||
if (wsLoading) {
|
||||
return <CenteredLoadingDiv />;
|
||||
}
|
||||
if (wsLoadable.state === "hasError") {
|
||||
return <CenteredDiv>Error: {wsLoadable.error?.toString()}</CenteredDiv>;
|
||||
}
|
||||
const ws: Workspace = wsLoadable.data;
|
||||
return (
|
||||
<div className="workspace">
|
||||
<TabBar />
|
||||
<TabBar workspace={ws} waveWindow={windowData} />
|
||||
<div className="workspace-tabcontent">
|
||||
<TabContent key={workspaceId} tabId={activeTabId} />
|
||||
<Widgets />
|
||||
|
2
frontend/types/custom.d.ts
vendored
2
frontend/types/custom.d.ts
vendored
@ -81,7 +81,7 @@ declare global {
|
||||
oid: string;
|
||||
};
|
||||
|
||||
type Window = {
|
||||
type WaveWindow = {
|
||||
otype: string;
|
||||
oid: string;
|
||||
version: number;
|
||||
|
@ -7,7 +7,7 @@ import { App } from "./app/app";
|
||||
import { loadFonts } from "./util/fontutil";
|
||||
import { ClientService } from "@/bindings/clientservice";
|
||||
import { Client } from "@/gopkg/wstore";
|
||||
import { globalStore, atoms } from "@/store/global";
|
||||
import { globalStore, atoms, GetClientObject, GetObject, makeORef } from "@/store/global";
|
||||
import * as wailsRuntime from "@wailsio/runtime";
|
||||
import * as wstore from "@/gopkg/wstore";
|
||||
import { immerable } from "immer";
|
||||
@ -30,9 +30,9 @@ wstore.WinSize.prototype[immerable] = true;
|
||||
loadFonts();
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
const client = await ClientService.GetClientData();
|
||||
const client = await GetClientObject();
|
||||
globalStore.set(atoms.clientAtom, client);
|
||||
const window = await ClientService.GetWindow(windowId);
|
||||
const window = await GetObject<WaveWindow>(makeORef("window", windowId));
|
||||
globalStore.set(atoms.windowData, window);
|
||||
let reactElem = React.createElement(App, null, null);
|
||||
let elem = document.getElementById("main");
|
||||
|
@ -25,6 +25,16 @@ func parseORef(oref string) (*waveobj.ORef, error) {
|
||||
return &waveobj.ORef{OType: fields[0], OID: fields[1]}, nil
|
||||
}
|
||||
|
||||
func (svc *ObjectService) GetClientObject() (any, error) {
|
||||
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
|
||||
defer cancelFn()
|
||||
client, err := wstore.DBGetSingleton[*wstore.Client](ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting client: %w", err)
|
||||
}
|
||||
return waveobj.ToJsonMap(client)
|
||||
}
|
||||
|
||||
func (svc *ObjectService) GetObject(orefStr string) (any, error) {
|
||||
oref, err := parseORef(orefStr)
|
||||
if err != nil {
|
||||
@ -36,7 +46,7 @@ func (svc *ObjectService) GetObject(orefStr string) (any, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting object: %w", err)
|
||||
}
|
||||
return obj, nil
|
||||
return waveobj.ToJsonMap(obj)
|
||||
}
|
||||
|
||||
func (svc *ObjectService) GetObjects(orefStrArr []string) (any, error) {
|
||||
|
@ -124,7 +124,7 @@ func SetVersion(waveObj WaveObj, version int) {
|
||||
reflect.ValueOf(waveObj).Elem().FieldByIndex(desc.VersionField.Index).SetInt(int64(version))
|
||||
}
|
||||
|
||||
func ToJson(w WaveObj) ([]byte, error) {
|
||||
func ToJsonMap(w WaveObj) (map[string]any, error) {
|
||||
m := make(map[string]any)
|
||||
dconfig := &mapstructure.DecoderConfig{
|
||||
Result: &m,
|
||||
@ -139,6 +139,16 @@ func ToJson(w WaveObj) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
m[OTypeKeyName] = w.GetOType()
|
||||
m[OIDKeyName] = GetOID(w)
|
||||
m[VersionKeyName] = GetVersion(w)
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func ToJson(w WaveObj) ([]byte, error) {
|
||||
m, err := ToJsonMap(w)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
@ -253,10 +263,18 @@ func typeToTSType(t reflect.Type) (string, []reflect.Type) {
|
||||
}
|
||||
}
|
||||
|
||||
var tsRenameMap = map[string]string{
|
||||
"Window": "WaveWindow",
|
||||
}
|
||||
|
||||
func generateTSTypeInternal(rtype reflect.Type) (string, []reflect.Type) {
|
||||
var buf bytes.Buffer
|
||||
waveObjType := reflect.TypeOf((*WaveObj)(nil)).Elem()
|
||||
buf.WriteString(fmt.Sprintf("type %s = {\n", rtype.Name()))
|
||||
tsTypeName := rtype.Name()
|
||||
if tsRename, ok := tsRenameMap[tsTypeName]; ok {
|
||||
tsTypeName = tsRename
|
||||
}
|
||||
buf.WriteString(fmt.Sprintf("type %s = {\n", tsTypeName))
|
||||
var isWaveObj bool
|
||||
if rtype.Implements(waveObjType) || reflect.PointerTo(rtype).Implements(waveObjType) {
|
||||
isWaveObj = true
|
||||
|
@ -209,8 +209,8 @@ func EnsureInitialData() error {
|
||||
return fmt.Errorf("error inserting workspace: %w", err)
|
||||
}
|
||||
tab := &Tab{
|
||||
OID: uuid.New().String(),
|
||||
Name: "Tab 1",
|
||||
OID: tabId,
|
||||
Name: "Tab-1",
|
||||
BlockIds: []string{},
|
||||
}
|
||||
err = DBInsert(ctx, tab)
|
||||
|
Loading…
Reference in New Issue
Block a user