2024-10-17 23:34:02 +02:00
|
|
|
// Copyright 2024, Command Line Inc.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
import * as electron from "electron";
|
Update data and config paths to match platform defaults (#1047)
Going forward for new installations, config and data files will be
stored at the platform default paths, as defined by
[env-paths](https://www.npmjs.com/package/env-paths).
For backwards compatibility, if the `~/.waveterm` or `WAVETERM_HOME`
directory exists and contains valid data, it will be used. If this check
fails, then `WAVETERM_DATA_HOME` and `WAVETERM_CONFIG_HOME` will be
used. If these are not defined, then `XDG_DATA_HOME` and
`XDG_CONFIG_HOME` will be used. Finally, if none of these are defined,
the [env-paths](https://www.npmjs.com/package/env-paths) defaults will
be used.
As with the existing app, dev instances will write to `waveterm-dev`
directories, while all others will write to `waveterm`.
2024-10-22 18:26:58 +02:00
|
|
|
import * as path from "path";
|
|
|
|
import { debounce } from "throttle-debounce";
|
|
|
|
import { ClientService, FileService, ObjectService, WindowService } from "../frontend/app/store/services";
|
|
|
|
import * as keyutil from "../frontend/util/keyutil";
|
|
|
|
import { configureAuthKeyRequestInjection } from "./authkey";
|
2024-10-31 23:25:30 +01:00
|
|
|
import {
|
|
|
|
getGlobalIsQuitting,
|
|
|
|
getGlobalIsRelaunching,
|
|
|
|
getGlobalIsStarting,
|
|
|
|
setWasActive,
|
|
|
|
setWasInFg,
|
|
|
|
} from "./emain-activity";
|
2024-10-17 23:34:02 +02:00
|
|
|
import {
|
|
|
|
delay,
|
|
|
|
ensureBoundsAreVisible,
|
|
|
|
handleCtrlShiftFocus,
|
|
|
|
handleCtrlShiftState,
|
|
|
|
shFrameNavHandler,
|
|
|
|
shNavHandler,
|
Update data and config paths to match platform defaults (#1047)
Going forward for new installations, config and data files will be
stored at the platform default paths, as defined by
[env-paths](https://www.npmjs.com/package/env-paths).
For backwards compatibility, if the `~/.waveterm` or `WAVETERM_HOME`
directory exists and contains valid data, it will be used. If this check
fails, then `WAVETERM_DATA_HOME` and `WAVETERM_CONFIG_HOME` will be
used. If these are not defined, then `XDG_DATA_HOME` and
`XDG_CONFIG_HOME` will be used. Finally, if none of these are defined,
the [env-paths](https://www.npmjs.com/package/env-paths) defaults will
be used.
As with the existing app, dev instances will write to `waveterm-dev`
directories, while all others will write to `waveterm`.
2024-10-22 18:26:58 +02:00
|
|
|
} from "./emain-util";
|
2024-10-17 23:34:02 +02:00
|
|
|
import { getElectronAppBasePath, isDevVite } from "./platform";
|
|
|
|
import { updater } from "./updater";
|
|
|
|
|
|
|
|
let MaxCacheSize = 10;
|
|
|
|
let HotSpareTab: WaveTabView = null;
|
|
|
|
|
|
|
|
const waveWindowMap = new Map<string, WaveBrowserWindow>(); // waveWindowId -> WaveBrowserWindow
|
|
|
|
let focusedWaveWindow = null; // on blur we do not set this to null (but on destroy we do)
|
|
|
|
const wcvCache = new Map<string, WaveTabView>();
|
|
|
|
const wcIdToWaveTabMap = new Map<number, WaveTabView>();
|
|
|
|
|
|
|
|
export function setMaxTabCacheSize(size: number) {
|
|
|
|
console.log("setMaxTabCacheSize", size);
|
|
|
|
MaxCacheSize = size;
|
|
|
|
}
|
|
|
|
|
|
|
|
function computeBgColor(fullConfig: FullConfigType): string {
|
|
|
|
const settings = fullConfig?.settings;
|
|
|
|
const isTransparent = settings?.["window:transparent"] ?? false;
|
|
|
|
const isBlur = !isTransparent && (settings?.["window:blur"] ?? false);
|
|
|
|
if (isTransparent) {
|
|
|
|
return "#00000000";
|
|
|
|
} else if (isBlur) {
|
|
|
|
return "#00000000";
|
|
|
|
} else {
|
|
|
|
return "#222222";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function createBareTabView(fullConfig: FullConfigType): WaveTabView {
|
|
|
|
console.log("createBareTabView");
|
|
|
|
const tabView = new electron.WebContentsView({
|
|
|
|
webPreferences: {
|
|
|
|
preload: path.join(getElectronAppBasePath(), "preload", "index.cjs"),
|
|
|
|
webviewTag: true,
|
|
|
|
},
|
|
|
|
}) as WaveTabView;
|
|
|
|
tabView.createdTs = Date.now();
|
|
|
|
tabView.savedInitOpts = null;
|
|
|
|
tabView.initPromise = new Promise((resolve, _) => {
|
|
|
|
tabView.initResolve = resolve;
|
|
|
|
});
|
|
|
|
tabView.initPromise.then(() => {
|
|
|
|
console.log("tabview init", Date.now() - tabView.createdTs + "ms");
|
|
|
|
});
|
|
|
|
tabView.waveReadyPromise = new Promise((resolve, _) => {
|
|
|
|
tabView.waveReadyResolve = resolve;
|
|
|
|
});
|
|
|
|
wcIdToWaveTabMap.set(tabView.webContents.id, tabView);
|
|
|
|
if (isDevVite) {
|
|
|
|
tabView.webContents.loadURL(`${process.env.ELECTRON_RENDERER_URL}/index.html}`);
|
|
|
|
} else {
|
|
|
|
tabView.webContents.loadFile(path.join(getElectronAppBasePath(), "frontend", "index.html"));
|
|
|
|
}
|
|
|
|
tabView.webContents.on("destroyed", () => {
|
|
|
|
wcIdToWaveTabMap.delete(tabView.webContents.id);
|
|
|
|
removeWaveTabView(tabView.waveWindowId, tabView.waveTabId);
|
|
|
|
});
|
|
|
|
tabView.setBackgroundColor(computeBgColor(fullConfig));
|
|
|
|
return tabView;
|
|
|
|
}
|
|
|
|
|
|
|
|
function positionTabOffScreen(tabView: WaveTabView, winBounds: Electron.Rectangle) {
|
|
|
|
if (tabView == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
tabView.setBounds({
|
|
|
|
x: -10000,
|
|
|
|
y: -10000,
|
|
|
|
width: winBounds.width,
|
|
|
|
height: winBounds.height,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async function repositionTabsSlowly(
|
|
|
|
newTabView: WaveTabView,
|
|
|
|
oldTabView: WaveTabView,
|
|
|
|
delayMs: number,
|
|
|
|
winBounds: Electron.Rectangle
|
|
|
|
) {
|
|
|
|
if (newTabView == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
newTabView.setBounds({
|
|
|
|
x: winBounds.width - 10,
|
|
|
|
y: winBounds.height - 10,
|
|
|
|
width: winBounds.width,
|
|
|
|
height: winBounds.height,
|
|
|
|
});
|
|
|
|
await delay(delayMs);
|
|
|
|
newTabView.setBounds({ x: 0, y: 0, width: winBounds.width, height: winBounds.height });
|
|
|
|
oldTabView?.setBounds({
|
|
|
|
x: -10000,
|
|
|
|
y: -10000,
|
|
|
|
width: winBounds.width,
|
|
|
|
height: winBounds.height,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function positionTabOnScreen(tabView: WaveTabView, winBounds: Electron.Rectangle) {
|
|
|
|
if (tabView == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
tabView.setBounds({ x: 0, y: 0, width: winBounds.width, height: winBounds.height });
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getWaveTabViewByWebContentsId(webContentsId: number): WaveTabView {
|
|
|
|
return wcIdToWaveTabMap.get(webContentsId);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getWaveWindowByWebContentsId(webContentsId: number): WaveBrowserWindow {
|
|
|
|
const tabView = wcIdToWaveTabMap.get(webContentsId);
|
|
|
|
if (tabView == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return waveWindowMap.get(tabView.waveWindowId);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getWaveWindowById(windowId: string): WaveBrowserWindow {
|
|
|
|
return waveWindowMap.get(windowId);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getAllWaveWindows(): WaveBrowserWindow[] {
|
|
|
|
return Array.from(waveWindowMap.values());
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getFocusedWaveWindow(): WaveBrowserWindow {
|
|
|
|
return focusedWaveWindow;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function ensureHotSpareTab(fullConfig: FullConfigType) {
|
|
|
|
console.log("ensureHotSpareTab");
|
|
|
|
if (HotSpareTab == null) {
|
|
|
|
HotSpareTab = createBareTabView(fullConfig);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function destroyWindow(waveWindow: WaveBrowserWindow) {
|
|
|
|
if (waveWindow == null) {
|
|
|
|
return;
|
|
|
|
}
|
2024-10-31 23:25:30 +01:00
|
|
|
console.log("destroy win", waveWindow.waveWindowId);
|
2024-10-17 23:34:02 +02:00
|
|
|
for (const tabView of waveWindow.allTabViews.values()) {
|
|
|
|
destroyTab(tabView);
|
|
|
|
}
|
|
|
|
waveWindowMap.delete(waveWindow.waveWindowId);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function destroyTab(tabView: WaveTabView) {
|
|
|
|
if (tabView == null) {
|
|
|
|
return;
|
|
|
|
}
|
2024-10-31 23:25:30 +01:00
|
|
|
console.log("destroy tab", tabView.waveTabId);
|
2024-10-17 23:34:02 +02:00
|
|
|
tabView.webContents.close();
|
|
|
|
wcIdToWaveTabMap.delete(tabView.webContents.id);
|
|
|
|
removeWaveTabView(tabView.waveWindowId, tabView.waveTabId);
|
|
|
|
const waveWindow = waveWindowMap.get(tabView.waveWindowId);
|
|
|
|
if (waveWindow) {
|
|
|
|
waveWindow.allTabViews.delete(tabView.waveTabId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getSpareTab(fullConfig: FullConfigType): WaveTabView {
|
|
|
|
setTimeout(ensureHotSpareTab, 500);
|
|
|
|
if (HotSpareTab != null) {
|
|
|
|
const rtn = HotSpareTab;
|
|
|
|
HotSpareTab = null;
|
|
|
|
console.log("getSpareTab: returning hotspare");
|
|
|
|
return rtn;
|
|
|
|
} else {
|
|
|
|
console.log("getSpareTab: creating new tab");
|
|
|
|
return createBareTabView(fullConfig);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getWaveTabView(waveWindowId: string, waveTabId: string): WaveTabView | undefined {
|
|
|
|
const cacheKey = waveWindowId + "|" + waveTabId;
|
|
|
|
const rtn = wcvCache.get(cacheKey);
|
|
|
|
if (rtn) {
|
|
|
|
rtn.lastUsedTs = Date.now();
|
|
|
|
}
|
|
|
|
return rtn;
|
|
|
|
}
|
|
|
|
|
|
|
|
function setWaveTabView(waveWindowId: string, waveTabId: string, wcv: WaveTabView): void {
|
|
|
|
const cacheKey = waveWindowId + "|" + waveTabId;
|
|
|
|
wcvCache.set(cacheKey, wcv);
|
|
|
|
checkAndEvictCache();
|
|
|
|
}
|
|
|
|
|
|
|
|
function removeWaveTabView(waveWindowId: string, waveTabId: string): void {
|
|
|
|
const cacheKey = waveWindowId + "|" + waveTabId;
|
|
|
|
wcvCache.delete(cacheKey);
|
|
|
|
}
|
|
|
|
|
|
|
|
function forceRemoveAllTabsForWindow(waveWindowId: string): void {
|
|
|
|
const keys = Array.from(wcvCache.keys());
|
|
|
|
for (const key of keys) {
|
|
|
|
if (key.startsWith(waveWindowId)) {
|
|
|
|
wcvCache.delete(key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function checkAndEvictCache(): void {
|
|
|
|
if (wcvCache.size <= MaxCacheSize) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const sorted = Array.from(wcvCache.values()).sort((a, b) => {
|
|
|
|
// Prioritize entries which are active
|
|
|
|
if (a.isActiveTab && !b.isActiveTab) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
// Otherwise, sort by lastUsedTs
|
|
|
|
return a.lastUsedTs - b.lastUsedTs;
|
|
|
|
});
|
|
|
|
for (let i = 0; i < sorted.length - MaxCacheSize; i++) {
|
|
|
|
if (sorted[i].isActiveTab) {
|
|
|
|
// don't evict WaveTabViews that are currently showing in a window
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const tabView = sorted[i];
|
|
|
|
destroyTab(tabView);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function clearTabCache() {
|
|
|
|
const wcVals = Array.from(wcvCache.values());
|
|
|
|
for (let i = 0; i < wcVals.length; i++) {
|
|
|
|
const tabView = wcVals[i];
|
|
|
|
if (tabView.isActiveTab) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
destroyTab(tabView);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// returns [tabview, initialized]
|
|
|
|
function getOrCreateWebViewForTab(fullConfig: FullConfigType, windowId: string, tabId: string): [WaveTabView, boolean] {
|
|
|
|
let tabView = getWaveTabView(windowId, tabId);
|
|
|
|
if (tabView) {
|
|
|
|
return [tabView, true];
|
|
|
|
}
|
|
|
|
tabView = getSpareTab(fullConfig);
|
|
|
|
tabView.lastUsedTs = Date.now();
|
|
|
|
tabView.waveTabId = tabId;
|
|
|
|
tabView.waveWindowId = windowId;
|
|
|
|
setWaveTabView(windowId, tabId, tabView);
|
|
|
|
tabView.webContents.on("will-navigate", shNavHandler);
|
|
|
|
tabView.webContents.on("will-frame-navigate", shFrameNavHandler);
|
|
|
|
tabView.webContents.on("did-attach-webview", (event, wc) => {
|
|
|
|
wc.setWindowOpenHandler((details) => {
|
|
|
|
tabView.webContents.send("webview-new-window", wc.id, details);
|
|
|
|
return { action: "deny" };
|
|
|
|
});
|
|
|
|
});
|
|
|
|
tabView.webContents.on("before-input-event", (e, input) => {
|
|
|
|
const waveEvent = keyutil.adaptFromElectronKeyEvent(input);
|
|
|
|
// console.log("WIN bie", tabView.waveTabId.substring(0, 8), waveEvent.type, waveEvent.code);
|
|
|
|
handleCtrlShiftState(tabView.webContents, waveEvent);
|
|
|
|
setWasActive(true);
|
|
|
|
});
|
|
|
|
tabView.webContents.on("zoom-changed", (e) => {
|
|
|
|
tabView.webContents.send("zoom-changed");
|
|
|
|
});
|
|
|
|
tabView.webContents.setWindowOpenHandler(({ url, frameName }) => {
|
|
|
|
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("file://")) {
|
|
|
|
console.log("openExternal fallback", url);
|
|
|
|
electron.shell.openExternal(url);
|
|
|
|
}
|
|
|
|
console.log("window-open denied", url);
|
|
|
|
return { action: "deny" };
|
|
|
|
});
|
|
|
|
tabView.webContents.on("focus", () => {
|
|
|
|
setWasInFg(true);
|
|
|
|
setWasActive(true);
|
|
|
|
if (getGlobalIsStarting()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
tabView.webContents.on("blur", () => {
|
|
|
|
handleCtrlShiftFocus(tabView.webContents, false);
|
|
|
|
});
|
|
|
|
configureAuthKeyRequestInjection(tabView.webContents.session);
|
|
|
|
return [tabView, false];
|
|
|
|
}
|
|
|
|
|
|
|
|
async function mainResizeHandler(_: any, windowId: string, win: WaveBrowserWindow) {
|
|
|
|
if (win == null || win.isDestroyed() || win.fullScreen) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const bounds = win.getBounds();
|
|
|
|
try {
|
|
|
|
await WindowService.SetWindowPosAndSize(
|
|
|
|
windowId,
|
|
|
|
{ x: bounds.x, y: bounds.y },
|
|
|
|
{ width: bounds.width, height: bounds.height }
|
|
|
|
);
|
|
|
|
} catch (e) {
|
|
|
|
console.log("error resizing window", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type WindowOpts = {
|
|
|
|
unamePlatform: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
function createBaseWaveBrowserWindow(
|
|
|
|
waveWindow: WaveWindow,
|
|
|
|
fullConfig: FullConfigType,
|
|
|
|
opts: WindowOpts
|
|
|
|
): WaveBrowserWindow {
|
2024-10-31 23:25:30 +01:00
|
|
|
console.log("create win", waveWindow.oid);
|
2024-10-17 23:34:02 +02:00
|
|
|
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 = electron.screen.getPrimaryDisplay();
|
|
|
|
const { width } = primaryDisplay.workAreaSize;
|
|
|
|
winWidth = width - winPosX - 100;
|
|
|
|
if (winWidth > 2000) {
|
|
|
|
winWidth = 2000;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (winHeight == null || winHeight == 0) {
|
|
|
|
const primaryDisplay = electron.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: Electron.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";
|
|
|
|
}
|
|
|
|
const bwin = new electron.BaseWindow(winOpts);
|
|
|
|
const win: WaveBrowserWindow = bwin as WaveBrowserWindow;
|
|
|
|
win.waveWindowId = waveWindow.oid;
|
|
|
|
win.alreadyClosed = false;
|
|
|
|
win.allTabViews = new Map<string, WaveTabView>();
|
|
|
|
win.on(
|
2024-10-17 23:52:51 +02:00
|
|
|
// @ts-expect-error
|
2024-10-17 23:34:02 +02:00
|
|
|
"resize",
|
|
|
|
debounce(400, (e) => mainResizeHandler(e, waveWindow.oid, win))
|
|
|
|
);
|
|
|
|
win.on("resize", () => {
|
|
|
|
if (win.isDestroyed() || win.fullScreen) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
positionTabOnScreen(win.activeTabView, win.getContentBounds());
|
|
|
|
});
|
|
|
|
win.on(
|
2024-10-17 23:52:51 +02:00
|
|
|
// @ts-expect-error
|
2024-10-17 23:34:02 +02:00
|
|
|
"move",
|
|
|
|
debounce(400, (e) => mainResizeHandler(e, waveWindow.oid, win))
|
|
|
|
);
|
|
|
|
win.on("enter-full-screen", async () => {
|
|
|
|
const tabView = win.activeTabView;
|
|
|
|
if (tabView) {
|
|
|
|
tabView.webContents.send("fullscreen-change", true);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
win.on("leave-full-screen", async () => {
|
|
|
|
const tabView = win.activeTabView;
|
|
|
|
if (tabView) {
|
|
|
|
tabView.webContents.send("fullscreen-change", false);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
win.on("focus", () => {
|
2024-10-31 23:25:30 +01:00
|
|
|
if (getGlobalIsRelaunching()) {
|
|
|
|
return;
|
|
|
|
}
|
2024-10-17 23:34:02 +02:00
|
|
|
focusedWaveWindow = win;
|
|
|
|
console.log("focus win", win.waveWindowId);
|
|
|
|
ClientService.FocusWindow(win.waveWindowId);
|
|
|
|
});
|
|
|
|
win.on("blur", () => {
|
|
|
|
if (focusedWaveWindow == win) {
|
|
|
|
focusedWaveWindow = null;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
win.on("close", (e) => {
|
2024-10-31 23:25:30 +01:00
|
|
|
console.log("win 'close' handler fired", win.waveWindowId);
|
|
|
|
if (getGlobalIsQuitting() || updater?.status == "installing" || getGlobalIsRelaunching()) {
|
2024-10-17 23:34:02 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
const numWindows = waveWindowMap.size;
|
|
|
|
if (numWindows == 1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const choice = electron.dialog.showMessageBoxSync(win, {
|
|
|
|
type: "question",
|
|
|
|
buttons: ["Cancel", "Yes"],
|
|
|
|
title: "Confirm",
|
|
|
|
message: "Are you sure you want to close this window (all tabs and blocks will be deleted)?",
|
|
|
|
});
|
|
|
|
if (choice === 0) {
|
|
|
|
e.preventDefault();
|
2024-11-01 02:13:27 +01:00
|
|
|
} else {
|
|
|
|
win.deleteAllowed = true;
|
2024-10-17 23:34:02 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
win.on("closed", () => {
|
2024-10-31 23:25:30 +01:00
|
|
|
console.log("win 'closed' handler fired", win.waveWindowId);
|
2024-10-17 23:34:02 +02:00
|
|
|
if (getGlobalIsQuitting() || updater?.status == "installing") {
|
|
|
|
return;
|
|
|
|
}
|
2024-10-31 23:25:30 +01:00
|
|
|
if (getGlobalIsRelaunching()) {
|
|
|
|
destroyWindow(win);
|
|
|
|
return;
|
|
|
|
}
|
2024-10-17 23:34:02 +02:00
|
|
|
const numWindows = waveWindowMap.size;
|
|
|
|
if (numWindows == 0) {
|
|
|
|
return;
|
|
|
|
}
|
2024-11-01 02:13:27 +01:00
|
|
|
if (!win.alreadyClosed && win.deleteAllowed) {
|
2024-10-31 23:25:30 +01:00
|
|
|
console.log("win removing window from backend DB", win.waveWindowId);
|
2024-10-17 23:34:02 +02:00
|
|
|
WindowService.CloseWindow(waveWindow.oid, true);
|
|
|
|
}
|
|
|
|
destroyWindow(win);
|
|
|
|
});
|
|
|
|
waveWindowMap.set(waveWindow.oid, win);
|
|
|
|
return win;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getLastFocusedWaveWindow(): WaveBrowserWindow {
|
|
|
|
return focusedWaveWindow;
|
|
|
|
}
|
|
|
|
|
|
|
|
// note, this does not *show* the window.
|
|
|
|
// to show, await win.readyPromise and then win.show()
|
|
|
|
export function createBrowserWindow(
|
|
|
|
clientId: string,
|
|
|
|
waveWindow: WaveWindow,
|
|
|
|
fullConfig: FullConfigType,
|
|
|
|
opts: WindowOpts
|
|
|
|
): WaveBrowserWindow {
|
|
|
|
const bwin = createBaseWaveBrowserWindow(waveWindow, fullConfig, opts);
|
|
|
|
// TODO fix null activetabid if it exists
|
|
|
|
if (waveWindow.activetabid != null) {
|
|
|
|
setActiveTab(bwin, waveWindow.activetabid);
|
|
|
|
}
|
|
|
|
return bwin;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function setActiveTab(waveWindow: WaveBrowserWindow, tabId: string) {
|
|
|
|
const windowId = waveWindow.waveWindowId;
|
|
|
|
await ObjectService.SetActiveTab(waveWindow.waveWindowId, tabId);
|
|
|
|
const fullConfig = await FileService.GetFullConfig();
|
|
|
|
const [tabView, tabInitialized] = getOrCreateWebViewForTab(fullConfig, windowId, tabId);
|
|
|
|
setTabViewIntoWindow(waveWindow, tabView, tabInitialized);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function setTabViewIntoWindow(bwin: WaveBrowserWindow, tabView: WaveTabView, tabInitialized: boolean) {
|
|
|
|
const curTabView: WaveTabView = bwin.getContentView() as any;
|
|
|
|
const clientData = await ClientService.GetClientData();
|
|
|
|
if (curTabView != null) {
|
|
|
|
curTabView.isActiveTab = false;
|
|
|
|
}
|
|
|
|
if (bwin.activeTabView == tabView) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const oldActiveView = bwin.activeTabView;
|
|
|
|
tabView.isActiveTab = true;
|
|
|
|
if (oldActiveView != null) {
|
|
|
|
oldActiveView.isActiveTab = false;
|
|
|
|
}
|
|
|
|
bwin.activeTabView = tabView;
|
|
|
|
bwin.allTabViews.set(tabView.waveTabId, tabView);
|
|
|
|
if (!tabInitialized) {
|
|
|
|
console.log("initializing a new tab");
|
|
|
|
await tabView.initPromise;
|
|
|
|
bwin.contentView.addChildView(tabView);
|
|
|
|
const initOpts = {
|
|
|
|
tabId: tabView.waveTabId,
|
|
|
|
clientId: clientData.oid,
|
|
|
|
windowId: bwin.waveWindowId,
|
|
|
|
activate: true,
|
|
|
|
};
|
|
|
|
tabView.savedInitOpts = { ...initOpts };
|
|
|
|
tabView.savedInitOpts.activate = false;
|
|
|
|
let startTime = Date.now();
|
|
|
|
tabView.webContents.send("wave-init", initOpts);
|
|
|
|
console.log("before wave ready");
|
|
|
|
await tabView.waveReadyPromise;
|
|
|
|
// positionTabOnScreen(tabView, bwin.getContentBounds());
|
|
|
|
console.log("wave-ready init time", Date.now() - startTime + "ms");
|
|
|
|
// positionTabOffScreen(oldActiveView, bwin.getContentBounds());
|
|
|
|
repositionTabsSlowly(tabView, oldActiveView, 100, bwin.getContentBounds());
|
|
|
|
} else {
|
|
|
|
console.log("reusing an existing tab");
|
|
|
|
repositionTabsSlowly(tabView, oldActiveView, 35, bwin.getContentBounds());
|
|
|
|
tabView.webContents.send("wave-init", tabView.savedInitOpts); // reinit
|
|
|
|
}
|
|
|
|
|
|
|
|
// something is causing the new tab to lose focus so it requires manual refocusing
|
|
|
|
tabView.webContents.focus();
|
|
|
|
setTimeout(() => {
|
|
|
|
if (bwin.activeTabView == tabView && !tabView.webContents.isFocused()) {
|
|
|
|
tabView.webContents.focus();
|
|
|
|
}
|
|
|
|
}, 10);
|
|
|
|
setTimeout(() => {
|
|
|
|
if (bwin.activeTabView == tabView && !tabView.webContents.isFocused()) {
|
|
|
|
tabView.webContents.focus();
|
|
|
|
}
|
|
|
|
}, 30);
|
|
|
|
}
|