diff --git a/emain/preload.ts b/emain/preload.ts index c6bdf14988..ee60c13604 100644 --- a/emain/preload.ts +++ b/emain/preload.ts @@ -1,7 +1,7 @@ // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -import { contextBridge, ipcRenderer, Rectangle, WebviewTag } from "electron"; +import { contextBridge, ipcRenderer, Rectangle, webUtils, WebviewTag } from "electron"; // update type in custom.d.ts (ElectronApi type) contextBridge.exposeInMainWorld("api", { @@ -68,6 +68,7 @@ contextBridge.exposeInMainWorld("api", { openBuilder: (appId?: string) => ipcRenderer.send("open-builder", appId), setBuilderWindowAppId: (appId: string) => ipcRenderer.send("set-builder-window-appid", appId), doRefresh: () => ipcRenderer.send("do-refresh"), + getPathForFile: (file: File) => webUtils.getPathForFile(file), }); // Custom event for "new-window" diff --git a/frontend/app/view/term/term.tsx b/frontend/app/view/term/term.tsx index aae7fe1295..16dd8c5d20 100644 --- a/frontend/app/view/term/term.tsx +++ b/frontend/app/view/term/term.tsx @@ -8,7 +8,7 @@ import { waveEventSubscribe } from "@/app/store/wps"; import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; import type { TermViewModel } from "@/app/view/term/term-model"; -import { atoms, getOverrideConfigAtom, getSettingsPrefixAtom, globalStore, WOS } from "@/store/global"; +import { atoms, getApi, getOverrideConfigAtom, getSettingsPrefixAtom, globalStore, WOS } from "@/store/global"; import { fireAndForget, useAtomValueSafe } from "@/util/util"; import { computeBgStyleFromMeta } from "@/util/waveutil"; import { ISearchOptions } from "@xterm/addon-search"; @@ -353,8 +353,74 @@ const TerminalView = ({ blockId, model }: ViewComponentProps) => const termBg = computeBgStyleFromMeta(blockData?.meta); + // Handle drag and drop + // Helper to check if drag event contains files + const isFileDrop = (e: React.DragEvent): boolean => { + return e.dataTransfer?.types?.includes("Files") ?? false; + }; + + const handleDragOver = React.useCallback((e: React.DragEvent) => { + if (!isFileDrop(e)) return; + e.preventDefault(); + e.stopPropagation(); + e.dataTransfer.dropEffect = "copy"; + }, []); + + const handleDrop = React.useCallback( + (e: React.DragEvent) => { + if (!isFileDrop(e)) return; + e.preventDefault(); + e.stopPropagation(); + + const files = Array.from(e.dataTransfer.files); + if (files.length === 0) return; + + const paths = files + .map((file: File) => { + try { + const fullPath = getApi().getPathForFile(file); + if (/[\s'"]/.test(fullPath)) { + return `"${fullPath}"`; + } + return fullPath; + } catch (err) { + console.error("Could not get path for file:", file.name, err); + return null; + } + }) + .filter((path): path is string => path !== null); + + if (paths.length === 0) return; + + const pathString = paths.join(" "); + if (model.termRef.current && pathString) { + model.sendDataToController(pathString); + } + }, + [model] + ); + + const handleDragEnter = React.useCallback((e: React.DragEvent) => { + if (!isFileDrop(e)) return; + e.preventDefault(); + e.stopPropagation(); + }, []); + + const handleDragLeave = React.useCallback((e: React.DragEvent) => { + if (!isFileDrop(e)) return; + e.preventDefault(); + e.stopPropagation(); + }, []); + return ( -
+
{termBg &&
} diff --git a/frontend/types/custom.d.ts b/frontend/types/custom.d.ts index db2999a500..e10b56eb67 100644 --- a/frontend/types/custom.d.ts +++ b/frontend/types/custom.d.ts @@ -134,6 +134,7 @@ declare global { openBuilder: (appId?: string) => void; // open-builder setBuilderWindowAppId: (appId: string) => void; // set-builder-window-appid doRefresh: () => void; // do-refresh + getPathForFile: (file: File) => string; // webUtils.getPathForFile }; type ElectronContextMenuItem = { diff --git a/logs/.7472c96afe59df11c248c2c4b43edab0797adede-audit.json b/logs/.7472c96afe59df11c248c2c4b43edab0797adede-audit.json new file mode 100644 index 0000000000..5fe62cdd89 --- /dev/null +++ b/logs/.7472c96afe59df11c248c2c4b43edab0797adede-audit.json @@ -0,0 +1,15 @@ +{ + "keep": { + "days": true, + "amount": 14 + }, + "auditLog": "/Users/steven/dev/waveterm/logs/.7472c96afe59df11c248c2c4b43edab0797adede-audit.json", + "files": [ + { + "date": 1767427944263, + "name": "/Users/steven/dev/waveterm/logs/application-2026-01-03.log", + "hash": "4852234ebb0d7b86af5881840c7f016992f13bc84fa53701120e3082f7000cbd" + } + ], + "hashType": "sha256" +} \ No newline at end of file diff --git a/logs/.e0987c2f02cc1cb106cf88cd0bfd7b7d8ea27fc9-audit.json b/logs/.e0987c2f02cc1cb106cf88cd0bfd7b7d8ea27fc9-audit.json new file mode 100644 index 0000000000..f8e56ed535 --- /dev/null +++ b/logs/.e0987c2f02cc1cb106cf88cd0bfd7b7d8ea27fc9-audit.json @@ -0,0 +1,15 @@ +{ + "keep": { + "days": true, + "amount": 14 + }, + "auditLog": "/Users/steven/dev/waveterm/logs/.e0987c2f02cc1cb106cf88cd0bfd7b7d8ea27fc9-audit.json", + "files": [ + { + "date": 1767427944265, + "name": "/Users/steven/dev/waveterm/logs/error-2026-01-03.log", + "hash": "b8f49551269d85055d4b9d19fae3079c8c61830d7ae035302ba43c8dc38a96b1" + } + ], + "hashType": "sha256" +} \ No newline at end of file