feat: support multiple desktop windows
This commit is contained in:
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"
|
||||
|
||||
Reference in new issue
Block a user