From 0fbb42863c3cb93a01df24595c669d1b1e9fd1a9 Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Mon, 22 Jul 2024 13:33:10 -0700 Subject: [PATCH] Remove left indent for tab-bar for non-Mac targets and when in full screen (#128) This adds a new global atom to track whether a window is in full screen. It also updates the behavior of the tab bar so that it will only add an extra left indent on macOS windows that are not in full screen. Otherwise, the indent will be much smaller. --- emain/emain.ts | 28 +++++++++++++++++----------- emain/preload.ts | 4 +++- frontend/app/app.tsx | 9 ++++++--- frontend/app/store/global.ts | 11 +++++++++++ frontend/app/tab/tab.less | 4 ++-- frontend/app/tab/tabbar.less | 27 ++++++++++++++++++++------- frontend/app/tab/tabbar.tsx | 18 ++++++++++-------- frontend/types/custom.d.ts | 2 ++ frontend/wave.ts | 1 + 9 files changed, 72 insertions(+), 32 deletions(-) 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)})`;