diff --git a/apps/desktop/src/App.vue b/apps/desktop/src/App.vue index a50c5377e4..6a508a0f36 100644 --- a/apps/desktop/src/App.vue +++ b/apps/desktop/src/App.vue @@ -85,6 +85,7 @@ import { isSendSelectionToAiShortcut, isSwitchToNextTabShortcut, isSwitchToPreviousTabShortcut, + isToggleResultsPaneShortcut, isToggleSidebarShortcut, isZoomInShortcut, isZoomOutShortcut, @@ -2507,6 +2508,11 @@ async function handleKeydown(e: KeyboardEvent) { contentAreaRef.value?.refreshData(); return; } + if (isToggleResultsPaneShortcut(e, shortcuts) && contentAreaRef.value?.toggleResultsPane()) { + e.preventDefault(); + e.stopPropagation(); + return; + } if (isNewQueryShortcut(e, shortcuts)) { e.preventDefault(); e.stopPropagation(); diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue index 2dd4715f59..321fa6206a 100644 --- a/apps/desktop/src/components/layout/ContentArea.vue +++ b/apps/desktop/src/components/layout/ContentArea.vue @@ -651,6 +651,12 @@ const resultsPaneSize = ref(Number(safeLocalStorageGet("dbx-results-pane-size")) const editorPaneSize = computed(() => (resultsPaneOpen.value ? 100 - resultsPaneSize.value : 100)); const queryRunningElapsed = ref(0); +function toggleResultsPane(): boolean { + if (props.activeTab.mode !== "query" || !hasQueryOutput.value) return false; + resultsPaneOpen.value = !resultsPaneOpen.value; + return true; +} + function onResultsResized(payload: { panes: { size: number }[] }) { const resultsPane = payload.panes[1]; if (resultsPane?.size != null && resultsPane.size >= 20 && resultsPane.size <= 85) { @@ -1153,6 +1159,7 @@ async function executeRedisCommand(command: string): Promise { defineExpose({ focusSearch, refreshData, + toggleResultsPane, refreshQueryEditorCompletionCache, handleModRTarget, requestQueryEditorExecute, diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index 3578a73642..25cd516f94 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -6539,6 +6539,7 @@ export default { shortcutZoomOutUi: "Zoom out UI", shortcutResetUiZoom: "Reset UI zoom", shortcutRefreshData: "Refresh data", + shortcutToggleResultsPane: "Toggle results pane", shortcutToggleTranspose: "Toggle transpose view", shortcutCancelSearch: "Cancel search", shortcutToggleSidebar: "Toggle sidebar", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index 09a574bb55..f0c08147f6 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -6211,6 +6211,7 @@ export default withEnglishFallback({ shortcutZoomOutUi: "Reducir interfaz", shortcutResetUiZoom: "Restablecer zoom de interfaz", shortcutRefreshData: "Actualizar datos", + shortcutToggleResultsPane: "Alternar panel de resultados", shortcutToggleTranspose: "Alternar vista transpuesta", shortcutCancelSearch: "Cancelar búsqueda", shortcutCopySidebarSelection: "Copiar selección de la barra lateral", diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index fb5f3aa6d0..abad2da69c 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -6210,6 +6210,7 @@ export default withEnglishFallback({ shortcutZoomOutUi: "Rimpicciolisci UI", shortcutResetUiZoom: "Reimposta zoom UI", shortcutRefreshData: "Aggiorna dati", + shortcutToggleResultsPane: "Attiva/disattiva pannello risultati", shortcutToggleTranspose: "Attiva/disattiva vista trasposta", shortcutCancelSearch: "Annulla ricerca", shortcutToggleSidebar: "Attiva/disattiva barra laterale", diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index 3a57435849..42240f2eb9 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -6224,6 +6224,7 @@ export default withEnglishFallback({ shortcutZoomOutUi: "UIを縮小", shortcutResetUiZoom: "UIズームをリセット", shortcutRefreshData: "データを更新", + shortcutToggleResultsPane: "結果ペインを切替", shortcutToggleTranspose: "転置ビューを切替", shortcutCancelSearch: "検索をキャンセル", shortcutToggleSidebar: "サイドバーを切替", diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts index 95e3d8627d..2ab29421d2 100644 --- a/apps/desktop/src/i18n/locales/ko.ts +++ b/apps/desktop/src/i18n/locales/ko.ts @@ -5921,6 +5921,7 @@ export default withEnglishFallback({ shortcutZoomOutUi: "UI 축소", shortcutResetUiZoom: "UI 확대/축소 재설정", shortcutRefreshData: "데이터 새로고침", + shortcutToggleResultsPane: "결과 창 전환", shortcutToggleTranspose: "전치 보기 전환", shortcutCancelSearch: "검색 취소", shortcutToggleSidebar: "사이드바 전환", diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index 2b8abc7599..33eda06afb 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -6213,6 +6213,7 @@ export default withEnglishFallback({ shortcutZoomOutUi: "Diminuir zoom da UI", shortcutResetUiZoom: "Redefinir zoom da UI", shortcutRefreshData: "Atualizar dados", + shortcutToggleResultsPane: "Alternar painel de resultados", shortcutToggleTranspose: "Alternar visualização transposta", shortcutCancelSearch: "Cancelar pesquisa", shortcutCopySidebarSelection: "Copiar seleção da barra lateral", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index 5632c6f137..6fb2005606 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -6526,6 +6526,7 @@ export default withEnglishFallback({ shortcutZoomOutUi: "缩小全局界面", shortcutResetUiZoom: "重置全局界面缩放", shortcutRefreshData: "刷新数据", + shortcutToggleResultsPane: "切换结果面板", shortcutToggleTranspose: "切换转置视图", shortcutCancelSearch: "取消搜索", shortcutCopySidebarSelection: "复制侧边栏选中项", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index 2dbcab1e82..b4d6b8e5a9 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -5518,6 +5518,7 @@ export default withEnglishFallback({ shortcutZoomOutUi: "縮小全域介面", shortcutResetUiZoom: "重設全域介面縮放", shortcutRefreshData: "重新整理資料", + shortcutToggleResultsPane: "切換結果面板", shortcutToggleTranspose: "切換轉置檢視", shortcutCancelSearch: "取消搜尋", shortcutCopySidebarSelection: "複製側邊欄選取項", diff --git a/apps/desktop/src/lib/editor/keyboardShortcuts.ts b/apps/desktop/src/lib/editor/keyboardShortcuts.ts index 3a8bf7447e..a83b0e49d6 100644 --- a/apps/desktop/src/lib/editor/keyboardShortcuts.ts +++ b/apps/desktop/src/lib/editor/keyboardShortcuts.ts @@ -154,6 +154,10 @@ export function isRefreshDataShortcut(event: ShortcutLikeEvent, shortcuts?: Part return matchesShortcut(event, actionShortcut("refreshData", shortcuts)); } +export function isToggleResultsPaneShortcut(event: ShortcutLikeEvent, shortcuts?: Partial): boolean { + return matchesShortcut(event, actionShortcut("toggleResultsPane", shortcuts)); +} + export function isModRShortcut(event: ShortcutLikeEvent): boolean { return matchesShortcut(event, "Mod+R"); } diff --git a/apps/desktop/src/lib/editor/shortcutRegistry.ts b/apps/desktop/src/lib/editor/shortcutRegistry.ts index 475a4bb16b..45ce89ab20 100644 --- a/apps/desktop/src/lib/editor/shortcutRegistry.ts +++ b/apps/desktop/src/lib/editor/shortcutRegistry.ts @@ -56,6 +56,7 @@ export type ShortcutActionId = | "find" | "replace" | "refreshData" + | "toggleResultsPane" | "toggleTranspose" | "cancelSearch" | "toggleSidebar" @@ -435,6 +436,12 @@ export const SHORTCUT_DEFINITIONS: ShortcutDefinition[] = [ scope: "global", defaultShortcut: "F5", }, + { + id: "toggleResultsPane", + labelKey: "settings.shortcutToggleResultsPane", + scope: "global", + defaultShortcut: "", + }, { id: "toggleTranspose", labelKey: "settings.shortcutToggleTranspose", diff --git a/packages/app-tests/keyboardShortcuts.test.ts b/packages/app-tests/keyboardShortcuts.test.ts index affdc69559..fcf3e3fc87 100644 --- a/packages/app-tests/keyboardShortcuts.test.ts +++ b/packages/app-tests/keyboardShortcuts.test.ts @@ -25,6 +25,7 @@ import { isSwitchToPreviousTabShortcut, isCopyCurrentRowShortcut, isDeleteCurrentRowShortcut, + isToggleResultsPaneShortcut, isToggleTransposeShortcut, isZoomInShortcut, isZoomOutShortcut, @@ -315,6 +316,23 @@ test("matches F5 for refreshing data", () => { assert.equal(isRefreshDataShortcut({ key: "F5" }), true); }); +test("keeps the results pane shortcut unbound until the user configures it", () => { + const configured = { toggleResultsPane: "Mod+G" } as any; + + assert.equal(DEFAULT_SHORTCUT_SETTINGS.toggleResultsPane, ""); + assert.equal(normalizeShortcutSettings({}).toggleResultsPane, ""); + assert.equal(isToggleResultsPaneShortcut({ key: "g", ctrlKey: true }), false); + assert.equal(isToggleResultsPaneShortcut({ key: "g", ctrlKey: true }, configured), true); + assert.equal(isToggleResultsPaneShortcut({ key: "g", ctrlKey: true, isComposing: true }, configured), false); + assert.equal(isToggleResultsPaneShortcut({ key: "g", ctrlKey: true }, { toggleResultsPane: "" } as any), false); +}); + +test("reports conflicts for a configured global results pane shortcut", () => { + const shortcuts = { ...DEFAULT_SHORTCUT_SETTINGS, toggleResultsPane: "Mod+F" }; + + assert.equal(findShortcutConflict("toggleResultsPane", "Mod+F", shortcuts), "focusSearch"); +}); + test("matches configurable shortcut for toggling transpose view", () => { assert.equal(isToggleTransposeShortcut({ key: "Tab" }), true); assert.equal(isToggleTransposeShortcut({ key: "Tab" }, { toggleTranspose: "Alt+T" } as any), false); diff --git a/packages/app-tests/queryResultToolbar.test.ts b/packages/app-tests/queryResultToolbar.test.ts index 972d5ca285..d0cac8c7b5 100644 --- a/packages/app-tests/queryResultToolbar.test.ts +++ b/packages/app-tests/queryResultToolbar.test.ts @@ -106,6 +106,28 @@ test("the close-tab shortcut clears query results before closing the tab", () => assert.ok(closeShortcut.indexOf("await queryStore.clearQueryResults(queryStore.activeTabId)") < closeShortcut.indexOf("queryStore.closeTab(queryStore.activeTabId)")); }); +test("the configurable results pane shortcut only toggles existing output", () => { + const contentArea = source(contentAreaPath); + const app = source(appPath); + const toggleStart = contentArea.indexOf("function toggleResultsPane()"); + const toggleEnd = contentArea.indexOf("function onResultsResized", toggleStart); + const toggle = contentArea.slice(toggleStart, toggleEnd); + const shortcutStart = app.indexOf("if (isToggleResultsPaneShortcut(e, shortcuts)"); + const shortcutEnd = app.indexOf("if (isNewQueryShortcut", shortcutStart); + const shortcut = app.slice(shortcutStart, shortcutEnd); + + assert.ok(toggleStart >= 0); + assert.match(toggle, /if \(props\.activeTab\.mode !== "query" \|\| !hasQueryOutput\.value\) return false/); + assert.match(toggle, /resultsPaneOpen\.value = !resultsPaneOpen\.value/); + assert.match(toggle, /return true/); + assert.doesNotMatch(toggle, /closeQueryResult|clearQueryResults|closeResultSession/); + assert.match(contentArea, /defineExpose\(\{[\s\S]*toggleResultsPane/); + assert.ok(shortcutStart >= 0); + assert.match(shortcut, /contentAreaRef\.value\?\.toggleResultsPane\(\)/); + assert.match(shortcut, /e\.preventDefault\(\)/); + assert.match(shortcut, /e\.stopPropagation\(\)/); +}); + test("ContentArea keeps MySQL standard explain results available in the shared toolbar", () => { const contentArea = source(contentAreaPath);