feat(tabs): add Ctrl+Tab MRU switcher
This commit is contained in:
1 parent
81872165da
commit
7ce992b8f8
18 files changed
+615
-1
No files matched your search
@@ -87,9 +87,13 @@ import {
|
||||
isZoomInShortcut,
|
||||
isZoomOutShortcut,
|
||||
switchToTabIndexFromShortcut,
|
||||
tabSwitcherDirectionFromShortcut,
|
||||
} from "@/lib/editor/keyboardShortcuts";
|
||||
import { isPreviewTab } from "@/lib/tabs/tabPresentation";
|
||||
import { createTabNavigationHistory, moveInTabNavigationHistory, recordTabVisit } from "@/lib/tabs/tabNavigationHistory";
|
||||
import { initialTabSwitcherSelection, moveTabSwitcherSelection, tabSwitcherOrder } from "@/lib/tabs/tabSwitcher";
|
||||
import { createTabSwitcherKeyboardController } from "@/lib/tabs/tabSwitcherKeyboard";
|
||||
import { formatShortcutDisplay } from "@/lib/editor/shortcutDisplay";
|
||||
import { supportsSqlFileExecution } from "@/lib/database/databaseCapabilities";
|
||||
import { classifyAiSqlExecution } from "@/lib/ai/aiSqlExecutionPolicy";
|
||||
import { buildAppendedEditorSql } from "@/lib/ai/aiSqlAppend";
|
||||
@@ -134,6 +138,7 @@ const UpdateDialog = defineAsyncComponent(() => import("@/components/layout/Upda
|
||||
const CloseActionPromptDialog = defineAsyncComponent(() => import("@/components/layout/CloseActionPromptDialog.vue"));
|
||||
const LoginPage = defineAsyncComponent(() => import("@/components/auth/LoginPage.vue"));
|
||||
const QuickOpenDialog = defineAsyncComponent(() => import("@/components/quick-open/QuickOpenDialog.vue"));
|
||||
const TabSwitcherDialog = defineAsyncComponent(() => import("@/components/tabs/TabSwitcherDialog.vue"));
|
||||
const QueryEditorDdlViewDialog = defineAsyncComponent(() => import("@/components/objects/DdlViewDialog.vue"));
|
||||
const QueryEditorObjectSourceDialog = defineAsyncComponent(() => import("@/components/objects/ObjectSourceDialog.vue"));
|
||||
|
||||
@@ -226,6 +231,8 @@ const settingsReturnSurface = ref<"query" | "driverStore" | "welcome">("welcome"
|
||||
const showDriverStore = computed(() => driverStoreTabOpen.value && driverStoreActive.value);
|
||||
const showSettingsPage = computed(() => settingsPageTabOpen.value && settingsStore.settingsPageActive);
|
||||
const showQuickOpen = ref(false);
|
||||
const showTabSwitcher = ref(false);
|
||||
const tabSwitcherIndex = ref(0);
|
||||
const agentDriverUpdateCount = ref(0);
|
||||
const showHistory = ref(false);
|
||||
const showAiPanel = ref(safeLocalStorageGet("dbx-ai-panel-open") === "true");
|
||||
@@ -2318,6 +2325,66 @@ function activateTabFromHistory(direction: -1 | 1): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tabSwitcherTabs = computed(() => tabSwitcherOrder(queryStore.tabs, queryStore.recentTabIds));
|
||||
const tabSwitcherShortcutHint = computed(() => formatShortcutDisplay(settingsStore.editorSettings.shortcuts.tabSwitcher));
|
||||
|
||||
function handleTabSwitcherShortcut(direction: -1 | 1, e: KeyboardEvent): boolean {
|
||||
if (!queryStore.tabs.length) return false;
|
||||
if (!showTabSwitcher.value) {
|
||||
tabSwitcherKeyboard.rememberOpeningEvent(e);
|
||||
tabSwitcherIndex.value = initialTabSwitcherSelection(tabSwitcherTabs.value.length);
|
||||
showTabSwitcher.value = true;
|
||||
} else {
|
||||
tabSwitcherIndex.value = moveTabSwitcherSelection(tabSwitcherIndex.value, direction, tabSwitcherTabs.value.length);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function closeTabSwitcher(commit: boolean) {
|
||||
tabSwitcherKeyboard.reset();
|
||||
if (!showTabSwitcher.value) return;
|
||||
showTabSwitcher.value = false;
|
||||
if (!commit) return;
|
||||
const tab = tabSwitcherTabs.value[tabSwitcherIndex.value];
|
||||
if (tab) activateQueryTab(tab.id);
|
||||
}
|
||||
|
||||
function handleKeyup(e: KeyboardEvent) {
|
||||
tabSwitcherKeyboard.handleKeyup(e);
|
||||
}
|
||||
|
||||
function handleTabSwitcherKeydownCapture(e: KeyboardEvent) {
|
||||
tabSwitcherKeyboard.handleKeydownCapture(e);
|
||||
}
|
||||
|
||||
function handleTabSwitcherWindowBlur() {
|
||||
tabSwitcherKeyboard.handleWindowBlur();
|
||||
}
|
||||
|
||||
function handleTabSwitcherVisibilityChange() {
|
||||
tabSwitcherKeyboard.handleVisibilityChange(document.visibilityState);
|
||||
}
|
||||
|
||||
function handleTabSwitcherSelect(tabId: string) {
|
||||
tabSwitcherKeyboard.reset();
|
||||
showTabSwitcher.value = false;
|
||||
activateQueryTab(tabId);
|
||||
}
|
||||
|
||||
function handleTabSwitcherOpenChange(open: boolean) {
|
||||
if (!open) closeTabSwitcher(false);
|
||||
}
|
||||
|
||||
const tabSwitcherKeyboard = createTabSwitcherKeyboardController({
|
||||
isOpen: () => showTabSwitcher.value,
|
||||
shortcut: () => settingsStore.editorSettings.shortcuts.tabSwitcher,
|
||||
move: (direction) => {
|
||||
tabSwitcherIndex.value = moveTabSwitcherSelection(tabSwitcherIndex.value, direction, tabSwitcherTabs.value.length);
|
||||
},
|
||||
commit: () => closeTabSwitcher(true),
|
||||
cancel: () => closeTabSwitcher(false),
|
||||
});
|
||||
|
||||
function handleNativeSelectAll(e: KeyboardEvent) {
|
||||
if (shouldBlockAppNativeSelectAll(e)) e.preventDefault();
|
||||
}
|
||||
@@ -2326,6 +2393,15 @@ async function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.defaultPrevented) return;
|
||||
|
||||
const shortcuts = settingsStore.editorSettings.shortcuts;
|
||||
const tabSwitcherDirection = tabSwitcherDirectionFromShortcut(e, shortcuts);
|
||||
if (showTabSwitcher.value) return;
|
||||
if (tabSwitcherDirection) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleTabSwitcherShortcut(tabSwitcherDirection, e);
|
||||
return;
|
||||
}
|
||||
|
||||
const switchTabIndex = switchToTabIndexFromShortcut(e, shortcuts);
|
||||
|
||||
if (isOpenSettingsShortcut(e, shortcuts)) {
|
||||
@@ -2599,7 +2675,11 @@ onMounted(async () => {
|
||||
applyTheme();
|
||||
void applyUiScale(settingsStore.editorSettings.uiScale);
|
||||
window.addEventListener("keydown", handleNativeSelectAll, true);
|
||||
window.addEventListener("keydown", handleTabSwitcherKeydownCapture, true);
|
||||
window.addEventListener("keydown", handleKeydown);
|
||||
window.addEventListener("keyup", handleKeyup, true);
|
||||
window.addEventListener("blur", handleTabSwitcherWindowBlur);
|
||||
document.addEventListener("visibilitychange", handleTabSwitcherVisibilityChange);
|
||||
window.addEventListener("dbx-open-driver-store", openDriverStoreFromEvent);
|
||||
window.addEventListener("dbx-mcp-status-changed", handleMcpStatusChanged);
|
||||
if (isDesktop) {
|
||||
@@ -2667,7 +2747,12 @@ onUnmounted(() => {
|
||||
clearInterval(updateCheckTimer);
|
||||
}
|
||||
window.removeEventListener("keydown", handleNativeSelectAll, true);
|
||||
window.removeEventListener("keydown", handleTabSwitcherKeydownCapture, true);
|
||||
window.removeEventListener("keydown", handleKeydown);
|
||||
window.removeEventListener("keyup", handleKeyup, true);
|
||||
window.removeEventListener("blur", handleTabSwitcherWindowBlur);
|
||||
document.removeEventListener("visibilitychange", handleTabSwitcherVisibilityChange);
|
||||
tabSwitcherKeyboard.reset();
|
||||
window.removeEventListener("dbx-open-driver-store", openDriverStoreFromEvent);
|
||||
window.removeEventListener("dbx-mcp-status-changed", handleMcpStatusChanged);
|
||||
document.removeEventListener("contextmenu", handleContextMenu);
|
||||
@@ -3020,6 +3105,7 @@ onUnmounted(() => {
|
||||
<ExternalSqlFileChangeDialog :prompt="externalSqlFilePrompt" @decide="externalSqlFileChanges.resolvePrompt" />
|
||||
<CloseActionPromptDialog v-if="isDesktop && showCloseActionPrompt" :open="showCloseActionPrompt" @update:open="handleCloseActionPromptOpenChange" @quit="chooseQuit" @minimize="chooseMinimize" />
|
||||
<QuickOpenDialog :open="showQuickOpen" @update:open="showQuickOpen = $event" @select="handleQuickOpenSelect" />
|
||||
<TabSwitcherDialog :open="showTabSwitcher" :tabs="tabSwitcherTabs" :selected-index="tabSwitcherIndex" :shortcut-hint="tabSwitcherShortcutHint" @update:open="handleTabSwitcherOpenChange" @update:selected-index="tabSwitcherIndex = $event" @select="handleTabSwitcherSelect" />
|
||||
</div>
|
||||
<Teleport to="body">
|
||||
<FileText
|
||||
|
||||
Reference in new issue
Block a user