feat: support multiple desktop windows

This commit is contained in:
t8y2 committed 2026-06-05 20:45:00 +08:00
1 parent 28e8a77fa3
commit 642fa39bb8
26 files changed
+522 -436

No files matched your search

@@ -690,7 +690,7 @@ const messageRenderer = computed(() => {
class="flex items-center gap-2 border-b px-3 shrink-0"
:class="settings.editorSettings.appLayout === 'classic' ? 'h-9' : 'h-10'"
>
<span class="flex flex-1 self-stretch items-center truncate text-xs font-medium" data-tauri-drag-region>
<span class="flex flex-1 self-stretch items-center truncate text-xs font-medium" data-window-drag-region>
{{ chatTitle }}
</span>
<Button variant="ghost" size="icon" class="h-6 w-6" @click="startNewChat" :title="t('ai.newChat')">
@@ -239,6 +239,7 @@ const props = defineProps<{
totalRowCount?: number;
loading?: boolean;
cacheKey?: string;
dataStale?: boolean;
onExecuteSql?: (sql: string) => Promise<void>;
fullExportResult?: () => Promise<QueryResult | undefined>;
customSave?: (changes: {
@@ -5816,6 +5817,12 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
variant="ghost"
size="sm"
class="h-5 text-xs px-1.5 shrink-0"
:class="
props.dataStale
? 'bg-amber-500/15 text-amber-700 hover:bg-amber-500/25 hover:text-amber-800 dark:text-amber-300 dark:hover:text-amber-200'
: ''
"
:title="props.dataStale ? t('grid.dataStaleRefresh') : undefined"
:disabled="isSaving"
@click="onToolbarRefresh"
>
@@ -62,10 +62,10 @@ defineExpose({ focusSearch });
class="flex items-center gap-px px-3 text-xs font-medium text-muted-foreground border-b bg-muted/20"
:class="classicLayout ? 'h-9' : 'h-10'"
>
<span class="flex self-stretch items-center truncate" data-tauri-drag-region>{{
<span class="flex self-stretch items-center truncate" data-window-drag-region>{{
t("sidebar.connections")
}}</span>
<span class="flex-1 self-stretch" data-tauri-drag-region />
<span class="flex-1 self-stretch" data-window-drag-region />
<Tooltip>
<TooltipTrigger as-child>
<span class="inline-flex">
@@ -23,6 +23,7 @@ import { useQueryStore } from "@/stores/queryStore";
import { useSettingsStore } from "@/stores/settingsStore";
import { useTabScroll } from "@/composables/useTabScroll";
import { useTabDrag } from "@/composables/useTabDrag";
import { openDesktopWindow } from "@/lib/desktopWindows";
import {
connectionColor,
shouldShowTabOverflowControls,
@@ -45,9 +46,14 @@ const emit = defineEmits<{
const { t } = useI18n();
const queryStore = useQueryStore();
const settingsStore = useSettingsStore();
const tabDrag = useTabDrag((draggedId, targetId, position) => {
queryStore.reorderTab(draggedId, targetId, position);
});
const tabDrag = useTabDrag(
(draggedId, targetId, position) => {
queryStore.reorderTab(draggedId, targetId, position);
},
(draggedId) => {
void detachTabToNewWindow(draggedId);
},
);
const editingTabId = ref<string | null>(null);
const editingTitle = ref("");
const compactTabTitle = computed({
@@ -87,6 +93,13 @@ function cancelRenameTab() {
editingTabId.value = null;
}
async function detachTabToNewWindow(tabId: string) {
const tab = queryStore.tabs.find((item) => item.id === tabId);
if (!tab) return;
const opened = await openDesktopWindow({ detachedTab: tab });
if (opened) queryStore.closeTab(tab.id);
}
function getTabMenuItems(tab: QueryTab): ContextMenuItem[] {
return [
{
@@ -271,6 +284,7 @@ const tabOverflowControlClass = computed(() =>
<template>
<div
v-if="queryStore.tabs.length > 0 || showDriverStore"
data-app-tab-bar
class="relative flex border-b shrink-0"
:class="
settingsStore.editorSettings.appLayout === 'classic'
@@ -415,7 +429,7 @@ const tabOverflowControlClass = computed(() =>
<X class="h-3 w-3" />
</button>
</div>
<div :class="tabTailDragRegionClass" data-tauri-drag-region />
<div :class="tabTailDragRegionClass" data-window-drag-region />
</div>
<div v-if="showTabOverflowControls" class="relative z-30 flex shrink-0 items-center">
<button
@@ -91,7 +91,7 @@ function onToolbarDblClick(e: MouseEvent) {
<div
class="h-10 flex items-center gap-1 px-2 border-b bg-muted/30 shrink-0"
:class="{ 'pl-17.5': shouldReserveMacTrafficLightInset(isMac, isFullscreen, isDesktop) }"
data-tauri-drag-region
data-window-drag-region
@dblclick="onToolbarDblClick"
>
<Button variant="ghost" size="sm" class="h-8 px-2 text-xs gap-1" @click="emit('new-connection')">
@@ -172,7 +172,7 @@ function onToolbarDblClick(e: MouseEvent) {
</span>
</Button>
<div class="flex-1" data-tauri-drag-region />
<div class="flex-1" data-window-drag-region />
<Tooltip>
<TooltipTrigger as-child>
@@ -505,6 +505,12 @@ defineExpose({ focusSearch, refreshData, handleModRTarget });
variant="ghost"
size="sm"
class="h-6 shrink-0 gap-1 px-2 text-xs text-muted-foreground hover:text-foreground"
:class="
activeTab.dataStale
? 'bg-amber-500/15 text-amber-700 hover:bg-amber-500/25 hover:text-amber-800 dark:text-amber-300 dark:hover:text-amber-200'
: ''
"
:title="activeTab.dataStale ? t('grid.dataStaleRefresh') : undefined"
:disabled="activeTab.isExecuting"
@click="refreshData"
>
@@ -554,6 +560,7 @@ defineExpose({ focusSearch, refreshData, handleModRTarget });
:initial-order-by-input="activeTab.orderByInput"
:sql="activeTab.lastExecutedSql || activeTab.sql"
:loading="activeTab.isExecuting"
:data-stale="activeTab.dataStale"
:editable="!!activeTab.queryAnalysis"
:source-columns="activeTab.querySourceColumns"
context="results"
@@ -804,12 +811,14 @@ defineExpose({ focusSearch, refreshData, handleModRTarget });
:initial-order-by-input="activeTab.orderByInput"
:sql="activeTab.sql"
:loading="activeTab.isExecuting"
:data-stale="activeTab.dataStale"
:editable="isTableDataEditable(activeEffectiveDatabaseType, activeTableMeta?.primaryKeys ?? [])"
context="table-data"
:initial-where-input="activeTab.whereInput"
:database-type="activeEffectiveDatabaseType"
:connection-id="activeTab.connectionId"
:database="activeTab.database"
:schema="activeTab.schema"
:table-meta="activeDataTabTableMeta"
:page-offset="activeTab.resultPageOffset"
:page-limit="activeTab.resultPageLimit"