waveterm/frontend/wave.ts
Evan Simkowitz 87aea21184
Better flicker prevention when switching tabs (#1389)
There was still some flicker when nohover took effect before the tab
view actually switched. Now, we override the active tab in the tabbar
when the app is switching tabs. We also override active tab behavior so
that the close button is always visible while nohover is in effect. This
effectively removes the flickering
2024-12-04 17:20:06 -08:00

194 lines
7.0 KiB
TypeScript

// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { App } from "@/app/app";
import {
registerControlShiftStateUpdateHandler,
registerElectronReinjectKeyHandler,
registerGlobalKeys,
} from "@/app/store/keymodel";
import { modalsModel } from "@/app/store/modalmodel";
import { FileService } from "@/app/store/services";
import { RpcApi } from "@/app/store/wshclientapi";
import { initWshrpc, TabRpcClient } from "@/app/store/wshrpcutil";
import { loadMonaco } from "@/app/view/codeeditor/codeeditor";
import { getLayoutModelForStaticTab } from "@/layout/index";
import {
atoms,
countersClear,
countersPrint,
getApi,
globalStore,
initGlobal,
initGlobalWaveEventSubs,
loadConnStatus,
overrideStaticTabAtom,
pushFlashError,
pushNotification,
removeNotificationById,
subscribeToConnEvents,
} from "@/store/global";
import * as WOS from "@/store/wos";
import { loadFonts } from "@/util/fontutil";
import { setKeyUtilPlatform } from "@/util/keyutil";
import { createElement } from "react";
import { createRoot } from "react-dom/client";
const platform = getApi().getPlatform();
document.title = `Wave Terminal`;
let savedInitOpts: WaveInitOpts = null;
(window as any).WOS = WOS;
(window as any).globalStore = globalStore;
(window as any).globalAtoms = atoms;
(window as any).RpcApi = RpcApi;
(window as any).isFullScreen = false;
(window as any).countersPrint = countersPrint;
(window as any).countersClear = countersClear;
(window as any).getLayoutModelForStaticTab = getLayoutModelForStaticTab;
(window as any).pushFlashError = pushFlashError;
(window as any).pushNotification = pushNotification;
(window as any).removeNotificationById = removeNotificationById;
(window as any).modalsModel = modalsModel;
async function initBare() {
getApi().sendLog("Init Bare");
document.body.style.visibility = "hidden";
document.body.style.opacity = "0";
document.body.classList.add("is-transparent");
getApi().onWaveInit(initWaveWrap);
setKeyUtilPlatform(platform);
loadFonts();
document.fonts.ready.then(() => {
console.log("Init Bare Done");
getApi().setWindowInitStatus("ready");
});
}
document.addEventListener("DOMContentLoaded", initBare);
async function initWaveWrap(initOpts: WaveInitOpts) {
try {
if (savedInitOpts) {
await reinitWave();
return;
}
savedInitOpts = initOpts;
await initWave(initOpts);
} catch (e) {
getApi().sendLog("Error in initWave " + e.message);
console.error("Error in initWave", e);
} finally {
document.body.style.visibility = null;
document.body.style.opacity = null;
document.body.classList.remove("is-transparent");
}
}
async function reinitWave() {
console.log("Reinit Wave");
getApi().sendLog("Reinit Wave");
// We use this hack to prevent a flicker of the previously-hovered tab when this view was last active. This class is set in setActiveTab in global.ts. See tab.scss for where this class is used.
// Also overrides the staticTabAtom to the new tab id so that the active tab is set correctly.
globalStore.set(overrideStaticTabAtom, savedInitOpts.tabId);
setTimeout(() => {
document.body.classList.remove("nohover");
}, 100);
const client = await WOS.reloadWaveObject<Client>(WOS.makeORef("client", savedInitOpts.clientId));
const waveWindow = await WOS.reloadWaveObject<WaveWindow>(WOS.makeORef("window", savedInitOpts.windowId));
const ws = await WOS.reloadWaveObject<Workspace>(WOS.makeORef("workspace", waveWindow.workspaceid));
const initialTab = await WOS.reloadWaveObject<Tab>(WOS.makeORef("tab", savedInitOpts.tabId));
await WOS.reloadWaveObject<LayoutState>(WOS.makeORef("layout", initialTab.layoutstate));
reloadAllWorkspaceTabs(ws);
document.title = `Wave Terminal - ${initialTab.name}`; // TODO update with tab name change
getApi().setWindowInitStatus("wave-ready");
globalStore.set(atoms.reinitVersion, globalStore.get(atoms.reinitVersion) + 1);
globalStore.set(atoms.updaterStatusAtom, getApi().getUpdaterStatus());
}
function reloadAllWorkspaceTabs(ws: Workspace) {
if (ws == null || ws.tabids == null) {
return;
}
ws.tabids.forEach((tabid) => {
WOS.reloadWaveObject<Tab>(WOS.makeORef("tab", tabid));
});
}
function loadAllWorkspaceTabs(ws: Workspace) {
if (ws == null || ws.tabids == null) {
return;
}
ws.tabids.forEach((tabid) => {
WOS.getObjectValue<Tab>(WOS.makeORef("tab", tabid));
});
}
async function initWave(initOpts: WaveInitOpts) {
getApi().sendLog("Init Wave " + JSON.stringify(initOpts));
console.log(
"Wave Init",
"tabid",
initOpts.tabId,
"clientid",
initOpts.clientId,
"windowid",
initOpts.windowId,
"platform",
platform
);
initGlobal({
tabId: initOpts.tabId,
clientId: initOpts.clientId,
windowId: initOpts.windowId,
platform,
environment: "renderer",
});
(window as any).globalAtoms = atoms;
// Init WPS event handlers
const globalWS = initWshrpc(initOpts.tabId);
(window as any).globalWS = globalWS;
(window as any).TabRpcClient = TabRpcClient;
await loadConnStatus();
initGlobalWaveEventSubs();
subscribeToConnEvents();
// ensures client/window/workspace are loaded into the cache before rendering
const [client, waveWindow, initialTab] = await Promise.all([
WOS.loadAndPinWaveObject<Client>(WOS.makeORef("client", initOpts.clientId)),
WOS.loadAndPinWaveObject<WaveWindow>(WOS.makeORef("window", initOpts.windowId)),
WOS.loadAndPinWaveObject<Tab>(WOS.makeORef("tab", initOpts.tabId)),
]);
const [ws, layoutState] = await Promise.all([
WOS.loadAndPinWaveObject<Workspace>(WOS.makeORef("workspace", waveWindow.workspaceid)),
WOS.reloadWaveObject<LayoutState>(WOS.makeORef("layout", initialTab.layoutstate)),
]);
loadAllWorkspaceTabs(ws);
WOS.wpsSubscribeToObject(WOS.makeORef("workspace", waveWindow.workspaceid));
document.title = `Wave Terminal - ${initialTab.name}`; // TODO update with tab name change
registerGlobalKeys();
registerElectronReinjectKeyHandler();
registerControlShiftStateUpdateHandler();
setTimeout(loadMonaco, 30);
const fullConfig = await FileService.GetFullConfig();
console.log("fullconfig", fullConfig);
globalStore.set(atoms.fullConfigAtom, fullConfig);
console.log("Wave First Render");
let firstRenderResolveFn: () => void = null;
let firstRenderPromise = new Promise<void>((resolve) => {
firstRenderResolveFn = resolve;
});
const reactElem = createElement(App, { onFirstRender: firstRenderResolveFn }, null);
const elem = document.getElementById("main");
const root = createRoot(elem);
root.render(reactElem);
await firstRenderPromise;
console.log("Wave First Render Done");
getApi().setWindowInitStatus("wave-ready");
}