feat(ui): wire quick connect input and global Ctrl+K shortcut
Quick connect bar in toolbar parses user@host:port format with auto-protocol detection (port 3389 -> RDP, default SSH:22). Global keydown listener triggers command palette on Ctrl/Cmd+K. MainLayout now integrates ThemePicker, ImportDialog, and CommandPalette overlays. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f5de568b32
commit
ae8127f33d
@ -8,8 +8,29 @@
|
||||
<span class="text-sm font-bold tracking-widest text-[var(--wraith-accent-blue)]">
|
||||
WRAITH
|
||||
</span>
|
||||
<div class="flex items-center gap-3 text-xs text-[var(--wraith-text-secondary)]">
|
||||
|
||||
<!-- Quick Connect -->
|
||||
<div class="flex-1 max-w-xs mx-4" style="--wails-draggable: no-drag">
|
||||
<input
|
||||
v-model="quickConnectInput"
|
||||
type="text"
|
||||
placeholder="Quick connect: user@host:port"
|
||||
class="w-full px-2.5 py-1 text-xs rounded bg-[var(--wraith-bg-tertiary)] border border-[var(--wraith-border)] text-[var(--wraith-text-primary)] placeholder-[var(--wraith-text-muted)] outline-none focus:border-[var(--wraith-accent-blue)] transition-colors"
|
||||
@keydown.enter="handleQuickConnect"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3 text-xs text-[var(--wraith-text-secondary)]" style="--wails-draggable: no-drag">
|
||||
<span>{{ sessionStore.sessionCount }} session{{ sessionStore.sessionCount !== 1 ? "s" : "" }}</span>
|
||||
<button
|
||||
class="hover:text-[var(--wraith-text-primary)] transition-colors cursor-pointer"
|
||||
title="Command palette (Ctrl+K)"
|
||||
@click="commandPalette?.toggle()"
|
||||
>
|
||||
<svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M11.5 7a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0zm-.82 4.74a6 6 0 1 1 1.06-1.06l3.04 3.04a.75.75 0 1 1-1.06 1.06l-3.04-3.04z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="hover:text-[var(--wraith-text-primary)] transition-colors cursor-pointer"
|
||||
title="Lock vault"
|
||||
@ -89,12 +110,21 @@
|
||||
</div>
|
||||
|
||||
<!-- Status bar -->
|
||||
<StatusBar />
|
||||
<StatusBar ref="statusBar" @open-theme-picker="themePicker?.open()" />
|
||||
|
||||
<!-- Command Palette (Ctrl+K) -->
|
||||
<CommandPalette ref="commandPalette" @open-import="importDialog?.open()" />
|
||||
|
||||
<!-- Theme Picker -->
|
||||
<ThemePicker ref="themePicker" @select="handleThemeSelect" />
|
||||
|
||||
<!-- Import Dialog -->
|
||||
<ImportDialog ref="importDialog" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
import { useAppStore } from "@/stores/app.store";
|
||||
import { useConnectionStore } from "@/stores/connection.store";
|
||||
import { useSessionStore } from "@/stores/session.store";
|
||||
@ -106,6 +136,10 @@ import TabBar from "@/components/session/TabBar.vue";
|
||||
import SessionContainer from "@/components/session/SessionContainer.vue";
|
||||
import StatusBar from "@/components/common/StatusBar.vue";
|
||||
import EditorWindow from "@/components/editor/EditorWindow.vue";
|
||||
import CommandPalette from "@/components/common/CommandPalette.vue";
|
||||
import ThemePicker from "@/components/common/ThemePicker.vue";
|
||||
import ImportDialog from "@/components/common/ImportDialog.vue";
|
||||
import type { ThemeDefinition } from "@/components/common/ThemePicker.vue";
|
||||
import type { SidebarTab } from "@/components/sidebar/SidebarToggle.vue";
|
||||
import type { FileEntry } from "@/composables/useSftp";
|
||||
|
||||
@ -115,15 +149,21 @@ const sessionStore = useSessionStore();
|
||||
|
||||
const sidebarWidth = ref(240);
|
||||
const sidebarTab = ref<SidebarTab>("connections");
|
||||
const quickConnectInput = ref("");
|
||||
|
||||
/** Currently open file in the editor panel (null = no file open). */
|
||||
const editorFile = ref<{ content: string; path: string; sessionId: string } | null>(null);
|
||||
|
||||
/** Handle file open from SFTP sidebar — loads mock content for now. */
|
||||
const commandPalette = ref<InstanceType<typeof CommandPalette> | null>(null);
|
||||
const themePicker = ref<InstanceType<typeof ThemePicker> | null>(null);
|
||||
const importDialog = ref<InstanceType<typeof ImportDialog> | null>(null);
|
||||
const statusBar = ref<InstanceType<typeof StatusBar> | null>(null);
|
||||
|
||||
/** Handle file open from SFTP sidebar -- loads mock content for now. */
|
||||
function handleOpenFile(entry: FileEntry): void {
|
||||
if (!sessionStore.activeSession) return;
|
||||
|
||||
// TODO: Replace with Wails binding call — SFTPService.ReadFile(sessionId, entry.path)
|
||||
// TODO: Replace with Wails binding call -- SFTPService.ReadFile(sessionId, entry.path)
|
||||
// Mock file content for development
|
||||
const mockContent = `# ${entry.name}\n\n` +
|
||||
`# File: ${entry.path}\n` +
|
||||
@ -138,4 +178,87 @@ function handleOpenFile(entry: FileEntry): void {
|
||||
sessionId: sessionStore.activeSession.id,
|
||||
};
|
||||
}
|
||||
|
||||
/** Handle theme selection from the ThemePicker. */
|
||||
function handleThemeSelect(theme: ThemeDefinition): void {
|
||||
statusBar.value?.setThemeName(theme.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick Connect: parse user@host:port and open a session.
|
||||
* Default protocol: SSH, default port: 22.
|
||||
* If port is 3389, use RDP.
|
||||
*/
|
||||
function handleQuickConnect(): void {
|
||||
const raw = quickConnectInput.value.trim();
|
||||
if (!raw) return;
|
||||
|
||||
let username = "";
|
||||
let hostname = "";
|
||||
let port = 22;
|
||||
let protocol: "ssh" | "rdp" = "ssh";
|
||||
|
||||
let hostPart = raw;
|
||||
|
||||
// Extract username if present (user@...)
|
||||
const atIdx = raw.indexOf("@");
|
||||
if (atIdx > 0) {
|
||||
username = raw.substring(0, atIdx);
|
||||
hostPart = raw.substring(atIdx + 1);
|
||||
}
|
||||
|
||||
// Extract port if present (...:port)
|
||||
const colonIdx = hostPart.lastIndexOf(":");
|
||||
if (colonIdx > 0) {
|
||||
const portStr = hostPart.substring(colonIdx + 1);
|
||||
const parsedPort = parseInt(portStr, 10);
|
||||
if (!isNaN(parsedPort) && parsedPort > 0 && parsedPort <= 65535) {
|
||||
port = parsedPort;
|
||||
hostPart = hostPart.substring(0, colonIdx);
|
||||
}
|
||||
}
|
||||
|
||||
hostname = hostPart;
|
||||
if (!hostname) return;
|
||||
|
||||
// Auto-detect RDP by port
|
||||
if (port === 3389) {
|
||||
protocol = "rdp";
|
||||
}
|
||||
|
||||
// Create a temporary connection and session
|
||||
// TODO: Replace with Wails binding — create ephemeral session via SSHService.Connect / RDPService.Connect
|
||||
const tempId = Math.max(...connectionStore.connections.map((c) => c.id), 0) + 1;
|
||||
const name = username ? `${username}@${hostname}` : hostname;
|
||||
|
||||
connectionStore.connections.push({
|
||||
id: tempId,
|
||||
name,
|
||||
hostname,
|
||||
port,
|
||||
protocol,
|
||||
groupId: 1,
|
||||
tags: [],
|
||||
});
|
||||
|
||||
sessionStore.connect(tempId);
|
||||
quickConnectInput.value = "";
|
||||
}
|
||||
|
||||
/** Global keyboard shortcut handler. */
|
||||
function handleKeydown(event: KeyboardEvent): void {
|
||||
// Ctrl+K or Cmd+K — open command palette
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === "k") {
|
||||
event.preventDefault();
|
||||
commandPalette.value?.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener("keydown", handleKeydown);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener("keydown", handleKeydown);
|
||||
});
|
||||
</script>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user