From b964b035fcd9a72eed819c7a759e245d2d56d70a Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Tue, 18 Feb 2025 14:57:10 -0800 Subject: [PATCH 01/11] feat: add error popups to directory preview --- .../app/view/preview/directorypreview.tsx | 66 +++++++++++++------ 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/frontend/app/view/preview/directorypreview.tsx b/frontend/app/view/preview/directorypreview.tsx index 70f8dd9bdf..912e0e4e66 100644 --- a/frontend/app/view/preview/directorypreview.tsx +++ b/frontend/app/view/preview/directorypreview.tsx @@ -301,13 +301,21 @@ function DirectoryTable({ newPath = path.substring(0, lastInstance) + newName; console.log(`replacing ${fileName} with ${newName}: ${path}`); fireAndForget(async () => { - await RpcApi.FileMoveCommand(TabRpcClient, { - srcuri: await model.formatRemoteUri(path, globalStore.get), - desturi: await model.formatRemoteUri(newPath, globalStore.get), - opts: { - recursive: true, - }, - }); + try { + await RpcApi.FileMoveCommand(TabRpcClient, { + srcuri: await model.formatRemoteUri(path, globalStore.get), + desturi: await model.formatRemoteUri(newPath, globalStore.get), + opts: { + recursive: true, + }, + }); + } catch (e) { + const errorStatus: ErrorMsg = { + status: "Rename Failed", + text: `${e}`, + }; + globalStore.set(model.errorMsgAtom, errorStatus); + } model.refreshCallback(); }); } @@ -643,10 +651,18 @@ function TableBody({ label: "Delete", click: () => { fireAndForget(async () => { - await RpcApi.FileDeleteCommand(TabRpcClient, { - path: await model.formatRemoteUri(finfo.path, globalStore.get), - recursive: false, - }).catch((e) => console.log(e)); + try { + await RpcApi.FileDeleteCommand(TabRpcClient, { + path: await model.formatRemoteUri(finfo.path, globalStore.get), + recursive: false, + }); + } catch (e) { + const errorStatus: ErrorMsg = { + status: "Delete Failed", + text: `${e}`, + }; + globalStore.set(model.errorMsgAtom, errorStatus); + } setRefreshVersion((current) => current + 1); }); }, @@ -796,16 +812,26 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) { useEffect(() => { const getContent = async () => { - const file = await RpcApi.FileReadCommand( - TabRpcClient, - { - info: { - path: await model.formatRemoteUri(dirPath, globalStore.get), + let entries: FileInfo[]; + try { + const file = await RpcApi.FileReadCommand( + TabRpcClient, + { + info: { + path: await model.formatRemoteUri(dirPath, globalStore.get), + }, }, - }, - null - ); - setUnfilteredData(file.entries); + null + ); + entries = file.entries ?? []; + } catch (e) { + const errorStatus: ErrorMsg = { + status: "Cannot Read Directory", + text: `${e}`, + }; + globalStore.set(model.errorMsgAtom, errorStatus); + } + setUnfilteredData(entries); }; getContent(); }, [conn, dirPath, refreshVersion]); From e60fb2aff20477906df1acffab63b77e81b0a83a Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Tue, 18 Feb 2025 14:59:13 -0800 Subject: [PATCH 02/11] move parentFileInfo get to click handler --- .../app/view/preview/directorypreview.tsx | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/frontend/app/view/preview/directorypreview.tsx b/frontend/app/view/preview/directorypreview.tsx index 912e0e4e66..a09a5343ba 100644 --- a/frontend/app/view/preview/directorypreview.tsx +++ b/frontend/app/view/preview/directorypreview.tsx @@ -529,17 +529,6 @@ function TableBody({ } const normPath = finfo.path; const fileName = finfo.path.split("/").pop(); - let parentFileInfo: FileInfo; - try { - parentFileInfo = await RpcApi.FileInfoCommand(TabRpcClient, { - info: { - path: await model.formatRemoteUri(finfo.dir, globalStore.get), - }, - }); - } catch (e) { - console.log("could not get parent file info. using child file info as fallback"); - parentFileInfo = finfo; - } const menu: ContextMenuItem[] = [ { label: "New File", @@ -620,9 +609,21 @@ function TableBody({ }, { label: makeNativeLabel(PLATFORM, true, true), - click: () => { - getApi().openNativePath(parentFileInfo.path); - }, + click: () => + fireAndForget(async () => { + let parentFileInfo: FileInfo; + try { + parentFileInfo = await RpcApi.FileInfoCommand(TabRpcClient, { + info: { + path: await model.formatRemoteUri(finfo.dir, globalStore.get), + }, + }); + } catch (e) { + console.log("could not get parent file info. using child file info as fallback"); + parentFileInfo = finfo; + } + getApi().openNativePath(parentFileInfo.path); + }), } ); } From ddf4825327c123aae1c455176f48e28b2f19a1ba Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Tue, 18 Feb 2025 15:12:39 -0800 Subject: [PATCH 03/11] fix: no need to stat parent for reveal in finder --- frontend/app/view/preview/directorypreview.tsx | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/frontend/app/view/preview/directorypreview.tsx b/frontend/app/view/preview/directorypreview.tsx index a09a5343ba..6cd61e0896 100644 --- a/frontend/app/view/preview/directorypreview.tsx +++ b/frontend/app/view/preview/directorypreview.tsx @@ -609,21 +609,9 @@ function TableBody({ }, { label: makeNativeLabel(PLATFORM, true, true), - click: () => - fireAndForget(async () => { - let parentFileInfo: FileInfo; - try { - parentFileInfo = await RpcApi.FileInfoCommand(TabRpcClient, { - info: { - path: await model.formatRemoteUri(finfo.dir, globalStore.get), - }, - }); - } catch (e) { - console.log("could not get parent file info. using child file info as fallback"); - parentFileInfo = finfo; - } - getApi().openNativePath(parentFileInfo.path); - }), + click: () => { + getApi().openNativePath(finfo.dir); + }, } ); } From 9fef9697483ae46598939a74bd48aaa58c174138 Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Tue, 18 Feb 2025 15:40:33 -0800 Subject: [PATCH 04/11] feat: connect preview rpc calls to error overlay This leaves out the fetchSuggestion case which will be handled after an upcoming merge. Others are left out because they are for utility and already work as expected. --- frontend/app/view/preview/preview.tsx | 30 ++++++++++++++++++--------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/frontend/app/view/preview/preview.tsx b/frontend/app/view/preview/preview.tsx index 09b961fae9..23f112b529 100644 --- a/frontend/app/view/preview/preview.tsx +++ b/frontend/app/view/preview/preview.tsx @@ -434,13 +434,20 @@ export class PreviewModel implements ViewModel { if (fileName == null) { return null; } - console.log("full file path", path); - const file = await RpcApi.FileReadCommand(TabRpcClient, { - info: { - path, - }, - }); - console.log("full file", file); + let file: FileData; + try { + file = await RpcApi.FileReadCommand(TabRpcClient, { + info: { + path, + }, + }); + } catch (e) { + const errorStatus: ErrorMsg = { + status: "File Read Failed", + text: `${e}`, + }; + globalStore.set(this.errorMsgAtom, errorStatus); + } return file; }); @@ -593,7 +600,6 @@ export class PreviewModel implements ViewModel { path: await this.formatRemoteUri(fileInfo.dir, globalStore.get), }, }); - console.log("parent file info", parentFileInfo); return parentFileInfo; } catch { return undefined; @@ -680,8 +686,12 @@ export class PreviewModel implements ViewModel { globalStore.set(this.fileContent, newFileContent); globalStore.set(this.newFileContent, null); console.log("saved file", filePath); - } catch (error) { - console.error("Error saving file:", error); + } catch (e) { + const errorStatus: ErrorMsg = { + status: "Save Failed", + text: `${e}`, + }; + globalStore.set(this.errorMsgAtom, errorStatus); } } From 332fedbedca435215c5c4433118cd8fbeae3e5c4 Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Tue, 18 Feb 2025 15:44:35 -0800 Subject: [PATCH 05/11] save --- frontend/app/app-bg.tsx | 5 +- frontend/app/app.tsx | 11 +-- frontend/app/element/quicktips.tsx | 4 +- frontend/app/store/global.ts | 7 +- frontend/app/tab/tabbar.tsx | 5 +- .../app/view/preview/directorypreview.tsx | 73 +++---------------- frontend/app/view/preview/preview.tsx | 57 +-------------- frontend/app/view/term/termwrap.ts | 7 +- frontend/app/view/vdom/vdom-model.tsx | 5 +- frontend/util/keyutil.ts | 18 +---- frontend/util/platformutil.ts | 27 +++++++ frontend/util/previewutil.ts | 43 +++++++++++ frontend/util/util.ts | 24 ------ 13 files changed, 104 insertions(+), 182 deletions(-) create mode 100644 frontend/util/platformutil.ts create mode 100644 frontend/util/previewutil.ts diff --git a/frontend/app/app-bg.tsx b/frontend/app/app-bg.tsx index 9ac70a550e..718e3b82af 100644 --- a/frontend/app/app-bg.tsx +++ b/frontend/app/app-bg.tsx @@ -1,12 +1,13 @@ // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +import { PLATFORM, PlatformMacOS } from "@/util/platformutil"; import { computeBgStyleFromMeta } from "@/util/waveutil"; import useResizeObserver from "@react-hook/resize-observer"; import { useAtomValue } from "jotai"; import { CSSProperties, useCallback, useLayoutEffect, useRef } from "react"; import { debounce } from "throttle-debounce"; -import { atoms, getApi, PLATFORM, WOS } from "./store/global"; +import { atoms, getApi, WOS } from "./store/global"; import { useWaveObjectValue } from "./store/wos"; export function AppBackground() { @@ -18,7 +19,7 @@ export function AppBackground() { debounce(30, () => { if ( bgRef.current && - PLATFORM !== "darwin" && + PLATFORM !== PlatformMacOS && bgRef.current && "windowControlsOverlay" in window.navigator ) { diff --git a/frontend/app/app.tsx b/frontend/app/app.tsx index 880a034d25..45d7e7560a 100644 --- a/frontend/app/app.tsx +++ b/frontend/app/app.tsx @@ -3,15 +3,7 @@ import { Workspace } from "@/app/workspace/workspace"; import { ContextMenuModel } from "@/store/contextmenu"; -import { - atoms, - createBlock, - getSettingsPrefixAtom, - globalStore, - isDev, - PLATFORM, - removeFlashError, -} from "@/store/global"; +import { atoms, createBlock, getSettingsPrefixAtom, globalStore, isDev, removeFlashError } from "@/store/global"; import { appHandleKeyDown, keyboardMouseDownHandler } from "@/store/keymodel"; import { getElemAsStr } from "@/util/focusutil"; import * as keyutil from "@/util/keyutil"; @@ -30,6 +22,7 @@ import { NotificationBubbles } from "./notification/notificationbubbles"; import "./app.scss"; // this should come after app.scss (don't remove the newline above otherwise prettier will reorder these imports) +import { PLATFORM } from "@/util/platformutil"; import "../tailwindsetup.css"; const dlog = debug("wave:app"); diff --git a/frontend/app/element/quicktips.tsx b/frontend/app/element/quicktips.tsx index 99e51d8912..36243b8b00 100644 --- a/frontend/app/element/quicktips.tsx +++ b/frontend/app/element/quicktips.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { MagnifyIcon } from "@/app/element/magnify"; -import { PLATFORM } from "@/app/store/global"; +import { PLATFORM, PlatformMacOS } from "@/util/platformutil"; import "./quicktips.scss"; const KeyBinding = ({ keyDecl }: { keyDecl: string }) => { @@ -10,7 +10,7 @@ const KeyBinding = ({ keyDecl }: { keyDecl: string }) => { const elems: React.ReactNode[] = []; for (let part of parts) { if (part === "Cmd") { - if (PLATFORM === "darwin") { + if (PLATFORM === PlatformMacOS) { elems.push(
⌘ Cmd diff --git a/frontend/app/store/global.ts b/frontend/app/store/global.ts index 00a386f223..5db71d62a5 100644 --- a/frontend/app/store/global.ts +++ b/frontend/app/store/global.ts @@ -17,6 +17,7 @@ import { } from "@/layout/lib/types"; import { getWebServerEndpoint } from "@/util/endpoints"; import { fetch } from "@/util/fetchutil"; +import { setPlatform } from "@/util/platformutil"; import { deepCompareReturnPrev, getPrefixedSettings, isBlank } from "@/util/util"; import { atom, Atom, PrimitiveAtom, useAtomValue } from "jotai"; import { globalStore } from "./jotaiStore"; @@ -25,7 +26,6 @@ import { ClientService, ObjectService } from "./services"; import * as WOS from "./wos"; import { getFileSubject, waveEventSubscribe } from "./wps"; -let PLATFORM: NodeJS.Platform = "darwin"; let atoms: GlobalAtomsType; let globalEnvironment: "electron" | "renderer"; const blockComponentModelMap = new Map(); @@ -46,10 +46,6 @@ function initGlobal(initOpts: GlobalInitOptions) { initGlobalAtoms(initOpts); } -function setPlatform(platform: NodeJS.Platform) { - PLATFORM = platform; -} - function initGlobalAtoms(initOpts: GlobalInitOptions) { const windowIdAtom = atom(initOpts.windowId) as PrimitiveAtom; const clientIdAtom = atom(initOpts.clientId) as PrimitiveAtom; @@ -790,7 +786,6 @@ export { isDev, loadConnStatus, openLink, - PLATFORM, pushFlashError, pushNotification, recordTEvent, diff --git a/frontend/app/tab/tabbar.tsx b/frontend/app/tab/tabbar.tsx index 6fe2f622cb..47cc15e2e6 100644 --- a/frontend/app/tab/tabbar.tsx +++ b/frontend/app/tab/tabbar.tsx @@ -5,7 +5,8 @@ import { Button } from "@/app/element/button"; import { modalsModel } from "@/app/store/modalmodel"; import { WindowDrag } from "@/element/windowdrag"; import { deleteLayoutModelForTab } from "@/layout/index"; -import { atoms, createTab, getApi, globalStore, isDev, PLATFORM, setActiveTab } from "@/store/global"; +import { atoms, createTab, getApi, globalStore, isDev, setActiveTab } from "@/store/global"; +import { PLATFORM, PlatformMacOS } from "@/util/platformutil"; import { fireAndForget } from "@/util/util"; import { useAtomValue } from "jotai"; import { OverlayScrollbars } from "overlayscrollbars"; @@ -641,7 +642,7 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
) : undefined; const appMenuButton = - PLATFORM !== "darwin" && !settings["window:showmenubar"] ? ( + PLATFORM !== PlatformMacOS && !settings["window:showmenubar"] ? (
diff --git a/frontend/app/view/preview/directorypreview.tsx b/frontend/app/view/preview/directorypreview.tsx index 6cd61e0896..c96f537c94 100644 --- a/frontend/app/view/preview/directorypreview.tsx +++ b/frontend/app/view/preview/directorypreview.tsx @@ -6,12 +6,13 @@ import { CopyButton } from "@/app/element/copybutton"; import { Input } from "@/app/element/input"; import { useDimensionsWithCallbackRef } from "@/app/hook/useDimensions"; import { ContextMenuModel } from "@/app/store/contextmenu"; -import { PLATFORM, atoms, createBlock, getApi, globalStore } from "@/app/store/global"; +import { atoms, createBlock, getApi, globalStore } from "@/app/store/global"; import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; import { type PreviewModel } from "@/app/view/preview/preview"; import { checkKeyPressed, isCharacterKeyEvent } from "@/util/keyutil"; -import { fireAndForget, isBlank, makeNativeLabel } from "@/util/util"; +import { PLATFORM, PlatformMacOS } from "@/util/platformutil"; +import { fireAndForget, isBlank } from "@/util/util"; import { formatRemoteUri } from "@/util/waveutil"; import { offset, useDismiss, useFloating, useInteractions } from "@floating-ui/react"; import { @@ -33,6 +34,7 @@ import React, { Fragment, memo, useCallback, useEffect, useMemo, useRef, useStat import { useDrag, useDrop } from "react-dnd"; import { quote as shellQuote } from "shell-quote"; import { debounce } from "throttle-debounce"; +import { addOpenMenuItems } from "../../../util/previewutil"; import "./directorypreview.scss"; const PageJumpSize = 20; @@ -595,43 +597,7 @@ function TableBody({ }), }, ]; - if (!conn) { - menu.push( - { - type: "separator", - }, - // TODO: resolve correct host path if connection is WSL - { - label: makeNativeLabel(PLATFORM, finfo.isdir, false), - click: () => { - getApi().openNativePath(normPath); - }, - }, - { - label: makeNativeLabel(PLATFORM, true, true), - click: () => { - getApi().openNativePath(finfo.dir); - }, - } - ); - } - if (finfo.mimetype == "directory") { - menu.push({ - label: "Open Terminal in New Block", - click: () => - fireAndForget(async () => { - const termBlockDef: BlockDef = { - meta: { - controller: "shell", - view: "term", - "cmd:cwd": await model.formatRemoteUri(finfo.path, globalStore.get), - connection: conn, - }, - }; - await createBlock(termBlockDef); - }), - }); - } + addOpenMenuItems(menu, conn, finfo); menu.push( { type: "separator", @@ -787,7 +753,8 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) { const [refreshVersion, setRefreshVersion] = useAtom(model.refreshVersion); const conn = useAtomValue(model.connection); const blockData = useAtomValue(model.blockAtom); - const dirPath = useAtomValue(model.normFilePath); + const finfo = useAtomValue(model.statFile); + const dirPath = finfo?.path; const [copyStatus, setCopyStatus] = useState(null); useEffect(() => { @@ -875,7 +842,7 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) { if ( checkKeyPressed(waveEvent, "Space") && searchText == "" && - PLATFORM == "darwin" && + PLATFORM == PlatformMacOS && !blockData?.meta?.connection ) { getApi().onQuicklook(selectedPath); @@ -1034,29 +1001,7 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) { type: "separator", }, ]; - if (!conn) { - // TODO: resolve correct host path if connection is WSL - menu.push({ - label: makeNativeLabel(PLATFORM, true, true), - click: () => { - getApi().openNativePath(dirPath); - }, - }); - } - menu.push({ - label: "Open Terminal in New Block", - click: async () => { - const termBlockDef: BlockDef = { - meta: { - controller: "shell", - view: "term", - "cmd:cwd": dirPath, - connection: conn, - }, - }; - await createBlock(termBlockDef); - }, - }); + addOpenMenuItems(menu, conn, finfo); ContextMenuModel.showContextMenu(menu, e); }, diff --git a/frontend/app/view/preview/preview.tsx b/frontend/app/view/preview/preview.tsx index 09b961fae9..365b2f70fd 100644 --- a/frontend/app/view/preview/preview.tsx +++ b/frontend/app/view/preview/preview.tsx @@ -15,13 +15,10 @@ import { CodeEditor } from "@/app/view/codeeditor/codeeditor"; import { Markdown } from "@/element/markdown"; import { atoms, - createBlock, - getApi, getConnStatusAtom, getOverrideConfigAtom, getSettingsKeyAtom, globalStore, - PLATFORM, refocusNode, } from "@/store/global"; import * as services from "@/store/services"; @@ -29,15 +26,8 @@ import * as WOS from "@/store/wos"; import { getWebServerEndpoint } from "@/util/endpoints"; import { goHistory, goHistoryBack, goHistoryForward } from "@/util/historyutil"; import { adaptFromReactOrNativeKeyEvent, checkKeyPressed, keydownWrapper } from "@/util/keyutil"; -import { - base64ToString, - fireAndForget, - isBlank, - jotaiLoadableValue, - makeConnRoute, - makeNativeLabel, - stringToBase64, -} from "@/util/util"; +import { addOpenMenuItems } from "@/util/previewutil"; +import { base64ToString, fireAndForget, isBlank, jotaiLoadableValue, makeConnRoute, stringToBase64 } from "@/util/util"; import { formatRemoteUri } from "@/util/waveutil"; import { Monaco } from "@monaco-editor/react"; import clsx from "clsx"; @@ -742,47 +732,8 @@ export class PreviewModel implements ViewModel { await navigator.clipboard.writeText(fileInfo.name); }), }); - const mimeType = jotaiLoadableValue(globalStore.get(this.fileMimeTypeLoadable), ""); - if (mimeType == "directory") { - menuItems.push({ - label: "Open Terminal in New Block", - click: () => - fireAndForget(async () => { - const conn = await globalStore.get(this.connection); - const fileInfo = await globalStore.get(this.statFile); - const termBlockDef: BlockDef = { - meta: { - view: "term", - controller: "shell", - "cmd:cwd": fileInfo.path, - connection: conn, - }, - }; - await createBlock(termBlockDef); - }), - }); - const conn = globalStore.get(this.connectionImmediate); - if (!conn) { - menuItems.push({ - label: makeNativeLabel(PLATFORM, true, true), - click: async () => { - const fileInfo = await globalStore.get(this.statFile); - getApi().openNativePath(fileInfo.path); - }, - }); - } - } else { - const conn = globalStore.get(this.connectionImmediate); - if (!conn) { - menuItems.push({ - label: makeNativeLabel(PLATFORM, false, false), - click: async () => { - const fileInfo = await globalStore.get(this.statFile); - getApi().openNativePath(fileInfo.path); - }, - }); - } - } + const finfo = jotaiLoadableValue(globalStore.get(this.loadableFileInfo), null); + addOpenMenuItems(menuItems, globalStore.get(this.connectionImmediate), finfo); const loadableSV = globalStore.get(this.loadableSpecializedView); const wordWrapAtom = getOverrideConfigAtom(this.blockId, "editor:wordwrap"); const wordWrap = globalStore.get(wordWrapAtom) ?? false; diff --git a/frontend/app/view/term/termwrap.ts b/frontend/app/view/term/termwrap.ts index 46089c4aa7..370312b090 100644 --- a/frontend/app/view/term/termwrap.ts +++ b/frontend/app/view/term/termwrap.ts @@ -5,8 +5,9 @@ import { getFileSubject } from "@/app/store/wps"; import { sendWSCommand } from "@/app/store/ws"; import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; -import { PLATFORM, WOS, atoms, fetchWaveFile, getSettingsKeyAtom, globalStore, openLink } from "@/store/global"; +import { WOS, atoms, fetchWaveFile, getSettingsKeyAtom, globalStore, openLink } from "@/store/global"; import * as services from "@/store/services"; +import { PLATFORM, PlatformMacOS } from "@/util/platformutil"; import { base64ToArray, fireAndForget } from "@/util/util"; import { SearchAddon } from "@xterm/addon-search"; import { SerializeAddon } from "@xterm/addon-serialize"; @@ -166,7 +167,7 @@ export class TermWrap { this.hasResized = false; this.terminal = new Terminal(options); this.fitAddon = new FitAddon(); - this.fitAddon.noScrollbar = PLATFORM == "darwin"; + this.fitAddon.noScrollbar = PLATFORM === PlatformMacOS; this.serializeAddon = new SerializeAddon(); this.searchAddon = new SearchAddon(); this.terminal.loadAddon(this.searchAddon); @@ -176,7 +177,7 @@ export class TermWrap { new WebLinksAddon((e, uri) => { e.preventDefault(); switch (PLATFORM) { - case "darwin": + case PlatformMacOS: if (e.metaKey) { fireAndForget(() => openLink(uri)); } diff --git a/frontend/app/view/vdom/vdom-model.tsx b/frontend/app/view/vdom/vdom-model.tsx index 5c07799f30..745d808ee8 100644 --- a/frontend/app/view/vdom/vdom-model.tsx +++ b/frontend/app/view/vdom/vdom-model.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { BlockNodeModel } from "@/app/block/blocktypes"; -import { getBlockMetaKeyAtom, globalStore, PLATFORM, WOS } from "@/app/store/global"; +import { getBlockMetaKeyAtom, globalStore, WOS } from "@/app/store/global"; import { makeORef } from "@/app/store/wos"; import { waveEventSubscribe } from "@/app/store/wps"; import { RpcResponseHelper, WshClient } from "@/app/store/wshclient"; @@ -13,6 +13,7 @@ import { VDomView } from "@/app/view/vdom/vdom"; import { applyCanvasOp, mergeBackendUpdates, restoreVDomElems } from "@/app/view/vdom/vdom-utils"; import { getWebServerEndpoint } from "@/util/endpoints"; import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil"; +import { PLATFORM, PlatformMacOS } from "@/util/platformutil"; import debug from "debug"; import * as jotai from "jotai"; @@ -73,7 +74,7 @@ function annotateEvent(event: VDomEvent, propName: string, reactEvent: React.Syn movementx: mouseEvent.movementX, movementy: mouseEvent.movementY, }; - if (PLATFORM == "darwin") { + if (PLATFORM == PlatformMacOS) { event.mousedata.cmd = event.mousedata.meta; event.mousedata.option = event.mousedata.alt; } else { diff --git a/frontend/util/keyutil.ts b/frontend/util/keyutil.ts index 867dfcb4e2..342359be4e 100644 --- a/frontend/util/keyutil.ts +++ b/frontend/util/keyutil.ts @@ -1,23 +1,13 @@ // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -import * as util from "./util"; +import { PLATFORM, PlatformMacOS } from "./platformutil"; +import { countGraphemes } from "./util"; const KeyTypeCodeRegex = /c{(.*)}/; const KeyTypeKey = "key"; const KeyTypeCode = "code"; -let PLATFORM: NodeJS.Platform = "darwin"; -const PlatformMacOS = "darwin"; - -function setKeyUtilPlatform(platform: NodeJS.Platform) { - PLATFORM = platform; -} - -function getKeyUtilPlatform(): NodeJS.Platform { - return PLATFORM; -} - function keydownWrapper( fn: (waveEvent: WaveKeyboardEvent) => boolean ): (event: KeyboardEvent | React.KeyboardEvent) => void { @@ -135,7 +125,7 @@ function isCharacterKeyEvent(event: WaveKeyboardEvent): boolean { if (event.alt || event.meta || event.control) { return false; } - return util.countGraphemes(event.key) == 1; + return countGraphemes(event.key) == 1; } const inputKeyMap = new Map([ @@ -325,12 +315,10 @@ export { adaptFromElectronKeyEvent, adaptFromReactOrNativeKeyEvent, checkKeyPressed, - getKeyUtilPlatform, isCharacterKeyEvent, isInputEvent, keyboardEventToASCII, keydownWrapper, parseKeyDescription, - setKeyUtilPlatform, waveEventToKeyDesc, }; diff --git a/frontend/util/platformutil.ts b/frontend/util/platformutil.ts new file mode 100644 index 0000000000..e2db630678 --- /dev/null +++ b/frontend/util/platformutil.ts @@ -0,0 +1,27 @@ +export const PlatformMacOS = "darwin"; +export let PLATFORM: NodeJS.Platform = PlatformMacOS; + +export function setPlatform(platform: NodeJS.Platform) { + PLATFORM = platform; +} + +export function makeNativeLabel(isDirectory: boolean) { + let managerName: string; + if (!isDirectory) { + managerName = "Default Application"; + } else if (PLATFORM === PlatformMacOS) { + managerName = "Finder"; + } else if (PLATFORM == "win32") { + managerName = "Explorer"; + } else { + managerName = "File Manager"; + } + + let fileAction: string; + if (isDirectory) { + fileAction = "Reveal"; + } else { + fileAction = "Open File"; + } + return `${fileAction} in ${managerName}`; +} diff --git a/frontend/util/previewutil.ts b/frontend/util/previewutil.ts new file mode 100644 index 0000000000..423e760caa --- /dev/null +++ b/frontend/util/previewutil.ts @@ -0,0 +1,43 @@ +import { createBlock, getApi } from "@/app/store/global"; +import { makeNativeLabel } from "@/util/platformutil"; +import { fireAndForget } from "@/util/util"; +import { formatRemoteUri } from "./waveutil"; + +export function addOpenMenuItems(menu: ContextMenuItem[], conn: string, finfo: FileInfo): ContextMenuItem[] { + if (!finfo) { + return menu; + } + if (!conn) { + // TODO: resolve correct host path if connection is WSL + // if the entry is a directory, reveal it in the file manager, if the entry is a file, reveal its parent directory + menu.push({ + label: makeNativeLabel(true), + click: () => { + getApi().openNativePath(finfo.isdir ? finfo.path : finfo.dir); + }, + }); + if (!finfo.isdir) { + menu.push({ + label: makeNativeLabel(false), + click: () => { + getApi().openNativePath(finfo.path); + }, + }); + } + } + menu.push({ + label: "Open Terminal in New Block", + click: () => { + const termBlockDef: BlockDef = { + meta: { + controller: "shell", + view: "term", + "cmd:cwd": formatRemoteUri(finfo.path, conn), + connection: conn, + }, + }; + fireAndForget(() => createBlock(termBlockDef)); + }, + }); + return menu; +} diff --git a/frontend/util/util.ts b/frontend/util/util.ts index 04b581521a..34391f979c 100644 --- a/frontend/util/util.ts +++ b/frontend/util/util.ts @@ -306,29 +306,6 @@ function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } -function makeNativeLabel(platform: string, isDirectory: boolean, isParent: boolean) { - let managerName: string; - if (!isDirectory && !isParent) { - managerName = "Default Application"; - } else if (platform == "darwin") { - managerName = "Finder"; - } else if (platform == "win32") { - managerName = "Explorer"; - } else { - managerName = "File Manager"; - } - - let fileAction: string; - if (isParent) { - fileAction = "Reveal"; - } else if (isDirectory) { - fileAction = "Open Directory"; - } else { - fileAction = "Open File"; - } - return `${fileAction} in ${managerName}`; -} - function mergeMeta(meta: MetaType, metaUpdate: MetaType, prefix?: string): MetaType { const rtn: MetaType = {}; @@ -419,7 +396,6 @@ export { makeConnRoute, makeExternLink, makeIconClass, - makeNativeLabel, mergeMeta, sleep, stringToBase64, From c3f5f88489ac3b38b7fcb245b38ec2909317ab29 Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Tue, 18 Feb 2025 15:54:57 -0800 Subject: [PATCH 06/11] fix regression --- frontend/app/view/preview/directorypreview.tsx | 2 +- frontend/util/keyutil.ts | 18 +++++++++++++++--- frontend/util/previewutil.ts | 4 ++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/frontend/app/view/preview/directorypreview.tsx b/frontend/app/view/preview/directorypreview.tsx index c96f537c94..6bddd4621b 100644 --- a/frontend/app/view/preview/directorypreview.tsx +++ b/frontend/app/view/preview/directorypreview.tsx @@ -12,6 +12,7 @@ import { TabRpcClient } from "@/app/store/wshrpcutil"; import { type PreviewModel } from "@/app/view/preview/preview"; import { checkKeyPressed, isCharacterKeyEvent } from "@/util/keyutil"; import { PLATFORM, PlatformMacOS } from "@/util/platformutil"; +import { addOpenMenuItems } from "@/util/previewutil"; import { fireAndForget, isBlank } from "@/util/util"; import { formatRemoteUri } from "@/util/waveutil"; import { offset, useDismiss, useFloating, useInteractions } from "@floating-ui/react"; @@ -34,7 +35,6 @@ import React, { Fragment, memo, useCallback, useEffect, useMemo, useRef, useStat import { useDrag, useDrop } from "react-dnd"; import { quote as shellQuote } from "shell-quote"; import { debounce } from "throttle-debounce"; -import { addOpenMenuItems } from "../../../util/previewutil"; import "./directorypreview.scss"; const PageJumpSize = 20; diff --git a/frontend/util/keyutil.ts b/frontend/util/keyutil.ts index 342359be4e..867dfcb4e2 100644 --- a/frontend/util/keyutil.ts +++ b/frontend/util/keyutil.ts @@ -1,13 +1,23 @@ // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -import { PLATFORM, PlatformMacOS } from "./platformutil"; -import { countGraphemes } from "./util"; +import * as util from "./util"; const KeyTypeCodeRegex = /c{(.*)}/; const KeyTypeKey = "key"; const KeyTypeCode = "code"; +let PLATFORM: NodeJS.Platform = "darwin"; +const PlatformMacOS = "darwin"; + +function setKeyUtilPlatform(platform: NodeJS.Platform) { + PLATFORM = platform; +} + +function getKeyUtilPlatform(): NodeJS.Platform { + return PLATFORM; +} + function keydownWrapper( fn: (waveEvent: WaveKeyboardEvent) => boolean ): (event: KeyboardEvent | React.KeyboardEvent) => void { @@ -125,7 +135,7 @@ function isCharacterKeyEvent(event: WaveKeyboardEvent): boolean { if (event.alt || event.meta || event.control) { return false; } - return countGraphemes(event.key) == 1; + return util.countGraphemes(event.key) == 1; } const inputKeyMap = new Map([ @@ -315,10 +325,12 @@ export { adaptFromElectronKeyEvent, adaptFromReactOrNativeKeyEvent, checkKeyPressed, + getKeyUtilPlatform, isCharacterKeyEvent, isInputEvent, keyboardEventToASCII, keydownWrapper, parseKeyDescription, + setKeyUtilPlatform, waveEventToKeyDesc, }; diff --git a/frontend/util/previewutil.ts b/frontend/util/previewutil.ts index 423e760caa..5101c99c9d 100644 --- a/frontend/util/previewutil.ts +++ b/frontend/util/previewutil.ts @@ -1,6 +1,6 @@ import { createBlock, getApi } from "@/app/store/global"; -import { makeNativeLabel } from "@/util/platformutil"; -import { fireAndForget } from "@/util/util"; +import { makeNativeLabel } from "./platformutil"; +import { fireAndForget } from "./util"; import { formatRemoteUri } from "./waveutil"; export function addOpenMenuItems(menu: ContextMenuItem[], conn: string, finfo: FileInfo): ContextMenuItem[] { From 1f24054cb0ded4dd1804d6834ac7d4f59e8a1a5a Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Tue, 18 Feb 2025 16:00:42 -0800 Subject: [PATCH 07/11] more open items moved to helper fn --- .../app/view/preview/directorypreview.tsx | 29 +---------------- frontend/util/previewutil.ts | 32 ++++++++++++++++++- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/frontend/app/view/preview/directorypreview.tsx b/frontend/app/view/preview/directorypreview.tsx index b75997ec90..cccb413e13 100644 --- a/frontend/app/view/preview/directorypreview.tsx +++ b/frontend/app/view/preview/directorypreview.tsx @@ -6,7 +6,7 @@ import { CopyButton } from "@/app/element/copybutton"; import { Input } from "@/app/element/input"; import { useDimensionsWithCallbackRef } from "@/app/hook/useDimensions"; import { ContextMenuModel } from "@/app/store/contextmenu"; -import { atoms, createBlock, getApi, globalStore } from "@/app/store/global"; +import { atoms, getApi, globalStore } from "@/app/store/global"; import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; import { type PreviewModel } from "@/app/view/preview/preview"; @@ -569,33 +569,6 @@ function TableBody({ label: "Copy Full File Name (Shell Quoted)", click: () => fireAndForget(() => navigator.clipboard.writeText(shellQuote([finfo.path]))), }, - { - type: "separator", - }, - { - label: "Download File", - click: () => { - const remoteUri = formatRemoteUri(finfo.path, conn); - getApi().downloadFile(remoteUri); - }, - }, - { - type: "separator", - }, - { - label: "Open Preview in New Block", - click: () => - fireAndForget(async () => { - const blockDef: BlockDef = { - meta: { - view: "preview", - file: finfo.path, - connection: conn, - }, - }; - await createBlock(blockDef); - }), - }, ]; addOpenMenuItems(menu, conn, finfo); menu.push( diff --git a/frontend/util/previewutil.ts b/frontend/util/previewutil.ts index 5101c99c9d..0dda91d8f6 100644 --- a/frontend/util/previewutil.ts +++ b/frontend/util/previewutil.ts @@ -7,6 +7,9 @@ export function addOpenMenuItems(menu: ContextMenuItem[], conn: string, finfo: F if (!finfo) { return menu; } + menu.push({ + type: "separator", + }); if (!conn) { // TODO: resolve correct host path if connection is WSL // if the entry is a directory, reveal it in the file manager, if the entry is a file, reveal its parent directory @@ -24,6 +27,33 @@ export function addOpenMenuItems(menu: ContextMenuItem[], conn: string, finfo: F }, }); } + } else { + menu.push({ + label: "Download File", + click: () => { + const remoteUri = formatRemoteUri(finfo.path, conn); + getApi().downloadFile(remoteUri); + }, + }); + } + menu.push({ + type: "separator", + }); + if (!finfo.isdir) { + menu.push({ + label: "Open Preview in New Block", + click: () => + fireAndForget(async () => { + const blockDef: BlockDef = { + meta: { + view: "preview", + file: finfo.path, + connection: conn, + }, + }; + await createBlock(blockDef); + }), + }); } menu.push({ label: "Open Terminal in New Block", @@ -32,7 +62,7 @@ export function addOpenMenuItems(menu: ContextMenuItem[], conn: string, finfo: F meta: { controller: "shell", view: "term", - "cmd:cwd": formatRemoteUri(finfo.path, conn), + "cmd:cwd": formatRemoteUri(finfo.isdir ? finfo.path : finfo.dir, conn), connection: conn, }, }; From 0b4b5d4aea7b4ebfa53c2c3ad4dc931e8c3c1ac5 Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Tue, 18 Feb 2025 16:02:31 -0800 Subject: [PATCH 08/11] don't show "open terminal" option for s3 paths --- frontend/util/previewutil.ts | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/frontend/util/previewutil.ts b/frontend/util/previewutil.ts index 0dda91d8f6..84b2785e33 100644 --- a/frontend/util/previewutil.ts +++ b/frontend/util/previewutil.ts @@ -55,19 +55,22 @@ export function addOpenMenuItems(menu: ContextMenuItem[], conn: string, finfo: F }), }); } - menu.push({ - label: "Open Terminal in New Block", - click: () => { - const termBlockDef: BlockDef = { - meta: { - controller: "shell", - view: "term", - "cmd:cwd": formatRemoteUri(finfo.isdir ? finfo.path : finfo.dir, conn), - connection: conn, - }, - }; - fireAndForget(() => createBlock(termBlockDef)); - }, - }); + // TODO: improve behavior as we add more connection types + if (!conn.startsWith("aws:")) { + menu.push({ + label: "Open Terminal in New Block", + click: () => { + const termBlockDef: BlockDef = { + meta: { + controller: "shell", + view: "term", + "cmd:cwd": formatRemoteUri(finfo.isdir ? finfo.path : finfo.dir, conn), + connection: conn, + }, + }; + fireAndForget(() => createBlock(termBlockDef)); + }, + }); + } return menu; } From 7bb2936c75fe478e060ec1a12c977eaa3c9c504e Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Tue, 18 Feb 2025 16:03:11 -0800 Subject: [PATCH 09/11] fix null check --- frontend/util/previewutil.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/util/previewutil.ts b/frontend/util/previewutil.ts index 84b2785e33..e4706d497f 100644 --- a/frontend/util/previewutil.ts +++ b/frontend/util/previewutil.ts @@ -56,7 +56,7 @@ export function addOpenMenuItems(menu: ContextMenuItem[], conn: string, finfo: F }); } // TODO: improve behavior as we add more connection types - if (!conn.startsWith("aws:")) { + if (!conn?.startsWith("aws:")) { menu.push({ label: "Open Terminal in New Block", click: () => { From d0ca1b935db0af72b6ebc98b96f7b3536a4bf5ca Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Tue, 18 Feb 2025 16:11:12 -0800 Subject: [PATCH 10/11] fix open funcs --- emain/emain.ts | 1 + frontend/util/previewutil.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/emain/emain.ts b/emain/emain.ts index ff6cccd155..002176c33d 100644 --- a/emain/emain.ts +++ b/emain/emain.ts @@ -370,6 +370,7 @@ electron.ipcMain.on("quicklook", (event, filePath: string) => { electron.ipcMain.on("open-native-path", (event, filePath: string) => { console.log("open-native-path", filePath); + filePath = filePath.replace("~", electronApp.getPath("home")); fireAndForget(() => callWithOriginalXdgCurrentDesktopAsync(() => electron.shell.openPath(filePath).then((excuse) => { diff --git a/frontend/util/previewutil.ts b/frontend/util/previewutil.ts index e4706d497f..e730f588d6 100644 --- a/frontend/util/previewutil.ts +++ b/frontend/util/previewutil.ts @@ -19,6 +19,7 @@ export function addOpenMenuItems(menu: ContextMenuItem[], conn: string, finfo: F getApi().openNativePath(finfo.isdir ? finfo.path : finfo.dir); }, }); + // if the entry is a file, open it in the default application if (!finfo.isdir) { menu.push({ label: makeNativeLabel(false), @@ -64,7 +65,7 @@ export function addOpenMenuItems(menu: ContextMenuItem[], conn: string, finfo: F meta: { controller: "shell", view: "term", - "cmd:cwd": formatRemoteUri(finfo.isdir ? finfo.path : finfo.dir, conn), + "cmd:cwd": finfo.isdir ? finfo.path : finfo.dir, connection: conn, }, }; From e3068af311b4e3b4fec9f6dd5c0005cd9c8e4e9f Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 18 Feb 2025 16:19:27 -0800 Subject: [PATCH 11/11] move the import so the comment still applies --- frontend/app/app.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/app/app.tsx b/frontend/app/app.tsx index 45d7e7560a..dfe5679fc0 100644 --- a/frontend/app/app.tsx +++ b/frontend/app/app.tsx @@ -7,6 +7,7 @@ import { atoms, createBlock, getSettingsPrefixAtom, globalStore, isDev, removeFl import { appHandleKeyDown, keyboardMouseDownHandler } from "@/store/keymodel"; import { getElemAsStr } from "@/util/focusutil"; import * as keyutil from "@/util/keyutil"; +import { PLATFORM } from "@/util/platformutil"; import * as util from "@/util/util"; import clsx from "clsx"; import debug from "debug"; @@ -21,8 +22,7 @@ import { NotificationBubbles } from "./notification/notificationbubbles"; import "./app.scss"; -// this should come after app.scss (don't remove the newline above otherwise prettier will reorder these imports) -import { PLATFORM } from "@/util/platformutil"; +// tailwindsetup.css should come *after* app.scss (don't remove the newline above otherwise prettier will reorder these imports) import "../tailwindsetup.css"; const dlog = debug("wave:app");