From 662037295c6c94201481f64e373ae87d7413962a Mon Sep 17 00:00:00 2001 From: guoziyangnb <136451743+guoziyangnb@users.noreply.github.com> Date: Mon, 31 Aug 2026 23:46:03 +0800 Subject: [PATCH] feat(tabs): add close tabs to the left action --- .../src/components/layout/AppTabBar.vue | 20 ++++++++++++++++++ .../layout/__tests__/AppTabBar.spec.ts | 21 +++++++++++++++++++ apps/desktop/src/i18n/locales/en.ts | 1 + apps/desktop/src/i18n/locales/es.ts | 1 + apps/desktop/src/i18n/locales/it.ts | 1 + apps/desktop/src/i18n/locales/ja.ts | 1 + apps/desktop/src/i18n/locales/ko.ts | 2 ++ apps/desktop/src/i18n/locales/pt-BR.ts | 1 + apps/desktop/src/i18n/locales/zh-CN.ts | 1 + apps/desktop/src/i18n/locales/zh-TW.ts | 1 + apps/desktop/src/stores/queryStore.ts | 17 +++++++++++++++ 11 files changed, 67 insertions(+) diff --git a/apps/desktop/src/components/layout/AppTabBar.vue b/apps/desktop/src/components/layout/AppTabBar.vue index 8797fa9e3d..ba8c73858a 100644 --- a/apps/desktop/src/components/layout/AppTabBar.vue +++ b/apps/desktop/src/components/layout/AppTabBar.vue @@ -209,6 +209,20 @@ function closeTabsToRightFromTab(tab: QueryTab) { }); } +function tabsToLeftInGroup(tab: QueryTab) { + const groupedTabs = tab.pinned ? fixedTabs.value : regularTabs.value; + const targetIndex = groupedTabs.findIndex((item) => item.id === tab.id); + return targetIndex < 0 ? [] : groupedTabs.slice(0, targetIndex); +} + +function hasTabsToLeft(tab: QueryTab) { + return tabsToLeftInGroup(tab).length > 0; +} + +function closeTabsToLeftFromTab(tab: QueryTab) { + queryStore.closeLeftTabs(tab.id); +} + function hasSpecialRegularSurfaceToRight(surface: SpecialRegularSurface) { return surface === "settings" && !!props.driverStoreOpen; } @@ -345,6 +359,12 @@ function getTabMenuItems(tab: QueryTab): ContextMenuItem[] { icon: X, shortcut: settingsStore.editorSettings.shortcuts.closeOtherTabs, }, + { + label: t("contextMenu.closeLeftTabs"), + action: () => closeTabsToLeftFromTab(tab), + disabled: !hasTabsToLeft(tab), + icon: X, + }, { label: t("contextMenu.closeRightTabs"), action: () => closeTabsToRightFromTab(tab), diff --git a/apps/desktop/src/components/layout/__tests__/AppTabBar.spec.ts b/apps/desktop/src/components/layout/__tests__/AppTabBar.spec.ts index 2387c74e77..2d50e95b62 100644 --- a/apps/desktop/src/components/layout/__tests__/AppTabBar.spec.ts +++ b/apps/desktop/src/components/layout/__tests__/AppTabBar.spec.ts @@ -108,6 +108,27 @@ describe("AppTabBar right-side close action", () => { }); }); +describe("AppTabBar left-side close action", () => { + it("places the action after close-other, before close-right, and disables it when the target has no tabs to its left", () => { + expect(tabBarSource).toContain('label: t("contextMenu.closeLeftTabs")'); + expect(tabBarSource).toContain("action: () => closeTabsToLeftFromTab(tab)"); + expect(tabBarSource).toContain("disabled: !hasTabsToLeft(tab)"); + + const closeOtherPositions = [...tabBarSource.matchAll(/label: closeOtherLabel,/g)].map((match) => match.index); + const closeLeftPositions = [...tabBarSource.matchAll(/label: t\("contextMenu\.closeLeftTabs"\),/g)].map((match) => match.index); + const closeRightPositions = [...tabBarSource.matchAll(/label: t\("contextMenu\.closeRightTabs"\),/g)].map((match) => match.index); + expect(closeOtherPositions).toHaveLength(2); + // The special-surface menu has no left-side action: settings and the + // driver store sit at the rightmost end of the tab bar, so nothing can be + // to the left of them. Only the regular/pinned tab menu gets the item. + expect(closeLeftPositions).toHaveLength(1); + expect(closeRightPositions).toHaveLength(2); + const closeLeftPosition = closeLeftPositions[0]; + expect(closeLeftPosition).toBeGreaterThan(closeOtherPositions[1]); + expect(closeLeftPosition).toBeLessThan(closeRightPositions[1]); + }); +}); + describe("AppTabBar special page selection", () => { it("shows the active settings or driver-manager tab with the same ring used by regular tabs", () => { expect(tabBarSource).toContain("function specialTabActiveStyle(active: boolean | undefined)"); diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index a8211c5445..b47bbafb13 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -2971,6 +2971,7 @@ export default { fullTabTitle: "Show Full Tab Titles", closeTab: "Close Tab", closeFixedTab: "Close Fixed Tab", + closeLeftTabs: "Close Tabs to the Left", closeRightTabs: "Close Tabs to the Right", closeOtherTabs: "Close Other Tabs", closeOtherRegularTabs: "Close Other Regular Tabs", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index 2aa54a03f3..3619eef320 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -3195,6 +3195,7 @@ export default withEnglishFallback({ createFunction: "Nueva función", createTrigger: "Nuevo disparador", changeOpenMode: "Modificar modo de apertura", + closeLeftTabs: "Cerrar pestañas a la izquierda", closeRightTabs: "Cerrar pestañas a la derecha", viewObject: "View", eventEditorTitle: "MySQL Event", diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index cfd3f1dea0..919aa900b1 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -3193,6 +3193,7 @@ export default withEnglishFallback({ createFunction: "Nuova funzione", createTrigger: "Nuovo trigger", changeOpenMode: "Modifica modalità apertura", + closeLeftTabs: "Chiudi le schede a sinistra", closeRightTabs: "Chiudi le schede a destra", viewObject: "View", eventEditorTitle: "MySQL Event", diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index 6fc0b4bd8f..872de50486 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -3219,6 +3219,7 @@ export default withEnglishFallback({ createFunction: "新規関数", createTrigger: "新規トリガー", changeOpenMode: "開き方を変更", + closeLeftTabs: "左側のタブを閉じる", closeRightTabs: "右側のタブを閉じる", viewObject: "View", eventEditorTitle: "MySQL Event", diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts index 38cceeb0dc..44f3fd1c3b 100644 --- a/apps/desktop/src/i18n/locales/ko.ts +++ b/apps/desktop/src/i18n/locales/ko.ts @@ -2790,6 +2790,8 @@ export default withEnglishFallback({ fullTabTitle: "전체 탭 제목 표시", closeTab: "탭 닫기", closeFixedTab: "고정 탭 닫기", + closeLeftTabs: "왼쪽 탭 닫기", + closeRightTabs: "오른쪽 탭 닫기", closeOtherTabs: "다른 탭 닫기", closeOtherRegularTabs: "다른 일반 탭 닫기", closeOtherFixedTabs: "다른 고정 탭 닫기", diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index 0e9e1bc744..7c44ac3841 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -3191,6 +3191,7 @@ export default withEnglishFallback({ createFunction: "Nova função", createTrigger: "Novo gatilho", changeOpenMode: "Alterar modo de abertura", + closeLeftTabs: "Fechar abas à esquerda", closeRightTabs: "Fechar abas à direita", viewObject: "View", eventEditorTitle: "MySQL Event", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index cec2d62ff1..57f0907d6b 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -2894,6 +2894,7 @@ export default withEnglishFallback({ fullTabTitle: "完整标签标题", closeTab: "关闭标签页", closeFixedTab: "关闭固定标签页", + closeLeftTabs: "关闭左侧标签页", closeRightTabs: "关闭右侧标签页", closeOtherTabs: "关闭其他标签页", closeOtherRegularTabs: "关闭其他普通标签页", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index d6b82a0b0f..a721856806 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -3192,6 +3192,7 @@ export default withEnglishFallback({ createFunction: "新增函數", createTrigger: "新增觸發器", changeOpenMode: "修改開啟方式", + closeLeftTabs: "關閉左側標籤頁", closeRightTabs: "關閉右側標籤頁", viewObject: "View", eventEditorTitle: "MySQL Event", diff --git a/apps/desktop/src/stores/queryStore.ts b/apps/desktop/src/stores/queryStore.ts index ee01caefc7..7434a8b017 100644 --- a/apps/desktop/src/stores/queryStore.ts +++ b/apps/desktop/src/stores/queryStore.ts @@ -2858,6 +2858,22 @@ export const useQueryStore = defineStore("query", () => { ); } + function closeLeftTabs(id: string, onComplete?: () => void) { + const target = tabs.value.find((tab) => tab.id === id); + if (!target) return; + + const groupedTabs = tabs.value.filter((tab) => Boolean(tab.pinned) === Boolean(target.pinned)); + const targetIndex = groupedTabs.findIndex((tab) => tab.id === id); + const ids = groupedTabs.slice(0, targetIndex).map((tab) => tab.id); + if (ids.length === 0) { + onComplete?.(); + return; + } + + const finalActiveTabId = activeTabId.value && !ids.includes(activeTabId.value) ? activeTabId.value : id; + beginBatchClose(ids, finalActiveTabId, onComplete); + } + function closeRightTabs(id: string, onComplete?: () => void) { const target = tabs.value.find((tab) => tab.id === id); if (!target) return; @@ -6813,6 +6829,7 @@ export const useQueryStore = defineStore("query", () => { discardTabChanges, requestAppCloseConfirmation, closeOtherTabs, + closeLeftTabs, closeRightTabs, closeOtherRegularTabs, closeRegularTabs,