feat: SFTP editor opens as popup window instead of inline overlay
Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Failing after 6s
Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Failing after 6s
Right-click → Edit now opens a separate Tauri window with the file content in a monospace editor. Ctrl+S saves back to remote via SFTP. Tab inserts 4 spaces. Modified indicator in toolbar. Removed the inline EditorWindow overlay that covered the terminal. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
016906fc9d
commit
3e548ed10e
@ -1 +1 @@
|
||||
{"default":{"identifier":"default","description":"Default capabilities for the main Wraith window","local":true,"windows":["main","tool-*"],"permissions":["core:default","core:event:default","core:window:default","shell:allow-open"]}}
|
||||
{"default":{"identifier":"default","description":"Default capabilities for the main Wraith window","local":true,"windows":["main","tool-*"],"permissions":["core:default","core:event:default","core:window:default","core:window:allow-create","core:webview:default","core:webview:allow-create-webview-window","shell:allow-open"]}}
|
||||
107
src/components/tools/FileEditor.vue
Normal file
107
src/components/tools/FileEditor.vue
Normal file
@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<div class="flex flex-col h-full bg-[#0d1117]">
|
||||
<!-- Toolbar -->
|
||||
<div class="flex items-center gap-2 px-3 py-2 bg-[#161b22] border-b border-[#30363d] shrink-0">
|
||||
<span class="text-xs text-[#8b949e] font-mono truncate flex-1">{{ filePath }}</span>
|
||||
<span v-if="modified" class="text-[10px] text-[#e3b341]">modified</span>
|
||||
<button
|
||||
class="px-3 py-1 text-xs font-bold rounded bg-[#238636] text-white cursor-pointer disabled:opacity-40"
|
||||
:disabled="saving || !modified"
|
||||
@click="save"
|
||||
>
|
||||
{{ saving ? "Saving..." : "Save" }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Editor area -->
|
||||
<div ref="editorContainer" class="flex-1 min-h-0" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from "vue";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
const props = defineProps<{
|
||||
sessionId: string;
|
||||
}>();
|
||||
|
||||
const filePath = ref("");
|
||||
const content = ref("");
|
||||
const modified = ref(false);
|
||||
const saving = ref(false);
|
||||
const editorContainer = ref<HTMLElement | null>(null);
|
||||
let editorContent = "";
|
||||
|
||||
onMounted(async () => {
|
||||
// Parse path from URL
|
||||
const params = new URLSearchParams(window.location.hash.split("?")[1] || "");
|
||||
filePath.value = decodeURIComponent(params.get("path") || "");
|
||||
|
||||
if (!filePath.value || !props.sessionId) return;
|
||||
|
||||
// Load file content
|
||||
try {
|
||||
content.value = await invoke<string>("sftp_read_file", {
|
||||
sessionId: props.sessionId,
|
||||
path: filePath.value,
|
||||
});
|
||||
editorContent = content.value;
|
||||
} catch (err) {
|
||||
content.value = `Error loading file: ${err}`;
|
||||
}
|
||||
|
||||
// Create a simple textarea editor (CodeMirror can be added later)
|
||||
if (editorContainer.value) {
|
||||
const textarea = document.createElement("textarea");
|
||||
textarea.value = content.value;
|
||||
textarea.spellcheck = false;
|
||||
textarea.style.cssText = `
|
||||
width: 100%; height: 100%; resize: none; border: none; outline: none;
|
||||
background: #0d1117; color: #e0e0e0; padding: 12px; font-size: 13px;
|
||||
font-family: 'Cascadia Mono', 'Cascadia Code', Consolas, 'JetBrains Mono', monospace;
|
||||
line-height: 1.5; tab-size: 4;
|
||||
`;
|
||||
textarea.addEventListener("input", () => {
|
||||
editorContent = textarea.value;
|
||||
modified.value = editorContent !== content.value;
|
||||
});
|
||||
textarea.addEventListener("keydown", (e) => {
|
||||
// Ctrl+S to save
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === "s") {
|
||||
e.preventDefault();
|
||||
save();
|
||||
}
|
||||
// Tab inserts spaces
|
||||
if (e.key === "Tab") {
|
||||
e.preventDefault();
|
||||
const start = textarea.selectionStart;
|
||||
const end = textarea.selectionEnd;
|
||||
textarea.value = textarea.value.substring(0, start) + " " + textarea.value.substring(end);
|
||||
textarea.selectionStart = textarea.selectionEnd = start + 4;
|
||||
editorContent = textarea.value;
|
||||
modified.value = true;
|
||||
}
|
||||
});
|
||||
editorContainer.value.appendChild(textarea);
|
||||
textarea.focus();
|
||||
}
|
||||
});
|
||||
|
||||
async function save(): Promise<void> {
|
||||
if (!modified.value || saving.value) return;
|
||||
saving.value = true;
|
||||
try {
|
||||
await invoke("sftp_write_file", {
|
||||
sessionId: props.sessionId,
|
||||
path: filePath.value,
|
||||
content: editorContent,
|
||||
});
|
||||
content.value = editorContent;
|
||||
modified.value = false;
|
||||
} catch (err) {
|
||||
alert(`Save failed: ${err}`);
|
||||
}
|
||||
saving.value = false;
|
||||
}
|
||||
</script>
|
||||
@ -9,6 +9,7 @@
|
||||
<WhoisTool v-else-if="tool === 'whois'" :session-id="sessionId" />
|
||||
<BandwidthTest v-else-if="tool === 'bandwidth'" :session-id="sessionId" />
|
||||
<SubnetCalc v-else-if="tool === 'subnet-calc'" />
|
||||
<FileEditor v-else-if="tool === 'editor'" :session-id="sessionId" />
|
||||
<SshKeyGen v-else-if="tool === 'ssh-keygen'" />
|
||||
<PasswordGen v-else-if="tool === 'password-gen'" />
|
||||
<div v-else class="flex-1 flex items-center justify-center text-sm text-[#484f58]">
|
||||
@ -27,6 +28,7 @@ import DnsLookup from "./DnsLookup.vue";
|
||||
import WhoisTool from "./WhoisTool.vue";
|
||||
import BandwidthTest from "./BandwidthTest.vue";
|
||||
import SubnetCalc from "./SubnetCalc.vue";
|
||||
import FileEditor from "./FileEditor.vue";
|
||||
import SshKeyGen from "./SshKeyGen.vue";
|
||||
import PasswordGen from "./PasswordGen.vue";
|
||||
|
||||
|
||||
@ -235,15 +235,6 @@
|
||||
<!-- Tab bar -->
|
||||
<TabBar />
|
||||
|
||||
<!-- Inline file editor -->
|
||||
<EditorWindow
|
||||
v-if="editorFile"
|
||||
:content="editorFile.content"
|
||||
:file-path="editorFile.path"
|
||||
:session-id="editorFile.sessionId"
|
||||
@close="editorFile = null"
|
||||
/>
|
||||
|
||||
<!-- Session area -->
|
||||
<SessionContainer ref="sessionContainer" />
|
||||
</div>
|
||||
@ -384,9 +375,20 @@ function handleThemeSelect(theme: ThemeDefinition): void {
|
||||
async function handleOpenFile(entry: FileEntry): Promise<void> {
|
||||
if (!activeSessionId.value) return;
|
||||
try {
|
||||
const content = await invoke<string>("sftp_read_file", { sessionId: activeSessionId.value, path: entry.path });
|
||||
editorFile.value = { path: entry.path, content, sessionId: activeSessionId.value };
|
||||
} catch (err) { console.error("Failed to open SFTP file:", err); }
|
||||
const { WebviewWindow } = await import("@tauri-apps/api/webviewWindow");
|
||||
const fileName = entry.path.split("/").pop() || entry.path;
|
||||
const label = `editor-${Date.now()}`;
|
||||
const sessionId = activeSessionId.value;
|
||||
|
||||
new WebviewWindow(label, {
|
||||
title: `${fileName} — Wraith Editor`,
|
||||
width: 800,
|
||||
height: 600,
|
||||
resizable: true,
|
||||
center: true,
|
||||
url: `index.html#/tool/editor?sessionId=${sessionId}&path=${encodeURIComponent(entry.path)}`,
|
||||
});
|
||||
} catch (err) { console.error("Failed to open editor:", err); }
|
||||
}
|
||||
|
||||
async function handleQuickConnect(): Promise<void> {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user