2024-12-02 19:56:56 +01:00
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
2024-12-07 00:33:00 +01:00
import { ClientService , FileService , ObjectService , WindowService , WorkspaceService } from "@/app/store/services" ;
2024-12-02 19:56:56 +01:00
import { fireAndForget } from "@/util/util" ;
import { BaseWindow , BaseWindowConstructorOptions , dialog , ipcMain , screen } from "electron" ;
import path from "path" ;
import { debounce } from "throttle-debounce" ;
2024-12-07 00:33:00 +01:00
import {
getGlobalIsQuitting ,
getGlobalIsRelaunching ,
setGlobalIsRelaunching ,
setWasActive ,
setWasInFg ,
} from "./emain-activity" ;
2024-12-02 19:56:56 +01:00
import { getOrCreateWebViewForTab , getWaveTabViewByWebContentsId , WaveTabView } from "./emain-tabview" ;
import { delay , ensureBoundsAreVisible } from "./emain-util" ;
2024-12-07 00:33:00 +01:00
import { log } from "./log" ;
2024-12-02 19:56:56 +01:00
import { getElectronAppBasePath , unamePlatform } from "./platform" ;
import { updater } from "./updater" ;
export type WindowOpts = {
unamePlatform : string ;
} ;
export const waveWindowMap = new Map < string , WaveBrowserWindow > ( ) ; // waveWindowId -> WaveBrowserWindow
2024-12-10 19:58:50 +01:00
// on blur we do not set this to null (but on destroy we do), so this tracks the *last* focused window
// e.g. it persists when the app itself is not focused
export let focusedWaveWindow : WaveBrowserWindow = null ;
2024-12-02 19:56:56 +01:00
2024-12-06 09:10:17 +01:00
let cachedClientId : string = null ;
async function getClientId() {
if ( cachedClientId != null ) {
return cachedClientId ;
}
const clientData = await ClientService . GetClientData ( ) ;
cachedClientId = clientData ? . oid ;
return cachedClientId ;
}
2024-12-10 00:47:56 +01:00
type WindowActionQueueEntry =
2024-12-06 21:00:24 +01:00
| {
2024-12-10 00:47:56 +01:00
op : "switchtab" ;
2024-12-06 21:00:24 +01:00
tabId : string ;
setInBackend : boolean ;
}
| {
2024-12-10 00:47:56 +01:00
op : "createtab" ;
2024-12-06 21:00:24 +01:00
pinned : boolean ;
2024-12-07 00:42:29 +01:00
}
| {
2024-12-10 00:47:56 +01:00
op : "closetab" ;
2024-12-07 00:42:29 +01:00
tabId : string ;
2024-12-10 00:47:56 +01:00
}
| {
op : "switchworkspace" ;
workspaceId : string ;
2024-12-06 21:00:24 +01:00
} ;
2024-12-13 02:21:09 +01:00
function isNonEmptyUnsavedWorkspace ( workspace : Workspace ) : boolean {
2024-12-10 19:15:33 +01:00
return ! workspace . name && ! workspace . icon && ( workspace . tabids ? . length > 1 || workspace . pinnedtabids ? . length > 1 ) ;
}
2024-12-02 19:56:56 +01:00
export class WaveBrowserWindow extends BaseWindow {
waveWindowId : string ;
workspaceId : string ;
2024-12-06 03:26:20 +01:00
allLoadedTabViews : Map < string , WaveTabView > ;
2024-12-02 19:56:56 +01:00
activeTabView : WaveTabView ;
private canClose : boolean ;
private deleteAllowed : boolean ;
2024-12-10 00:47:56 +01:00
private actionQueue : WindowActionQueueEntry [ ] ;
2024-12-02 19:56:56 +01:00
constructor ( waveWindow : WaveWindow , fullConfig : FullConfigType , opts : WindowOpts ) {
console . log ( "create win" , waveWindow . oid ) ;
let winWidth = waveWindow ? . winsize ? . width ;
let winHeight = waveWindow ? . winsize ? . height ;
let winPosX = waveWindow . pos . x ;
let winPosY = waveWindow . pos . y ;
if ( winWidth == null || winWidth == 0 ) {
const primaryDisplay = screen . getPrimaryDisplay ( ) ;
const { width } = primaryDisplay . workAreaSize ;
winWidth = width - winPosX - 100 ;
if ( winWidth > 2000 ) {
winWidth = 2000 ;
}
}
if ( winHeight == null || winHeight == 0 ) {
const primaryDisplay = screen . getPrimaryDisplay ( ) ;
const { height } = primaryDisplay . workAreaSize ;
winHeight = height - winPosY - 100 ;
if ( winHeight > 1200 ) {
winHeight = 1200 ;
}
}
let winBounds = {
x : winPosX ,
y : winPosY ,
width : winWidth ,
height : winHeight ,
} ;
winBounds = ensureBoundsAreVisible ( winBounds ) ;
const settings = fullConfig ? . settings ;
const winOpts : BaseWindowConstructorOptions = {
titleBarStyle :
opts . unamePlatform === "darwin"
? "hiddenInset"
: settings [ "window:nativetitlebar" ]
? "default"
: "hidden" ,
titleBarOverlay :
opts . unamePlatform !== "darwin"
? {
symbolColor : "white" ,
color : "#00000000" ,
}
: false ,
x : winBounds.x ,
y : winBounds.y ,
width : winBounds.width ,
height : winBounds.height ,
minWidth : 400 ,
minHeight : 300 ,
icon :
opts . unamePlatform == "linux"
? path . join ( getElectronAppBasePath ( ) , "public/logos/wave-logo-dark.png" )
: undefined ,
show : false ,
autoHideMenuBar : ! settings ? . [ "window:showmenubar" ] ,
} ;
const isTransparent = settings ? . [ "window:transparent" ] ? ? false ;
const isBlur = ! isTransparent && ( settings ? . [ "window:blur" ] ? ? false ) ;
if ( isTransparent ) {
winOpts . transparent = true ;
} else if ( isBlur ) {
switch ( opts . unamePlatform ) {
case "win32" : {
winOpts . backgroundMaterial = "acrylic" ;
break ;
}
case "darwin" : {
winOpts . vibrancy = "fullscreen-ui" ;
break ;
}
}
} else {
winOpts . backgroundColor = "#222222" ;
}
super ( winOpts ) ;
2024-12-10 00:47:56 +01:00
this . actionQueue = [ ] ;
2024-12-02 19:56:56 +01:00
this . waveWindowId = waveWindow . oid ;
this . workspaceId = waveWindow . workspaceid ;
2024-12-06 03:26:20 +01:00
this . allLoadedTabViews = new Map < string , WaveTabView > ( ) ;
2024-12-02 19:56:56 +01:00
const winBoundsPoller = setInterval ( ( ) = > {
if ( this . isDestroyed ( ) ) {
clearInterval ( winBoundsPoller ) ;
return ;
}
2024-12-10 00:47:56 +01:00
if ( this . actionQueue . length > 0 ) {
2024-12-02 19:56:56 +01:00
return ;
}
this . finalizePositioning ( ) ;
} , 1000 ) ;
this . on (
// @ts-expect-error
"resize" ,
debounce ( 400 , ( e ) = > this . mainResizeHandler ( e ) )
) ;
this . on ( "resize" , ( ) = > {
if ( this . isDestroyed ( ) ) {
return ;
}
this . activeTabView ? . positionTabOnScreen ( this . getContentBounds ( ) ) ;
} ) ;
this . on (
// @ts-expect-error
"move" ,
debounce ( 400 , ( e ) = > this . mainResizeHandler ( e ) )
) ;
this . on ( "enter-full-screen" , async ( ) = > {
if ( this . isDestroyed ( ) ) {
return ;
}
console . log ( "enter-full-screen event" , this . getContentBounds ( ) ) ;
const tabView = this . activeTabView ;
if ( tabView ) {
tabView . webContents . send ( "fullscreen-change" , true ) ;
}
this . activeTabView ? . positionTabOnScreen ( this . getContentBounds ( ) ) ;
} ) ;
this . on ( "leave-full-screen" , async ( ) = > {
if ( this . isDestroyed ( ) ) {
return ;
}
const tabView = this . activeTabView ;
if ( tabView ) {
tabView . webContents . send ( "fullscreen-change" , false ) ;
}
this . activeTabView ? . positionTabOnScreen ( this . getContentBounds ( ) ) ;
} ) ;
this . on ( "focus" , ( ) = > {
if ( this . isDestroyed ( ) ) {
return ;
}
if ( getGlobalIsRelaunching ( ) ) {
return ;
}
focusedWaveWindow = this ;
console . log ( "focus win" , this . waveWindowId ) ;
2024-12-06 03:09:54 +01:00
fireAndForget ( ( ) = > ClientService . FocusWindow ( this . waveWindowId ) ) ;
2024-12-02 19:56:56 +01:00
setWasInFg ( true ) ;
setWasActive ( true ) ;
} ) ;
this . on ( "blur" , ( ) = > {
2024-12-10 19:58:50 +01:00
// nothing for now
2024-12-02 19:56:56 +01:00
} ) ;
this . on ( "close" , ( e ) = > {
if ( this . canClose ) {
return ;
}
if ( this . isDestroyed ( ) ) {
return ;
}
console . log ( "win 'close' handler fired" , this . waveWindowId ) ;
if ( getGlobalIsQuitting ( ) || updater ? . status == "installing" || getGlobalIsRelaunching ( ) ) {
return ;
}
e . preventDefault ( ) ;
fireAndForget ( async ( ) = > {
const numWindows = waveWindowMap . size ;
if ( numWindows > 1 ) {
console . log ( "numWindows > 1" , numWindows ) ;
const workspace = await WorkspaceService . GetWorkspace ( this . workspaceId ) ;
console . log ( "workspace" , workspace ) ;
2024-12-13 02:21:09 +01:00
if ( isNonEmptyUnsavedWorkspace ( workspace ) ) {
2024-12-02 19:56:56 +01:00
console . log ( "workspace has no name, icon, and multiple tabs" , workspace ) ;
const choice = dialog . showMessageBoxSync ( this , {
type : "question" ,
2024-12-10 19:15:33 +01:00
buttons : [ "Cancel" , "Close Window" ] ,
2024-12-02 19:56:56 +01:00
title : "Confirm" ,
2024-12-10 19:15:33 +01:00
message : "Window has unsaved tabs, closing window will delete existing tabs.\n\nContinue?" ,
2024-12-02 19:56:56 +01:00
} ) ;
if ( choice === 0 ) {
console . log ( "user cancelled close window" , this . waveWindowId ) ;
return ;
}
}
console . log ( "deleteAllowed = true" , this . waveWindowId ) ;
this . deleteAllowed = true ;
}
console . log ( "canClose = true" , this . waveWindowId ) ;
this . canClose = true ;
this . close ( ) ;
} ) ;
} ) ;
this . on ( "closed" , ( ) = > {
console . log ( "win 'closed' handler fired" , this . waveWindowId ) ;
if ( getGlobalIsQuitting ( ) || updater ? . status == "installing" ) {
console . log ( "win quitting or updating" , this . waveWindowId ) ;
return ;
}
2024-12-07 00:42:29 +01:00
waveWindowMap . delete ( this . waveWindowId ) ;
if ( focusedWaveWindow == this ) {
focusedWaveWindow = null ;
}
this . removeAllChildViews ( ) ;
2024-12-02 19:56:56 +01:00
if ( getGlobalIsRelaunching ( ) ) {
console . log ( "win relaunching" , this . waveWindowId ) ;
this . destroy ( ) ;
return ;
}
const numWindows = waveWindowMap . size ;
if ( numWindows == 0 ) {
console . log ( "win no windows left" , this . waveWindowId ) ;
return ;
}
if ( this . deleteAllowed ) {
console . log ( "win removing window from backend DB" , this . waveWindowId ) ;
2024-12-06 03:09:54 +01:00
fireAndForget ( ( ) = > WindowService . CloseWindow ( this . waveWindowId , true ) ) ;
2024-12-02 19:56:56 +01:00
}
} ) ;
waveWindowMap . set ( waveWindow . oid , this ) ;
}
2024-12-10 00:47:56 +01:00
private removeAllChildViews() {
2024-12-07 00:42:29 +01:00
for ( const tabView of this . allLoadedTabViews . values ( ) ) {
if ( ! this . isDestroyed ( ) ) {
this . contentView . removeChildView ( tabView ) ;
}
tabView ? . destroy ( ) ;
}
}
2024-12-02 19:56:56 +01:00
async switchWorkspace ( workspaceId : string ) {
2024-12-02 23:52:34 +01:00
console . log ( "switchWorkspace" , workspaceId , this . waveWindowId ) ;
2024-12-07 00:33:00 +01:00
if ( workspaceId == this . workspaceId ) {
console . log ( "switchWorkspace already on this workspace" , this . waveWindowId ) ;
return ;
}
2024-12-07 01:35:01 +01:00
// If the workspace is already owned by a window, then we can just call SwitchWorkspace without first prompting the user, since it'll just focus to the other window.
const workspaceList = await WorkspaceService . ListWorkspaces ( ) ;
2024-12-13 00:16:34 +01:00
if ( ! workspaceList ? . find ( ( wse ) = > wse . workspaceid === workspaceId ) ? . windowid ) {
2024-12-07 01:35:01 +01:00
const curWorkspace = await WorkspaceService . GetWorkspace ( this . workspaceId ) ;
2024-12-13 02:21:09 +01:00
if ( isNonEmptyUnsavedWorkspace ( curWorkspace ) ) {
console . log (
` existing unsaved workspace ${ this . workspaceId } has content, opening workspace ${ workspaceId } in new window `
) ;
await createWindowForWorkspace ( workspaceId ) ;
return ;
2024-12-02 19:56:56 +01:00
}
}
2024-12-10 00:47:56 +01:00
await this . _queueActionInternal ( { op : "switchworkspace" , workspaceId } ) ;
2024-12-02 19:56:56 +01:00
}
2024-12-02 21:59:03 +01:00
async setActiveTab ( tabId : string , setInBackend : boolean ) {
console . log ( "setActiveTab" , tabId , this . waveWindowId , this . workspaceId , setInBackend ) ;
2024-12-10 00:47:56 +01:00
await this . _queueActionInternal ( { op : "switchtab" , tabId , setInBackend } ) ;
2024-12-02 19:56:56 +01:00
}
2024-12-10 00:47:56 +01:00
private async initializeTab ( tabView : WaveTabView ) {
2024-12-06 09:10:17 +01:00
const clientId = await getClientId ( ) ;
await tabView . initPromise ;
this . contentView . addChildView ( tabView ) ;
const initOpts = {
tabId : tabView.waveTabId ,
clientId : clientId ,
windowId : this.waveWindowId ,
activate : true ,
} ;
tabView . savedInitOpts = { . . . initOpts } ;
tabView . savedInitOpts . activate = false ;
let startTime = Date . now ( ) ;
console . log ( "before wave ready, init tab, sending wave-init" , tabView . waveTabId ) ;
tabView . webContents . send ( "wave-init" , initOpts ) ;
await tabView . waveReadyPromise ;
console . log ( "wave-ready init time" , Date . now ( ) - startTime + "ms" ) ;
}
2024-12-10 00:47:56 +01:00
private async setTabViewIntoWindow ( tabView : WaveTabView , tabInitialized : boolean ) {
2024-12-02 19:56:56 +01:00
if ( this . activeTabView == tabView ) {
return ;
}
const oldActiveView = this . activeTabView ;
tabView . isActiveTab = true ;
if ( oldActiveView != null ) {
oldActiveView . isActiveTab = false ;
}
this . activeTabView = tabView ;
2024-12-06 03:26:20 +01:00
this . allLoadedTabViews . set ( tabView . waveTabId , tabView ) ;
2024-12-02 19:56:56 +01:00
if ( ! tabInitialized ) {
console . log ( "initializing a new tab" ) ;
2024-12-06 09:10:17 +01:00
const p1 = this . initializeTab ( tabView ) ;
const p2 = this . repositionTabsSlowly ( 100 ) ;
await Promise . all ( [ p1 , p2 ] ) ;
2024-12-02 19:56:56 +01:00
} else {
2024-12-06 09:10:17 +01:00
console . log ( "reusing an existing tab, calling wave-init" , tabView . waveTabId ) ;
2024-12-02 19:56:56 +01:00
const p1 = this . repositionTabsSlowly ( 35 ) ;
const p2 = tabView . webContents . send ( "wave-init" , tabView . savedInitOpts ) ; // reinit
await Promise . all ( [ p1 , p2 ] ) ;
}
// something is causing the new tab to lose focus so it requires manual refocusing
tabView . webContents . focus ( ) ;
setTimeout ( ( ) = > {
2024-12-10 00:47:56 +01:00
if ( tabView . webContents && this . activeTabView == tabView && ! tabView . webContents . isFocused ( ) ) {
2024-12-02 19:56:56 +01:00
tabView . webContents . focus ( ) ;
}
} , 10 ) ;
setTimeout ( ( ) = > {
2024-12-10 00:47:56 +01:00
if ( tabView . webContents && this . activeTabView == tabView && ! tabView . webContents . isFocused ( ) ) {
2024-12-02 19:56:56 +01:00
tabView . webContents . focus ( ) ;
}
} , 30 ) ;
}
2024-12-10 00:47:56 +01:00
private async repositionTabsSlowly ( delayMs : number ) {
2024-12-02 19:56:56 +01:00
const activeTabView = this . activeTabView ;
const winBounds = this . getContentBounds ( ) ;
if ( activeTabView == null ) {
return ;
}
if ( activeTabView . isOnScreen ( ) ) {
activeTabView . setBounds ( {
x : 0 ,
y : 0 ,
width : winBounds.width ,
height : winBounds.height ,
} ) ;
} else {
activeTabView . setBounds ( {
x : winBounds.width - 10 ,
y : winBounds.height - 10 ,
width : winBounds.width ,
height : winBounds.height ,
} ) ;
}
await delay ( delayMs ) ;
if ( this . activeTabView != activeTabView ) {
// another tab view has been set, do not finalize this layout
return ;
}
this . finalizePositioning ( ) ;
}
2024-12-10 00:47:56 +01:00
private finalizePositioning() {
2024-12-02 19:56:56 +01:00
if ( this . isDestroyed ( ) ) {
return ;
}
const curBounds = this . getContentBounds ( ) ;
this . activeTabView ? . positionTabOnScreen ( curBounds ) ;
2024-12-06 03:26:20 +01:00
for ( const tabView of this . allLoadedTabViews . values ( ) ) {
2024-12-02 19:56:56 +01:00
if ( tabView == this . activeTabView ) {
continue ;
}
tabView ? . positionTabOffScreen ( curBounds ) ;
}
}
2024-12-06 21:00:24 +01:00
async queueCreateTab ( pinned = false ) {
2024-12-10 00:47:56 +01:00
await this . _queueActionInternal ( { op : "createtab" , pinned } ) ;
2024-12-07 00:42:29 +01:00
}
async queueCloseTab ( tabId : string ) {
2024-12-10 00:47:56 +01:00
await this . _queueActionInternal ( { op : "closetab" , tabId } ) ;
2024-12-06 21:00:24 +01:00
}
2024-12-10 00:47:56 +01:00
private async _queueActionInternal ( entry : WindowActionQueueEntry ) {
if ( this . actionQueue . length >= 2 ) {
this . actionQueue [ 1 ] = entry ;
2024-12-02 19:56:56 +01:00
return ;
}
2024-12-10 00:47:56 +01:00
const wasEmpty = this . actionQueue . length === 0 ;
this . actionQueue . push ( entry ) ;
2024-12-06 09:10:17 +01:00
if ( wasEmpty ) {
2024-12-10 00:47:56 +01:00
await this . processActionQueue ( ) ;
2024-12-02 19:56:56 +01:00
}
}
2024-12-10 00:47:56 +01:00
private removeTabViewLater ( tabId : string , delayMs : number ) {
2024-12-07 00:42:29 +01:00
setTimeout ( ( ) = > {
this . removeTabView ( tabId , false ) ;
} , 1000 ) ;
}
2024-12-10 00:47:56 +01:00
// the queue and this function are used to serialize operations that update the window contents view
// processActionQueue will replace [1] if it is already set
2024-12-06 09:10:17 +01:00
// we don't mess with [0] because it is "in process"
2024-12-10 00:47:56 +01:00
// we replace [1] because there is no point to run an action that is going to be overwritten
private async processActionQueue() {
while ( this . actionQueue . length > 0 ) {
2024-12-06 09:10:17 +01:00
try {
2024-12-10 19:58:50 +01:00
if ( this . isDestroyed ( ) ) {
break ;
}
2024-12-10 00:47:56 +01:00
const entry = this . actionQueue [ 0 ] ;
2024-12-06 21:00:24 +01:00
let tabId : string = null ;
// have to use "===" here to get the typechecker to work :/
2024-12-10 00:47:56 +01:00
switch ( entry . op ) {
case "createtab" :
tabId = await WorkspaceService . CreateTab ( this . workspaceId , null , true , entry . pinned ) ;
break ;
case "switchtab" :
tabId = entry . tabId ;
if ( this . activeTabView ? . waveTabId == tabId ) {
continue ;
}
if ( entry . setInBackend ) {
await WorkspaceService . SetActiveTab ( this . workspaceId , tabId ) ;
}
break ;
case "closetab" :
tabId = entry . tabId ;
const rtn = await WorkspaceService . CloseTab ( this . workspaceId , tabId , true ) ;
if ( rtn == null ) {
console . log (
"[error] closeTab: no return value" ,
tabId ,
this . workspaceId ,
this . waveWindowId
) ;
return ;
}
this . removeTabViewLater ( tabId , 1000 ) ;
if ( rtn . closewindow ) {
this . close ( ) ;
return ;
}
if ( ! rtn . newactivetabid ) {
return ;
}
tabId = rtn . newactivetabid ;
break ;
case "switchworkspace" :
const newWs = await WindowService . SwitchWorkspace ( this . waveWindowId , entry . workspaceId ) ;
if ( ! newWs ) {
return ;
}
console . log ( "processActionQueue switchworkspace newWs" , newWs ) ;
this . removeAllChildViews ( ) ;
console . log ( "destroyed all tabs" , this . waveWindowId ) ;
this . workspaceId = entry . workspaceId ;
this . allLoadedTabViews = new Map ( ) ;
tabId = newWs . activetabid ;
break ;
2024-12-06 09:10:17 +01:00
}
2024-12-06 21:00:24 +01:00
if ( tabId == null ) {
return ;
2024-12-06 09:10:17 +01:00
}
2024-12-07 00:42:29 +01:00
const [ tabView , tabInitialized ] = await getOrCreateWebViewForTab ( this . waveWindowId , tabId ) ;
2024-12-06 09:10:17 +01:00
await this . setTabViewIntoWindow ( tabView , tabInitialized ) ;
} catch ( e ) {
2024-12-10 00:47:56 +01:00
console . log ( "error caught in processActionQueue" , e ) ;
2024-12-06 09:10:17 +01:00
} finally {
2024-12-10 00:47:56 +01:00
this . actionQueue . shift ( ) ;
2024-12-06 09:10:17 +01:00
}
2024-12-02 19:56:56 +01:00
}
}
2024-12-10 00:47:56 +01:00
private async mainResizeHandler ( _ : any ) {
2024-12-02 19:56:56 +01:00
if ( this == null || this . isDestroyed ( ) || this . fullScreen ) {
return ;
}
const bounds = this . getBounds ( ) ;
try {
await WindowService . SetWindowPosAndSize (
this . waveWindowId ,
{ x : bounds.x , y : bounds.y } ,
{ width : bounds.width , height : bounds.height }
) ;
} catch ( e ) {
console . log ( "error sending new window bounds to backend" , e ) ;
}
}
2024-12-07 00:42:29 +01:00
removeTabView ( tabId : string , force : boolean ) {
if ( ! force && this . activeTabView ? . waveTabId == tabId ) {
console . log ( "cannot remove active tab" , tabId , this . waveWindowId ) ;
return ;
}
const tabView = this . allLoadedTabViews . get ( tabId ) ;
if ( tabView == null ) {
console . log ( "removeTabView -- tabView not found" , tabId , this . waveWindowId ) ;
// the tab was never loaded, so just return
return ;
}
this . contentView . removeChildView ( tabView ) ;
this . allLoadedTabViews . delete ( tabId ) ;
tabView . destroy ( ) ;
}
2024-12-02 19:56:56 +01:00
destroy() {
console . log ( "destroy win" , this . waveWindowId ) ;
2024-12-06 01:18:42 +01:00
this . deleteAllowed = true ;
2024-12-02 19:56:56 +01:00
super . destroy ( ) ;
}
}
export function getWaveWindowByTabId ( tabId : string ) : WaveBrowserWindow {
for ( const ww of waveWindowMap . values ( ) ) {
2024-12-06 03:26:20 +01:00
if ( ww . allLoadedTabViews . has ( tabId ) ) {
2024-12-02 19:56:56 +01:00
return ww ;
}
}
}
export function getWaveWindowByWebContentsId ( webContentsId : number ) : WaveBrowserWindow {
const tabView = getWaveTabViewByWebContentsId ( webContentsId ) ;
if ( tabView == null ) {
return null ;
}
return getWaveWindowByTabId ( tabView . waveTabId ) ;
}
export function getWaveWindowById ( windowId : string ) : WaveBrowserWindow {
return waveWindowMap . get ( windowId ) ;
}
export function getWaveWindowByWorkspaceId ( workspaceId : string ) : WaveBrowserWindow {
for ( const waveWindow of waveWindowMap . values ( ) ) {
if ( waveWindow . workspaceId === workspaceId ) {
return waveWindow ;
}
}
}
export function getAllWaveWindows ( ) : WaveBrowserWindow [ ] {
return Array . from ( waveWindowMap . values ( ) ) ;
}
2024-12-13 02:21:09 +01:00
export async function createWindowForWorkspace ( workspaceId : string ) {
const newWin = await WindowService . CreateWindow ( null , workspaceId ) ;
if ( ! newWin ) {
console . log ( "error creating new window" , this . waveWindowId ) ;
}
const newBwin = await createBrowserWindow ( newWin , await FileService . GetFullConfig ( ) , {
unamePlatform ,
} ) ;
newBwin . show ( ) ;
}
2024-12-02 19:56:56 +01:00
// note, this does not *show* the window.
// to show, await win.readyPromise and then win.show()
export async function createBrowserWindow (
waveWindow : WaveWindow ,
fullConfig : FullConfigType ,
opts : WindowOpts
) : Promise < WaveBrowserWindow > {
if ( ! waveWindow ) {
console . log ( "createBrowserWindow: no waveWindow" ) ;
waveWindow = await WindowService . CreateWindow ( null , "" ) ;
}
let workspace = await WorkspaceService . GetWorkspace ( waveWindow . workspaceid ) ;
if ( ! workspace ) {
console . log ( "createBrowserWindow: no workspace, creating new window" ) ;
await WindowService . CloseWindow ( waveWindow . oid , true ) ;
waveWindow = await WindowService . CreateWindow ( null , "" ) ;
workspace = await WorkspaceService . GetWorkspace ( waveWindow . workspaceid ) ;
}
console . log ( "createBrowserWindow" , waveWindow . oid , workspace . oid , workspace ) ;
const bwin = new WaveBrowserWindow ( waveWindow , fullConfig , opts ) ;
if ( workspace . activetabid ) {
2024-12-02 21:59:03 +01:00
await bwin . setActiveTab ( workspace . activetabid , false ) ;
2024-12-02 19:56:56 +01:00
}
return bwin ;
}
ipcMain . on ( "set-active-tab" , async ( event , tabId ) = > {
const ww = getWaveWindowByWebContentsId ( event . sender . id ) ;
console . log ( "set-active-tab" , tabId , ww ? . waveWindowId ) ;
2024-12-02 21:59:03 +01:00
await ww ? . setActiveTab ( tabId , true ) ;
2024-12-02 19:56:56 +01:00
} ) ;
ipcMain . on ( "create-tab" , async ( event , opts ) = > {
const senderWc = event . sender ;
const ww = getWaveWindowByWebContentsId ( senderWc . id ) ;
2024-12-06 03:26:20 +01:00
if ( ww != null ) {
2024-12-06 21:00:24 +01:00
await ww . queueCreateTab ( ) ;
2024-12-02 19:56:56 +01:00
}
event . returnValue = true ;
return null ;
} ) ;
2024-12-06 03:26:20 +01:00
ipcMain . on ( "close-tab" , async ( event , workspaceId , tabId ) = > {
const ww = getWaveWindowByWorkspaceId ( workspaceId ) ;
if ( ww == null ) {
console . log ( ` close-tab: no window found for workspace ws= ${ workspaceId } tab= ${ tabId } ` ) ;
return ;
}
2024-12-07 00:42:29 +01:00
await ww . queueCloseTab ( tabId ) ;
2024-12-02 19:56:56 +01:00
event . returnValue = true ;
return null ;
} ) ;
2024-12-07 00:33:00 +01:00
ipcMain . on ( "switch-workspace" , ( event , workspaceId ) = > {
fireAndForget ( async ( ) = > {
const ww = getWaveWindowByWebContentsId ( event . sender . id ) ;
console . log ( "switch-workspace" , workspaceId , ww ? . waveWindowId ) ;
await ww ? . switchWorkspace ( workspaceId ) ;
} ) ;
2024-12-02 19:56:56 +01:00
} ) ;
2024-12-07 00:33:00 +01:00
export async function createWorkspace ( window : WaveBrowserWindow ) {
2024-12-13 02:21:09 +01:00
const newWsId = await WorkspaceService . CreateWorkspace ( "" , "" , "" , true ) ;
2024-12-07 00:33:00 +01:00
if ( newWsId ) {
2024-12-13 02:21:09 +01:00
if ( window ) {
await window . switchWorkspace ( newWsId ) ;
} else {
await createWindowForWorkspace ( newWsId ) ;
}
2024-12-02 19:56:56 +01:00
}
2024-12-07 00:33:00 +01:00
}
ipcMain . on ( "create-workspace" , ( event ) = > {
fireAndForget ( async ( ) = > {
const ww = getWaveWindowByWebContentsId ( event . sender . id ) ;
console . log ( "create-workspace" , ww ? . waveWindowId ) ;
await createWorkspace ( ww ) ;
} ) ;
} ) ;
ipcMain . on ( "delete-workspace" , ( event , workspaceId ) = > {
fireAndForget ( async ( ) = > {
const ww = getWaveWindowByWebContentsId ( event . sender . id ) ;
console . log ( "delete-workspace" , workspaceId , ww ? . waveWindowId ) ;
2024-12-10 19:15:33 +01:00
const workspaceList = await WorkspaceService . ListWorkspaces ( ) ;
const workspaceHasWindow = ! ! workspaceList . find ( ( wse ) = > wse . workspaceid === workspaceId ) ? . windowid ;
const choice = dialog . showMessageBoxSync ( this , {
type : "question" ,
buttons : [ "Cancel" , "Delete Workspace" ] ,
title : "Confirm" ,
message : ` Deleting workspace will also delete its contents. ${ workspaceHasWindow ? "\nWorkspace is open in a window, which will be closed." : "" } \ n \ nContinue? ` ,
} ) ;
if ( choice === 0 ) {
console . log ( "user cancelled workspace delete" , workspaceId , ww ? . waveWindowId ) ;
return ;
}
2024-12-07 00:33:00 +01:00
await WorkspaceService . DeleteWorkspace ( workspaceId ) ;
console . log ( "delete-workspace done" , workspaceId , ww ? . waveWindowId ) ;
if ( ww ? . workspaceId == workspaceId ) {
console . log ( "delete-workspace closing window" , workspaceId , ww ? . waveWindowId ) ;
ww . destroy ( ) ;
}
} ) ;
2024-12-02 19:56:56 +01:00
} ) ;
2024-12-07 00:33:00 +01:00
export async function createNewWaveWindow() {
log ( "createNewWaveWindow" ) ;
const clientData = await ClientService . GetClientData ( ) ;
const fullConfig = await FileService . GetFullConfig ( ) ;
let recreatedWindow = false ;
const allWindows = getAllWaveWindows ( ) ;
if ( allWindows . length === 0 && clientData ? . windowids ? . length >= 1 ) {
console . log ( "no windows, but clientData has windowids, recreating first window" ) ;
// reopen the first window
const existingWindowId = clientData . windowids [ 0 ] ;
const existingWindowData = ( await ObjectService . GetObject ( "window:" + existingWindowId ) ) as WaveWindow ;
if ( existingWindowData != null ) {
const win = await createBrowserWindow ( existingWindowData , fullConfig , { unamePlatform } ) ;
win . show ( ) ;
recreatedWindow = true ;
}
}
if ( recreatedWindow ) {
console . log ( "recreated window, returning" ) ;
return ;
}
console . log ( "creating new window" ) ;
const newBrowserWindow = await createBrowserWindow ( null , fullConfig , { unamePlatform } ) ;
newBrowserWindow . show ( ) ;
}
export async function relaunchBrowserWindows() {
console . log ( "relaunchBrowserWindows" ) ;
setGlobalIsRelaunching ( true ) ;
const windows = getAllWaveWindows ( ) ;
2024-12-07 00:42:29 +01:00
if ( windows . length > 0 ) {
for ( const window of windows ) {
console . log ( "relaunch -- closing window" , window . waveWindowId ) ;
window . close ( ) ;
}
await delay ( 1200 ) ;
2024-12-07 00:33:00 +01:00
}
setGlobalIsRelaunching ( false ) ;
const clientData = await ClientService . GetClientData ( ) ;
const fullConfig = await FileService . GetFullConfig ( ) ;
const wins : WaveBrowserWindow [ ] = [ ] ;
for ( const windowId of clientData . windowids . slice ( ) . reverse ( ) ) {
const windowData : WaveWindow = await WindowService . GetWindow ( windowId ) ;
if ( windowData == null ) {
console . log ( "relaunch -- window data not found, closing window" , windowId ) ;
await WindowService . CloseWindow ( windowId , true ) ;
continue ;
}
console . log ( "relaunch -- creating window" , windowId , windowData ) ;
const win = await createBrowserWindow ( windowData , fullConfig , { unamePlatform } ) ;
wins . push ( win ) ;
}
for ( const win of wins ) {
console . log ( "show window" , win . waveWindowId ) ;
win . show ( ) ;
}
}