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)]">
|
<span class="text-sm font-bold tracking-widest text-[var(--wraith-accent-blue)]">
|
||||||
WRAITH
|
WRAITH
|
||||||
</span>
|
</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>
|
<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
|
<button
|
||||||
class="hover:text-[var(--wraith-text-primary)] transition-colors cursor-pointer"
|
class="hover:text-[var(--wraith-text-primary)] transition-colors cursor-pointer"
|
||||||
title="Lock vault"
|
title="Lock vault"
|
||||||
@ -89,12 +110,21 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Status bar -->
|
<!-- 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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref, onMounted, onUnmounted } from "vue";
|
||||||
import { useAppStore } from "@/stores/app.store";
|
import { useAppStore } from "@/stores/app.store";
|
||||||
import { useConnectionStore } from "@/stores/connection.store";
|
import { useConnectionStore } from "@/stores/connection.store";
|
||||||
import { useSessionStore } from "@/stores/session.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 SessionContainer from "@/components/session/SessionContainer.vue";
|
||||||
import StatusBar from "@/components/common/StatusBar.vue";
|
import StatusBar from "@/components/common/StatusBar.vue";
|
||||||
import EditorWindow from "@/components/editor/EditorWindow.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 { SidebarTab } from "@/components/sidebar/SidebarToggle.vue";
|
||||||
import type { FileEntry } from "@/composables/useSftp";
|
import type { FileEntry } from "@/composables/useSftp";
|
||||||
|
|
||||||
@ -115,15 +149,21 @@ const sessionStore = useSessionStore();
|
|||||||
|
|
||||||
const sidebarWidth = ref(240);
|
const sidebarWidth = ref(240);
|
||||||
const sidebarTab = ref<SidebarTab>("connections");
|
const sidebarTab = ref<SidebarTab>("connections");
|
||||||
|
const quickConnectInput = ref("");
|
||||||
|
|
||||||
/** Currently open file in the editor panel (null = no file open). */
|
/** Currently open file in the editor panel (null = no file open). */
|
||||||
const editorFile = ref<{ content: string; path: string; sessionId: string } | null>(null);
|
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 {
|
function handleOpenFile(entry: FileEntry): void {
|
||||||
if (!sessionStore.activeSession) return;
|
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
|
// Mock file content for development
|
||||||
const mockContent = `# ${entry.name}\n\n` +
|
const mockContent = `# ${entry.name}\n\n` +
|
||||||
`# File: ${entry.path}\n` +
|
`# File: ${entry.path}\n` +
|
||||||
@ -138,4 +178,87 @@ function handleOpenFile(entry: FileEntry): void {
|
|||||||
sessionId: sessionStore.activeSession.id,
|
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>
|
</script>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user