mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-21 16:38:23 +01:00
773 lines
29 KiB
TypeScript
773 lines
29 KiB
TypeScript
// Copyright 2024, Command Line Inc.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
import { ClientService, FileService, ObjectService, WindowService, WorkspaceService } from "@/app/store/services";
|
|
import { fireAndForget } from "@/util/util";
|
|
import { BaseWindow, BaseWindowConstructorOptions, dialog, ipcMain, screen } from "electron";
|
|
import path from "path";
|
|
import { debounce } from "throttle-debounce";
|
|
import {
|
|
getGlobalIsQuitting,
|
|
getGlobalIsRelaunching,
|
|
setGlobalIsRelaunching,
|
|
setWasActive,
|
|
setWasInFg,
|
|
} from "./emain-activity";
|
|
import { getOrCreateWebViewForTab, getWaveTabViewByWebContentsId, WaveTabView } from "./emain-tabview";
|
|
import { delay, ensureBoundsAreVisible } from "./emain-util";
|
|
import { log } from "./log";
|
|
import { getElectronAppBasePath, unamePlatform } from "./platform";
|
|
import { updater } from "./updater";
|
|
export type WindowOpts = {
|
|
unamePlatform: string;
|
|
};
|
|
|
|
export const waveWindowMap = new Map<string, WaveBrowserWindow>(); // waveWindowId -> WaveBrowserWindow
|
|
|
|
// 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;
|
|
|
|
let cachedClientId: string = null;
|
|
|
|
async function getClientId() {
|
|
if (cachedClientId != null) {
|
|
return cachedClientId;
|
|
}
|
|
const clientData = await ClientService.GetClientData();
|
|
cachedClientId = clientData?.oid;
|
|
return cachedClientId;
|
|
}
|
|
|
|
type WindowActionQueueEntry =
|
|
| {
|
|
op: "switchtab";
|
|
tabId: string;
|
|
setInBackend: boolean;
|
|
}
|
|
| {
|
|
op: "createtab";
|
|
pinned: boolean;
|
|
}
|
|
| {
|
|
op: "closetab";
|
|
tabId: string;
|
|
}
|
|
| {
|
|
op: "switchworkspace";
|
|
workspaceId: string;
|
|
};
|
|
|
|
function showCloseConfirmDialog(workspace: Workspace): boolean {
|
|
return !workspace.name && !workspace.icon && (workspace.tabids?.length > 1 || workspace.pinnedtabids?.length > 1);
|
|
}
|
|
|
|
export class WaveBrowserWindow extends BaseWindow {
|
|
waveWindowId: string;
|
|
workspaceId: string;
|
|
allLoadedTabViews: Map<string, WaveTabView>;
|
|
activeTabView: WaveTabView;
|
|
private canClose: boolean;
|
|
private deleteAllowed: boolean;
|
|
private actionQueue: WindowActionQueueEntry[];
|
|
|
|
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);
|
|
this.actionQueue = [];
|
|
this.waveWindowId = waveWindow.oid;
|
|
this.workspaceId = waveWindow.workspaceid;
|
|
this.allLoadedTabViews = new Map<string, WaveTabView>();
|
|
const winBoundsPoller = setInterval(() => {
|
|
if (this.isDestroyed()) {
|
|
clearInterval(winBoundsPoller);
|
|
return;
|
|
}
|
|
if (this.actionQueue.length > 0) {
|
|
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);
|
|
fireAndForget(() => ClientService.FocusWindow(this.waveWindowId));
|
|
setWasInFg(true);
|
|
setWasActive(true);
|
|
});
|
|
this.on("blur", () => {
|
|
// nothing for now
|
|
});
|
|
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);
|
|
if (showCloseConfirmDialog(workspace)) {
|
|
console.log("workspace has no name, icon, and multiple tabs", workspace);
|
|
const choice = dialog.showMessageBoxSync(this, {
|
|
type: "question",
|
|
buttons: ["Cancel", "Close Window"],
|
|
title: "Confirm",
|
|
message: "Window has unsaved tabs, closing window will delete existing tabs.\n\nContinue?",
|
|
});
|
|
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;
|
|
}
|
|
waveWindowMap.delete(this.waveWindowId);
|
|
if (focusedWaveWindow == this) {
|
|
focusedWaveWindow = null;
|
|
}
|
|
this.removeAllChildViews();
|
|
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);
|
|
fireAndForget(() => WindowService.CloseWindow(this.waveWindowId, true));
|
|
}
|
|
});
|
|
waveWindowMap.set(waveWindow.oid, this);
|
|
}
|
|
|
|
private removeAllChildViews() {
|
|
for (const tabView of this.allLoadedTabViews.values()) {
|
|
if (!this.isDestroyed()) {
|
|
this.contentView.removeChildView(tabView);
|
|
}
|
|
tabView?.destroy();
|
|
}
|
|
}
|
|
|
|
async switchWorkspace(workspaceId: string) {
|
|
console.log("switchWorkspace", workspaceId, this.waveWindowId);
|
|
if (workspaceId == this.workspaceId) {
|
|
console.log("switchWorkspace already on this workspace", this.waveWindowId);
|
|
return;
|
|
}
|
|
|
|
// 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();
|
|
if (!workspaceList.find((wse) => wse.workspaceid === workspaceId)?.windowid) {
|
|
const curWorkspace = await WorkspaceService.GetWorkspace(this.workspaceId);
|
|
if (showCloseConfirmDialog(curWorkspace)) {
|
|
const choice = dialog.showMessageBoxSync(this, {
|
|
type: "question",
|
|
buttons: ["Cancel", "Open in New Window", "Switch Workspace"],
|
|
title: "Confirm",
|
|
message: "Window has unsaved tabs, switching workspaces will delete existing tabs.\n\nContinue?",
|
|
});
|
|
if (choice === 0) {
|
|
console.log("user cancelled switch workspace", this.waveWindowId);
|
|
return;
|
|
} else if (choice === 1) {
|
|
console.log("user chose open in new window", this.waveWindowId);
|
|
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();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
await this._queueActionInternal({ op: "switchworkspace", workspaceId });
|
|
}
|
|
|
|
async setActiveTab(tabId: string, setInBackend: boolean) {
|
|
console.log("setActiveTab", tabId, this.waveWindowId, this.workspaceId, setInBackend);
|
|
await this._queueActionInternal({ op: "switchtab", tabId, setInBackend });
|
|
}
|
|
|
|
private async initializeTab(tabView: WaveTabView) {
|
|
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");
|
|
}
|
|
|
|
private async setTabViewIntoWindow(tabView: WaveTabView, tabInitialized: boolean) {
|
|
if (this.activeTabView == tabView) {
|
|
return;
|
|
}
|
|
const oldActiveView = this.activeTabView;
|
|
tabView.isActiveTab = true;
|
|
if (oldActiveView != null) {
|
|
oldActiveView.isActiveTab = false;
|
|
}
|
|
this.activeTabView = tabView;
|
|
this.allLoadedTabViews.set(tabView.waveTabId, tabView);
|
|
if (!tabInitialized) {
|
|
console.log("initializing a new tab");
|
|
const p1 = this.initializeTab(tabView);
|
|
const p2 = this.repositionTabsSlowly(100);
|
|
await Promise.all([p1, p2]);
|
|
} else {
|
|
console.log("reusing an existing tab, calling wave-init", tabView.waveTabId);
|
|
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(() => {
|
|
if (tabView.webContents && this.activeTabView == tabView && !tabView.webContents.isFocused()) {
|
|
tabView.webContents.focus();
|
|
}
|
|
}, 10);
|
|
setTimeout(() => {
|
|
if (tabView.webContents && this.activeTabView == tabView && !tabView.webContents.isFocused()) {
|
|
tabView.webContents.focus();
|
|
}
|
|
}, 30);
|
|
}
|
|
|
|
private async repositionTabsSlowly(delayMs: number) {
|
|
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();
|
|
}
|
|
|
|
private finalizePositioning() {
|
|
if (this.isDestroyed()) {
|
|
return;
|
|
}
|
|
const curBounds = this.getContentBounds();
|
|
this.activeTabView?.positionTabOnScreen(curBounds);
|
|
for (const tabView of this.allLoadedTabViews.values()) {
|
|
if (tabView == this.activeTabView) {
|
|
continue;
|
|
}
|
|
tabView?.positionTabOffScreen(curBounds);
|
|
}
|
|
}
|
|
|
|
async queueCreateTab(pinned = false) {
|
|
await this._queueActionInternal({ op: "createtab", pinned });
|
|
}
|
|
|
|
async queueCloseTab(tabId: string) {
|
|
await this._queueActionInternal({ op: "closetab", tabId });
|
|
}
|
|
|
|
private async _queueActionInternal(entry: WindowActionQueueEntry) {
|
|
if (this.actionQueue.length >= 2) {
|
|
this.actionQueue[1] = entry;
|
|
return;
|
|
}
|
|
const wasEmpty = this.actionQueue.length === 0;
|
|
this.actionQueue.push(entry);
|
|
if (wasEmpty) {
|
|
await this.processActionQueue();
|
|
}
|
|
}
|
|
|
|
private removeTabViewLater(tabId: string, delayMs: number) {
|
|
setTimeout(() => {
|
|
this.removeTabView(tabId, false);
|
|
}, 1000);
|
|
}
|
|
|
|
// 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
|
|
// we don't mess with [0] because it is "in process"
|
|
// 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) {
|
|
try {
|
|
if (this.isDestroyed()) {
|
|
break;
|
|
}
|
|
const entry = this.actionQueue[0];
|
|
let tabId: string = null;
|
|
// have to use "===" here to get the typechecker to work :/
|
|
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;
|
|
}
|
|
if (tabId == null) {
|
|
return;
|
|
}
|
|
const [tabView, tabInitialized] = await getOrCreateWebViewForTab(this.waveWindowId, tabId);
|
|
await this.setTabViewIntoWindow(tabView, tabInitialized);
|
|
} catch (e) {
|
|
console.log("error caught in processActionQueue", e);
|
|
} finally {
|
|
this.actionQueue.shift();
|
|
}
|
|
}
|
|
}
|
|
|
|
private async mainResizeHandler(_: any) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
destroy() {
|
|
console.log("destroy win", this.waveWindowId);
|
|
this.deleteAllowed = true;
|
|
super.destroy();
|
|
}
|
|
}
|
|
|
|
export function getWaveWindowByTabId(tabId: string): WaveBrowserWindow {
|
|
for (const ww of waveWindowMap.values()) {
|
|
if (ww.allLoadedTabViews.has(tabId)) {
|
|
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());
|
|
}
|
|
|
|
// 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) {
|
|
await bwin.setActiveTab(workspace.activetabid, false);
|
|
}
|
|
return bwin;
|
|
}
|
|
|
|
ipcMain.on("set-active-tab", async (event, tabId) => {
|
|
const ww = getWaveWindowByWebContentsId(event.sender.id);
|
|
console.log("set-active-tab", tabId, ww?.waveWindowId);
|
|
await ww?.setActiveTab(tabId, true);
|
|
});
|
|
|
|
ipcMain.on("create-tab", async (event, opts) => {
|
|
const senderWc = event.sender;
|
|
const ww = getWaveWindowByWebContentsId(senderWc.id);
|
|
if (ww != null) {
|
|
await ww.queueCreateTab();
|
|
}
|
|
event.returnValue = true;
|
|
return null;
|
|
});
|
|
|
|
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;
|
|
}
|
|
await ww.queueCloseTab(tabId);
|
|
event.returnValue = true;
|
|
return null;
|
|
});
|
|
|
|
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);
|
|
});
|
|
});
|
|
|
|
export async function createWorkspace(window: WaveBrowserWindow) {
|
|
if (!window) {
|
|
return;
|
|
}
|
|
const newWsId = await WorkspaceService.CreateWorkspace();
|
|
if (newWsId) {
|
|
await window.switchWorkspace(newWsId);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
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;
|
|
}
|
|
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();
|
|
}
|
|
});
|
|
});
|
|
|
|
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();
|
|
if (windows.length > 0) {
|
|
for (const window of windows) {
|
|
console.log("relaunch -- closing window", window.waveWindowId);
|
|
window.close();
|
|
}
|
|
await delay(1200);
|
|
}
|
|
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();
|
|
}
|
|
}
|