Fix close active tab behavior (#1367)

This fixes a bug where closing the active tab would clean up the closed
tab view before switching to an un-closed tab view, putting the window
into an unrecoverable state. This also moves around some of the CloseTab
logic so that it's more standardized and reduces unnecessary
frontend-backend comms and DB writes
This commit is contained in:
Evan Simkowitz 2024-12-02 15:59:03 -05:00 committed by GitHub
parent cdc91529b5
commit f368a61c70
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 58 additions and 79 deletions

View File

@ -282,10 +282,11 @@ export class WaveBrowserWindow extends BaseWindow {
await this.queueTabSwitch(tabView, tabInitialized); await this.queueTabSwitch(tabView, tabInitialized);
} }
async setActiveTab(tabId: string) { async setActiveTab(tabId: string, setInBackend: boolean) {
console.log("setActiveTab", this); console.log("setActiveTab", tabId, this.waveWindowId, this.workspaceId, setInBackend);
const workspace = await WorkspaceService.GetWorkspace(this.workspaceId); if (setInBackend) {
await WorkspaceService.SetActiveTab(workspace.oid, tabId); await WorkspaceService.SetActiveTab(this.workspaceId, tabId);
}
const fullConfig = await FileService.GetFullConfig(); const fullConfig = await FileService.GetFullConfig();
const [tabView, tabInitialized] = getOrCreateWebViewForTab(fullConfig, tabId); const [tabView, tabInitialized] = getOrCreateWebViewForTab(fullConfig, tabId);
await this.queueTabSwitch(tabView, tabInitialized); await this.queueTabSwitch(tabView, tabInitialized);
@ -293,19 +294,20 @@ export class WaveBrowserWindow extends BaseWindow {
async createTab() { async createTab() {
const tabId = await WorkspaceService.CreateTab(this.workspaceId, null, true); const tabId = await WorkspaceService.CreateTab(this.workspaceId, null, true);
this.setActiveTab(tabId); await this.setActiveTab(tabId, false);
} }
async closeTab(tabId: string) { async closeTab(tabId: string) {
console.log("closeTab", tabId, this.waveWindowId, this.workspaceId);
const tabView = this.allTabViews.get(tabId); const tabView = this.allTabViews.get(tabId);
if (tabView) { if (tabView) {
const rtn = await WorkspaceService.CloseTab(this.workspaceId, tabId, true); const rtn = await WorkspaceService.CloseTab(this.workspaceId, tabId, true);
this.allTabViews.delete(tabId);
if (rtn?.closewindow) { if (rtn?.closewindow) {
this.close(); this.close();
} else if (rtn?.newactivetabid) { } else if (rtn?.newactivetabid) {
this.setActiveTab(rtn.newactivetabid); await this.setActiveTab(rtn.newactivetabid, false);
} }
this.allTabViews.delete(tabId);
} }
} }
@ -519,7 +521,7 @@ export async function createBrowserWindow(
console.log("createBrowserWindow", waveWindow.oid, workspace.oid, workspace); console.log("createBrowserWindow", waveWindow.oid, workspace.oid, workspace);
const bwin = new WaveBrowserWindow(waveWindow, fullConfig, opts); const bwin = new WaveBrowserWindow(waveWindow, fullConfig, opts);
if (workspace.activetabid) { if (workspace.activetabid) {
await bwin.setActiveTab(workspace.activetabid); await bwin.setActiveTab(workspace.activetabid, false);
} }
return bwin; return bwin;
} }
@ -527,7 +529,7 @@ export async function createBrowserWindow(
ipcMain.on("set-active-tab", async (event, tabId) => { ipcMain.on("set-active-tab", async (event, tabId) => {
const ww = getWaveWindowByWebContentsId(event.sender.id); const ww = getWaveWindowByWebContentsId(event.sender.id);
console.log("set-active-tab", tabId, ww?.waveWindowId); console.log("set-active-tab", tabId, ww?.waveWindowId);
await ww?.setActiveTab(tabId); await ww?.setActiveTab(tabId, true);
}); });
ipcMain.on("create-tab", async (event, opts) => { ipcMain.on("create-tab", async (event, opts) => {

View File

@ -164,54 +164,28 @@ func (svc *WorkspaceService) CloseTab(ctx context.Context, workspaceId string, t
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("error getting tab: %w", err) return nil, nil, fmt.Errorf("error getting tab: %w", err)
} }
ws, err := wstore.DBMustGet[*waveobj.Workspace](ctx, workspaceId)
if err != nil {
return nil, nil, fmt.Errorf("error getting workspace: %w", err)
}
tabIndex := -1
for i, id := range ws.TabIds {
if id == tabId {
tabIndex = i
break
}
}
go func() { go func() {
for _, blockId := range tab.BlockIds { for _, blockId := range tab.BlockIds {
blockcontroller.StopBlockController(blockId) blockcontroller.StopBlockController(blockId)
} }
}() }()
if err := wcore.DeleteTab(ctx, workspaceId, tabId); err != nil { newActiveTabId, err := wcore.DeleteTab(ctx, workspaceId, tabId)
if err != nil {
return nil, nil, fmt.Errorf("error closing tab: %w", err) return nil, nil, fmt.Errorf("error closing tab: %w", err)
} }
rtn := &CloseTabRtnType{} rtn := &CloseTabRtnType{}
if ws.ActiveTabId == tabId && tabIndex != -1 { if newActiveTabId == "" {
if len(ws.TabIds) == 1 { windowId, err := wstore.DBFindWindowForWorkspaceId(ctx, workspaceId)
windowId, err := wstore.DBFindWindowForWorkspaceId(ctx, workspaceId) if err != nil {
if err != nil { return rtn, nil, fmt.Errorf("unable to find window for workspace id %v: %w", workspaceId, err)
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 {
if tabIndex < len(ws.TabIds)-1 {
newActiveTabId := ws.TabIds[tabIndex+1]
err := wcore.SetActiveTab(ctx, ws.OID, newActiveTabId)
if err != nil {
return rtn, nil, err
}
rtn.NewActiveTabId = newActiveTabId
} else {
newActiveTabId := ws.TabIds[tabIndex-1]
err := wcore.SetActiveTab(ctx, ws.OID, newActiveTabId)
if err != nil {
return rtn, nil, err
}
rtn.NewActiveTabId = newActiveTabId
}
} }
rtn.CloseWindow = true
err = wcore.CloseWindow(ctx, windowId, fromElectron)
if err != nil {
return rtn, nil, err
}
} else {
rtn.NewActiveTabId = newActiveTabId
} }
updates := waveobj.ContextGetUpdatesRtn(ctx) updates := waveobj.ContextGetUpdatesRtn(ctx)
go func() { go func() {

View File

@ -108,7 +108,7 @@ func CreateWindow(ctx context.Context, winSize *waveobj.WinSize, workspaceId str
if err != nil { if err != nil {
return nil, fmt.Errorf("error inserting window: %w", err) return nil, fmt.Errorf("error inserting window: %w", err)
} }
client, err := wstore.DBGetSingleton[*waveobj.Client](ctx) client, err := GetClientData(ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting client: %w", err) return nil, fmt.Errorf("error getting client: %w", err)
} }
@ -117,12 +117,12 @@ func CreateWindow(ctx context.Context, winSize *waveobj.WinSize, workspaceId str
if err != nil { if err != nil {
return nil, fmt.Errorf("error updating client: %w", err) return nil, fmt.Errorf("error updating client: %w", err)
} }
return wstore.DBMustGet[*waveobj.Window](ctx, windowId) return GetWindow(ctx, windowId)
} }
func CloseWindow(ctx context.Context, windowId string, fromElectron bool) error { func CloseWindow(ctx context.Context, windowId string, fromElectron bool) error {
log.Printf("CloseWindow %s\n", windowId) log.Printf("CloseWindow %s\n", windowId)
window, err := wstore.DBMustGet[*waveobj.Window](ctx, windowId) window, err := GetWindow(ctx, windowId)
if err == nil { if err == nil {
log.Printf("got window %s\n", windowId) log.Printf("got window %s\n", windowId)
deleted, err := DeleteWorkspace(ctx, window.WorkspaceId, false) deleted, err := DeleteWorkspace(ctx, window.WorkspaceId, false)

View File

@ -39,7 +39,7 @@ func DeleteWorkspace(ctx context.Context, workspaceId string, force bool) (bool,
} }
for _, tabId := range workspace.TabIds { for _, tabId := range workspace.TabIds {
log.Printf("deleting tab %s\n", tabId) log.Printf("deleting tab %s\n", tabId)
err := DeleteTab(ctx, workspaceId, tabId) _, err := DeleteTab(ctx, workspaceId, tabId)
if err != nil { if err != nil {
return false, fmt.Errorf("error closing tab: %w", err) return false, fmt.Errorf("error closing tab: %w", err)
} }
@ -57,9 +57,9 @@ func GetWorkspace(ctx context.Context, wsID string) (*waveobj.Workspace, error)
} }
func createTabObj(ctx context.Context, workspaceId string, name string) (*waveobj.Tab, error) { func createTabObj(ctx context.Context, workspaceId string, name string) (*waveobj.Tab, error) {
ws, _ := wstore.DBGet[*waveobj.Workspace](ctx, workspaceId) ws, err := GetWorkspace(ctx, workspaceId)
if ws == nil { if err != nil {
return nil, fmt.Errorf("workspace not found: %q", workspaceId) return nil, fmt.Errorf("workspace %s not found: %w", workspaceId, err)
} }
layoutStateId := uuid.NewString() layoutStateId := uuid.NewString()
tab := &waveobj.Tab{ tab := &waveobj.Tab{
@ -81,19 +81,11 @@ func createTabObj(ctx context.Context, workspaceId string, name string) (*waveob
// returns tabid // returns tabid
func CreateTab(ctx context.Context, workspaceId string, tabName string, activateTab bool) (string, error) { func CreateTab(ctx context.Context, workspaceId string, tabName string, activateTab bool) (string, error) {
if tabName == "" { if tabName == "" {
client, err := wstore.DBGetSingleton[*waveobj.Client](ctx) ws, err := GetWorkspace(ctx, workspaceId)
if err != nil { if err != nil {
return "", fmt.Errorf("error getting client: %w", err) return "", fmt.Errorf("workspace %s not found: %w", workspaceId, err)
}
ws, _ := wstore.DBGet[*waveobj.Workspace](ctx, workspaceId)
if ws == nil {
return "", fmt.Errorf("workspace not found: %q", workspaceId)
} }
tabName = "T" + fmt.Sprint(len(ws.TabIds)+1) tabName = "T" + fmt.Sprint(len(ws.TabIds)+1)
err = wstore.DBUpdate(ctx, client)
if err != nil {
return "", fmt.Errorf("error updating client: %w", err)
}
} }
tab, err := createTabObj(ctx, workspaceId, tabName) tab, err := createTabObj(ctx, workspaceId, tabName)
if err != nil { if err != nil {
@ -109,49 +101,60 @@ func CreateTab(ctx context.Context, workspaceId string, tabName string, activate
return tab.OID, nil return tab.OID, nil
} }
// must delete all blocks individually first // Must delete all blocks individually first.
// also deletes LayoutState // Also deletes LayoutState.
func DeleteTab(ctx context.Context, workspaceId string, tabId string) error { // Returns new active tab id, error.
func DeleteTab(ctx context.Context, workspaceId string, tabId string) (string, error) {
ws, _ := wstore.DBGet[*waveobj.Workspace](ctx, workspaceId) ws, _ := wstore.DBGet[*waveobj.Workspace](ctx, workspaceId)
if ws == nil { if ws == nil {
return fmt.Errorf("workspace not found: %q", workspaceId) return "", fmt.Errorf("workspace not found: %q", workspaceId)
} }
tab, _ := wstore.DBGet[*waveobj.Tab](ctx, tabId) tab, _ := wstore.DBGet[*waveobj.Tab](ctx, tabId)
if tab == nil { if tab == nil {
return fmt.Errorf("tab not found: %q", tabId) return "", fmt.Errorf("tab not found: %q", tabId)
} }
// close blocks (sends events + stops block controllers) // close blocks (sends events + stops block controllers)
for _, blockId := range tab.BlockIds { for _, blockId := range tab.BlockIds {
err := DeleteBlock(ctx, blockId) err := DeleteBlock(ctx, blockId)
if err != nil { if err != nil {
return fmt.Errorf("error deleting block %s: %w", blockId, err) return "", fmt.Errorf("error deleting block %s: %w", blockId, err)
} }
} }
tabIdx := utilfn.FindStringInSlice(ws.TabIds, tabId) tabIdx := utilfn.FindStringInSlice(ws.TabIds, tabId)
if tabIdx == -1 { if tabIdx == -1 {
return nil return "", nil
} }
ws.TabIds = append(ws.TabIds[:tabIdx], ws.TabIds[tabIdx+1:]...) ws.TabIds = append(ws.TabIds[:tabIdx], ws.TabIds[tabIdx+1:]...)
newActiveTabId := ws.ActiveTabId
if len(ws.TabIds) > 0 {
if ws.ActiveTabId == tabId {
newActiveTabId = ws.TabIds[max(0, min(tabIdx-1, len(ws.TabIds)-1))]
}
} else {
newActiveTabId = ""
}
ws.ActiveTabId = newActiveTabId
wstore.DBUpdate(ctx, ws) wstore.DBUpdate(ctx, ws)
wstore.DBDelete(ctx, waveobj.OType_Tab, tabId) wstore.DBDelete(ctx, waveobj.OType_Tab, tabId)
wstore.DBDelete(ctx, waveobj.OType_LayoutState, tab.LayoutState) wstore.DBDelete(ctx, waveobj.OType_LayoutState, tab.LayoutState)
return nil return newActiveTabId, nil
} }
func SetActiveTab(ctx context.Context, workspaceId string, tabId string) error { func SetActiveTab(ctx context.Context, workspaceId string, tabId string) error {
workspace, _ := wstore.DBGet[*waveobj.Workspace](ctx, workspaceId)
if workspace == nil {
return fmt.Errorf("workspace not found: %q", workspaceId)
}
if tabId != "" { if tabId != "" {
workspace, err := GetWorkspace(ctx, workspaceId)
if err != nil {
return fmt.Errorf("workspace %s not found: %w", workspaceId, err)
}
tab, _ := wstore.DBGet[*waveobj.Tab](ctx, tabId) tab, _ := wstore.DBGet[*waveobj.Tab](ctx, tabId)
if tab == nil { if tab == nil {
return fmt.Errorf("tab not found: %q", tabId) return fmt.Errorf("tab not found: %q", tabId)
} }
workspace.ActiveTabId = tabId
wstore.DBUpdate(ctx, workspace)
} }
workspace.ActiveTabId = tabId
wstore.DBUpdate(ctx, workspace)
return nil return nil
} }