From 3f45945cb4e923003365e5d972f4bf60228d77b7 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 27 May 2024 16:33:31 -0700 Subject: [PATCH] delete block and close tab working --- README.md | 2 +- frontend/app/block/block.tsx | 2 +- frontend/app/store/wos.ts | 21 +++++-- frontend/app/tab/tab.tsx | 8 ++- frontend/app/workspace/workspace.less | 15 +++++ frontend/app/workspace/workspace.tsx | 25 +++++--- pkg/service/objectservice/objectservice.go | 47 +++++++++++++++ pkg/wstore/wstore.go | 69 +++++++++++++++++++--- 8 files changed, 165 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 846e245a8..03cd60060 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Now to run the dev version of the app: wails3 dev ``` -You should see a very poorly laid out app :) +You should see the app! Now to build a MacOS application: diff --git a/frontend/app/block/block.tsx b/frontend/app/block/block.tsx index c50143039..328a86bfb 100644 --- a/frontend/app/block/block.tsx +++ b/frontend/app/block/block.tsx @@ -16,7 +16,7 @@ const Block = ({ tabId, blockId }: { tabId: string; blockId: string }) => { const [dims, setDims] = React.useState({ width: 0, height: 0 }); function handleClose() { - // TODO + WOS.DeleteBlock(blockId); } React.useEffect(() => { diff --git a/frontend/app/store/wos.ts b/frontend/app/store/wos.ts index 80c806247..5e76fb42c 100644 --- a/frontend/app/store/wos.ts +++ b/frontend/app/store/wos.ts @@ -178,6 +178,7 @@ function updateWaveObject(update: WaveObjUpdate) { waveObjectValueCache.set(oref, wov); } if (update.updatetype == "delete") { + console.log("WaveObj deleted", oref); globalStore.set(wov.dataAtom, { value: null, loading: false }); } else { if (!isValidWaveObj(update.obj)) { @@ -188,6 +189,7 @@ function updateWaveObject(update: WaveObjUpdate) { if (curValue.value != null && curValue.value.version >= update.obj.version) { return; } + console.log("WaveObj updated", oref); globalStore.set(wov.dataAtom, { value: update.obj, loading: false }); } wov.holdTime = Date.now() + defaultHoldTime; @@ -226,12 +228,14 @@ Events.On("waveobj:update", (event: any) => { function wrapObjectServiceCall(fnName: string, ...args: any[]): Promise { const uiContext = globalStore.get(atoms.uiContext); + const startTs = Date.now(); let prtn = $Call.ByName( "github.com/wavetermdev/thenextwave/pkg/service/objectservice.ObjectService." + fnName, uiContext, ...args ); prtn = prtn.then((val) => { + console.log("Call", fnName, Date.now() - startTs + "ms"); if (val.updates) { updateWaveObjects(val.updates); } @@ -249,18 +253,26 @@ function getStaticObjectValue(oref: string, getFn: jotai.Getter): T { return atomVal.value; } -function AddTabToWorkspace(tabName: string, activateTab: boolean): Promise<{ tabId: string }> { +export function AddTabToWorkspace(tabName: string, activateTab: boolean): Promise<{ tabId: string }> { return wrapObjectServiceCall("AddTabToWorkspace", tabName, activateTab); } -function SetActiveTab(tabId: string): Promise { +export function SetActiveTab(tabId: string): Promise { return wrapObjectServiceCall("SetActiveTab", tabId); } -function CreateBlock(blockDef: BlockDef, rtOpts: RuntimeOpts): Promise<{ blockId: string }> { +export function CreateBlock(blockDef: BlockDef, rtOpts: RuntimeOpts): Promise<{ blockId: string }> { return wrapObjectServiceCall("CreateBlock", blockDef, rtOpts); } +export function DeleteBlock(blockId: string): Promise { + return wrapObjectServiceCall("DeleteBlock", blockId); +} + +export function CloseTab(tabId: string): Promise { + return wrapObjectServiceCall("CloseTab", tabId); +} + export { makeORef, useWaveObject, @@ -272,7 +284,4 @@ export { updateWaveObjects, cleanWaveObjectCache, getStaticObjectValue, - AddTabToWorkspace, - SetActiveTab, - CreateBlock, }; diff --git a/frontend/app/tab/tab.tsx b/frontend/app/tab/tab.tsx index e237585b2..609d94ec0 100644 --- a/frontend/app/tab/tab.tsx +++ b/frontend/app/tab/tab.tsx @@ -8,7 +8,7 @@ import { atoms } from "@/store/global"; import * as WOS from "@/store/wos"; import "./tab.less"; -import { CenteredLoadingDiv } from "../element/quickelems"; +import { CenteredDiv, CenteredLoadingDiv } from "../element/quickelems"; const TabContent = ({ tabId }: { tabId: string }) => { const [tabData, tabLoading] = WOS.useWaveObjectValue(WOS.makeORef("tab", tabId)); @@ -16,7 +16,11 @@ const TabContent = ({ tabId }: { tabId: string }) => { return ; } if (!tabData) { - return
Tab not found
; + return ( +
+ Tab Not Found +
+ ); } return (
diff --git a/frontend/app/workspace/workspace.less b/frontend/app/workspace/workspace.less index 76ddc8a3a..a37ced560 100644 --- a/frontend/app/workspace/workspace.less +++ b/frontend/app/workspace/workspace.less @@ -55,9 +55,24 @@ height: 100%; border-right: 1px solid var(--border-color); cursor: pointer; + position: relative; + &.active { background-color: var(--highlight-bg-color); } + + &.active:hover .tab-close { + display: block; + } + + .tab-close { + position: absolute; + display: none; + padding: 5px; + right: 2px; + top: 5px; + cursor: pointer; + } } .tab-add { diff --git a/frontend/app/workspace/workspace.tsx b/frontend/app/workspace/workspace.tsx index a31443b5a..c3b8abce7 100644 --- a/frontend/app/workspace/workspace.tsx +++ b/frontend/app/workspace/workspace.tsx @@ -14,17 +14,22 @@ import "./workspace.less"; function Tab({ tabId }: { tabId: string }) { const windowData = jotai.useAtomValue(atoms.waveWindow); const [tabData, tabLoading] = WOS.useWaveObjectValue(WOS.makeORef("tab", tabId)); - function setActiveTab(tabId: string) { - if (tabId == null) { - return; - } + function setActiveTab() { WOS.SetActiveTab(tabId); } + function handleCloseTab() { + WOS.CloseTab(tabId); + } return (
setActiveTab(tabData?.oid)} + onClick={() => setActiveTab()} > +
handleCloseTab()}> +
+ +
+
{tabData?.name ?? "..."}
); @@ -115,8 +120,14 @@ function WorkspaceElem() {
- - + {activeTabId == "" ? ( + No Active Tab + ) : ( + <> + + + + )}
); diff --git a/pkg/service/objectservice/objectservice.go b/pkg/service/objectservice/objectservice.go index 1aa7f42e3..0a27a0a54 100644 --- a/pkg/service/objectservice/objectservice.go +++ b/pkg/service/objectservice/objectservice.go @@ -132,3 +132,50 @@ func (svc *ObjectService) CreateBlock(uiContext wstore.UIContext, blockDef *wsto rtn["blockid"] = blockData.OID return updatesRtn(ctx, rtn) } + +func (svc *ObjectService) DeleteBlock(uiContext wstore.UIContext, blockId string) (any, error) { + ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout) + defer cancelFn() + ctx = wstore.ContextWithUpdates(ctx) + err := wstore.DeleteBlock(ctx, uiContext.ActiveTabId, blockId) + if err != nil { + return nil, fmt.Errorf("error deleting block: %w", err) + } + blockcontroller.StopBlockController(blockId) + return updatesRtn(ctx, nil) +} + +func (svc *ObjectService) CloseTab(uiContext wstore.UIContext, tabId string) (any, error) { + ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout) + defer cancelFn() + ctx = wstore.ContextWithUpdates(ctx) + window, err := wstore.DBMustGet[*wstore.Window](ctx, uiContext.WindowId) + if err != nil { + return nil, fmt.Errorf("error getting window: %w", err) + } + tab, err := wstore.DBMustGet[*wstore.Tab](ctx, tabId) + if err != nil { + return nil, fmt.Errorf("error getting tab: %w", err) + } + for _, blockId := range tab.BlockIds { + blockcontroller.StopBlockController(blockId) + } + err = wstore.CloseTab(ctx, window.WorkspaceId, tabId) + if err != nil { + return nil, fmt.Errorf("error closing tab: %w", err) + } + if window.ActiveTabId == tabId { + ws, err := wstore.DBMustGet[*wstore.Workspace](ctx, window.WorkspaceId) + if err != nil { + return nil, fmt.Errorf("error getting workspace: %w", err) + } + var newActiveTabId string + if len(ws.TabIds) > 0 { + newActiveTabId = ws.TabIds[0] + } else { + newActiveTabId = "" + } + wstore.SetActiveTab(ctx, uiContext.WindowId, newActiveTabId) + } + return updatesRtn(ctx, nil) +} diff --git a/pkg/wstore/wstore.go b/pkg/wstore/wstore.go index 8d92d3a43..40d984936 100644 --- a/pkg/wstore/wstore.go +++ b/pkg/wstore/wstore.go @@ -159,10 +159,12 @@ func (update WaveObjUpdate) MarshalJSON() ([]byte, error) { rtn["updatetype"] = update.UpdateType rtn["otype"] = update.OType rtn["oid"] = update.OID - var err error - rtn["obj"], err = waveobj.ToJsonMap(update.Obj) - if err != nil { - return nil, err + if update.Obj != nil { + var err error + rtn["obj"], err = waveobj.ToJsonMap(update.Obj) + if err != nil { + return nil, err + } } return json.Marshal(rtn) } @@ -308,9 +310,11 @@ func SetActiveTab(ctx context.Context, windowId string, tabId string) error { if window == nil { return fmt.Errorf("window not found: %q", windowId) } - tab, _ := DBGet[*Tab](tx.Context(), tabId) - if tab == nil { - return fmt.Errorf("tab not found: %q", tabId) + if tabId != "" { + tab, _ := DBGet[*Tab](tx.Context(), tabId) + if tab == nil { + return fmt.Errorf("tab not found: %q", tabId) + } } window.ActiveTabId = tabId DBUpdate(tx.Context(), window) @@ -340,6 +344,57 @@ func CreateBlock(ctx context.Context, tabId string, blockDef *BlockDef, rtOpts * }) } +func findStringInSlice(slice []string, val string) int { + for idx, v := range slice { + if v == val { + return idx + } + } + return -1 +} + +func DeleteBlock(ctx context.Context, tabId string, blockId string) error { + return WithTx(ctx, func(tx *TxWrap) error { + tab, _ := DBGet[*Tab](tx.Context(), tabId) + if tab == nil { + return fmt.Errorf("tab not found: %q", tabId) + } + blockIdx := findStringInSlice(tab.BlockIds, blockId) + if blockIdx == -1 { + return nil + } + tab.BlockIds = append(tab.BlockIds[:blockIdx], tab.BlockIds[blockIdx+1:]...) + DBUpdate(tx.Context(), tab) + DBDelete(tx.Context(), "block", blockId) + return nil + }) + +} + +func CloseTab(ctx context.Context, workspaceId string, tabId string) error { + return WithTx(ctx, func(tx *TxWrap) error { + ws, _ := DBGet[*Workspace](tx.Context(), workspaceId) + if ws == nil { + return fmt.Errorf("workspace not found: %q", workspaceId) + } + tab, _ := DBGet[*Tab](tx.Context(), tabId) + if tab == nil { + return fmt.Errorf("tab not found: %q", tabId) + } + tabIdx := findStringInSlice(ws.TabIds, tabId) + if tabIdx == -1 { + return nil + } + ws.TabIds = append(ws.TabIds[:tabIdx], ws.TabIds[tabIdx+1:]...) + DBUpdate(tx.Context(), ws) + DBDelete(tx.Context(), "tab", tabId) + for _, blockId := range tab.BlockIds { + DBDelete(tx.Context(), "block", blockId) + } + return nil + }) +} + func EnsureInitialData() error { // does not need to run in a transaction since it is called on startup ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)