Compare commits
No commits in common. "main" and "v1.12.1" have entirely different histories.
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
@ -14,4 +14,3 @@ pub mod updater;
|
|||||||
pub mod tools_commands_r2;
|
pub mod tools_commands_r2;
|
||||||
pub mod workspace_commands;
|
pub mod workspace_commands;
|
||||||
pub mod docker_commands;
|
pub mod docker_commands;
|
||||||
pub mod window_commands;
|
|
||||||
|
|||||||
@ -1,40 +0,0 @@
|
|||||||
use tauri::AppHandle;
|
|
||||||
use tauri::WebviewWindowBuilder;
|
|
||||||
|
|
||||||
/// Open a child window from the Rust side using WebviewWindowBuilder.
|
|
||||||
///
|
|
||||||
/// The `url` parameter supports hash fragments (e.g. "index.html#/tool/ping?sessionId=abc").
|
|
||||||
/// WebviewUrl::App takes a PathBuf and cannot handle hash/query — so we load plain
|
|
||||||
/// index.html and set the hash via JS after the window is created.
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn open_child_window(
|
|
||||||
app_handle: AppHandle,
|
|
||||||
label: String,
|
|
||||||
title: String,
|
|
||||||
url: String,
|
|
||||||
width: f64,
|
|
||||||
height: f64,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
// Split "index.html#/tool/ping?sessionId=abc" into path and fragment
|
|
||||||
let (path, hash) = match url.split_once('#') {
|
|
||||||
Some((p, h)) => (p.to_string(), Some(format!("#{}", h))),
|
|
||||||
None => (url.clone(), None),
|
|
||||||
};
|
|
||||||
|
|
||||||
let webview_url = tauri::WebviewUrl::App(path.into());
|
|
||||||
let window = WebviewWindowBuilder::new(&app_handle, &label, webview_url)
|
|
||||||
.title(&title)
|
|
||||||
.inner_size(width, height)
|
|
||||||
.resizable(true)
|
|
||||||
.center()
|
|
||||||
.build()
|
|
||||||
.map_err(|e| format!("Failed to create window '{}': {}", label, e))?;
|
|
||||||
|
|
||||||
// Set the hash fragment after the window loads — this triggers App.vue's
|
|
||||||
// onMounted hash detection to render the correct tool/detached component.
|
|
||||||
if let Some(hash) = hash {
|
|
||||||
let _ = window.eval(&format!("window.location.hash = '{}';", hash));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@ -234,7 +234,6 @@ pub fn run() {
|
|||||||
commands::updater::check_for_updates,
|
commands::updater::check_for_updates,
|
||||||
commands::workspace_commands::save_workspace, commands::workspace_commands::load_workspace,
|
commands::workspace_commands::save_workspace, commands::workspace_commands::load_workspace,
|
||||||
commands::docker_commands::docker_list_containers, commands::docker_commands::docker_list_images, commands::docker_commands::docker_list_volumes, commands::docker_commands::docker_action,
|
commands::docker_commands::docker_list_containers, commands::docker_commands::docker_list_images, commands::docker_commands::docker_list_volumes, commands::docker_commands::docker_action,
|
||||||
commands::window_commands::open_child_window,
|
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"security": {
|
"security": {
|
||||||
"csp": null
|
"csp": "default-src 'self' 'unsafe-inline' asset: https://asset.localhost; img-src 'self' asset: https://asset.localhost data: blob:; connect-src ipc: http://ipc.localhost https://ipc.localhost"
|
||||||
},
|
},
|
||||||
"withGlobalTauri": false
|
"withGlobalTauri": false
|
||||||
},
|
},
|
||||||
|
|||||||
55
src/App.vue
55
src/App.vue
@ -1,65 +1,48 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onErrorCaptured, defineAsyncComponent } from "vue";
|
import { ref, onMounted, defineAsyncComponent } from "vue";
|
||||||
import { useAppStore } from "@/stores/app.store";
|
import { useAppStore } from "@/stores/app.store";
|
||||||
import UnlockLayout from "@/layouts/UnlockLayout.vue";
|
import UnlockLayout from "@/layouts/UnlockLayout.vue";
|
||||||
import ToolWindow from "@/components/tools/ToolWindow.vue";
|
|
||||||
|
|
||||||
const MainLayout = defineAsyncComponent({
|
const MainLayout = defineAsyncComponent(
|
||||||
loader: () => import("@/layouts/MainLayout.vue"),
|
() => import("@/layouts/MainLayout.vue")
|
||||||
onError(error) { console.error("[App] MainLayout load failed:", error); },
|
);
|
||||||
});
|
const ToolWindow = defineAsyncComponent(
|
||||||
const DetachedSession = defineAsyncComponent({
|
() => import("@/components/tools/ToolWindow.vue")
|
||||||
loader: () => import("@/components/session/DetachedSession.vue"),
|
);
|
||||||
onError(error) { console.error("[App] DetachedSession load failed:", error); },
|
const DetachedSession = defineAsyncComponent(
|
||||||
});
|
() => import("@/components/session/DetachedSession.vue")
|
||||||
|
);
|
||||||
|
|
||||||
const app = useAppStore();
|
const app = useAppStore();
|
||||||
const appError = ref<string | null>(null);
|
|
||||||
|
|
||||||
|
// Tool window mode — detected from URL hash: #/tool/network-scanner?sessionId=abc
|
||||||
const isToolMode = ref(false);
|
const isToolMode = ref(false);
|
||||||
const isDetachedMode = ref(false);
|
const isDetachedMode = ref(false);
|
||||||
const toolName = ref("");
|
const toolName = ref("");
|
||||||
const toolSessionId = ref("");
|
const toolSessionId = ref("");
|
||||||
|
|
||||||
onErrorCaptured((err) => {
|
onMounted(async () => {
|
||||||
appError.value = err instanceof Error ? err.message : String(err);
|
const hash = window.location.hash;
|
||||||
console.error("[App] Uncaught error:", err);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
/** Parse hash and set mode flags. Called on mount and on hashchange. */
|
|
||||||
function applyHash(hash: string): void {
|
|
||||||
if (hash.startsWith("#/tool/")) {
|
if (hash.startsWith("#/tool/")) {
|
||||||
isToolMode.value = true;
|
isToolMode.value = true;
|
||||||
const rest = hash.substring(7);
|
const rest = hash.substring(7); // after "#/tool/"
|
||||||
const [name, query] = rest.split("?");
|
const [name, query] = rest.split("?");
|
||||||
toolName.value = name;
|
toolName.value = name;
|
||||||
toolSessionId.value = new URLSearchParams(query || "").get("sessionId") || "";
|
toolSessionId.value = new URLSearchParams(query || "").get("sessionId") || "";
|
||||||
} else if (hash.startsWith("#/detached-session")) {
|
} else if (hash.startsWith("#/detached-session")) {
|
||||||
isDetachedMode.value = true;
|
isDetachedMode.value = true;
|
||||||
}
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
// Check hash at load time (present if JS-side WebviewWindow set it in the URL)
|
|
||||||
applyHash(window.location.hash);
|
|
||||||
|
|
||||||
// Also listen for hash changes (Rust-side window sets hash via eval after load)
|
|
||||||
window.addEventListener("hashchange", () => applyHash(window.location.hash));
|
|
||||||
|
|
||||||
// Only init vault for the main app window (no hash)
|
|
||||||
if (!isToolMode.value && !isDetachedMode.value) {
|
|
||||||
await app.checkVaultState();
|
await app.checkVaultState();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="appError" class="fixed inset-0 z-50 flex items-center justify-center bg-[#0d1117] text-red-400 p-8 text-sm font-mono whitespace-pre-wrap">
|
<!-- Detached session window mode -->
|
||||||
{{ appError }}
|
<DetachedSession v-if="isDetachedMode" />
|
||||||
</div>
|
<!-- Tool popup window mode -->
|
||||||
<DetachedSession v-else-if="isDetachedMode" />
|
|
||||||
<ToolWindow v-else-if="isToolMode" :tool="toolName" :session-id="toolSessionId" />
|
<ToolWindow v-else-if="isToolMode" :tool="toolName" :session-id="toolSessionId" />
|
||||||
|
<!-- Normal app mode -->
|
||||||
<div v-else class="app-root">
|
<div v-else class="app-root">
|
||||||
<UnlockLayout v-if="!app.isUnlocked" />
|
<UnlockLayout v-if="!app.isUnlocked" />
|
||||||
<MainLayout v-else />
|
<MainLayout v-else />
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
.terminal-container {
|
.terminal-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 0;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: var(--wraith-bg-primary);
|
background: var(--wraith-bg-primary);
|
||||||
@ -20,16 +20,12 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* WKWebView focus fix: xterm.js hides its helper textarea with opacity: 0,
|
/* Selection styling — xterm.js v6 handles selection via canvas renderer.
|
||||||
width/height: 0, left: -9999em. macOS WKWebView doesn't reliably focus
|
No CSS override needed; colors come from terminal.options.theme. */
|
||||||
elements with zero dimensions positioned off-screen. Override to keep it
|
|
||||||
within the viewport with non-zero dimensions so focus events fire. */
|
/* Cursor styling */
|
||||||
.terminal-container .xterm .xterm-helper-textarea {
|
.terminal-container .xterm-cursor-layer {
|
||||||
left: 0 !important;
|
z-index: 4;
|
||||||
top: 0 !important;
|
|
||||||
width: 1px !important;
|
|
||||||
height: 1px !important;
|
|
||||||
opacity: 0.01 !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scrollbar inside terminal */
|
/* Scrollbar inside terminal */
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="h-[48px] flex items-center justify-between px-6 bg-[var(--wraith-bg-secondary)] border-t border-[var(--wraith-border)] text-base text-[var(--wraith-text-muted)] shrink-0">
|
<div class="h-9 flex items-center justify-between px-4 bg-[var(--wraith-bg-secondary)] border-t border-[var(--wraith-border)] text-sm text-[var(--wraith-text-muted)] shrink-0">
|
||||||
<!-- Left: connection info -->
|
<!-- Left: connection info -->
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<template v-if="sessionStore.activeSession">
|
<template v-if="sessionStore.activeSession">
|
||||||
|
|||||||
@ -204,45 +204,17 @@ onBeforeUnmount(() => {
|
|||||||
if (resizeTimeout) { clearTimeout(resizeTimeout); resizeTimeout = null; }
|
if (resizeTimeout) { clearTimeout(resizeTimeout); resizeTimeout = null; }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Focus canvas, re-check dimensions, and force full frame on tab switch.
|
// Focus canvas and force full frame refresh when switching to this tab
|
||||||
// Uses 300ms delay to let the flex layout fully settle (copilot panel toggle, etc.)
|
|
||||||
watch(
|
watch(
|
||||||
() => props.isActive,
|
() => props.isActive,
|
||||||
(active) => {
|
(active) => {
|
||||||
if (!active || !canvasRef.value) return;
|
if (active && canvasRef.value) {
|
||||||
|
// Force full frame fetch to repaint the canvas immediately
|
||||||
// Immediate focus so keyboard works right away
|
|
||||||
if (keyboardGrabbed.value) canvasRef.value.focus();
|
|
||||||
|
|
||||||
// Immediate force refresh to show SOMETHING while we check dimensions
|
|
||||||
invoke("rdp_force_refresh", { sessionId: props.sessionId }).catch(() => {});
|
invoke("rdp_force_refresh", { sessionId: props.sessionId }).catch(() => {});
|
||||||
|
if (keyboardGrabbed.value) {
|
||||||
// Delayed dimension check — layout needs time to settle
|
setTimeout(() => canvasRef.value?.focus(), 0);
|
||||||
setTimeout(() => {
|
|
||||||
const wrapper = canvasWrapper.value;
|
|
||||||
const canvas = canvasRef.value;
|
|
||||||
if (!wrapper || !canvas) return;
|
|
||||||
|
|
||||||
const { width: cw, height: ch } = wrapper.getBoundingClientRect();
|
|
||||||
const newW = Math.round(cw) & ~1;
|
|
||||||
const newH = Math.round(ch);
|
|
||||||
|
|
||||||
if (newW >= 200 && newH >= 200 && (newW !== canvas.width || newH !== canvas.height)) {
|
|
||||||
invoke("rdp_resize", {
|
|
||||||
sessionId: props.sessionId,
|
|
||||||
width: newW,
|
|
||||||
height: newH,
|
|
||||||
}).then(() => {
|
|
||||||
if (canvas) {
|
|
||||||
canvas.width = newW;
|
|
||||||
canvas.height = newH;
|
|
||||||
}
|
}
|
||||||
setTimeout(() => {
|
|
||||||
invoke("rdp_force_refresh", { sessionId: props.sessionId }).catch(() => {});
|
|
||||||
}, 500);
|
|
||||||
}).catch(() => {});
|
|
||||||
}
|
}
|
||||||
}, 300);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -133,14 +133,16 @@ async function detachTab(): Promise<void> {
|
|||||||
session.active = false;
|
session.active = false;
|
||||||
|
|
||||||
// Open a new Tauri window for this session
|
// Open a new Tauri window for this session
|
||||||
try {
|
const { WebviewWindow } = await import("@tauri-apps/api/webviewWindow");
|
||||||
await invoke("open_child_window", {
|
const label = `detached-${session.id.substring(0, 8)}-${Date.now()}`;
|
||||||
label: `detached-${session.id.substring(0, 8)}-${Date.now()}`,
|
new WebviewWindow(label, {
|
||||||
title: `${session.name} — Wraith`,
|
title: `${session.name} — Wraith`,
|
||||||
|
width: 900,
|
||||||
|
height: 600,
|
||||||
|
resizable: true,
|
||||||
|
center: true,
|
||||||
url: `index.html#/detached-session?sessionId=${session.id}&name=${encodeURIComponent(session.name)}&protocol=${session.protocol}`,
|
url: `index.html#/detached-session?sessionId=${session.id}&name=${encodeURIComponent(session.name)}&protocol=${session.protocol}`,
|
||||||
width: 900, height: 600,
|
|
||||||
});
|
});
|
||||||
} catch (err) { console.error("Detach window error:", err); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeMenuTab(): void {
|
function closeMenuTab(): void {
|
||||||
|
|||||||
@ -33,9 +33,9 @@ function applyTheme(): void {
|
|||||||
foreground: theme.foreground,
|
foreground: theme.foreground,
|
||||||
cursor: theme.cursor,
|
cursor: theme.cursor,
|
||||||
cursorAccent: theme.background,
|
cursorAccent: theme.background,
|
||||||
selectionBackground: theme.selectionBackground ?? "#264f78",
|
selectionBackground: theme.selectionBackground ?? "rgba(88, 166, 255, 0.4)",
|
||||||
selectionForeground: theme.selectionForeground ?? "#ffffff",
|
selectionForeground: theme.selectionForeground ?? "#ffffff",
|
||||||
selectionInactiveBackground: theme.selectionBackground ?? "#264f78",
|
selectionInactiveBackground: theme.selectionBackground ?? "rgba(88, 166, 255, 0.2)",
|
||||||
black: theme.black,
|
black: theme.black,
|
||||||
red: theme.red,
|
red: theme.red,
|
||||||
green: theme.green,
|
green: theme.green,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="stats"
|
v-if="stats"
|
||||||
class="flex items-center gap-4 px-6 h-[48px] bg-[var(--wraith-bg-tertiary)] border-t border-[var(--wraith-border)] text-base font-mono shrink-0 select-none"
|
class="flex items-center gap-4 px-3 h-6 bg-[var(--wraith-bg-tertiary)] border-t border-[var(--wraith-border)] text-[10px] font-mono shrink-0 select-none"
|
||||||
>
|
>
|
||||||
<!-- CPU -->
|
<!-- CPU -->
|
||||||
<span class="flex items-center gap-1">
|
<span class="flex items-center gap-1">
|
||||||
|
|||||||
@ -77,10 +77,6 @@ const containerRef = ref<HTMLElement | null>(null);
|
|||||||
const { terminal, searchAddon, mount, fit } = useTerminal(props.sessionId);
|
const { terminal, searchAddon, mount, fit } = useTerminal(props.sessionId);
|
||||||
let resizeDisposable: IDisposable | null = null;
|
let resizeDisposable: IDisposable | null = null;
|
||||||
|
|
||||||
function handleFocus(): void {
|
|
||||||
terminal.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Search state ---
|
// --- Search state ---
|
||||||
const searchVisible = ref(false);
|
const searchVisible = ref(false);
|
||||||
const searchQuery = ref("");
|
const searchQuery = ref("");
|
||||||
@ -156,6 +152,10 @@ onMounted(() => {
|
|||||||
}, 50);
|
}, 50);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function handleFocus(): void {
|
||||||
|
terminal.focus();
|
||||||
|
}
|
||||||
|
|
||||||
// Re-fit and focus terminal when switching back to this tab.
|
// Re-fit and focus terminal when switching back to this tab.
|
||||||
// Must wait for the container to have real dimensions after becoming visible.
|
// Must wait for the container to have real dimensions after becoming visible.
|
||||||
watch(
|
watch(
|
||||||
@ -190,9 +190,9 @@ function applyTheme(): void {
|
|||||||
foreground: theme.foreground,
|
foreground: theme.foreground,
|
||||||
cursor: theme.cursor,
|
cursor: theme.cursor,
|
||||||
cursorAccent: theme.background,
|
cursorAccent: theme.background,
|
||||||
selectionBackground: theme.selectionBackground ?? "#264f78",
|
selectionBackground: theme.selectionBackground ?? "rgba(88, 166, 255, 0.4)",
|
||||||
selectionForeground: theme.selectionForeground ?? "#ffffff",
|
selectionForeground: theme.selectionForeground ?? "#ffffff",
|
||||||
selectionInactiveBackground: theme.selectionBackground ?? "#264f78",
|
selectionInactiveBackground: theme.selectionBackground ?? "rgba(88, 166, 255, 0.2)",
|
||||||
black: theme.black,
|
black: theme.black,
|
||||||
red: theme.red,
|
red: theme.red,
|
||||||
green: theme.green,
|
green: theme.green,
|
||||||
|
|||||||
@ -14,9 +14,8 @@ const defaultTheme = {
|
|||||||
foreground: "#e0e0e0",
|
foreground: "#e0e0e0",
|
||||||
cursor: "#58a6ff",
|
cursor: "#58a6ff",
|
||||||
cursorAccent: "#0d1117",
|
cursorAccent: "#0d1117",
|
||||||
selectionBackground: "#264f78",
|
selectionBackground: "rgba(88, 166, 255, 0.4)",
|
||||||
selectionForeground: "#ffffff",
|
selectionForeground: "#ffffff",
|
||||||
selectionInactiveBackground: "#264f78",
|
|
||||||
black: "#0d1117",
|
black: "#0d1117",
|
||||||
red: "#f85149",
|
red: "#f85149",
|
||||||
green: "#3fb950",
|
green: "#3fb950",
|
||||||
|
|||||||
@ -367,14 +367,16 @@ function closeHelpMenuDeferred(): void {
|
|||||||
|
|
||||||
async function handleHelpAction(page: string): Promise<void> {
|
async function handleHelpAction(page: string): Promise<void> {
|
||||||
showHelpMenu.value = false;
|
showHelpMenu.value = false;
|
||||||
try {
|
const { WebviewWindow } = await import("@tauri-apps/api/webviewWindow");
|
||||||
await invoke("open_child_window", {
|
const label = `help-${page}-${Date.now()}`;
|
||||||
label: `help-${page}-${Date.now()}`,
|
new WebviewWindow(label, {
|
||||||
title: "Wraith — Help",
|
title: `Wraith — Help`,
|
||||||
|
width: 750,
|
||||||
|
height: 600,
|
||||||
|
resizable: true,
|
||||||
|
center: true,
|
||||||
url: `index.html#/tool/help?page=${page}`,
|
url: `index.html#/tool/help?page=${page}`,
|
||||||
width: 750, height: 600,
|
|
||||||
});
|
});
|
||||||
} catch (err) { console.error("Help window error:", err); alert("Window error: " + String(err)); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleToolAction(tool: string): Promise<void> {
|
async function handleToolAction(tool: string): Promise<void> {
|
||||||
@ -388,6 +390,8 @@ async function handleToolAction(tool: string): Promise<void> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { WebviewWindow } = await import("@tauri-apps/api/webviewWindow");
|
||||||
|
|
||||||
const toolConfig: Record<string, { title: string; width: number; height: number }> = {
|
const toolConfig: Record<string, { title: string; width: number; height: number }> = {
|
||||||
"network-scanner": { title: "Network Scanner", width: 800, height: 600 },
|
"network-scanner": { title: "Network Scanner", width: 800, height: 600 },
|
||||||
"port-scanner": { title: "Port Scanner", width: 700, height: 500 },
|
"port-scanner": { title: "Port Scanner", width: 700, height: 500 },
|
||||||
@ -408,14 +412,16 @@ async function handleToolAction(tool: string): Promise<void> {
|
|||||||
|
|
||||||
const sessionId = activeSessionId.value || "";
|
const sessionId = activeSessionId.value || "";
|
||||||
|
|
||||||
try {
|
// Open tool in a new Tauri window
|
||||||
await invoke("open_child_window", {
|
const label = `tool-${tool}-${Date.now()}`;
|
||||||
label: `tool-${tool}-${Date.now()}`,
|
new WebviewWindow(label, {
|
||||||
title: `Wraith — ${config.title}`,
|
title: `Wraith — ${config.title}`,
|
||||||
|
width: config.width,
|
||||||
|
height: config.height,
|
||||||
|
resizable: true,
|
||||||
|
center: true,
|
||||||
url: `index.html#/tool/${tool}?sessionId=${sessionId}`,
|
url: `index.html#/tool/${tool}?sessionId=${sessionId}`,
|
||||||
width: config.width, height: config.height,
|
|
||||||
});
|
});
|
||||||
} catch (err) { console.error("Tool window error:", err); alert("Tool window error: " + String(err)); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleFileMenuAction(action: string): Promise<void> {
|
async function handleFileMenuAction(action: string): Promise<void> {
|
||||||
@ -435,13 +441,18 @@ function handleThemeSelect(theme: ThemeDefinition): void {
|
|||||||
async function handleOpenFile(entry: FileEntry): Promise<void> {
|
async function handleOpenFile(entry: FileEntry): Promise<void> {
|
||||||
if (!activeSessionId.value) return;
|
if (!activeSessionId.value) return;
|
||||||
try {
|
try {
|
||||||
|
const { WebviewWindow } = await import("@tauri-apps/api/webviewWindow");
|
||||||
const fileName = entry.path.split("/").pop() || entry.path;
|
const fileName = entry.path.split("/").pop() || entry.path;
|
||||||
|
const label = `editor-${Date.now()}`;
|
||||||
const sessionId = activeSessionId.value;
|
const sessionId = activeSessionId.value;
|
||||||
await invoke("open_child_window", {
|
|
||||||
label: `editor-${Date.now()}`,
|
new WebviewWindow(label, {
|
||||||
title: `${fileName} — Wraith Editor`,
|
title: `${fileName} — Wraith Editor`,
|
||||||
|
width: 800,
|
||||||
|
height: 600,
|
||||||
|
resizable: true,
|
||||||
|
center: true,
|
||||||
url: `index.html#/tool/editor?sessionId=${sessionId}&path=${encodeURIComponent(entry.path)}`,
|
url: `index.html#/tool/editor?sessionId=${sessionId}&path=${encodeURIComponent(entry.path)}`,
|
||||||
width: 800, height: 600,
|
|
||||||
});
|
});
|
||||||
} catch (err) { console.error("Failed to open editor:", err); }
|
} catch (err) { console.error("Failed to open editor:", err); }
|
||||||
}
|
}
|
||||||
@ -491,19 +502,6 @@ onMounted(async () => {
|
|||||||
|
|
||||||
await connectionStore.loadAll();
|
await connectionStore.loadAll();
|
||||||
|
|
||||||
// Restore saved theme so every terminal opens with the user's preferred colors
|
|
||||||
try {
|
|
||||||
const savedThemeName = await invoke<string | null>("get_setting", { key: "active_theme" });
|
|
||||||
if (savedThemeName) {
|
|
||||||
const themes = await invoke<Array<{ name: string; foreground: string; background: string; cursor: string; black: string; red: string; green: string; yellow: string; blue: string; magenta: string; cyan: string; white: string; brightBlack: string; brightRed: string; brightGreen: string; brightYellow: string; brightBlue: string; brightMagenta: string; brightCyan: string; brightWhite: string }>>("list_themes");
|
|
||||||
const theme = themes?.find(t => t.name === savedThemeName);
|
|
||||||
if (theme) {
|
|
||||||
sessionStore.setTheme(theme);
|
|
||||||
statusBar.value?.setThemeName(theme.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
// Restore workspace — reconnect saved tabs (non-blocking, non-fatal)
|
// Restore workspace — reconnect saved tabs (non-blocking, non-fatal)
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -1,20 +1,10 @@
|
|||||||
import { defineConfig, type Plugin } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import vue from "@vitejs/plugin-vue";
|
import vue from "@vitejs/plugin-vue";
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
import { resolve } from "path";
|
import { resolve } from "path";
|
||||||
|
|
||||||
/** Strip crossorigin attribute from HTML — WKWebView + Tauri custom protocol compatibility. */
|
|
||||||
function stripCrossOrigin(): Plugin {
|
|
||||||
return {
|
|
||||||
name: "strip-crossorigin",
|
|
||||||
transformIndexHtml(html) {
|
|
||||||
return html.replace(/ crossorigin/g, "");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue(), tailwindcss(), stripCrossOrigin()],
|
plugins: [vue(), tailwindcss()],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@": resolve(__dirname, "src"),
|
"@": resolve(__dirname, "src"),
|
||||||
@ -33,9 +23,5 @@ export default defineConfig({
|
|||||||
target: ["es2021", "chrome100", "safari13"],
|
target: ["es2021", "chrome100", "safari13"],
|
||||||
minify: !process.env.TAURI_DEBUG ? "esbuild" : false,
|
minify: !process.env.TAURI_DEBUG ? "esbuild" : false,
|
||||||
sourcemap: !!process.env.TAURI_DEBUG,
|
sourcemap: !!process.env.TAURI_DEBUG,
|
||||||
// Disable crossorigin attribute on script/link tags — WKWebView on
|
|
||||||
// macOS may reject CORS-mode requests for Tauri's custom tauri://
|
|
||||||
// protocol in dynamically created child WebviewWindows.
|
|
||||||
crossOriginLoading: false,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user