diff --git a/frontend/app/view/preview/directorypreview.tsx b/frontend/app/view/preview/directorypreview.tsx index 7ecd4a009e..a58a77328f 100644 --- a/frontend/app/view/preview/directorypreview.tsx +++ b/frontend/app/view/preview/directorypreview.tsx @@ -9,7 +9,7 @@ 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 } from "@/util/util"; +import { fireAndForget, isBlank, makeConnRoute, makeNativeLabel } from "@/util/util"; import { offset, useDismiss, useFloating, useInteractions } from "@floating-ui/react"; import { Column, @@ -508,7 +508,7 @@ function TableBody({ }, [focusIndex]); const handleFileContextMenu = useCallback( - (e: any, finfo: FileInfo) => { + async (e: any, finfo: FileInfo) => { e.preventDefault(); e.stopPropagation(); if (finfo == null) { @@ -516,16 +516,14 @@ function TableBody({ } const normPath = getNormFilePath(finfo); const fileName = finfo.path.split("/").pop(); - let openNativeLabel = "Open File"; - if (finfo.isdir) { - openNativeLabel = "Open Directory in File Manager"; - if (PLATFORM == "darwin") { - openNativeLabel = "Open Directory in Finder"; - } else if (PLATFORM == "win32") { - openNativeLabel = "Open Directory in Explorer"; - } - } else { - openNativeLabel = "Open File in Default Application"; + let parentFileInfo: FileInfo; + try { + parentFileInfo = await RpcApi.RemoteFileJoinCommand(TabRpcClient, [normPath, ".."], { + route: makeConnRoute(conn), + }); + } catch (e) { + console.log("could not get parent file info. using child file info as fallback"); + parentFileInfo = finfo; } const menu: ContextMenuItem[] = [ { @@ -577,16 +575,6 @@ function TableBody({ { type: "separator", }, - // TODO: Only show this option for local files, resolve correct host path if connection is WSL - { - label: openNativeLabel, - click: () => { - getApi().openNativePath(normPath); - }, - }, - { - type: "separator", - }, { label: "Open Preview in New Block", click: () => @@ -595,12 +583,33 @@ function TableBody({ meta: { view: "preview", file: finfo.path, + connection: conn, }, }; await createBlock(blockDef); }), }, ]; + 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(parentFileInfo.dir); + }, + } + ); + } if (finfo.mimetype == "directory") { menu.push({ label: "Open Terminal in New Block", @@ -611,6 +620,7 @@ function TableBody({ controller: "shell", view: "term", "cmd:cwd": await model.formatRemoteUri(finfo.path, globalStore.get), + connection: conn, }, }; await createBlock(termBlockDef); @@ -858,12 +868,6 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) { (e: any) => { e.preventDefault(); e.stopPropagation(); - let openNativeLabel = "Open Directory in File Manager"; - if (PLATFORM == "darwin") { - openNativeLabel = "Open Directory in Finder"; - } else if (PLATFORM == "win32") { - openNativeLabel = "Open Directory in Explorer"; - } const menu: ContextMenuItem[] = [ { label: "New File", @@ -880,15 +884,16 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) { { type: "separator", }, - // TODO: Only show this option for local files, resolve correct host path if connection is WSL - { - label: openNativeLabel, + ]; + if (!conn) { + // TODO: resolve correct host path if connection is WSL + menu.push({ + label: makeNativeLabel(PLATFORM, true, true), click: () => { - console.log(`opening ${dirPath}`); getApi().openNativePath(dirPath); }, - }, - ]; + }); + } menu.push({ label: "Open Terminal in New Block", click: async () => { @@ -897,6 +902,7 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) { controller: "shell", view: "term", "cmd:cwd": dirPath, + connection: conn, }, }; await createBlock(termBlockDef); diff --git a/frontend/app/view/preview/preview.tsx b/frontend/app/view/preview/preview.tsx index 4c3c03b204..f51c707aaf 100644 --- a/frontend/app/view/preview/preview.tsx +++ b/frontend/app/view/preview/preview.tsx @@ -13,10 +13,12 @@ import { Markdown } from "@/element/markdown"; import { atoms, createBlock, + getApi, getConnStatusAtom, getOverrideConfigAtom, getSettingsKeyAtom, globalStore, + PLATFORM, refocusNode, } from "@/store/global"; import * as services from "@/store/services"; @@ -24,7 +26,15 @@ 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, stringToBase64 } from "@/util/util"; +import { + base64ToString, + fireAndForget, + isBlank, + jotaiLoadableValue, + makeConnRoute, + makeNativeLabel, + stringToBase64, +} from "@/util/util"; import { Monaco } from "@monaco-editor/react"; import clsx from "clsx"; import { Atom, atom, Getter, PrimitiveAtom, useAtomValue, useSetAtom, WritableAtom } from "jotai"; @@ -139,6 +149,7 @@ export class PreviewModel implements ViewModel { loadableStatFilePath: Atom>; loadableFileInfo: Atom>; connection: Atom>; + connectionImmediate: Atom; statFile: Atom>; fullFile: Atom>; fileMimeType: Atom>; @@ -364,6 +375,9 @@ export class PreviewModel implements ViewModel { } return connName; }); + this.connectionImmediate = atom((get) => { + return get(this.blockAtom)?.meta?.connection; + }); this.statFile = atom>(async (get) => { const fileName = get(this.metaFilePath); if (fileName == null) { @@ -677,17 +691,40 @@ export class PreviewModel implements ViewModel { 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.dir, + 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.dir); + }, + }); + } + } 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.dir}/${fileInfo.name}`); + }, + }); + } } const loadableSV = globalStore.get(this.loadableSpecializedView); const wordWrapAtom = getOverrideConfigAtom(this.blockId, "editor:wordwrap"); diff --git a/frontend/util/util.ts b/frontend/util/util.ts index a37575887a..ae5df29e9c 100644 --- a/frontend/util/util.ts +++ b/frontend/util/util.ts @@ -306,6 +306,29 @@ 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}`; +} + export { atomWithDebounce, atomWithThrottle, @@ -325,6 +348,7 @@ export { makeConnRoute, makeExternLink, makeIconClass, + makeNativeLabel, sleep, stringToBase64, useAtomValueSafe,