Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions emain/emain-tabview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ export function clearTabCache() {
// returns [tabview, initialized]
export async function getOrCreateWebViewForTab(waveWindowId: string, tabId: string): Promise<[WaveTabView, boolean]> {
let tabView = getWaveTabView(tabId);
if (tabView && !tabView.isDestroyed) {
if (tabView && !tabView.isDestroyed && !tabView.webContents?.isDestroyed()) {
return [tabView, true];
}
const fullConfig = await RpcApi.GetFullConfigCommand(ElectronWshClient);
Expand Down Expand Up @@ -346,7 +346,7 @@ export async function getOrCreateWebViewForTab(waveWindowId: string, tabId: stri
}
}
});
tabView.webContents.setWindowOpenHandler(({ url, frameName }) => {
tabView.webContents.setWindowOpenHandler(({ url }) => {
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("file://")) {
console.log("openExternal fallback", url);
shell.openExternal(url);
Expand Down
6 changes: 2 additions & 4 deletions emain/emain-window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,7 @@ export class WaveBrowserWindow extends BaseWindow {
this.finalizePositioning();
} else {
console.log("reusing an existing tab, calling wave-init", tabView.waveTabId);
this.contentView.addChildView(tabView);
tabView.webContents.send("wave-init", tabView.savedInitOpts); // reinit
this.finalizePositioning();
}
Expand Down Expand Up @@ -605,10 +606,7 @@ export class WaveBrowserWindow extends BaseWindow {
if (!newWs) {
return;
}
console.log("processActionQueue switchworkspace newWs", newWs, "keep-alive tabs:", this.allLoadedTabViews.size);
// Keep BrowserViews alive (don't destroy) — position off-screen instead.
// They stay in contentView and wcvCache, and will be positioned on-screen
// when switching back to this workspace (setTabViewIntoWindow → finalizePositioning).
console.log("processActionQueue switchworkspace keep-alive tabs:", this.allLoadedTabViews.size);
const curBounds = this.getContentBounds();
for (const tabView of this.allLoadedTabViews.values()) {
tabView.isActiveTab = false;
Expand Down
20 changes: 13 additions & 7 deletions frontend/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,25 @@ const dlog = debug("wave:app");
const focusLog = debug("wave:focus");

const App = ({ onFirstRender }: { onFirstRender: () => void }) => {
return (
<Provider store={globalStore}>
<AppContextProviders onFirstRender={onFirstRender} />
</Provider>
);
};

const AppContextProviders = ({ onFirstRender }: { onFirstRender: () => void }) => {
const tabId = useAtomValue(atoms.staticTabId);
const waveEnvRef = useRef(makeWaveEnvImpl());
useEffect(() => {
onFirstRender();
}, []);
return (
<Provider store={globalStore}>
<WaveEnvContext.Provider value={waveEnvRef.current}>
<TabModelContext.Provider value={getTabModelByTabId(tabId)}>
<AppInner />
</TabModelContext.Provider>
</WaveEnvContext.Provider>
</Provider>
<WaveEnvContext.Provider value={waveEnvRef.current}>
<TabModelContext.Provider value={getTabModelByTabId(tabId)}>
<AppInner />
</TabModelContext.Provider>
</WaveEnvContext.Provider>
);
};

Expand Down
12 changes: 4 additions & 8 deletions frontend/app/store/global-atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ const ConnStatusMapAtom = atom(new Map<string, PrimitiveAtom<ConnStatus>>());
const orefAtomCache = new Map<string, Map<string, Atom<any>>>();

function initGlobalAtoms(initOpts: GlobalInitOptions) {
const windowIdAtom = atom(initOpts.windowId) as PrimitiveAtom<string>;
const builderIdAtom = atom(initOpts.builderId) as PrimitiveAtom<string>;
const builderAppIdAtom = atom<string>(null) as PrimitiveAtom<string>;
setWaveWindowType(initOpts.isPreview ? "preview" : initOpts.builderId != null ? "builder" : "tab");
// Tab views are reused across workspace switches, so reinit updates this to the workspace active tab.
const staticTabIdAtom = atom(initOpts.tabId) as PrimitiveAtom<string>;
const uiContextAtom = atom((get) => {
const uiContext: UIContext = {
windowid: initOpts.windowId,
activetabid: initOpts.tabId,
activetabid: get(staticTabIdAtom),
};
return uiContext;
}) as Atom<UIContext>;
Expand All @@ -43,10 +44,7 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) {
console.log("failed to initialize zoomFactorAtom", e);
}

const workspaceIdAtom: Atom<string> = atom((get) => {
const windowData = WOS.getObjectValue<WaveWindow>(WOS.makeORef("window", get(windowIdAtom)), get);
return windowData?.workspaceid ?? null;
});
const workspaceIdAtom = atom<string>(null) as PrimitiveAtom<string>;
const workspaceAtom: Atom<Workspace> = atom((get) => {
const workspaceId = get(workspaceIdAtom);
if (workspaceId == null) {
Expand Down Expand Up @@ -75,8 +73,6 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) {
const fullConfig = get(fullConfigAtom);
return fullConfig?.configerrors != null && fullConfig.configerrors.length > 0;
}) as Atom<boolean>;
// this is *the* tab that this tabview represents. it should never change.
const staticTabIdAtom: Atom<string> = atom(initOpts.tabId);
const controlShiftDelayAtom = atom(false);
const updaterStatusAtom = atom<UpdaterStatus>("up-to-date") as PrimitiveAtom<UpdaterStatus>;
try {
Expand Down
3 changes: 2 additions & 1 deletion frontend/preview/mock/mockwaveenv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ function makeMockGlobalAtoms(
}
const fullConfigAtom = atom(fullConfig) as PrimitiveAtom<FullConfigType>;
const settingsAtom = atom((get) => get(fullConfigAtom)?.settings ?? {}) as Atom<SettingsType>;
const workspaceIdAtom: Atom<string> = atomOverrides?.workspaceId ?? (atom(null as string) as Atom<string>);
const workspaceIdAtom: PrimitiveAtom<string> =
atomOverrides?.workspaceId ?? (atom(null as string) as PrimitiveAtom<string>);
const workspaceAtom: Atom<Workspace> = atom((get) => {
const wsId = get(workspaceIdAtom);
if (wsId == null) {
Expand Down
4 changes: 2 additions & 2 deletions frontend/types/custom.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ declare global {
builderId: jotai.Atom<string>; // readonly (for builder mode)
builderAppId: jotai.PrimitiveAtom<string>; // app being edited in builder mode
uiContext: jotai.Atom<UIContext>; // driven from windowId, tabId
workspaceId: jotai.Atom<string>; // derived from window WOS object
workspaceId: jotai.PrimitiveAtom<string>;
workspace: jotai.Atom<Workspace>; // driven from workspaceId via WOS
fullConfigAtom: jotai.PrimitiveAtom<FullConfigType>; // driven from WOS, settings -- updated via WebSocket
waveaiModeConfigAtom: jotai.PrimitiveAtom<Record<string, AIModeConfigType>>; // resolved AI mode configs -- updated via WebSocket
settingsAtom: jotai.Atom<SettingsType>; // derrived from fullConfig
hasCustomAIPresetsAtom: jotai.Atom<boolean>; // derived from fullConfig
hasConfigErrors: jotai.Atom<boolean>; // derived from fullConfig
staticTabId: jotai.Atom<string>;
staticTabId: jotai.PrimitiveAtom<string>;
isFullScreen: jotai.PrimitiveAtom<boolean>;
zoomFactorAtom: jotai.PrimitiveAtom<number>;
controlShiftDelayAtom: jotai.PrimitiveAtom<boolean>;
Expand Down
49 changes: 36 additions & 13 deletions frontend/wave.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { createRoot } from "react-dom/client";
const platform = getApi().getPlatform();
document.title = `Wave Terminal`;
let savedInitOpts: WaveInitOpts = null;
let unsubscribeWorkspace: () => void = null;

(window as any).WOS = WOS;
(window as any).globalStore = globalStore;
Expand Down Expand Up @@ -80,6 +81,8 @@ document.addEventListener("DOMContentLoaded", initBare);
async function initWaveWrap(initOpts: WaveInitOpts) {
try {
if (savedInitOpts) {
globalStore.set(activeTabIdAtom, initOpts.tabId);
globalStore.set(atoms.staticTabId, initOpts.tabId);
await reinitWave();
return;
}
Expand All @@ -92,6 +95,7 @@ async function initWaveWrap(initOpts: WaveInitOpts) {
document.body.style.visibility = null;
document.body.style.opacity = null;
document.body.classList.remove("is-transparent");
document.body.classList.remove("init");
}
}

Expand All @@ -109,35 +113,53 @@ async function reinitWave() {

await WOS.reloadWaveObject<Client>(WOS.makeORef("client", savedInitOpts.clientId));
const waveWindow = await WOS.reloadWaveObject<WaveWindow>(WOS.makeORef("window", savedInitOpts.windowId));
if (waveWindow == null) {
return;
}
const ws = await WOS.reloadWaveObject<Workspace>(WOS.makeORef("workspace", waveWindow.workspaceid));
const initialTab = await WOS.reloadWaveObject<Tab>(WOS.makeORef("tab", savedInitOpts.tabId));
if (ws == null) {
return;
}
globalStore.set(atoms.workspaceId, ws.oid);
const activeTabId = ws.activetabid || savedInitOpts.tabId;
const initialTab = await WOS.reloadWaveObject<Tab>(WOS.makeORef("tab", activeTabId));
if (initialTab == null) {
return;
}
await WOS.reloadWaveObject<LayoutState>(WOS.makeORef("layout", initialTab.layoutstate));
reloadAllWorkspaceTabs(ws);
await reloadWorkspaceTabsAndLayouts(ws);
document.title = `Wave Terminal - ${initialTab.name}`; // TODO update with tab name change
getApi().setWindowInitStatus("wave-ready");
globalStore.set(activeTabIdAtom, activeTabId);
globalStore.set(atoms.staticTabId, activeTabId);
globalStore.set(atoms.reinitVersion, globalStore.get(atoms.reinitVersion) + 1);
globalStore.set(atoms.updaterStatusAtom, getApi().getUpdaterStatus());
subscribeToWorkspace(ws.oid);
setTimeout(() => {
globalRefocus();
}, 50);
}

function reloadAllWorkspaceTabs(ws: Workspace) {
if (ws == null || !ws.tabids?.length) {
function subscribeToWorkspace(workspaceId: string) {
if (workspaceId == null) {
return;
}
ws.tabids?.forEach((tabid) => {
WOS.reloadWaveObject<Tab>(WOS.makeORef("tab", tabid));
});
unsubscribeWorkspace?.();
unsubscribeWorkspace = WOS.wpsSubscribeToObject(WOS.makeORef("workspace", workspaceId));
}

function loadAllWorkspaceTabs(ws: Workspace) {
async function reloadWorkspaceTabsAndLayouts(ws: Workspace) {
if (ws == null || !ws.tabids?.length) {
return;
}
ws.tabids?.forEach((tabid) => {
WOS.getObjectValue<Tab>(WOS.makeORef("tab", tabid));
});
await Promise.all(
ws.tabids.map(async (tabid) => {
const tab = await WOS.reloadWaveObject<Tab>(WOS.makeORef("tab", tabid));
if (tab?.layoutstate) {
await WOS.reloadWaveObject<LayoutState>(WOS.makeORef("layout", tab.layoutstate));
}
})
);
}

async function initWave(initOpts: WaveInitOpts) {
Expand Down Expand Up @@ -180,8 +202,9 @@ async function initWave(initOpts: WaveInitOpts) {
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));
globalStore.set(atoms.workspaceId, ws.oid);
await reloadWorkspaceTabsAndLayouts(ws);
subscribeToWorkspace(ws.oid);
document.title = `Wave Terminal - ${initialTab.name}`; // TODO update with tab name change
} catch (e) {
console.error("Failed initialization error", e);
Expand Down
Loading