diff --git a/emain/emain.ts b/emain/emain.ts index 1cb572f6c..b6f955f77 100644 --- a/emain/emain.ts +++ b/emain/emain.ts @@ -15,7 +15,7 @@ import { debounce } from "throttle-debounce"; import * as util from "util"; import winston from "winston"; import * as services from "../frontend/app/store/services"; -import { WSServerEndpointVarName, WebServerEndpointVarName, getWebServerEndpoint } from "../frontend/util/endpoints"; +import { getWebServerEndpoint, WebServerEndpointVarName, WSServerEndpointVarName } from "../frontend/util/endpoints"; import * as keyutil from "../frontend/util/keyutil"; import { fireAndForget } from "../frontend/util/util"; @@ -35,7 +35,7 @@ const waveSrvReady: Promise = new Promise((resolve, _) => { let globalIsQuitting = false; let globalIsStarting = true; -const isDev = !electron.app.isPackaged; +const isDev = !electronApp.isPackaged; const isDevVite = isDev && process.env.ELECTRON_RENDERER_URL; if (isDev) { process.env[WaveDevVarName] = "1"; @@ -183,7 +183,7 @@ function runWaveSrv(): Promise { const addrs = /ws:([a-z0-9.:]+) web:([a-z0-9.:]+)/gm.exec(line); if (addrs == null) { console.log("error parsing WAVESRV-ESTART line", line); - electron.app.quit(); + electronApp.quit(); return; } process.env[WSServerEndpointVarName] = addrs[1]; @@ -352,6 +352,12 @@ function createBrowserWindow(clientId: string, waveWindow: WaveWindow): WaveBrow console.log("focus", waveWindow.oid); services.ClientService.FocusWindow(waveWindow.oid); }); + win.on("enter-full-screen", async () => { + win.webContents.send("fullscreen-change", true); + }); + win.on("leave-full-screen", async () => { + win.webContents.send("fullscreen-change", false); + }); win.on("close", (e) => { if (globalIsQuitting) { return; @@ -390,7 +396,7 @@ function isWindowFullyVisible(bounds: electron.Rectangle): boolean { const displays = electron.screen.getAllDisplays(); // Helper function to check if a point is inside any display - function isPointInDisplay(x, y) { + function isPointInDisplay(x: number, y: number) { for (const display of displays) { const { x: dx, y: dy, width, height } = display.bounds; if (x >= dx && x < dx + width && y >= dy && y < dy + height) { @@ -620,25 +626,25 @@ function makeAppMenu() { electron.Menu.setApplicationMenu(menu); } -electron.app.on("window-all-closed", () => { +electronApp.on("window-all-closed", () => { if (unamePlatform !== "darwin") { - electron.app.quit(); + electronApp.quit(); } }); -electron.app.on("before-quit", () => { +electronApp.on("before-quit", () => { globalIsQuitting = true; }); process.on("SIGINT", () => { console.log("Caught SIGINT, shutting down"); - electron.app.quit(); + electronApp.quit(); }); process.on("SIGHUP", () => { console.log("Caught SIGHUP, shutting down"); - electron.app.quit(); + electronApp.quit(); }); process.on("SIGTERM", () => { console.log("Caught SIGTERM, shutting down"); - electron.app.quit(); + electronApp.quit(); }); let caughtException = false; process.on("uncaughtException", (error) => { @@ -648,7 +654,7 @@ process.on("uncaughtException", (error) => { logger.error("Uncaught Exception, shutting down: ", error); caughtException = true; // Optionally, handle cleanup or exit the app - electron.app.quit(); + electronApp.quit(); }); // ====== AUTO-UPDATER ====== // diff --git a/emain/preload.ts b/emain/preload.ts index 93b1f1988..f9b2118cc 100644 --- a/emain/preload.ts +++ b/emain/preload.ts @@ -8,7 +8,7 @@ contextBridge.exposeInMainWorld("api", { getCursorPoint: () => ipcRenderer.sendSync("getCursorPoint"), openNewWindow: () => ipcRenderer.send("openNewWindow"), showContextMenu: (menu, position) => ipcRenderer.send("contextmenu-show", menu, position), - onContextMenuClick: (callback) => ipcRenderer.on("contextmenu-click", callback), + onContextMenuClick: (callback) => ipcRenderer.on("contextmenu-click", (_event, id) => callback(id)), downloadFile: (filePath) => ipcRenderer.send("download", { filePath }), openExternal: (url) => { if (url && typeof url === "string") { @@ -18,6 +18,8 @@ contextBridge.exposeInMainWorld("api", { } }, getEnv: (varName) => ipcRenderer.sendSync("getEnv", varName), + onFullScreenChange: (callback) => + ipcRenderer.on("fullscreen-change", (_event, isFullScreen) => callback(isFullScreen)), }); // Custom event for "new-window" diff --git a/frontend/app/app.tsx b/frontend/app/app.tsx index 427ce10c8..f8400db95 100644 --- a/frontend/app/app.tsx +++ b/frontend/app/app.tsx @@ -4,7 +4,7 @@ import { Workspace } from "@/app/workspace/workspace"; import { getLayoutStateAtomForTab, globalLayoutTransformsMap } from "@/faraday/lib/layoutAtom"; import { ContextMenuModel } from "@/store/contextmenu"; -import { WOS, atoms, globalStore, setBlockFocus } from "@/store/global"; +import { PLATFORM, WOS, atoms, globalStore, setBlockFocus } from "@/store/global"; import * as services from "@/store/services"; import * as keyutil from "@/util/keyutil"; import * as layoututil from "@/util/layoututil"; @@ -15,6 +15,7 @@ import { DndProvider } from "react-dnd"; import { HTML5Backend } from "react-dnd-html5-backend"; import { CenteredDiv } from "./element/quickelems"; +import clsx from "clsx"; import "overlayscrollbars/overlayscrollbars.css"; import "./app.less"; import "./term.less"; @@ -96,7 +97,7 @@ function switchTab(offset: number) { services.ObjectService.SetActiveTab(newActiveTabId); } -var transformRegexp = /translate3d\(\s*([0-9.]+)px\s*,\s*([0-9.]+)px,\s*0\)/; +const transformRegexp = /translate3d\(\s*([0-9.]+)px\s*,\s*([0-9.]+)px,\s*0\)/; function parseFloatFromCSS(s: string | number): number { if (typeof s == "number") { @@ -247,8 +248,10 @@ const AppInner = () => { document.removeEventListener("keydown", staticKeyDownHandler); }; }, []); + + const isFullScreen = jotai.useAtomValue(atoms.isFullScreen); return ( -
+
diff --git a/frontend/app/store/global.ts b/frontend/app/store/global.ts index 7e4317837..97fc267dd 100644 --- a/frontend/app/store/global.ts +++ b/frontend/app/store/global.ts @@ -49,6 +49,16 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) { return uiContext; }) as jotai.Atom; + const isFullScreenAtom = jotai.atom(false) as jotai.PrimitiveAtom; + try { + getApi().onFullScreenChange((isFullScreen) => { + console.log("fullscreen change", isFullScreen); + globalStore.set(isFullScreenAtom, isFullScreen); + }); + } catch (_) { + // do nothing + } + const clientAtom: jotai.Atom = jotai.atom((get) => { const clientId = get(clientIdAtom); if (clientId == null) { @@ -99,6 +109,7 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) { tabAtom: tabAtom, activeTabId: activeTabIdAtom, userInput: userInputAtom, + isFullScreen: isFullScreenAtom, }; } diff --git a/frontend/app/tab/tab.less b/frontend/app/tab/tab.less index 66eb6a797..8a5501d22 100644 --- a/frontend/app/tab/tab.less +++ b/frontend/app/tab/tab.less @@ -44,7 +44,7 @@ position: absolute; top: 50%; left: 50%; - transform: translate(-50%, -50%); + transform: translate3d(-50%, -50%, 0); user-select: none; z-index: 3; font-size: 11px; @@ -63,7 +63,7 @@ position: absolute; top: 50%; right: 0px; - transform: translateY(-50%); + transform: translate3d(0, -50%, 0); width: 20px; height: 20px; display: flex; diff --git a/frontend/app/tab/tabbar.less b/frontend/app/tab/tabbar.less index 7e4f359f7..83c7a8bb3 100644 --- a/frontend/app/tab/tabbar.less +++ b/frontend/app/tab/tabbar.less @@ -1,15 +1,31 @@ // Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +.tab-bar-wrapper { + --default-indent: 10px; + --darwin-not-fullscreen-indent: 74px; +} + +.darwin:not(.fullscreen) .tab-bar-wrapper { + .tab-bar { + margin-left: var(--darwin-not-fullscreen-indent); + } + + .window-drag.left { + width: var(--darwin-not-fullscreen-indent); + } +} + .tab-bar-wrapper { position: relative; // border-bottom: 1px solid var(--border-color); user-select: none; display: flex; + flex-direction: row; .tab-bar { position: relative; // Needed for absolute positioning of child tabs - margin-left: 100px; + margin-left: var(--default-indent); height: 33px; // 36 is the width of add tab button // 100 is offset from the left, for macOS window controls and dragging @@ -19,10 +35,9 @@ } .add-tab-btn { - width: 36px; + width: 22px; height: 100%; cursor: pointer; - position: absolute; font-size: 14px; text-align: center; user-select: none; @@ -36,6 +51,8 @@ } i { + overflow: hidden; + margin-top: 5px; font-size: 11px; } } @@ -43,10 +60,6 @@ .window-drag { position: absolute; height: 100%; - - &.left { - width: 100px; - } } // Customize scrollbar styles diff --git a/frontend/app/tab/tabbar.tsx b/frontend/app/tab/tabbar.tsx index b3f0b1381..b918b68da 100644 --- a/frontend/app/tab/tabbar.tsx +++ b/frontend/app/tab/tabbar.tsx @@ -69,6 +69,8 @@ const TabBar = React.memo(({ workspace }: TabBarProps) => { const windowData = useAtomValue(atoms.waveWindow); const { activetabid } = windowData; + const isFullScreen = useAtomValue(atoms.isFullScreen); + let prevDelta: number; let prevDragDirection: string; @@ -145,7 +147,7 @@ const TabBar = React.memo(({ workspace }: TabBarProps) => { tabRefs.current.forEach((ref, index) => { if (ref.current) { ref.current.style.width = `${newTabWidth}px`; - ref.current.style.transform = `translateX(${index * newTabWidth}px)`; + ref.current.style.transform = `translate3d(${index * newTabWidth}px,0,0)`; } }); @@ -173,9 +175,9 @@ const TabBar = React.memo(({ workspace }: TabBarProps) => { const lastTabRect = lastTabRef.current.getBoundingClientRect(); addButton.style.position = "absolute"; if (newScrollable) { - addButton.style.transform = `translateX(${tabBarRect.left + tabBarWidth + 1}px)`; + addButton.style.transform = `translate3d(${tabBarRect.left + tabBarWidth + 1}px,0,0)`; } else { - addButton.style.transform = `translateX(${lastTabRect.right + 1}px)`; + addButton.style.transform = `translate3d(${lastTabRect.right + 1}px,0,0)`; } } // Update dragger right position if needed @@ -183,12 +185,12 @@ const TabBar = React.memo(({ workspace }: TabBarProps) => { if (draggerRight && addButton) { const addButtonRect = addButton.getBoundingClientRect(); const targetPos = addButtonRect.left + addButtonRect.width; - draggerRight.style.transform = `translateX(${targetPos}px)`; + draggerRight.style.transform = `translate3d(${targetPos}px,0,0)`; draggerRight.style.width = `${document.documentElement.offsetWidth - targetPos}px`; } debouncedUpdateTabPositions(); - }, [tabIds]); + }, [tabIds, isFullScreen]); useEffect(() => { window.addEventListener("resize", () => handleResizeTabs()); @@ -324,7 +326,7 @@ const TabBar = React.memo(({ workspace }: TabBarProps) => { currentX = Math.min(Math.max(currentX, minLeft), maxRight); } - ref.current!.style.transform = `translateX(${currentX}px)`; + ref.current!.style.transform = `translate3d(${currentX}px,0,0)`; ref.current!.style.zIndex = "100"; const tabIndex = draggingTabDataRef.current.tabIndex; @@ -350,7 +352,7 @@ const TabBar = React.memo(({ workspace }: TabBarProps) => { tabIds.forEach((localTabId, index) => { const ref = tabRefs.current.find((ref) => ref.current.dataset.tabId === localTabId); if (ref.current && localTabId !== tabId) { - ref.current.style.transform = `translateX(${index * tabWidth}px)`; + ref.current.style.transform = `translate3d(${index * tabWidth}px,0,0)`; ref.current.classList.add("animate"); } }); @@ -369,7 +371,7 @@ const TabBar = React.memo(({ workspace }: TabBarProps) => { const ref = tabRefs.current.find((ref) => ref.current.dataset.tabId === draggingTab); if (ref.current) { ref.current.classList.add("animate"); - ref.current.style.transform = `translateX(${finalLeftPosition}px)`; + ref.current.style.transform = `translate3d(${finalLeftPosition}px,0,0)`; } if (dragged) { diff --git a/frontend/types/custom.d.ts b/frontend/types/custom.d.ts index 3408c9357..4c518d9b3 100644 --- a/frontend/types/custom.d.ts +++ b/frontend/types/custom.d.ts @@ -16,6 +16,7 @@ declare global { tabAtom: jotai.Atom; // driven from WOS activeTabId: jotai.Atom; // derrived from windowDataAtom userInput: jotai.PrimitiveAtom>; + isFullScreen: jotai.PrimitiveAtom; }; type TabLayoutData = { @@ -41,6 +42,7 @@ declare global { onIframeNavigate: (callback: (url: string) => void) => void; downloadFile: (path: string) => void; openExternal: (url: string) => void; + onFullScreenChange: (callback: (isFullScreen: boolean) => void) => void; }; type ElectronContextMenuItem = { diff --git a/frontend/wave.ts b/frontend/wave.ts index 2c4d50c83..8dd1c7577 100644 --- a/frontend/wave.ts +++ b/frontend/wave.ts @@ -28,6 +28,7 @@ loadFonts(); (window as any).WOS = WOS; (window as any).globalStore = globalStore; (window as any).WshServer = WshServer; +(window as any).isFullScreen = false; document.title = `The Next Wave (${windowId.substring(0, 8)})`;