diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index f613758d..d84ca667 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -1280,6 +1280,16 @@ const Sidebar: React.FC<{ getActiveContext: () => useStore.getState().activeContext, }); + useEffect(() => { + const handleWorkbenchAddExternalSQLDirectory = () => { + void handleAddExternalSQLDirectory({ type: 'external-sql-root' }); + }; + window.addEventListener('gonavi:add-external-sql-directory', handleWorkbenchAddExternalSQLDirectory); + return () => { + window.removeEventListener('gonavi:add-external-sql-directory', handleWorkbenchAddExternalSQLDirectory); + }; + }, [handleAddExternalSQLDirectory]); + const getNodeDatabaseContext = (node: any): { connectionId: string; dbName: string; dbNodeKey: string } | null => { if (!node) return null; if (node.type === 'database') { diff --git a/frontend/src/components/TabManager.recent.test.ts b/frontend/src/components/TabManager.recent.test.ts index 08019282..f439bac1 100644 --- a/frontend/src/components/TabManager.recent.test.ts +++ b/frontend/src/components/TabManager.recent.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { buildRecentConnectionShortcuts } from './TabManager'; +import { buildPinnedTableShortcuts, buildRecentConnectionShortcuts } from './TabManager'; import type { SavedConnection } from '../types'; const connection = (id: string, type: string): SavedConnection => ({ @@ -32,4 +32,25 @@ describe('recent workbench shortcuts', () => { }), ]); }); + + it('only exposes valid pinned tables whose connection still exists', () => { + const shortcuts = buildPinnedTableShortcuts([ + connection('mysql-1', 'mysql'), + ], [ + JSON.stringify(['mysql-1', 'orders', 'public', 'line_items']), + JSON.stringify(['missing-1', 'orders', '', 'orphaned_table']), + '{bad json', + JSON.stringify(['mysql-1', '', '', 'missing_database']), + JSON.stringify(['mysql-1', 'orders', 'public', 'line_items']), + ]); + + expect(shortcuts).toEqual([ + expect.objectContaining({ + connection: expect.objectContaining({ id: 'mysql-1' }), + dbName: 'orders', + schemaName: 'public', + tableName: 'line_items', + }), + ]); + }); }); diff --git a/frontend/src/components/TabManager.tsx b/frontend/src/components/TabManager.tsx index d109d2f0..e913f052 100644 --- a/frontend/src/components/TabManager.tsx +++ b/frontend/src/components/TabManager.tsx @@ -1,14 +1,14 @@ import Modal from './common/ResizableDraggableModal'; import React, { useCallback, useMemo, useRef, useState } from 'react'; import { Button, Dropdown, message, Tabs, Tooltip } from 'antd'; -import { AppstoreOutlined, CloseOutlined, ConsoleSqlOutlined, DatabaseOutlined, FileTextOutlined, HistoryOutlined, PlusOutlined, RightOutlined, RobotOutlined, SettingOutlined } from '@ant-design/icons'; +import { CloseOutlined, ConsoleSqlOutlined, DatabaseOutlined, FileTextOutlined, FolderOpenOutlined, HistoryOutlined, PlusOutlined, PushpinOutlined, RightOutlined, RobotOutlined, SearchOutlined, SettingOutlined } from '@ant-design/icons'; import type { MenuProps, TabsProps } from 'antd'; import { DndContext, PointerSensor, closestCenter, useSensor, useSensors } from '@dnd-kit/core'; import type { DragEndEvent, DragMoveEvent, DragStartEvent } from '@dnd-kit/core'; import { SortableContext, useSortable, horizontalListSortingStrategy } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { useStore, type RecentConnectionTarget, type RecentSQLFile } from '../store'; -import type { SavedConnection, SavedQuery, TabData } from '../types'; +import type { ExternalSQLDirectory, SavedConnection, SavedQuery, TabData } from '../types'; import { t } from '../i18n'; import { buildTabDisplayModel, @@ -70,6 +70,19 @@ type RecentConnectionShortcut = { dbName?: string; }; +export type PinnedTableShortcut = { + connection: SavedConnection; + dbName: string; + schemaName?: string; + tableName: string; +}; + +type LinkedExternalSQLDirectoryShortcut = { + connection: SavedConnection; + dbName?: string; + directory: ExternalSQLDirectory; +}; + const RECENT_WORKBENCH_ITEM_LIMIT = 6; export const buildRecentConnectionShortcuts = ( @@ -107,6 +120,58 @@ export const buildRecentConnectionShortcuts = ( return result; }; +export const buildPinnedTableShortcuts = ( + connections: SavedConnection[], + pinnedTableKeys: string[], +): PinnedTableShortcut[] => { + const connectionById = new Map(connections.map((connection) => [connection.id, connection])); + const seen = new Set(); + const result: PinnedTableShortcut[] = []; + + for (const rawKey of pinnedTableKeys) { + if (result.length >= RECENT_WORKBENCH_ITEM_LIMIT) break; + try { + const parsed = JSON.parse(rawKey); + if (!Array.isArray(parsed) || parsed.length !== 4) continue; + const [rawConnectionId, rawDbName, rawSchemaName, rawTableName] = parsed; + const connectionId = String(rawConnectionId || '').trim(); + const dbName = String(rawDbName || '').trim(); + const schemaName = String(rawSchemaName || '').trim(); + const tableName = String(rawTableName || '').trim(); + const connection = connectionById.get(connectionId); + const key = `${connectionId}::${dbName}::${schemaName}::${tableName}`; + if (!connection || !dbName || !tableName || seen.has(key)) continue; + seen.add(key); + result.push({ + connection, + dbName, + ...(schemaName ? { schemaName } : {}), + tableName, + }); + } catch { + // 旧版本或损坏的本地偏好不应阻塞工作台首页。 + } + } + return result; +}; + +const buildLinkedExternalSQLDirectoryShortcuts = ( + connections: SavedConnection[], + directories: ExternalSQLDirectory[], +): LinkedExternalSQLDirectoryShortcut[] => { + const connectionById = new Map(connections.map((connection) => [connection.id, connection])); + return [...directories] + .sort((left, right) => Number(right.createdAt || 0) - Number(left.createdAt || 0)) + .flatMap((directory) => { + const connectionId = String(directory.connectionId || '').trim(); + const connection = connectionById.get(connectionId); + if (!connection) return []; + const dbName = String(directory.dbName || connection.config.database || '').trim() || undefined; + return [{ connection, ...(dbName ? { dbName } : {}), directory }]; + }) + .slice(0, RECENT_WORKBENCH_ITEM_LIMIT); +}; + const buildWorkbenchQueryTabId = (): string => `query-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; @@ -453,8 +518,10 @@ const TabManager: React.FC = React.memo(() => { const detachedWorkbenchWindows = useStore(state => state.detachedWorkbenchWindows); const connections = useStore(state => state.connections); const savedQueries = useStore(state => state.savedQueries); + const externalSQLDirectories = useStore(state => state.externalSQLDirectories); const recentConnectionTargets = useStore(state => state.recentConnectionTargets); const recentSQLFiles = useStore(state => state.recentSQLFiles); + const pinnedSidebarTables = useStore(state => state.pinnedSidebarTables); const theme = useStore(state => state.theme); const appearance = useStore(state => state.appearance); const languagePreference = useStore(state => state.languagePreference); @@ -882,6 +949,14 @@ const TabManager: React.FC = React.memo(() => { .slice(0, RECENT_WORKBENCH_ITEM_LIMIT), [connectionById, recentSQLFiles], ); + const pinnedTableShortcuts = useMemo( + () => buildPinnedTableShortcuts(queryCapableConnections, pinnedSidebarTables), + [pinnedSidebarTables, queryCapableConnections], + ); + const linkedExternalSQLDirectoryShortcuts = useMemo( + () => buildLinkedExternalSQLDirectoryShortcuts(queryCapableConnections, externalSQLDirectories), + [externalSQLDirectories, queryCapableConnections], + ); const handleOpenConnectionModal = () => { const target = document.querySelector('[data-gonavi-create-connection-action="true"]'); @@ -892,6 +967,14 @@ const TabManager: React.FC = React.memo(() => { setAIPanelVisible(true); }; + const handleFocusObjectSearch = () => { + window.dispatchEvent(new CustomEvent('gonavi:focus-sidebar-search')); + }; + + const handleAddExternalSQLDirectory = () => { + window.dispatchEvent(new CustomEvent('gonavi:add-external-sql-directory')); + }; + const handleOpenRecentConnection = useCallback((shortcut: RecentConnectionShortcut) => { addTab({ id: buildWorkbenchQueryTabId(), @@ -903,6 +986,24 @@ const TabManager: React.FC = React.memo(() => { }); }, [addTab]); + const handleOpenPinnedTable = useCallback((shortcut: PinnedTableShortcut) => { + const displayName = shortcut.schemaName + ? `${shortcut.schemaName}.${shortcut.tableName}` + : shortcut.tableName; + addTab({ + id: `pinned-table:${[shortcut.connection.id, shortcut.dbName, shortcut.schemaName || '', shortcut.tableName] + .map(encodeURIComponent) + .join(':')}`, + title: displayName, + type: 'table', + connectionId: shortcut.connection.id, + dbName: shortcut.dbName, + tableName: shortcut.tableName, + ...(shortcut.schemaName ? { schemaName: shortcut.schemaName } : {}), + objectType: 'table', + }); + }, [addTab]); + const handleOpenSavedQuery = useCallback((query: SavedQuery) => { if (!connectionById.has(query.connectionId)) { message.error(t('sidebar.message.connection_config_not_found')); @@ -988,130 +1089,177 @@ const TabManager: React.FC = React.memo(() => { + + + -
-
-
- {t('tab_manager.empty.recent.connection.heading')} - {recentConnectionShortcuts.length} +
+
+
+
+ {t('tab_manager.empty.recent.connection.heading')} + {recentConnectionShortcuts.length} +
+ {recentConnectionShortcuts.length > 0 ? ( +
+ {recentConnectionShortcuts.map((shortcut) => ( + + ))}
- {recentConnectionShortcuts.length > 0 ? ( -
- {recentConnectionShortcuts.map((shortcut) => ( + ) : ( +

{t('tab_manager.empty.recent.connection.empty')}

+ )} +
+
+
+ {t('tab_manager.empty.recent.saved_query.heading')} + {recentSavedQueries.length} +
+ {recentSavedQueries.length > 0 ? ( +
+ {recentSavedQueries.map((query) => { + const connection = connectionById.get(query.connectionId); + return ( - ))} -
- ) : ( -

{t('tab_manager.empty.recent.connection.empty')}

- )} -
-
-
- {t('tab_manager.empty.recent.saved_query.heading')} - {recentSavedQueries.length} + ); + })}
- {recentSavedQueries.length > 0 ? ( -
- {recentSavedQueries.map((query) => { - const connection = connectionById.get(query.connectionId); - return ( - - ); - })} -
- ) : ( -

{t('tab_manager.empty.recent.saved_query.empty')}

- )} -
-
-
- {t('tab_manager.empty.recent.sql_file.heading')} - {recentSQLFileShortcuts.length} + ) : ( +

{t('tab_manager.empty.recent.saved_query.empty')}

+ )} +
+
+
+ {t('tab_manager.empty.recent.sql_file.heading')} + {recentSQLFileShortcuts.length} +
+ {recentSQLFileShortcuts.length > 0 ? ( +
+ {recentSQLFileShortcuts.map((file) => { + const connection = connectionById.get(file.connectionId); + const openKey = `${file.connectionId}::${file.dbName || ''}::${file.filePath}`; + return ( + + ); + })}
- {recentSQLFileShortcuts.length > 0 ? ( -
- {recentSQLFileShortcuts.map((file) => { - const connection = connectionById.get(file.connectionId); - const openKey = `${file.connectionId}::${file.dbName || ''}::${file.filePath}`; - return ( - - ); - })} -
- ) : ( -

{t('tab_manager.empty.recent.sql_file.empty')}

- )} -
-
+ ) : ( +

{t('tab_manager.empty.recent.sql_file.empty')}

+ )} + -
-
- {t('tab_manager.empty.quick.heading')} - -
- - - +
+
+
+ {t('sidebar.action.pin_table')} + {pinnedTableShortcuts.length} +
+ {pinnedTableShortcuts.length > 0 ? ( +
+ {pinnedTableShortcuts.map((shortcut) => { + const displayName = shortcut.schemaName + ? `${shortcut.schemaName}.${shortcut.tableName}` + : shortcut.tableName; + return ( + + ); + })} +
+ ) : ( +
+ +

{t('tab_manager.empty.resource.pinned_tables.empty')}

+ +
+ )} +
+
+
+ {t('sidebar.external_sql.root')} + {linkedExternalSQLDirectoryShortcuts.length} +
+ {linkedExternalSQLDirectoryShortcuts.length > 0 ? ( +
+ {linkedExternalSQLDirectoryShortcuts.map((shortcut) => ( + + ))} +
+ ) : ( +
+ +

{t('tab_manager.empty.resource.sql_directory.empty')}

+ +
+ )} +
); diff --git a/frontend/src/components/TabManager.workbench-layout.test.ts b/frontend/src/components/TabManager.workbench-layout.test.ts new file mode 100644 index 00000000..39fe16cc --- /dev/null +++ b/frontend/src/components/TabManager.workbench-layout.test.ts @@ -0,0 +1,27 @@ +import { readFileSync } from 'node:fs'; + +import { describe, expect, it } from 'vitest'; + +const themeSource = readFileSync(new URL('../v2-theme.css', import.meta.url), 'utf8'); + +const readRule = (selector: string): string => { + const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const match = themeSource.match(new RegExp(`${escapedSelector}\\s*\\{(?[^}]*)\\}`, 's')); + expect(match, `missing CSS rule for ${selector}`).not.toBeNull(); + return match?.groups?.body ?? ''; +}; + +describe('empty workbench layout', () => { + it('keeps the start page in a single compact content column', () => { + const workbenchRule = readRule('body[data-ui-version="v2"] .gn-v2-empty-workbench'); + + expect(workbenchRule).toContain('display: flex;'); + expect(workbenchRule).toContain('flex-direction: column;'); + expect(workbenchRule).not.toContain('grid-template-columns'); + }); + + it('removes the oversized quick-workflow side panel', () => { + expect(themeSource).not.toContain('gn-v2-empty-panel'); + expect(themeSource).not.toContain('gn-v2-panel-heading'); + }); +}); diff --git a/frontend/src/v2-theme.css b/frontend/src/v2-theme.css index 6f5d4a49..5a83f7a3 100644 --- a/frontend/src/v2-theme.css +++ b/frontend/src/v2-theme.css @@ -3182,17 +3182,19 @@ body[data-ui-version="v2"] .gn-v2-sidebar-sql-audit-button:hover { } body[data-ui-version="v2"] .gn-v2-empty-workbench { - min-height: 100%; - display: grid; - grid-template-columns: minmax(0, 1.35fr) minmax(280px, 0.65fr); - align-content: stretch; + height: 100%; + min-height: 0; + flex: 1 1 auto; + display: flex; + flex-direction: column; gap: 0; padding: 0; background: var(--gn-bg-panel-2); + overflow: auto; + overscroll-behavior: contain; } -body[data-ui-version="v2"] .gn-v2-empty-hero, -body[data-ui-version="v2"] .gn-v2-empty-panel { +body[data-ui-version="v2"] .gn-v2-empty-hero { align-self: stretch; border: 0; border-radius: 0; @@ -3206,7 +3208,7 @@ body[data-ui-version="v2"] .gn-v2-empty-hero { justify-content: flex-start; min-height: 0; background: var(--gn-bg-panel-2); - padding: 28px 28px 24px 30px; + padding: 24px 28px 18px 30px; } body[data-ui-version="v2"] .gn-v2-empty-eyebrow { @@ -3222,9 +3224,9 @@ body[data-ui-version="v2"] .gn-v2-empty-eyebrow { body[data-ui-version="v2"] .gn-v2-empty-hero h1 { max-width: 720px; - margin: 12px 0 0; + margin: 10px 0 0; color: var(--gn-fg-1); - font-size: 34px; + font-size: 30px; line-height: 1.1; font-weight: 800; } @@ -3237,7 +3239,7 @@ body[data-ui-version="v2"] .gn-v2-empty-hero h1 { body[data-ui-version="v2"] .gn-v2-empty-hero p { max-width: 560px; - margin: 12px 0 0; + margin: 8px 0 0; color: var(--gn-fg-3); font-size: 13px; line-height: 1.6; @@ -3247,7 +3249,7 @@ body[data-ui-version="v2"] .gn-v2-empty-actions { display: flex; flex-wrap: wrap; gap: 10px; - margin-top: 20px; + margin-top: 16px; } body[data-ui-version="v2"] .gn-v2-empty-actions .ant-btn { @@ -3256,14 +3258,17 @@ body[data-ui-version="v2"] .gn-v2-empty-actions .ant-btn { } body[data-ui-version="v2"] .gn-v2-empty-recent { - width: min(100%, 1040px); + width: 100%; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; - margin-top: 28px; + margin: 0; + padding: 0 28px 18px 30px; + align-items: stretch; } -body[data-ui-version="v2"] .gn-v2-empty-recent-card { +body[data-ui-version="v2"] .gn-v2-empty-recent-card, +body[data-ui-version="v2"] .gn-v2-empty-resource-card { min-width: 0; min-height: 166px; display: flex; @@ -3274,6 +3279,20 @@ body[data-ui-version="v2"] .gn-v2-empty-recent-card { overflow: hidden; } +body[data-ui-version="v2"] .gn-v2-empty-resources { + min-height: 0; + min-width: 0; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; + padding: 0 28px 28px 30px; + align-content: start; +} + +body[data-ui-version="v2"] .gn-v2-empty-resource-card { + min-height: 150px; +} + body[data-ui-version="v2"] .gn-v2-empty-recent-heading { display: flex; align-items: center; @@ -3345,6 +3364,12 @@ body[data-ui-version="v2"] .gn-v2-empty-recent-item:hover:not(:disabled) { background: var(--gn-bg-hover); } +body[data-ui-version="v2"] .gn-v2-empty-recent-item:focus-visible, +body[data-ui-version="v2"] .gn-v2-empty-resource-empty .ant-btn:focus-visible { + outline: 2px solid color-mix(in srgb, var(--gn-accent) 72%, transparent); + outline-offset: -2px; +} + body[data-ui-version="v2"] .gn-v2-empty-recent-item:disabled { cursor: wait; opacity: 0.62; @@ -3400,105 +3425,47 @@ body[data-ui-version="v2"] .gn-v2-empty-recent-empty { line-height: 1.55; } -body[data-ui-version="v2"] .gn-v2-empty-panel { - border-left: 0.5px solid var(--gn-br-1); - background: var(--gn-bg-panel-2); - padding: 16px 16px 16px 18px; - display: flex; - flex-direction: column; - gap: 8px; - font-family: var(--gn-font-sans); -} - -body[data-ui-version="v2"] .gn-v2-panel-heading { - display: flex; - justify-content: space-between; - align-items: center; - color: var(--gn-fg-1); - font-family: var(--gn-font-sans); - font-size: 13px; - font-weight: 700; - line-height: 1.3; - letter-spacing: 0; - margin-bottom: 4px; -} - -body[data-ui-version="v2"] .gn-v2-empty-panel button { - width: 100%; - min-height: 62px; +body[data-ui-version="v2"] .gn-v2-empty-resource-empty { + flex: 1 1 auto; + min-height: 106px; display: grid; - grid-template-columns: 34px minmax(0, 1fr); + grid-template-columns: 28px minmax(0, 1fr) auto; gap: 10px; align-items: center; - text-align: left; - border: 0.5px solid var(--gn-br-1); - border-radius: 6px; - background: color-mix(in srgb, var(--gn-bg-panel-2) 88%, var(--gn-bg-panel) 12%); - color: var(--gn-fg-1); - cursor: pointer; - font-family: var(--gn-font-sans); - padding: 10px 12px; + padding: 14px 14px 14px 12px; + color: var(--gn-fg-4); } -body[data-ui-version="v2"] .gn-v2-empty-panel button:hover { - border-color: color-mix(in srgb, var(--gn-accent) 42%, var(--gn-br-1)); - background: var(--gn-bg-hover); -} - -body[data-ui-version="v2"] .gn-v2-empty-panel button > .anticon { - width: 34px; - height: 34px; +body[data-ui-version="v2"] .gn-v2-empty-resource-empty > .anticon { + width: 28px; + height: 28px; display: grid; place-items: center; - border-radius: 6px; - color: var(--gn-accent); + border-radius: 7px; background: var(--gn-accent-soft); + color: var(--gn-accent); } -body[data-ui-version="v2"] .gn-v2-empty-panel strong, -body[data-ui-version="v2"] .gn-v2-empty-panel small { - display: block; - font-family: var(--gn-font-sans); - letter-spacing: 0; -} - -body[data-ui-version="v2"] .gn-v2-empty-panel strong { - color: var(--gn-fg-1); - font-size: 13px; - font-weight: 650; - line-height: 1.35; -} - -body[data-ui-version="v2"] .gn-v2-empty-panel small { - color: var(--gn-fg-4); +body[data-ui-version="v2"] .gn-v2-empty-resource-empty p { + margin: 0; + font-size: 11px; + line-height: 1.55; +} + +body[data-ui-version="v2"] .gn-v2-empty-resource-empty .ant-btn { + padding-inline: 6px; font-size: 11px; - margin-top: 3px; - font-weight: 500; - line-height: 1.45; } @media (max-width: 1360px) { - body[data-ui-version="v2"] .gn-v2-empty-workbench { - grid-template-columns: minmax(0, 1.28fr) minmax(260px, 0.72fr); - } - body[data-ui-version="v2"] .gn-v2-empty-hero { - padding: 24px 22px 22px 24px; + padding: 22px 22px 16px 24px; } - body[data-ui-version="v2"] .gn-v2-empty-panel { - padding: 14px 14px 14px 16px; - } -} - -@media (max-width: 1120px) { - body[data-ui-version="v2"] .gn-v2-empty-workbench { - grid-template-columns: minmax(0, 1fr); - } - - body[data-ui-version="v2"] .gn-v2-empty-panel { - border-left: 0; - border-top: 0.5px solid var(--gn-br-1); + body[data-ui-version="v2"] .gn-v2-empty-recent, + body[data-ui-version="v2"] .gn-v2-empty-resources { + padding-left: 24px; + padding-right: 22px; } } @@ -3512,6 +3479,26 @@ body[data-ui-version="v2"] .gn-v2-empty-panel small { body[data-ui-version="v2"] .gn-v2-empty-recent { grid-template-columns: minmax(0, 1fr); } + + body[data-ui-version="v2"] .gn-v2-empty-resources { + grid-template-columns: minmax(0, 1fr); + } + + body[data-ui-version="v2"] .gn-v2-empty-hero, + body[data-ui-version="v2"] .gn-v2-empty-recent, + body[data-ui-version="v2"] .gn-v2-empty-resources { + padding-left: 16px; + padding-right: 16px; + } + + body[data-ui-version="v2"] .gn-v2-empty-resource-empty { + grid-template-columns: 28px minmax(0, 1fr); + } + + body[data-ui-version="v2"] .gn-v2-empty-resource-empty .ant-btn { + grid-column: 2; + justify-self: start; + } } /* ─── Full V2 workbench shell: app / topbar / workspace ─ */ diff --git a/shared/i18n/de-DE.json b/shared/i18n/de-DE.json index 94d73e18..92efbaaf 100644 --- a/shared/i18n/de-DE.json +++ b/shared/i18n/de-DE.json @@ -7668,8 +7668,12 @@ "tab_manager.empty.quick.configure_source.description": "URI, SSH, Proxy und Treiber zentral festlegen", "tab_manager.empty.quick.configure_source.title": "Datenquelle konfigurieren", "tab_manager.empty.quick.heading": "Schneller Workflow", + "tab_manager.empty.quick.search.description": "Tabellen, Verbindungen und häufige Aktionen durchsuchen", + "tab_manager.empty.quick.search.title": "Objekte suchen", "tab_manager.empty.quick.sql_workspace.description": "Abfrageeditor mit aktuellem Kontext öffnen", "tab_manager.empty.quick.sql_workspace.title": "SQL-Arbeitsbereich starten", + "tab_manager.empty.resource.pinned_tables.empty": "In der Seitenleiste angepinnte Tabellen erscheinen hier.", + "tab_manager.empty.resource.sql_directory.empty": "Fügen Sie einen SQL-Ordner hinzu, um die zugehörige Verbindung hier schnell zu öffnen.", "tab_manager.empty.recent.aria": "Häufig verwendete Arbeitsbereich-Verknüpfungen", "tab_manager.empty.recent.connection.default_database": "Standarddatenbank", "tab_manager.empty.recent.connection.empty": "Geöffnete Verbindungen und Datenbanken erscheinen hier.", diff --git a/shared/i18n/en-US.json b/shared/i18n/en-US.json index 3cf86a32..8fe91233 100644 --- a/shared/i18n/en-US.json +++ b/shared/i18n/en-US.json @@ -7668,8 +7668,12 @@ "tab_manager.empty.quick.configure_source.description": "Set URI, SSH, proxy, and driver in one place", "tab_manager.empty.quick.configure_source.title": "Configure data source", "tab_manager.empty.quick.heading": "Quick workflow", + "tab_manager.empty.quick.search.description": "Search tables, connections, and common actions", + "tab_manager.empty.quick.search.title": "Search objects", "tab_manager.empty.quick.sql_workspace.description": "Open the query editor with the current context", "tab_manager.empty.quick.sql_workspace.title": "Start SQL workspace", + "tab_manager.empty.resource.pinned_tables.empty": "Tables pinned in the sidebar will appear here.", + "tab_manager.empty.resource.sql_directory.empty": "Add a SQL folder to quickly open its linked connection here.", "tab_manager.empty.recent.aria": "Frequently used workbench shortcuts", "tab_manager.empty.recent.connection.default_database": "Default database", "tab_manager.empty.recent.connection.empty": "Opened connections and databases will appear here.", diff --git a/shared/i18n/ja-JP.json b/shared/i18n/ja-JP.json index d172eff4..f1778fa3 100644 --- a/shared/i18n/ja-JP.json +++ b/shared/i18n/ja-JP.json @@ -7668,8 +7668,12 @@ "tab_manager.empty.quick.configure_source.description": "URI、SSH、プロキシ、ドライバーを 1 か所で設定", "tab_manager.empty.quick.configure_source.title": "データソースを設定", "tab_manager.empty.quick.heading": "クイックワークフロー", + "tab_manager.empty.quick.search.description": "テーブル、接続、よく使う操作を検索", + "tab_manager.empty.quick.search.title": "オブジェクトを検索", "tab_manager.empty.quick.sql_workspace.description": "現在のコンテキストでクエリエディターを開く", "tab_manager.empty.quick.sql_workspace.title": "SQL ワークスペースを開始", + "tab_manager.empty.resource.pinned_tables.empty": "サイドバーでテーブルをピン留めすると、ここに表示されます。", + "tab_manager.empty.resource.sql_directory.empty": "SQL フォルダーを追加すると、関連付けられた接続をここからすばやく開けます。", "tab_manager.empty.recent.aria": "よく使うワークベンチのショートカット", "tab_manager.empty.recent.connection.default_database": "既定のデータベース", "tab_manager.empty.recent.connection.empty": "開いた接続とデータベースがここに表示されます。", diff --git a/shared/i18n/ru-RU.json b/shared/i18n/ru-RU.json index 70c607bd..7954f7e7 100644 --- a/shared/i18n/ru-RU.json +++ b/shared/i18n/ru-RU.json @@ -7668,8 +7668,12 @@ "tab_manager.empty.quick.configure_source.description": "Настроить URI, SSH, прокси и драйвер в одном месте", "tab_manager.empty.quick.configure_source.title": "Настроить источник данных", "tab_manager.empty.quick.heading": "Быстрый рабочий процесс", + "tab_manager.empty.quick.search.description": "Поиск таблиц, подключений и частых действий", + "tab_manager.empty.quick.search.title": "Поиск объектов", "tab_manager.empty.quick.sql_workspace.description": "Открыть редактор запросов с текущим контекстом", "tab_manager.empty.quick.sql_workspace.title": "Запустить рабочую область SQL", + "tab_manager.empty.resource.pinned_tables.empty": "Закрепленные в боковой панели таблицы появятся здесь.", + "tab_manager.empty.resource.sql_directory.empty": "Добавьте папку SQL, чтобы быстро открывать связанное подключение отсюда.", "tab_manager.empty.recent.aria": "Часто используемые ярлыки рабочей области", "tab_manager.empty.recent.connection.default_database": "База данных по умолчанию", "tab_manager.empty.recent.connection.empty": "Открытые подключения и базы данных появятся здесь.", diff --git a/shared/i18n/zh-CN.json b/shared/i18n/zh-CN.json index dd4defe7..e5a35c76 100644 --- a/shared/i18n/zh-CN.json +++ b/shared/i18n/zh-CN.json @@ -7668,8 +7668,12 @@ "tab_manager.empty.quick.configure_source.description": "在一处设置 URI、SSH、代理和驱动", "tab_manager.empty.quick.configure_source.title": "配置数据源", "tab_manager.empty.quick.heading": "快速工作流", + "tab_manager.empty.quick.search.description": "搜索表、连接和常用操作", + "tab_manager.empty.quick.search.title": "搜索对象", "tab_manager.empty.quick.sql_workspace.description": "使用当前上下文打开查询编辑器", "tab_manager.empty.quick.sql_workspace.title": "启动 SQL 工作区", + "tab_manager.empty.resource.pinned_tables.empty": "在左侧将表置顶后,会显示在这里。", + "tab_manager.empty.resource.sql_directory.empty": "添加 SQL 文件夹后,可在这里快速打开其关联的连接。", "tab_manager.empty.recent.aria": "常用工作区入口", "tab_manager.empty.recent.connection.default_database": "默认数据库", "tab_manager.empty.recent.connection.empty": "打开过的连接和数据库会显示在这里。", diff --git a/shared/i18n/zh-TW.json b/shared/i18n/zh-TW.json index 87b7c8f2..15085916 100644 --- a/shared/i18n/zh-TW.json +++ b/shared/i18n/zh-TW.json @@ -7668,8 +7668,12 @@ "tab_manager.empty.quick.configure_source.description": "在一處設定 URI、SSH、代理與驅動", "tab_manager.empty.quick.configure_source.title": "設定資料來源", "tab_manager.empty.quick.heading": "快速工作流程", + "tab_manager.empty.quick.search.description": "搜尋資料表、連線和常用操作", + "tab_manager.empty.quick.search.title": "搜尋物件", "tab_manager.empty.quick.sql_workspace.description": "使用目前上下文開啟查詢編輯器", "tab_manager.empty.quick.sql_workspace.title": "啟動 SQL 工作區", + "tab_manager.empty.resource.pinned_tables.empty": "在左側將資料表置頂後,會顯示在這裡。", + "tab_manager.empty.resource.sql_directory.empty": "新增 SQL 資料夾後,可在這裡快速開啟其關聯的連線。", "tab_manager.empty.recent.aria": "常用工作台入口", "tab_manager.empty.recent.connection.default_database": "預設資料庫", "tab_manager.empty.recent.connection.empty": "開啟過的連線和資料庫會顯示在這裡。",