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/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..dfe5679fc0 100644
--- a/frontend/app/app.tsx
+++ b/frontend/app/app.tsx
@@ -3,18 +3,11 @@
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";
+import { PLATFORM } from "@/util/platformutil";
import * as util from "@/util/util";
import clsx from "clsx";
import debug from "debug";
@@ -29,7 +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)
+// 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");
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 6350a840a0..cccb413e13 100644
--- a/frontend/app/view/preview/directorypreview.tsx
+++ b/frontend/app/view/preview/directorypreview.tsx
@@ -6,12 +6,14 @@ 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, 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 { addOpenMenuItems } from "@/util/previewutil";
+import { fireAndForget, isBlank } from "@/util/util";
import { formatRemoteUri } from "@/util/waveutil";
import { offset, useDismiss, useFloating, useInteractions } from "@floating-ui/react";
import {
@@ -301,13 +303,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();
});
}
@@ -521,17 +531,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",
@@ -570,71 +569,8 @@ 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);
- }),
- },
];
- 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.path);
- },
- }
- );
- }
- 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",
@@ -643,10 +579,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);
});
},
@@ -782,7 +726,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(() => {
@@ -796,16 +741,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]);
@@ -864,7 +819,7 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) {
if (
checkKeyPressed(waveEvent, "Space") &&
searchText == "" &&
- PLATFORM == "darwin" &&
+ PLATFORM == PlatformMacOS &&
!blockData?.meta?.connection
) {
getApi().onQuicklook(selectedPath);
@@ -1023,29 +978,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 afdc8cbe22..27896ccce3 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";
@@ -434,13 +424,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 +590,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 +676,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);
}
}
@@ -742,47 +742,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/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..e730f588d6
--- /dev/null
+++ b/frontend/util/previewutil.ts
@@ -0,0 +1,77 @@
+import { createBlock, getApi } from "@/app/store/global";
+import { makeNativeLabel } from "./platformutil";
+import { fireAndForget } from "./util";
+import { formatRemoteUri } from "./waveutil";
+
+export function addOpenMenuItems(menu: ContextMenuItem[], conn: string, finfo: FileInfo): ContextMenuItem[] {
+ 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
+ menu.push({
+ label: makeNativeLabel(true),
+ click: () => {
+ 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),
+ click: () => {
+ getApi().openNativePath(finfo.path);
+ },
+ });
+ }
+ } 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);
+ }),
+ });
+ }
+ // 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": finfo.isdir ? finfo.path : finfo.dir,
+ 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,