mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-03-13 13:39:48 +01:00
Reveal Parent Directories in External Applications (#1791)
Adds context menu options to the directory preview to open the parent directory in the native file viewer. Additionally, it adds context menu options in the block header to open either a parent directory or a different type of file in an external default application. These context menu items are only available for local directory previews.
This commit is contained in:
parent
6612b9c6ba
commit
6d32ae856c
@ -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);
|
||||
|
@ -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<Loadable<string>>;
|
||||
loadableFileInfo: Atom<Loadable<FileInfo>>;
|
||||
connection: Atom<Promise<string>>;
|
||||
connectionImmediate: Atom<string>;
|
||||
statFile: Atom<Promise<FileInfo>>;
|
||||
fullFile: Atom<Promise<FileData>>;
|
||||
fileMimeType: Atom<Promise<string>>;
|
||||
@ -364,6 +375,9 @@ export class PreviewModel implements ViewModel {
|
||||
}
|
||||
return connName;
|
||||
});
|
||||
this.connectionImmediate = atom<string>((get) => {
|
||||
return get(this.blockAtom)?.meta?.connection;
|
||||
});
|
||||
this.statFile = atom<Promise<FileInfo>>(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");
|
||||
|
@ -306,6 +306,29 @@ function sleep(ms: number): Promise<void> {
|
||||
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,
|
||||
|
Loading…
Reference in New Issue
Block a user