From 96b397f5a667d343f6c7f3de7ad961ff2a810ce0 Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Wed, 10 Dec 2025 18:46:14 -0800 Subject: [PATCH 1/3] default tool type to "function" (sometimes APIs omit this from deltas) (#2662) --- package-lock.json | 4 ++-- pkg/aiusechat/openaichat/openaichat-backend.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 02673dbd0..6e1f1d7db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "waveterm", - "version": "0.13.0-beta.2", + "version": "0.13.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "waveterm", - "version": "0.13.0-beta.2", + "version": "0.13.0", "hasInstallScript": true, "license": "Apache-2.0", "workspaces": [ diff --git a/pkg/aiusechat/openaichat/openaichat-backend.go b/pkg/aiusechat/openaichat/openaichat-backend.go index 4eb621742..d9aef7961 100644 --- a/pkg/aiusechat/openaichat/openaichat-backend.go +++ b/pkg/aiusechat/openaichat/openaichat-backend.go @@ -168,7 +168,7 @@ func processChatStream( for _, tcDelta := range choice.Delta.ToolCalls { idx := tcDelta.Index for len(toolCallsInProgress) <= idx { - toolCallsInProgress = append(toolCallsInProgress, ToolCall{}) + toolCallsInProgress = append(toolCallsInProgress, ToolCall{Type: "function"}) } tc := &toolCallsInProgress[idx] From 3caba75b4296640b5439968fcdf1a7168c338644 Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Wed, 10 Dec 2025 20:16:02 -0800 Subject: [PATCH 2/3] fix windows shortcut for waveai focus (#2655) ctrl-shift-0 doesn't work in windows (eaten by IME system). so need to switch to something else... chose Alt-0 for windows. --- docs/docs/waveai.mdx | 2 +- docs/src/components/kbd.tsx | 19 +++++++++++++++---- frontend/app/aipanel/aipanel.tsx | 20 +++++++++++++++----- frontend/app/store/keymodel.ts | 28 ++++++++++++++++++++-------- 4 files changed, 51 insertions(+), 18 deletions(-) diff --git a/docs/docs/waveai.mdx b/docs/docs/waveai.mdx index ab9259d5a..5189bc679 100644 --- a/docs/docs/waveai.mdx +++ b/docs/docs/waveai.mdx @@ -19,7 +19,7 @@ Context-aware terminal assistant with access to terminal output, widgets, and fi | Shortcut | Action | |----------|--------| | | Toggle AI panel | -| | Focus AI input | +| | Focus AI input | | | Clear chat / start new | | | Send message | | | New line | diff --git a/docs/src/components/kbd.tsx b/docs/src/components/kbd.tsx index 21eb0b3c6..b42b1d55c 100644 --- a/docs/src/components/kbd.tsx +++ b/docs/src/components/kbd.tsx @@ -44,9 +44,20 @@ function convertKey(platform: Platform, key: string): [any, string, boolean] { } // Custom KBD component -const KbdInternal = ({ k }: { k: string }) => { +const KbdInternal = ({ k, windows, mac, linux }: { k: string; windows?: string; mac?: string; linux?: string }) => { const { platform } = useContext(PlatformContext); - const keys = k.split(":"); + + // Determine which key binding to use based on platform overrides + let keyBinding = k; + if (platform === "windows" && windows) { + keyBinding = windows; + } else if (platform === "mac" && mac) { + keyBinding = mac; + } else if (platform === "linux" && linux) { + keyBinding = linux; + } + + const keys = keyBinding.split(":"); const keyElems = keys.map((key, i) => { const [displayKey, title, symbol] = convertKey(platform, key); return ( @@ -58,8 +69,8 @@ const KbdInternal = ({ k }: { k: string }) => { return
{keyElems}
; }; -export const Kbd = ({ k }: { k: string }) => { - return {k}
}>{() => }; +export const Kbd = ({ k, windows, mac, linux }: { k: string; windows?: string; mac?: string; linux?: string }) => { + return {k}
}>{() => }; }; export const KbdChord = ({ karr }: { karr: string[] }) => { diff --git a/frontend/app/aipanel/aipanel.tsx b/frontend/app/aipanel/aipanel.tsx index c386348b3..acc76ee5c 100644 --- a/frontend/app/aipanel/aipanel.tsx +++ b/frontend/app/aipanel/aipanel.tsx @@ -7,7 +7,7 @@ import { ErrorBoundary } from "@/app/element/errorboundary"; import { atoms, getSettingsKeyAtom } from "@/app/store/global"; import { globalStore } from "@/app/store/jotaiStore"; import { checkKeyPressed, keydownWrapper } from "@/util/keyutil"; -import { isMacOS } from "@/util/platformutil"; +import { isMacOS, isWindows } from "@/util/platformutil"; import { cn } from "@/util/util"; import { useChat } from "@ai-sdk/react"; import { DefaultChatTransport } from "ai"; @@ -135,10 +135,20 @@ const AIWelcomeMessage = memo(() => { to toggle panel
- Ctrl - Shift - 0 - to focus + {isWindows() ? ( + <> + Alt + 0 + to focus + + ) : ( + <> + Ctrl + Shift + 0 + to focus + + )}
diff --git a/frontend/app/store/keymodel.ts b/frontend/app/store/keymodel.ts index 414b3bde1..32b33abcf 100644 --- a/frontend/app/store/keymodel.ts +++ b/frontend/app/store/keymodel.ts @@ -23,6 +23,7 @@ import { TabBarModel } from "@/app/tab/tabbar-model"; import { WorkspaceLayoutModel } from "@/app/workspace/workspace-layout-model"; import { deleteLayoutModelForTab, getLayoutModelForStaticTab, NavigateDirection } from "@/layout/index"; import * as keyutil from "@/util/keyutil"; +import { isWindows } from "@/util/platformutil"; import { CHORD_TIMEOUT } from "@/util/sharedconst"; import { fireAndForget } from "@/util/util"; import * as jotai from "jotai"; @@ -606,14 +607,25 @@ function registerGlobalKeys() { return true; }); } - globalKeyMap.set("Ctrl:Shift:c{Digit0}", () => { - WaveAIModel.getInstance().focusInput(); - return true; - }); - globalKeyMap.set("Ctrl:Shift:c{Numpad0}", () => { - WaveAIModel.getInstance().focusInput(); - return true; - }); + if (isWindows()) { + globalKeyMap.set("Alt:c{Digit0}", () => { + WaveAIModel.getInstance().focusInput(); + return true; + }); + globalKeyMap.set("Alt:c{Numpad0}", () => { + WaveAIModel.getInstance().focusInput(); + return true; + }); + } else { + globalKeyMap.set("Ctrl:Shift:c{Digit0}", () => { + WaveAIModel.getInstance().focusInput(); + return true; + }); + globalKeyMap.set("Ctrl:Shift:c{Numpad0}", () => { + WaveAIModel.getInstance().focusInput(); + return true; + }); + } function activateSearch(event: WaveKeyboardEvent): boolean { const bcm = getBlockComponentModel(getFocusedBlockInStaticTab()); // Ctrl+f is reserved in most shells From a5599e9105299841599df981a71dd248e2b8cc2a Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Thu, 11 Dec 2025 17:50:02 -0800 Subject: [PATCH 3/3] QoL and Layout Fixes for Windows (#2661) * remove title bar on windows windows * re-integrate native controls into our tab bar * remove menu completely (weird "Alt" activation) * actually fix Ctrl-V in terminal * clamp zoom levels better (0.4 - 2.6) * simplify macos / windows drag regions (keep responsive to zoom) * fix settings schemas for windows --- cmd/server/main-server.go | 22 +-- emain/emain-ipc.ts | 36 +++-- emain/emain-menu.ts | 43 +++--- emain/emain-tabview.ts | 87 +++++++++++- emain/emain-util.ts | 16 +++ emain/emain-window.ts | 83 +++++++----- emain/emain.ts | 6 +- frontend/app/element/windowdrag.scss | 7 - frontend/app/element/windowdrag.tsx | 24 ---- frontend/app/modals/messagemodal.scss | 4 + frontend/app/modals/modal.tsx | 45 ++++++- frontend/app/store/global.ts | 23 +++- frontend/app/tab/tabbar.scss | 66 +-------- frontend/app/tab/tabbar.tsx | 126 +++++++++++------- .../app/view/codeeditor/schemaendpoints.ts | 36 +---- frontend/app/view/term/term-model.ts | 29 ++++ frontend/app/view/waveconfig/waveconfig.tsx | 2 +- frontend/app/workspace/widgets.tsx | 6 +- frontend/tailwindsetup.css | 2 + frontend/types/custom.d.ts | 1 + pkg/telemetry/telemetrydata/telemetrydata.go | 11 +- 21 files changed, 390 insertions(+), 285 deletions(-) delete mode 100644 frontend/app/element/windowdrag.scss delete mode 100644 frontend/app/element/windowdrag.tsx diff --git a/cmd/server/main-server.go b/cmd/server/main-server.go index 5259b60ff..5cac19089 100644 --- a/cmd/server/main-server.go +++ b/cmd/server/main-server.go @@ -234,7 +234,7 @@ func updateTelemetryCounts(lastCounts telemetrydata.TEventProps) telemetrydata.T SettingsCustomSettings: customSettings, SettingsCustomAIModes: customAIModes, } - + secretsCount, err := secretstore.CountSecrets() if err == nil { props.UserSet.SettingsSecretsCount = secretsCount @@ -315,17 +315,19 @@ func startupActivityUpdate(firstLaunch bool) { cohortISOWeek := fmt.Sprintf("%04d-W%02d", year, week) userSetOnce.CohortMonth = cohortMonth userSetOnce.CohortISOWeek = cohortISOWeek + fullConfig := wconfig.GetWatcher().GetFullConfig() props := telemetrydata.TEventProps{ UserSet: &telemetrydata.TEventUserProps{ - ClientVersion: "v" + WaveVersion, - ClientBuildTime: BuildTime, - ClientArch: wavebase.ClientArch(), - ClientOSRelease: wavebase.UnameKernelRelease(), - ClientIsDev: wavebase.IsDevMode(), - AutoUpdateChannel: autoUpdateChannel, - AutoUpdateEnabled: autoUpdateEnabled, - LocalShellType: shellType, - LocalShellVersion: shellVersion, + ClientVersion: "v" + WaveVersion, + ClientBuildTime: BuildTime, + ClientArch: wavebase.ClientArch(), + ClientOSRelease: wavebase.UnameKernelRelease(), + ClientIsDev: wavebase.IsDevMode(), + AutoUpdateChannel: autoUpdateChannel, + AutoUpdateEnabled: autoUpdateEnabled, + LocalShellType: shellType, + LocalShellVersion: shellVersion, + SettingsTransparent: fullConfig.Settings.WindowTransparent, }, UserSetOnce: userSetOnce, } diff --git a/emain/emain-ipc.ts b/emain/emain-ipc.ts index 0129d4337..09ba69d1e 100644 --- a/emain/emain-ipc.ts +++ b/emain/emain-ipc.ts @@ -12,13 +12,13 @@ import { RpcApi } from "../frontend/app/store/wshclientapi"; import { getWebServerEndpoint } from "../frontend/util/endpoints"; import * as keyutil from "../frontend/util/keyutil"; import { fireAndForget, parseDataUrl } from "../frontend/util/util"; +import { incrementTermCommandsRun } from "./emain-activity"; import { createBuilderWindow, getAllBuilderWindows, getBuilderWindowByWebContentsId } from "./emain-builder"; import { callWithOriginalXdgCurrentDesktopAsync, unamePlatform } from "./emain-platform"; import { getWaveTabViewByWebContentsId } from "./emain-tabview"; import { handleCtrlShiftState } from "./emain-util"; import { getWaveVersion } from "./emain-wavesrv"; import { createNewWaveWindow, focusedWaveWindow, getWaveWindowByWebContentsId } from "./emain-window"; -import { incrementTermCommandsRun } from "./emain-activity"; import { ElectronWshClient } from "./emain-wsh"; const electronApp = electron.app; @@ -29,9 +29,7 @@ let webviewKeys: string[] = []; export function openBuilderWindow(appId?: string) { const normalizedAppId = appId || ""; const existingBuilderWindows = getAllBuilderWindows(); - const existingWindow = existingBuilderWindows.find( - (win) => win.builderAppId === normalizedAppId - ); + const existingWindow = existingBuilderWindows.find((win) => win.builderAppId === normalizedAppId); if (existingWindow) { existingWindow.focus(); return; @@ -314,12 +312,12 @@ export function initIpcHandlers() { tabView?.setKeyboardChordMode(true); }); - if (unamePlatform !== "darwin") { - const fac = new FastAverageColor(); - - electron.ipcMain.on("update-window-controls-overlay", async (event, rect: Dimensions) => { + const fac = new FastAverageColor(); + electron.ipcMain.on("update-window-controls-overlay", async (event, rect: Dimensions) => { + if (unamePlatform === "darwin") return; + try { const fullConfig = await RpcApi.GetFullConfigCommand(ElectronWshClient); - if (fullConfig.settings["window:nativetitlebar"]) return; + if (fullConfig?.settings?.["window:nativetitlebar"] && unamePlatform !== "win32") return; const zoomFactor = event.sender.getZoomFactor(); const electronRect: Electron.Rectangle = { @@ -337,18 +335,18 @@ export function initIpcHandlers() { color: unamePlatform === "linux" ? color.rgba : "#00000000", symbolColor: color.isDark ? "white" : "black", }); - }); - } + } catch (e) { + console.error("Error updating window controls overlay:", e); + } + }); electron.ipcMain.on("quicklook", (event, filePath: string) => { - if (unamePlatform == "darwin") { - child_process.execFile("/usr/bin/qlmanage", ["-p", filePath], (error, stdout, stderr) => { - if (error) { - console.error(`Error opening Quick Look: ${error}`); - return; - } - }); - } + if (unamePlatform !== "darwin") return; + child_process.execFile("/usr/bin/qlmanage", ["-p", filePath], (error, stdout, stderr) => { + if (error) { + console.error(`Error opening Quick Look: ${error}`); + } + }); }); electron.ipcMain.handle("clear-webview-storage", async (event, webContentsId: number) => { diff --git a/emain/emain-menu.ts b/emain/emain-menu.ts index 79d7e1736..27b438c4a 100644 --- a/emain/emain-menu.ts +++ b/emain/emain-menu.ts @@ -9,6 +9,7 @@ import { focusedBuilderWindow, getBuilderWindowById } from "./emain-builder"; import { openBuilderWindow } from "./emain-ipc"; import { isDev, unamePlatform } from "./emain-platform"; import { clearTabCache } from "./emain-tabview"; +import { decreaseZoomLevel, increaseZoomLevel } from "./emain-util"; import { createNewWaveWindow, createWorkspace, @@ -123,7 +124,11 @@ function makeEditMenu(fullConfig?: FullConfigType): Electron.MenuItemConstructor ]; } -function makeFileMenu(numWaveWindows: number, callbacks: AppMenuCallbacks, fullConfig: FullConfigType): Electron.MenuItemConstructorOptions[] { +function makeFileMenu( + numWaveWindows: number, + callbacks: AppMenuCallbacks, + fullConfig: FullConfigType +): Electron.MenuItemConstructorOptions[] { const fileMenu: Electron.MenuItemConstructorOptions[] = [ { label: "New Window", @@ -242,12 +247,9 @@ function makeViewMenu( accelerator: "CommandOrControl+=", click: (_, window) => { const wc = getWindowWebContents(window) ?? webContents; - if (wc == null) { - return; + if (wc) { + increaseZoomLevel(wc); } - const newZoom = Math.min(5, wc.getZoomFactor() + 0.2); - wc.setZoomFactor(newZoom); - wc.send("zoom-factor-change", newZoom); }, }, { @@ -255,12 +257,9 @@ function makeViewMenu( accelerator: "CommandOrControl+Shift+=", click: (_, window) => { const wc = getWindowWebContents(window) ?? webContents; - if (wc == null) { - return; + if (wc) { + increaseZoomLevel(wc); } - const newZoom = Math.min(5, wc.getZoomFactor() + 0.2); - wc.setZoomFactor(newZoom); - wc.send("zoom-factor-change", newZoom); }, visible: false, acceleratorWorksWhenHidden: true, @@ -270,12 +269,9 @@ function makeViewMenu( accelerator: "CommandOrControl+-", click: (_, window) => { const wc = getWindowWebContents(window) ?? webContents; - if (wc == null) { - return; + if (wc) { + decreaseZoomLevel(wc); } - const newZoom = Math.max(0.2, wc.getZoomFactor() - 0.2); - wc.setZoomFactor(newZoom); - wc.send("zoom-factor-change", newZoom); }, }, { @@ -283,12 +279,9 @@ function makeViewMenu( accelerator: "CommandOrControl+Shift+-", click: (_, window) => { const wc = getWindowWebContents(window) ?? webContents; - if (wc == null) { - return; + if (wc) { + decreaseZoomLevel(wc); } - const newZoom = Math.max(0.2, wc.getZoomFactor() - 0.2); - wc.setZoomFactor(newZoom); - wc.send("zoom-factor-change", newZoom); }, visible: false, acceleratorWorksWhenHidden: true, @@ -380,7 +373,11 @@ export function instantiateAppMenu(workspaceOrBuilderId?: string): Promise { const menu = await instantiateAppMenu(); electron.Menu.setApplicationMenu(menu); @@ -389,7 +386,7 @@ export function makeAppMenu() { waveEventSubscribe({ eventType: "workspace:update", - handler: makeAppMenu, + handler: makeAndSetAppMenu, }); function getWebContentsByWorkspaceOrBuilderId(workspaceOrBuilderId: string): electron.WebContents { diff --git a/emain/emain-tabview.ts b/emain/emain-tabview.ts index 5855198ff..dcc15b1e5 100644 --- a/emain/emain-tabview.ts +++ b/emain/emain-tabview.ts @@ -2,17 +2,88 @@ // SPDX-License-Identifier: Apache-2.0 import { RpcApi } from "@/app/store/wshclientapi"; -import { adaptFromElectronKeyEvent } from "@/util/keyutil"; +import { adaptFromElectronKeyEvent, checkKeyPressed } from "@/util/keyutil"; import { CHORD_TIMEOUT } from "@/util/sharedconst"; import { Rectangle, shell, WebContentsView } from "electron"; -import { getWaveWindowById } from "emain/emain-window"; +import { createNewWaveWindow, getWaveWindowById } from "emain/emain-window"; import path from "path"; import { configureAuthKeyRequestInjection } from "./authkey"; import { setWasActive } from "./emain-activity"; -import { getElectronAppBasePath, isDevVite } from "./emain-platform"; -import { handleCtrlShiftFocus, handleCtrlShiftState, shFrameNavHandler, shNavHandler } from "./emain-util"; +import { getElectronAppBasePath, isDevVite, unamePlatform } from "./emain-platform"; +import { + decreaseZoomLevel, + handleCtrlShiftFocus, + handleCtrlShiftState, + increaseZoomLevel, + shFrameNavHandler, + shNavHandler, +} from "./emain-util"; import { ElectronWshClient } from "./emain-wsh"; +function handleWindowsMenuAccelerators(waveEvent: WaveKeyboardEvent, tabView: WaveTabView): boolean { + const waveWindow = getWaveWindowById(tabView.waveWindowId); + + if (checkKeyPressed(waveEvent, "Ctrl:Shift:n")) { + createNewWaveWindow(); + return true; + } + + if (checkKeyPressed(waveEvent, "Ctrl:Shift:r")) { + tabView.webContents.reloadIgnoringCache(); + return true; + } + + if (checkKeyPressed(waveEvent, "Ctrl:v")) { + tabView.webContents.paste(); + return true; + } + + if (checkKeyPressed(waveEvent, "Ctrl:0")) { + tabView.webContents.setZoomFactor(1); + tabView.webContents.send("zoom-factor-change", 1); + return true; + } + + if (checkKeyPressed(waveEvent, "Ctrl:=") || checkKeyPressed(waveEvent, "Ctrl:Shift:=")) { + increaseZoomLevel(tabView.webContents); + return true; + } + + if (checkKeyPressed(waveEvent, "Ctrl:-") || checkKeyPressed(waveEvent, "Ctrl:Shift:-")) { + decreaseZoomLevel(tabView.webContents); + return true; + } + + if (checkKeyPressed(waveEvent, "F11")) { + if (waveWindow) { + waveWindow.setFullScreen(!waveWindow.isFullScreen()); + } + return true; + } + + for (let i = 1; i <= 9; i++) { + if (checkKeyPressed(waveEvent, `Alt:Ctrl:${i}`)) { + const workspaceNum = i - 1; + RpcApi.WorkspaceListCommand(ElectronWshClient).then((workspaceList) => { + if (workspaceList && workspaceNum < workspaceList.length) { + const workspace = workspaceList[workspaceNum]; + if (waveWindow) { + waveWindow.switchWorkspace(workspace.workspacedata.oid); + } + } + }); + return true; + } + } + + if (checkKeyPressed(waveEvent, "Alt:Shift:i")) { + tabView.webContents.toggleDevTools(); + return true; + } + + return false; +} + function computeBgColor(fullConfig: FullConfigType): string { const settings = fullConfig?.settings; const isTransparent = settings?.["window:transparent"] ?? false; @@ -249,6 +320,14 @@ export async function getOrCreateWebViewForTab(waveWindowId: string, tabId: stri e.preventDefault(); tabView.setKeyboardChordMode(false); tabView.webContents.send("reinject-key", waveEvent); + return; + } + + if (unamePlatform === "win32" && input.type == "keyDown") { + if (handleWindowsMenuAccelerators(waveEvent, tabView)) { + e.preventDefault(); + return; + } } }); tabView.webContents.on("zoom-changed", (e) => { diff --git a/emain/emain-util.ts b/emain/emain-util.ts index 712aeb52a..b04fda0df 100644 --- a/emain/emain-util.ts +++ b/emain/emain-util.ts @@ -8,6 +8,22 @@ export const WaveAppPathVarName = "WAVETERM_APP_PATH"; export const WaveAppResourcesPathVarName = "WAVETERM_RESOURCES_PATH"; export const WaveAppElectronExecPath = "WAVETERM_ELECTRONEXECPATH"; +const MinZoomLevel = 0.4; +const MaxZoomLevel = 2.6; +const ZoomDelta = 0.2; + +export function increaseZoomLevel(webContents: electron.WebContents): void { + const newZoom = Math.min(MaxZoomLevel, webContents.getZoomFactor() + ZoomDelta); + webContents.setZoomFactor(newZoom); + webContents.send("zoom-factor-change", newZoom); +} + +export function decreaseZoomLevel(webContents: electron.WebContents): void { + const newZoom = Math.max(MinZoomLevel, webContents.getZoomFactor() - ZoomDelta); + webContents.setZoomFactor(newZoom); + webContents.send("zoom-factor-change", newZoom); +} + export function getElectronExecPath(): string { return process.execPath; } diff --git a/emain/emain-window.ts b/emain/emain-window.ts index 059d7e39b..6d30daebe 100644 --- a/emain/emain-window.ts +++ b/emain/emain-window.ts @@ -23,7 +23,7 @@ import { ElectronWshClient } from "./emain-wsh"; import { updater } from "./updater"; export type WindowOpts = { - unamePlatform: string; + unamePlatform: NodeJS.Platform; isPrimaryStartupWindow?: boolean; foregroundWindow?: boolean; }; @@ -41,7 +41,10 @@ export function calculateWindowBounds( let winPosX = pos?.x ?? 100; let winPosY = pos?.y ?? 100; - if ((winWidth == null || winWidth === 0 || winHeight == null || winHeight === 0) && settings?.["window:dimensions"]) { + if ( + (winWidth == null || winWidth === 0 || winHeight == null || winHeight === 0) && + settings?.["window:dimensions"] + ) { const dimensions = settings["window:dimensions"]; const match = dimensions.match(/^(\d+)[xX](\d+)$/); @@ -147,52 +150,64 @@ export class WaveBrowserWindow extends BaseWindow { console.log("create win", waveWindow.oid); const winBounds = calculateWindowBounds(waveWindow.winsize, waveWindow.pos, settings); const winOpts: 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: MinWindowWidth, minHeight: MinWindowHeight, - 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; - } + + if (opts.unamePlatform === "darwin") { + winOpts.titleBarStyle = "hiddenInset"; + winOpts.titleBarOverlay = false; + winOpts.autoHideMenuBar = !settings?.["window:showmenubar"]; + if (isTransparent) { + winOpts.transparent = true; + } else if (isBlur) { + winOpts.vibrancy = "fullscreen-ui"; + } else { + winOpts.backgroundColor = "#222222"; + } + } else if (opts.unamePlatform === "linux") { + winOpts.titleBarStyle = settings["window:nativetitlebar"] ? "default" : "hidden"; + winOpts.titleBarOverlay = { + symbolColor: "white", + color: "#00000000", + }; + winOpts.icon = path.join(getElectronAppBasePath(), "public/logos/wave-logo-dark.png"); + winOpts.autoHideMenuBar = !settings?.["window:showmenubar"]; + if (isTransparent) { + winOpts.transparent = true; + } else { + winOpts.backgroundColor = "#222222"; + } + } else if (opts.unamePlatform === "win32") { + winOpts.titleBarStyle = "hidden"; + winOpts.titleBarOverlay = { + color: "#222222", + symbolColor: "#c3c8c2", + height: 32, + }; + if (isTransparent) { + winOpts.transparent = true; + } else if (isBlur) { + winOpts.backgroundMaterial = "acrylic"; + } else { + winOpts.backgroundColor = "#222222"; } - } else { - winOpts.backgroundColor = "#222222"; } super(winOpts); + + if (opts.unamePlatform === "win32") { + this.setMenu(null); + } + const fullscreenOnLaunch = fullConfig?.settings["window:fullscreenonlaunch"]; if (fullscreenOnLaunch && opts.foregroundWindow) { this.once("show", () => { diff --git a/emain/emain.ts b/emain/emain.ts index e4d44e879..093a74bd4 100644 --- a/emain/emain.ts +++ b/emain/emain.ts @@ -23,7 +23,7 @@ import { } from "./emain-activity"; import { initIpcHandlers } from "./emain-ipc"; import { log } from "./emain-log"; -import { makeAppMenu, makeDockTaskbar } from "./emain-menu"; +import { makeAndSetAppMenu, makeDockTaskbar } from "./emain-menu"; import { checkIfRunningUnderARM64Translation, getElectronAppBasePath, @@ -318,7 +318,7 @@ globalEvents.on("windows-updated", () => { lastWaveWindowCount = wwCount; lastIsBuilderWindowActive = isBuilderActive; console.log("windows-updated", wwCount, "builder-active:", isBuilderActive); - makeAppMenu(); + makeAndSetAppMenu(); }); async function appMain() { @@ -360,7 +360,7 @@ async function appMain() { setTimeout(runActiveTimer, 5000); // start active timer, wait 5s just to be safe setTimeout(sendDisplaysTDataEvent, 5000); - makeAppMenu(); + makeAndSetAppMenu(); makeDockTaskbar(); await configureAutoUpdater(); setGlobalIsStarting(false); diff --git a/frontend/app/element/windowdrag.scss b/frontend/app/element/windowdrag.scss deleted file mode 100644 index 5229622eb..000000000 --- a/frontend/app/element/windowdrag.scss +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2024, Command Line Inc. -// SPDX-License-Identifier: Apache-2.0 - -.window-drag { - -webkit-app-region: drag !important; - z-index: var(--zindex-window-drag); -} diff --git a/frontend/app/element/windowdrag.tsx b/frontend/app/element/windowdrag.tsx deleted file mode 100644 index 7e4ed733d..000000000 --- a/frontend/app/element/windowdrag.tsx +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2025, Command Line Inc. -// SPDX-License-Identifier: Apache-2.0 - -import clsx from "clsx"; -import React, { forwardRef } from "react"; - -import "./windowdrag.scss"; - -interface WindowDragProps { - className?: string; - style?: React.CSSProperties; - children?: React.ReactNode; -} - -const WindowDrag = forwardRef(({ children, className, style }, ref) => { - return ( -
- {children} -
- ); -}); -WindowDrag.displayName = "WindowDrag"; - -export { WindowDrag }; diff --git a/frontend/app/modals/messagemodal.scss b/frontend/app/modals/messagemodal.scss index 41113f25b..f55374151 100644 --- a/frontend/app/modals/messagemodal.scss +++ b/frontend/app/modals/messagemodal.scss @@ -3,4 +3,8 @@ .message-modal { min-width: 400px; + + footer { + padding: 10px; + } } diff --git a/frontend/app/modals/modal.tsx b/frontend/app/modals/modal.tsx index 3683a576a..5733ec260 100644 --- a/frontend/app/modals/modal.tsx +++ b/frontend/app/modals/modal.tsx @@ -23,7 +23,21 @@ interface ModalProps { } const Modal = forwardRef( - ({ children, className, cancelLabel, okLabel, onCancel, onOk, onClose, onClickBackdrop, okDisabled, cancelDisabled }: ModalProps, ref) => { + ( + { + children, + className, + cancelLabel, + okLabel, + onCancel, + onOk, + onClose, + onClickBackdrop, + okDisabled, + cancelDisabled, + }: ModalProps, + ref + ) => { const renderBackdrop = (onClick) =>
; const renderFooter = () => { @@ -41,7 +55,14 @@ const Modal = forwardRef( {children} {renderFooter() && ( - + )} @@ -68,7 +89,14 @@ interface ModalFooterProps { cancelDisabled?: boolean; } -const ModalFooter = ({ onCancel, onOk, cancelLabel = "Cancel", okLabel = "Ok", okDisabled, cancelDisabled }: ModalFooterProps) => { +const ModalFooter = ({ + onCancel, + onOk, + cancelLabel = "Cancel", + okLabel = "Ok", + okDisabled, + cancelDisabled, +}: ModalFooterProps) => { return (
{onCancel && ( @@ -76,7 +104,11 @@ const ModalFooter = ({ onCancel, onOk, cancelLabel = "Cancel", okLabel = "Ok", o {cancelLabel} )} - {onOk && } + {onOk && ( + + )}
); }; @@ -87,8 +119,9 @@ interface FlexiModalProps { onClickBackdrop?: () => void; } -interface FlexiModalComponent - extends React.ForwardRefExoticComponent> { +interface FlexiModalComponent extends React.ForwardRefExoticComponent< + FlexiModalProps & React.RefAttributes +> { Content: typeof ModalContent; Footer: typeof ModalFooter; } diff --git a/frontend/app/store/global.ts b/frontend/app/store/global.ts index 0b6e35e75..47a544162 100644 --- a/frontend/app/store/global.ts +++ b/frontend/app/store/global.ts @@ -72,16 +72,26 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) { getApi().onFullScreenChange((isFullScreen) => { globalStore.set(isFullScreenAtom, isFullScreen); }); - } catch (_) { - // do nothing + } catch (e) { + console.log("failed to initialize isFullScreenAtom", e); + } + + const zoomFactorAtom = atom(1.0) as PrimitiveAtom; + try { + globalStore.set(zoomFactorAtom, getApi().getZoomFactor()); + getApi().onZoomFactorChange((zoomFactor) => { + globalStore.set(zoomFactorAtom, zoomFactor); + }); + } catch (e) { + console.log("failed to initialize zoomFactorAtom", e); } try { getApi().onMenuItemAbout(() => { modalsModel.pushModal("AboutModal"); }); - } catch (_) { - // do nothing + } catch (e) { + console.log("failed to initialize onMenuItemAbout handler", e); } const clientAtom: Atom = atom((get) => { @@ -135,8 +145,8 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) { getApi().onUpdaterStatusChange((status) => { globalStore.set(updaterStatusAtom, status); }); - } catch (_) { - // do nothing + } catch (e) { + console.log("failed to initialize updaterStatusAtom", e); } const reducedMotionSettingAtom = atom((get) => get(settingsAtom)?.["window:reducedmotion"]); @@ -187,6 +197,7 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) { tabAtom, staticTabId: staticTabIdAtom, isFullScreen: isFullScreenAtom, + zoomFactorAtom, controlShiftDelayAtom, updaterStatusAtom, prefersReducedMotionAtom, diff --git a/frontend/app/tab/tabbar.scss b/frontend/app/tab/tabbar.scss index dfd4df94f..9bb5c8058 100644 --- a/frontend/app/tab/tabbar.scss +++ b/frontend/app/tab/tabbar.scss @@ -1,28 +1,6 @@ // Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -.tab-bar-wrapper { - --default-indent: 10px; - --darwin-not-fullscreen-indent: calc(74px * var(--zoomfactor-inv)); -} - -.darwin:not(.fullscreen) .tab-bar-wrapper { - .window-drag.left { - width: var(--darwin-not-fullscreen-indent); - } -} - -.config-error-message { - max-width: 500px; - margin-bottom: 20px; - - h3 { - font-weight: bold; - font-size: 16px; - margin-bottom: 10px; - } -} - .tab-bar-wrapper { padding-top: 6px; position: relative; @@ -58,45 +36,13 @@ border: 1px solid var(--border-color); } - .dev-label, - .app-menu-button { - font-size: 26px; - user-select: none; - display: flex; - align-items: center; - justify-content: center; - padding: 0 6px 0 0; - } - - .app-menu-button { - cursor: pointer; - color: var(--secondary-text-color); - - &:hover { - color: var(--main-text-color); - } - } - - .dev-label { - color: var(--accent-color); - } - .tab-bar-right { display: flex; flex-direction: row; gap: 6px; height: 100%; align-items: center; - &:not(:empty) { - margin-left: auto; - margin-right: 6px; - } - } - - .config-error-button { - color: black; - padding: 0 6px; - flex: 0 0 fit-content; + margin-left: auto; } .add-tab { @@ -104,16 +50,6 @@ height: 27px; } - .window-drag { - height: 100%; - width: var(--default-indent); - flex-shrink: 0; - - &.right { - flex-grow: 1; - } - } - // Customize scrollbar styles .os-theme-dark, .os-theme-light { diff --git a/frontend/app/tab/tabbar.tsx b/frontend/app/tab/tabbar.tsx index c8051237d..2a479aa63 100644 --- a/frontend/app/tab/tabbar.tsx +++ b/frontend/app/tab/tabbar.tsx @@ -4,10 +4,9 @@ import { Button } from "@/app/element/button"; import { modalsModel } from "@/app/store/modalmodel"; import { WorkspaceLayoutModel } from "@/app/workspace/workspace-layout-model"; -import { WindowDrag } from "@/element/windowdrag"; import { deleteLayoutModelForTab } from "@/layout/index"; import { atoms, createTab, getApi, globalStore, setActiveTab } from "@/store/global"; -import { PLATFORM, PlatformMacOS } from "@/util/platformutil"; +import { isMacOS, isWindows } from "@/util/platformutil"; import { fireAndForget } from "@/util/util"; import { useAtomValue } from "jotai"; import { OverlayScrollbars } from "overlayscrollbars"; @@ -20,9 +19,9 @@ import "./tabbar.scss"; import { UpdateStatusBanner } from "./updatebanner"; import { WorkspaceSwitcher } from "./workspaceswitcher"; -const TAB_DEFAULT_WIDTH = 130; -const TAB_MIN_WIDTH = 100; -const OS_OPTIONS = { +const TabDefaultWidth = 130; +const TabMinWidth = 100; +const OSOptions = { overflow: { x: "scroll", y: "hidden", @@ -43,13 +42,34 @@ interface TabBarProps { workspace: Workspace; } +const WaveAIButton = memo(() => { + const aiPanelOpen = useAtomValue(WorkspaceLayoutModel.getInstance().panelVisibleAtom); + + const onClick = () => { + const currentVisible = WorkspaceLayoutModel.getInstance().getAIPanelVisible(); + WorkspaceLayoutModel.getInstance().setAIPanelVisible(!currentVisible); + }; + + return ( +
+ + AI +
+ ); +}); +WaveAIButton.displayName = "WaveAIButton"; + const ConfigErrorMessage = () => { const fullConfig = useAtomValue(atoms.fullConfigAtom); if (fullConfig?.configerrors == null || fullConfig?.configerrors.length == 0) { return ( -
-

Configuration Clean

+
+

Configuration Clean

There are no longer any errors detected in your config.

); @@ -57,8 +77,8 @@ const ConfigErrorMessage = () => { if (fullConfig?.configerrors.length == 1) { const singleError = fullConfig.configerrors[0]; return ( -
-

Configuration Error

+
+

Configuration Error

{singleError.file}: {singleError.err}
@@ -66,8 +86,8 @@ const ConfigErrorMessage = () => { ); } return ( -
-

Configuration Error

+
+

Configuration Error

    {fullConfig.configerrors.map((error, index) => (
  • @@ -92,7 +112,7 @@ const ConfigErrorIcon = ({ buttonRef }: { buttonRef: React.RefObject} - className="config-error-button red" + className="text-black flex-[0_0_fit-content] !h-full !px-3 red" onClick={handleClick} > @@ -164,17 +184,17 @@ const TabBar = memo(({ workspace }: TabBarProps) => { }); const osInstanceRef = useRef(null); const draggerLeftRef = useRef(null); + const draggerRightRef = useRef(null); const workspaceSwitcherRef = useRef(null); const appMenuButtonRef = useRef(null); - const tabWidthRef = useRef(TAB_DEFAULT_WIDTH); + const tabWidthRef = useRef(TabDefaultWidth); const scrollableRef = useRef(false); const updateStatusBannerRef = useRef(null); const configErrorButtonRef = useRef(null); const prevAllLoadedRef = useRef(false); const activeTabId = useAtomValue(atoms.staticTabId); const isFullScreen = useAtomValue(atoms.isFullScreen); - const aiPanelOpen = useAtomValue(WorkspaceLayoutModel.getInstance().panelVisibleAtom); - + const zoomFactor = useAtomValue(atoms.zoomFactorAtom); const settings = useAtomValue(atoms.settingsAtom); let prevDelta: number; @@ -190,16 +210,12 @@ const TabBar = memo(({ workspace }: TabBarProps) => { return; } // Compare current tabIds with new workspace.tabids - console.log("tabbar workspace", workspace); - const newTabIdsArr = [...(workspace.pinnedtabids ?? []), ...(workspace.tabids ?? [])]; const newPinnedTabSet = new Set(workspace.pinnedtabids ?? []); const areEqual = strArrayIsEqual(tabIds, newTabIdsArr) && setIsEqual(pinnedTabIds, newPinnedTabSet); if (!areEqual) { - console.log("newPinnedTabIds", newPinnedTabSet); - console.log("newTabIdList", newTabIdsArr); setTabIds(newTabIdsArr); setPinnedTabIds(newPinnedTabSet); } @@ -228,6 +244,7 @@ const TabBar = memo(({ workspace }: TabBarProps) => { const tabbarWrapperWidth = tabbarWrapperRef.current.getBoundingClientRect().width; const windowDragLeftWidth = draggerLeftRef.current.getBoundingClientRect().width; + const windowDragRightWidth = draggerRightRef.current?.getBoundingClientRect().width ?? 0; const addBtnWidth = addBtnRef.current.getBoundingClientRect().width; const updateStatusLabelWidth = updateStatusBannerRef.current?.getBoundingClientRect().width ?? 0; const configErrorWidth = configErrorButtonRef.current?.getBoundingClientRect().width ?? 0; @@ -236,6 +253,7 @@ const TabBar = memo(({ workspace }: TabBarProps) => { const nonTabElementsWidth = windowDragLeftWidth + + windowDragRightWidth + addBtnWidth + updateStatusLabelWidth + configErrorWidth + @@ -249,7 +267,7 @@ const TabBar = memo(({ workspace }: TabBarProps) => { let idealTabWidth = spaceForTabs / numberOfTabs; // Apply min/max constraints - idealTabWidth = Math.max(TAB_MIN_WIDTH, Math.min(idealTabWidth, TAB_DEFAULT_WIDTH)); + idealTabWidth = Math.max(TabMinWidth, Math.min(idealTabWidth, TabDefaultWidth)); // Determine if the tab bar needs to be scrollable const newScrollable = idealTabWidth * numberOfTabs > spaceForTabs; @@ -280,7 +298,7 @@ const TabBar = memo(({ workspace }: TabBarProps) => { // Initialize/destroy overlay scrollbars if (newScrollable) { - osInstanceRef.current = OverlayScrollbars(tabBarRef.current, { ...(OS_OPTIONS as any) }); + osInstanceRef.current = OverlayScrollbars(tabBarRef.current, { ...(OSOptions as any) }); } else { if (osInstanceRef.current) { osInstanceRef.current.destroy(); @@ -414,7 +432,7 @@ const TabBar = memo(({ workspace }: TabBarProps) => { // Constrain movement within the container bounds if (tabBarRef.current) { const numberOfTabs = tabIds.length; - const totalDefaultTabWidth = numberOfTabs * TAB_DEFAULT_WIDTH; + const totalDefaultTabWidth = numberOfTabs * TabDefaultWidth; if (totalDefaultTabWidth < tabBarRectWidth) { // Set to the total default tab width if there's vacant space tabBarRectWidth = totalDefaultTabWidth; @@ -634,28 +652,28 @@ const TabBar = memo(({ workspace }: TabBarProps) => { getApi().showWorkspaceAppMenu(workspace.oid); } - function onWaveAIClick() { - const currentVisible = WorkspaceLayoutModel.getInstance().getAIPanelVisible(); - WorkspaceLayoutModel.getInstance().setAIPanelVisible(!currentVisible); + const tabsWrapperWidth = tabIds.length * tabWidthRef.current; + const showAppMenuButton = isWindows() || (!isMacOS() && !settings["window:showmenubar"]); + + // Calculate window drag left width based on platform and state + let windowDragLeftWidth = 10; + if (isMacOS() && !isFullScreen) { + if (zoomFactor > 0) { + windowDragLeftWidth = 74 / zoomFactor; + } else { + windowDragLeftWidth = 74; + } } - const tabsWrapperWidth = tabIds.length * tabWidthRef.current; - const waveaiButton = ( -
    - - AI -
    - ); - const appMenuButton = - PLATFORM !== PlatformMacOS && !settings["window:showmenubar"] ? ( -
    - -
    - ) : undefined; + // Calculate window drag right width + let windowDragRightWidth = 6; + if (isWindows()) { + if (zoomFactor > 0) { + windowDragRightWidth = 139 / zoomFactor; + } else { + windowDragRightWidth = 139; + } + } const addtabButtonDecl: IconButtonDecl = { elemtype: "iconbutton", @@ -665,9 +683,22 @@ const TabBar = memo(({ workspace }: TabBarProps) => { }; return (
    - - {appMenuButton} - {waveaiButton} +
    + {showAppMenuButton && ( +
    + +
    + )} +
    @@ -699,6 +730,11 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
    +
    ); diff --git a/frontend/app/view/codeeditor/schemaendpoints.ts b/frontend/app/view/codeeditor/schemaendpoints.ts index 0f79837eb..90defbf19 100644 --- a/frontend/app/view/codeeditor/schemaendpoints.ts +++ b/frontend/app/view/codeeditor/schemaendpoints.ts @@ -1,7 +1,6 @@ // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -import { getApi } from "@/app/store/global"; import { getWebServerEndpoint } from "@/util/endpoints"; type EndpointInfo = { @@ -10,36 +9,13 @@ type EndpointInfo = { schema: object; }; -function prependWildcard(path: string): string { - return path.startsWith("/") ? `*${path}` : `*/${path}`; -} - -function convertToTildePath(absolutePath: string): string { - const homeDir = getApi().getHomeDir(); - if (absolutePath.startsWith(homeDir)) { - return "~" + absolutePath.slice(homeDir.length); - } - return absolutePath; -} - -function makeConfigPathMatches(suffix: string): Array { - const configPath = `${getApi().getConfigDir()}${suffix}`; - const tildePath = convertToTildePath(configPath); - const paths = [configPath, prependWildcard(configPath)]; - if (tildePath !== configPath) { - paths.push(tildePath); - paths.push(prependWildcard(tildePath)); - } - return paths; -} - const allFilepaths: Map> = new Map(); -allFilepaths.set(`${getWebServerEndpoint()}/schema/settings.json`, makeConfigPathMatches("/settings.json")); -allFilepaths.set(`${getWebServerEndpoint()}/schema/connections.json`, makeConfigPathMatches("/connections.json")); -allFilepaths.set(`${getWebServerEndpoint()}/schema/aipresets.json`, makeConfigPathMatches("/presets/ai.json")); -allFilepaths.set(`${getWebServerEndpoint()}/schema/bgpresets.json`, makeConfigPathMatches("/presets/bg.json")); -allFilepaths.set(`${getWebServerEndpoint()}/schema/waveai.json`, makeConfigPathMatches("/waveai.json")); -allFilepaths.set(`${getWebServerEndpoint()}/schema/widgets.json`, makeConfigPathMatches("/widgets.json")); +allFilepaths.set(`${getWebServerEndpoint()}/schema/settings.json`, ["*/WAVECONFIGPATH/settings.json"]); +allFilepaths.set(`${getWebServerEndpoint()}/schema/connections.json`, ["*/WAVECONFIGPATH/connections.json"]); +allFilepaths.set(`${getWebServerEndpoint()}/schema/aipresets.json`, ["*/WAVECONFIGPATH/presets/ai.json"]); +allFilepaths.set(`${getWebServerEndpoint()}/schema/bgpresets.json`, ["*/WAVECONFIGPATH/presets/bg.json"]); +allFilepaths.set(`${getWebServerEndpoint()}/schema/waveai.json`, ["*/WAVECONFIGPATH/waveai.json"]); +allFilepaths.set(`${getWebServerEndpoint()}/schema/widgets.json`, ["*/WAVECONFIGPATH/widgets.json"]); async function getSchemaEndpointInfo(endpoint: string): Promise { let schema: Object; diff --git a/frontend/app/view/term/term-model.ts b/frontend/app/view/term/term-model.ts index 0e783c7ee..a1b9eb60d 100644 --- a/frontend/app/view/term/term-model.ts +++ b/frontend/app/view/term/term-model.ts @@ -26,6 +26,7 @@ import { } from "@/store/global"; import * as services from "@/store/services"; import * as keyutil from "@/util/keyutil"; +import { isMacOS, isWindows } from "@/util/platformutil"; import { boundNumber, stringToBase64 } from "@/util/util"; import * as jotai from "jotai"; import * as React from "react"; @@ -496,6 +497,25 @@ export class TermViewModel implements ViewModel { return false; } + shouldHandleCtrlVPaste(): boolean { + // macOS never uses Ctrl-V for paste (uses Cmd-V) + if (isMacOS()) { + return false; + } + + // Get the app:ctrlvpaste setting + const ctrlVPasteAtom = getSettingsKeyAtom("app:ctrlvpaste"); + const ctrlVPasteSetting = globalStore.get(ctrlVPasteAtom); + + // If setting is explicitly set, use it + if (ctrlVPasteSetting != null) { + return ctrlVPasteSetting; + } + + // Default behavior: Windows=true, Linux/other=false + return isWindows(); + } + handleTerminalKeydown(event: KeyboardEvent): boolean { const waveEvent = keyutil.adaptFromReactOrNativeKeyEvent(event); if (waveEvent.type != "keydown") { @@ -525,6 +545,15 @@ export class TermViewModel implements ViewModel { return false; } } + + // Check for Ctrl-V paste (platform-dependent) + if (this.shouldHandleCtrlVPaste() && keyutil.checkKeyPressed(waveEvent, "Ctrl:v")) { + event.preventDefault(); + event.stopPropagation(); + getApi().nativePaste(); + return false; + } + if (keyutil.checkKeyPressed(waveEvent, "Ctrl:Shift:v")) { event.preventDefault(); event.stopPropagation(); diff --git a/frontend/app/view/waveconfig/waveconfig.tsx b/frontend/app/view/waveconfig/waveconfig.tsx index 1bce94474..861fadeae 100644 --- a/frontend/app/view/waveconfig/waveconfig.tsx +++ b/frontend/app/view/waveconfig/waveconfig.tsx @@ -275,7 +275,7 @@ const WaveConfigView = memo(({ blockId, model }: ViewComponentProps { )} {isDev() ? (
    @@ -526,7 +526,7 @@ const Widgets = memo(() => { ) : null} {isDev() ? (
    diff --git a/frontend/tailwindsetup.css b/frontend/tailwindsetup.css index 7661b3f91..a41c47605 100644 --- a/frontend/tailwindsetup.css +++ b/frontend/tailwindsetup.css @@ -70,6 +70,8 @@ --container-xs: 300px; --container-xxs: 200px; --container-tiny: 120px; + + --z-window-drag: 100; } :root { diff --git a/frontend/types/custom.d.ts b/frontend/types/custom.d.ts index 7f78df430..3476fe539 100644 --- a/frontend/types/custom.d.ts +++ b/frontend/types/custom.d.ts @@ -22,6 +22,7 @@ declare global { tabAtom: jotai.Atom; // driven from WOS staticTabId: jotai.Atom; isFullScreen: jotai.PrimitiveAtom; + zoomFactorAtom: jotai.PrimitiveAtom; controlShiftDelayAtom: jotai.PrimitiveAtom; prefersReducedMotionAtom: jotai.Atom; updaterStatusAtom: jotai.PrimitiveAtom; diff --git a/pkg/telemetry/telemetrydata/telemetrydata.go b/pkg/telemetry/telemetrydata/telemetrydata.go index deedeea90..db3e3c464 100644 --- a/pkg/telemetry/telemetrydata/telemetrydata.go +++ b/pkg/telemetry/telemetrydata/telemetrydata.go @@ -81,11 +81,12 @@ type TEventUserProps struct { LocCountryCode string `json:"loc:countrycode,omitempty"` LocRegionCode string `json:"loc:regioncode,omitempty"` - SettingsCustomWidgets int `json:"settings:customwidgets,omitempty"` - SettingsCustomAIPresets int `json:"settings:customaipresets,omitempty"` - SettingsCustomSettings int `json:"settings:customsettings,omitempty"` - SettingsCustomAIModes int `json:"settings:customaimodes,omitempty"` - SettingsSecretsCount int `json:"settings:secretscount,omitempty"` + SettingsCustomWidgets int `json:"settings:customwidgets,omitempty"` + SettingsCustomAIPresets int `json:"settings:customaipresets,omitempty"` + SettingsCustomSettings int `json:"settings:customsettings,omitempty"` + SettingsCustomAIModes int `json:"settings:customaimodes,omitempty"` + SettingsSecretsCount int `json:"settings:secretscount,omitempty"` + SettingsTransparent bool `json:"settings:transparent,omitempty"` } type TEventProps struct {