feat(tabs): add close tabs to the left action

This commit is contained in:
guoziyangnb authored and GitHub committed 2026-08-31 23:46:03 +08:00
1 parent 40a806a24a
commit 662037295c
11 files changed
+67

No files matched your search

@@ -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),
@@ -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)");
+1
View File
@@ -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",
+1
View File
@@ -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",
+1
View File
@@ -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",
+1
View File
@@ -3219,6 +3219,7 @@ export default withEnglishFallback({
createFunction: "新規関数",
createTrigger: "新規トリガー",
changeOpenMode: "開き方を変更",
closeLeftTabs: "左側のタブを閉じる",
closeRightTabs: "右側のタブを閉じる",
viewObject: "View",
eventEditorTitle: "MySQL Event",
+2
View File
@@ -2790,6 +2790,8 @@ export default withEnglishFallback({
fullTabTitle: "전체 탭 제목 표시",
closeTab: "탭 닫기",
closeFixedTab: "고정 탭 닫기",
closeLeftTabs: "왼쪽 탭 닫기",
closeRightTabs: "오른쪽 탭 닫기",
closeOtherTabs: "다른 탭 닫기",
closeOtherRegularTabs: "다른 일반 탭 닫기",
closeOtherFixedTabs: "다른 고정 탭 닫기",
+1
View File
@@ -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",
+1
View File
@@ -2894,6 +2894,7 @@ export default withEnglishFallback({
fullTabTitle: "完整标签标题",
closeTab: "关闭标签页",
closeFixedTab: "关闭固定标签页",
closeLeftTabs: "关闭左侧标签页",
closeRightTabs: "关闭右侧标签页",
closeOtherTabs: "关闭其他标签页",
closeOtherRegularTabs: "关闭其他普通标签页",
+1
View File
@@ -3192,6 +3192,7 @@ export default withEnglishFallback({
createFunction: "新增函數",
createTrigger: "新增觸發器",
changeOpenMode: "修改開啟方式",
closeLeftTabs: "關閉左側標籤頁",
closeRightTabs: "關閉右側標籤頁",
viewObject: "View",
eventEditorTitle: "MySQL Event",
+17
View File
@@ -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,