diff --git a/emain/emain.ts b/emain/emain.ts index 77cbc8f54..92ca38acb 100644 --- a/emain/emain.ts +++ b/emain/emain.ts @@ -39,6 +39,7 @@ import { getAllWaveWindows, getWaveWindowById, getWaveWindowByWebContentsId, + getWaveWindowByWorkspaceId, WaveBrowserWindow, } from "./emain-window"; import { ElectronWshClient, initElectronWshClient } from "./emain-wsh"; @@ -125,6 +126,14 @@ function handleWSEvent(evtMsg: WSEventType) { if (ww != null) { ww.destroy(); // bypass the "are you sure?" dialog } + } else if (evtMsg.eventtype == "electron:updateactivetab") { + const activeTabUpdate: { workspaceid: string; newactivetabid: string } = evtMsg.data; + console.log("electron:updateactivetab", activeTabUpdate); + const ww = getWaveWindowByWorkspaceId(activeTabUpdate.workspaceid); + if (ww == null) { + return; + } + await ww.setActiveTab(activeTabUpdate.newactivetabid, false); } else { console.log("unhandled electron ws eventtype", evtMsg.eventtype); } diff --git a/pkg/eventbus/eventbus.go b/pkg/eventbus/eventbus.go index 2a98f2e36..454103cee 100644 --- a/pkg/eventbus/eventbus.go +++ b/pkg/eventbus/eventbus.go @@ -13,9 +13,10 @@ import ( ) const ( - WSEvent_ElectronNewWindow = "electron:newwindow" - WSEvent_ElectronCloseWindow = "electron:closewindow" - WSEvent_Rpc = "rpc" + WSEvent_ElectronNewWindow = "electron:newwindow" + WSEvent_ElectronCloseWindow = "electron:closewindow" + WSEvent_ElectronUpdateActiveTab = "electron:updateactivetab" + WSEvent_Rpc = "rpc" ) type WSEventType struct { diff --git a/pkg/service/workspaceservice/workspaceservice.go b/pkg/service/workspaceservice/workspaceservice.go index 1c4f55b59..5c634684e 100644 --- a/pkg/service/workspaceservice/workspaceservice.go +++ b/pkg/service/workspaceservice/workspaceservice.go @@ -175,15 +175,7 @@ func (svc *WorkspaceService) CloseTab(ctx context.Context, workspaceId string, t } rtn := &CloseTabRtnType{} if newActiveTabId == "" { - windowId, err := wstore.DBFindWindowForWorkspaceId(ctx, workspaceId) - if err != nil { - return rtn, nil, fmt.Errorf("unable to find window for workspace id %v: %w", workspaceId, err) - } rtn.CloseWindow = true - err = wcore.CloseWindow(ctx, windowId, fromElectron) - if err != nil { - return rtn, nil, err - } } else { rtn.NewActiveTabId = newActiveTabId } diff --git a/pkg/waveobj/wtype.go b/pkg/waveobj/wtype.go index 22e5ed088..4e3cc0b34 100644 --- a/pkg/waveobj/wtype.go +++ b/pkg/waveobj/wtype.go @@ -159,6 +159,11 @@ type WorkspaceListEntry struct { type WorkspaceList []*WorkspaceListEntry +type ActiveTabUpdate struct { + WorkspaceId string `json:"workspaceid"` + NewActiveTabId string `json:"newactivetabid"` +} + type Workspace struct { OID string `json:"oid"` Version int `json:"version"` diff --git a/pkg/wcore/block.go b/pkg/wcore/block.go index 4741883e5..28559a032 100644 --- a/pkg/wcore/block.go +++ b/pkg/wcore/block.go @@ -3,11 +3,14 @@ package wcore import ( "context" "fmt" + "log" "time" + "github.com/google/uuid" "github.com/wavetermdev/waveterm/pkg/blockcontroller" "github.com/wavetermdev/waveterm/pkg/panichandler" "github.com/wavetermdev/waveterm/pkg/telemetry" + "github.com/wavetermdev/waveterm/pkg/util/utilfn" "github.com/wavetermdev/waveterm/pkg/waveobj" "github.com/wavetermdev/waveterm/pkg/wps" "github.com/wavetermdev/waveterm/pkg/wshrpc" @@ -21,13 +24,34 @@ func CreateSubBlock(ctx context.Context, blockId string, blockDef *waveobj.Block if blockDef.Meta == nil || blockDef.Meta.GetString(waveobj.MetaKey_View, "") == "" { return nil, fmt.Errorf("no view provided for new block") } - blockData, err := wstore.CreateSubBlock(ctx, blockId, blockDef) + blockData, err := createSubBlockObj(ctx, blockId, blockDef) if err != nil { return nil, fmt.Errorf("error creating sub block: %w", err) } return blockData, nil } +func createSubBlockObj(ctx context.Context, parentBlockId string, blockDef *waveobj.BlockDef) (*waveobj.Block, error) { + return wstore.WithTxRtn(ctx, func(tx *wstore.TxWrap) (*waveobj.Block, error) { + parentBlock, _ := wstore.DBGet[*waveobj.Block](tx.Context(), parentBlockId) + if parentBlock == nil { + return nil, fmt.Errorf("parent block not found: %q", parentBlockId) + } + blockId := uuid.NewString() + blockData := &waveobj.Block{ + OID: blockId, + ParentORef: waveobj.MakeORef(waveobj.OType_Block, parentBlockId).String(), + BlockDef: blockDef, + RuntimeOpts: nil, + Meta: blockDef.Meta, + } + wstore.DBInsert(tx.Context(), blockData) + parentBlock.SubBlockIds = append(parentBlock.SubBlockIds, blockId) + wstore.DBUpdate(tx.Context(), parentBlock) + return blockData, nil + }) +} + func CreateBlock(ctx context.Context, tabId string, blockDef *waveobj.BlockDef, rtOpts *waveobj.RuntimeOpts) (*waveobj.Block, error) { if blockDef == nil { return nil, fmt.Errorf("blockDef is nil") @@ -35,7 +59,7 @@ func CreateBlock(ctx context.Context, tabId string, blockDef *waveobj.BlockDef, if blockDef.Meta == nil || blockDef.Meta.GetString(waveobj.MetaKey_View, "") == "" { return nil, fmt.Errorf("no view provided for new block") } - blockData, err := wstore.CreateBlock(ctx, tabId, blockDef, rtOpts) + blockData, err := createBlockObj(ctx, tabId, blockDef, rtOpts) if err != nil { return nil, fmt.Errorf("error creating block: %w", err) } @@ -54,6 +78,27 @@ func CreateBlock(ctx context.Context, tabId string, blockDef *waveobj.BlockDef, return blockData, nil } +func createBlockObj(ctx context.Context, tabId string, blockDef *waveobj.BlockDef, rtOpts *waveobj.RuntimeOpts) (*waveobj.Block, error) { + return wstore.WithTxRtn(ctx, func(tx *wstore.TxWrap) (*waveobj.Block, error) { + tab, _ := wstore.DBGet[*waveobj.Tab](tx.Context(), tabId) + if tab == nil { + return nil, fmt.Errorf("tab not found: %q", tabId) + } + blockId := uuid.NewString() + blockData := &waveobj.Block{ + OID: blockId, + ParentORef: waveobj.MakeORef(waveobj.OType_Tab, tabId).String(), + BlockDef: blockDef, + RuntimeOpts: rtOpts, + Meta: blockDef.Meta, + } + wstore.DBInsert(tx.Context(), blockData) + tab.BlockIds = append(tab.BlockIds, blockId) + wstore.DBUpdate(tx.Context(), tab) + return blockData, nil + }) +} + func DeleteBlock(ctx context.Context, blockId string) error { block, err := wstore.DBMustGet[*waveobj.Block](ctx, blockId) if err != nil { @@ -70,15 +115,71 @@ func DeleteBlock(ctx context.Context, blockId string) error { } } } - err = wstore.DeleteBlock(ctx, blockId) + parentBlockCount, err := deleteBlockObj(ctx, blockId) if err != nil { return fmt.Errorf("error deleting block: %w", err) } + log.Printf("DeleteBlock: parentBlockCount: %d", parentBlockCount) + parentORef := waveobj.ParseORefNoErr(block.ParentORef) + + if parentORef.OType == waveobj.OType_Tab { + if parentBlockCount == 0 { + // if parent tab has no blocks, delete the tab + log.Printf("DeleteBlock: parent tab has no blocks, deleting tab %s", parentORef.OID) + parentWorkspaceId, err := wstore.DBFindWorkspaceForTabId(ctx, parentORef.OID) + if err != nil { + return fmt.Errorf("error finding workspace for tab to delete %s: %w", parentORef.OID, err) + } + newActiveTabId, err := DeleteTab(ctx, parentWorkspaceId, parentORef.OID) + if err != nil { + return fmt.Errorf("error deleting tab %s: %w", parentORef.OID, err) + } + SendActiveTabUpdate(ctx, parentWorkspaceId, newActiveTabId) + } + + } go blockcontroller.StopBlockController(blockId) sendBlockCloseEvent(blockId) return nil } +// returns the updated block count for the parent object +func deleteBlockObj(ctx context.Context, blockId string) (int, error) { + return wstore.WithTxRtn(ctx, func(tx *wstore.TxWrap) (int, error) { + block, err := wstore.DBGet[*waveobj.Block](tx.Context(), blockId) + if err != nil { + return -1, fmt.Errorf("error getting block: %w", err) + } + if block == nil { + return -1, fmt.Errorf("block not found: %q", blockId) + } + if len(block.SubBlockIds) > 0 { + return -1, fmt.Errorf("block has subblocks, must delete subblocks first") + } + parentORef := waveobj.ParseORefNoErr(block.ParentORef) + parentBlockCount := -1 + if parentORef != nil { + if parentORef.OType == waveobj.OType_Tab { + tab, _ := wstore.DBGet[*waveobj.Tab](tx.Context(), parentORef.OID) + if tab != nil { + tab.BlockIds = utilfn.RemoveElemFromSlice(tab.BlockIds, blockId) + wstore.DBUpdate(tx.Context(), tab) + parentBlockCount = len(tab.BlockIds) + } + } else if parentORef.OType == waveobj.OType_Block { + parentBlock, _ := wstore.DBGet[*waveobj.Block](tx.Context(), parentORef.OID) + if parentBlock != nil { + parentBlock.SubBlockIds = utilfn.RemoveElemFromSlice(parentBlock.SubBlockIds, blockId) + wstore.DBUpdate(tx.Context(), parentBlock) + parentBlockCount = len(parentBlock.SubBlockIds) + } + } + } + wstore.DBDelete(tx.Context(), waveobj.OType_Block, blockId) + return parentBlockCount, nil + }) +} + func sendBlockCloseEvent(blockId string) { waveEvent := wps.WaveEvent{ Event: wps.Event_BlockClose, diff --git a/pkg/wcore/workspace.go b/pkg/wcore/workspace.go index 2402512a8..930f20762 100644 --- a/pkg/wcore/workspace.go +++ b/pkg/wcore/workspace.go @@ -7,6 +7,7 @@ import ( "time" "github.com/google/uuid" + "github.com/wavetermdev/waveterm/pkg/eventbus" "github.com/wavetermdev/waveterm/pkg/telemetry" "github.com/wavetermdev/waveterm/pkg/util/utilfn" "github.com/wavetermdev/waveterm/pkg/waveobj" @@ -139,6 +140,17 @@ func DeleteTab(ctx context.Context, workspaceId string, tabId string) (string, e wstore.DBUpdate(ctx, ws) wstore.DBDelete(ctx, waveobj.OType_Tab, tabId) wstore.DBDelete(ctx, waveobj.OType_LayoutState, tab.LayoutState) + + if newActiveTabId == "" { + windowId, err := wstore.DBFindWindowForWorkspaceId(ctx, workspaceId) + if err != nil { + return newActiveTabId, fmt.Errorf("unable to find window for workspace id %v: %w", workspaceId, err) + } + err = CloseWindow(ctx, windowId, false) + if err != nil { + return newActiveTabId, err + } + } return newActiveTabId, nil } @@ -158,6 +170,13 @@ func SetActiveTab(ctx context.Context, workspaceId string, tabId string) error { return nil } +func SendActiveTabUpdate(ctx context.Context, workspaceId string, newActiveTabId string) { + eventbus.SendEventToElectron(eventbus.WSEventType{ + EventType: eventbus.WSEvent_ElectronUpdateActiveTab, + Data: &waveobj.ActiveTabUpdate{WorkspaceId: workspaceId, NewActiveTabId: newActiveTabId}, + }) +} + func UpdateWorkspaceTabIds(ctx context.Context, workspaceId string, tabIds []string) error { ws, _ := wstore.DBGet[*waveobj.Workspace](ctx, workspaceId) if ws == nil { diff --git a/pkg/wstore/wstore.go b/pkg/wstore/wstore.go index 2d724c757..508701a1c 100644 --- a/pkg/wstore/wstore.go +++ b/pkg/wstore/wstore.go @@ -7,7 +7,6 @@ import ( "context" "fmt" - "github.com/google/uuid" "github.com/wavetermdev/waveterm/pkg/util/utilfn" "github.com/wavetermdev/waveterm/pkg/waveobj" ) @@ -32,81 +31,6 @@ func UpdateTabName(ctx context.Context, tabId, name string) error { }) } -func CreateSubBlock(ctx context.Context, parentBlockId string, blockDef *waveobj.BlockDef) (*waveobj.Block, error) { - return WithTxRtn(ctx, func(tx *TxWrap) (*waveobj.Block, error) { - parentBlock, _ := DBGet[*waveobj.Block](tx.Context(), parentBlockId) - if parentBlock == nil { - return nil, fmt.Errorf("parent block not found: %q", parentBlockId) - } - blockId := uuid.NewString() - blockData := &waveobj.Block{ - OID: blockId, - ParentORef: waveobj.MakeORef(waveobj.OType_Block, parentBlockId).String(), - BlockDef: blockDef, - RuntimeOpts: nil, - Meta: blockDef.Meta, - } - DBInsert(tx.Context(), blockData) - parentBlock.SubBlockIds = append(parentBlock.SubBlockIds, blockId) - DBUpdate(tx.Context(), parentBlock) - return blockData, nil - }) -} - -func CreateBlock(ctx context.Context, tabId string, blockDef *waveobj.BlockDef, rtOpts *waveobj.RuntimeOpts) (*waveobj.Block, error) { - return WithTxRtn(ctx, func(tx *TxWrap) (*waveobj.Block, error) { - tab, _ := DBGet[*waveobj.Tab](tx.Context(), tabId) - if tab == nil { - return nil, fmt.Errorf("tab not found: %q", tabId) - } - blockId := uuid.NewString() - blockData := &waveobj.Block{ - OID: blockId, - ParentORef: waveobj.MakeORef(waveobj.OType_Tab, tabId).String(), - BlockDef: blockDef, - RuntimeOpts: rtOpts, - Meta: blockDef.Meta, - } - DBInsert(tx.Context(), blockData) - tab.BlockIds = append(tab.BlockIds, blockId) - DBUpdate(tx.Context(), tab) - return blockData, nil - }) -} - -func DeleteBlock(ctx context.Context, blockId string) error { - return WithTx(ctx, func(tx *TxWrap) error { - block, err := DBGet[*waveobj.Block](tx.Context(), blockId) - if err != nil { - return fmt.Errorf("error getting block: %w", err) - } - if block == nil { - return nil - } - if len(block.SubBlockIds) > 0 { - return fmt.Errorf("block has subblocks, must delete subblocks first") - } - parentORef := waveobj.ParseORefNoErr(block.ParentORef) - if parentORef != nil { - if parentORef.OType == waveobj.OType_Tab { - tab, _ := DBGet[*waveobj.Tab](tx.Context(), parentORef.OID) - if tab != nil { - tab.BlockIds = utilfn.RemoveElemFromSlice(tab.BlockIds, blockId) - DBUpdate(tx.Context(), tab) - } - } else if parentORef.OType == waveobj.OType_Block { - parentBlock, _ := DBGet[*waveobj.Block](tx.Context(), parentORef.OID) - if parentBlock != nil { - parentBlock.SubBlockIds = utilfn.RemoveElemFromSlice(parentBlock.SubBlockIds, blockId) - DBUpdate(tx.Context(), parentBlock) - } - } - } - DBDelete(tx.Context(), waveobj.OType_Block, blockId) - return nil - }) -} - // must delete all blocks individually first // also deletes LayoutState func DeleteTab(ctx context.Context, workspaceId string, tabId string) error {