feat: add tab close menu actions (#24)

Co-authored-by: SuLe <sule@SuLedeMacBook-Air.local>
This commit is contained in:
SuLea-IT
2026-04-30 16:35:01 +08:00
committed by GitHub
co-authored by SuLe
parent 9ee31aa188
commit aea6bf3239
6 changed files with 133 additions and 22 deletions
+57 -22
View File
@@ -5,6 +5,13 @@ import { DatabaseZap, FilePlus2, Play, Loader2, X, Globe, Moon, Sun, Upload, Dow
import { Splitpanes, Pane } from "splitpanes";
import "splitpanes/dist/splitpanes.css";
import { Button } from "@/components/ui/button";
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuTrigger,
} from "@/components/ui/context-menu";
import {
Select,
SelectContent,
@@ -701,34 +708,62 @@ async function setupFileDrop() {
<div class="h-full flex flex-col min-w-0">
<!-- Tabs Bar -->
<div v-if="queryStore.tabs.length > 0" class="h-8 flex items-center border-b bg-muted/20 overflow-x-auto shrink-0">
<div
<ContextMenu
v-for="tab in queryStore.tabs"
:key="tab.id"
class="group flex min-w-0 items-center gap-1 px-3 h-full text-xs cursor-pointer border-r hover:bg-accent transition-colors whitespace-nowrap"
:class="{ 'bg-background font-medium': tab.id === queryStore.activeTabId }"
@click="queryStore.activeTabId = tab.id"
>
<span v-if="connectionColor(tab.connectionId)" class="h-4 w-1 rounded-full shrink-0" :style="{ backgroundColor: connectionColor(tab.connectionId) }" />
<span class="min-w-0 truncate">{{ tabDisplayTitle(tab) }}</span>
<Tooltip>
<TooltipTrigger as-child>
<ContextMenuTrigger as-child>
<div
class="group flex min-w-0 items-center gap-1 px-3 h-full text-xs cursor-pointer border-r hover:bg-accent transition-colors whitespace-nowrap"
:class="{ 'bg-background font-medium': tab.id === queryStore.activeTabId }"
@click="queryStore.activeTabId = tab.id"
>
<span v-if="connectionColor(tab.connectionId)" class="h-4 w-1 rounded-full shrink-0" :style="{ backgroundColor: connectionColor(tab.connectionId) }" />
<span class="min-w-0 truncate">{{ tabDisplayTitle(tab) }}</span>
<Tooltip>
<TooltipTrigger as-child>
<button
class="ml-1 rounded p-0.5 text-muted-foreground hover:bg-muted-foreground/20 hover:text-foreground focus:opacity-100"
:class="tab.pinned ? 'opacity-100 text-primary' : 'opacity-0 group-hover:opacity-100'"
@click.stop="queryStore.togglePinnedTab(tab.id)"
>
<Pin class="h-3 w-3" :class="{ 'fill-current': tab.pinned }" />
</button>
</TooltipTrigger>
<TooltipContent>{{ tab.pinned ? t('contextMenu.unpin') : t('contextMenu.pin') }}</TooltipContent>
</Tooltip>
<button
class="ml-1 rounded p-0.5 text-muted-foreground hover:bg-muted-foreground/20 hover:text-foreground focus:opacity-100"
:class="tab.pinned ? 'opacity-100 text-primary' : 'opacity-0 group-hover:opacity-100'"
@click.stop="queryStore.togglePinnedTab(tab.id)"
class="rounded hover:bg-muted-foreground/20 p-0.5"
@click.stop="queryStore.closeTab(tab.id)"
>
<Pin class="h-3 w-3" :class="{ 'fill-current': tab.pinned }" />
<X class="h-3 w-3" />
</button>
</TooltipTrigger>
<TooltipContent>{{ tab.pinned ? t('contextMenu.unpin') : t('contextMenu.pin') }}</TooltipContent>
</Tooltip>
<button
class="rounded hover:bg-muted-foreground/20 p-0.5"
@click.stop="queryStore.closeTab(tab.id)"
>
<X class="h-3 w-3" />
</button>
</div>
</div>
</ContextMenuTrigger>
<ContextMenuContent class="w-44">
<ContextMenuItem @click="queryStore.togglePinnedTab(tab.id)">
<Pin class="w-3.5 h-3.5 mr-2" :class="{ 'fill-current': tab.pinned }" />
{{ tab.pinned ? t('contextMenu.unpin') : t('contextMenu.pin') }}
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem @click="queryStore.closeTab(tab.id)">
<X class="w-3.5 h-3.5 mr-2" />
{{ t('contextMenu.closeTab') }}
</ContextMenuItem>
<ContextMenuItem
:disabled="queryStore.tabs.length <= 1"
@click="queryStore.closeOtherTabs(tab.id)"
>
<X class="w-3.5 h-3.5 mr-2" />
{{ t('contextMenu.closeOtherTabs') }}
</ContextMenuItem>
<ContextMenuItem variant="destructive" @click="queryStore.closeAllTabs">
<X class="w-3.5 h-3.5 mr-2" />
{{ t('contextMenu.closeAllTabs') }}
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
</div>
<!-- Editor Panel -->
+3
View File
@@ -170,6 +170,9 @@ export default {
refreshChildren: "Refresh",
pin: "Pin",
unpin: "Unpin",
closeTab: "Close Tab",
closeOtherTabs: "Close Other Tabs",
closeAllTabs: "Close All Tabs",
copyName: "Copy Name",
},
tree: {
+3
View File
@@ -168,6 +168,9 @@ export default {
refreshChildren: "刷新",
pin: "置顶",
unpin: "取消置顶",
closeTab: "关闭标签页",
closeOtherTabs: "关闭其他标签页",
closeAllTabs: "关闭全部标签页",
copyName: "复制名称",
},
tree: {
+26
View File
@@ -0,0 +1,26 @@
export interface TabLike {
id: string;
}
export interface TabCloseState<T extends TabLike> {
tabs: T[];
activeTabId: string | null;
}
export function closeOtherTabsState<T extends TabLike>(
tabs: readonly T[],
activeTabId: string | null,
targetTabId: string,
): TabCloseState<T> {
const target = tabs.find((tab) => tab.id === targetTabId);
if (!target) return { tabs: [...tabs], activeTabId };
return { tabs: [target], activeTabId: target.id };
}
export function closeAllTabsState<T extends TabLike>(
_tabs: readonly T[],
_activeTabId: string | null,
): TabCloseState<T> {
return { tabs: [], activeTabId: null };
}
+15
View File
@@ -2,6 +2,7 @@ import { defineStore } from "pinia";
import { ref } from "vue";
import type { QueryTab } from "@/types/database";
import { orderPinnedFirst } from "@/lib/pinnedItems";
import { closeAllTabsState, closeOtherTabsState } from "@/lib/tabCloseActions";
import * as api from "@/lib/tauri";
export const useQueryStore = defineStore("query", () => {
@@ -46,6 +47,18 @@ export const useQueryStore = defineStore("query", () => {
}
}
function closeOtherTabs(id: string) {
const next = closeOtherTabsState(tabs.value, activeTabId.value, id);
tabs.value = next.tabs;
activeTabId.value = next.activeTabId;
}
function closeAllTabs() {
const next = closeAllTabsState(tabs.value, activeTabId.value);
tabs.value = next.tabs;
activeTabId.value = next.activeTabId;
}
function updateSql(id: string, sql: string) {
const tab = tabs.value.find((t) => t.id === id);
if (tab) tab.sql = sql;
@@ -123,6 +136,8 @@ export const useQueryStore = defineStore("query", () => {
activeTabId,
createTab,
closeTab,
closeOtherTabs,
closeAllTabs,
updateSql,
togglePinnedTab,
updateDatabase,