fe now rendering workspace/tab from db. got useWaveObject working. need to work on updates

This commit is contained in:
sawka 2024-05-27 00:47:10 -07:00
parent 95ce1cc86d
commit 6d3f76cb74
9 changed files with 145 additions and 127 deletions

View File

@ -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(() => {

View File

@ -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,

View File

@ -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>;
}

View File

@ -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 />

View File

@ -81,7 +81,7 @@ declare global {
oid: string;
};
type Window = {
type WaveWindow = {
otype: string;
oid: string;
version: number;

View File

@ -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");

View File

@ -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) {

View File

@ -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

View File

@ -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)