mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-12 17:39:42 +08:00
🐛 fix(ui): 修复数据表头和侧栏滚动显示
- 修复虚拟数据表横向滚动后表头与数据列错位 - 修复亮色主题字段元数据悬浮提示可读性 - 优化 v2 侧栏外部 SQL 菜单和定位入口文案 - 使用 rc-tree 横向滚动宽度估算并加粗侧栏树滚动条
This commit is contained in:
@@ -501,10 +501,16 @@ describe('DataGrid layout', () => {
|
||||
expect(source).toContain('type VirtualTableScrollReference = TableReference & {');
|
||||
expect(source).toContain('const tableRef = useRef<VirtualTableScrollReference | null>(null);');
|
||||
expect(source).toContain('resolveDataGridHorizontalWheelDelta({');
|
||||
expect(source).toContain('const virtualHorizontalAlignmentRafRef = useRef<number | null>(null);');
|
||||
expect(source).toContain('const scheduleVirtualHorizontalWheel = useCallback');
|
||||
expect(source).toContain('pendingTableHorizontalDeltaRef.current += delta;');
|
||||
expect(source).toContain('tableHorizontalWheelRafRef.current = requestAnimationFrame');
|
||||
expect(source).toContain('const scheduleVirtualHorizontalAlignment = useCallback((preferredLeft?: number) => {');
|
||||
expect(source).toContain('virtualHorizontalElementsRef.current = { tableContainer: null, holderEl: null, innerEl: null, headerEl: null };');
|
||||
expect(source).toContain('applyVirtualHorizontalOffset(tableContainer, nextLeft, { forceInternalScroll: true });');
|
||||
expect(source).toContain('}, [horizontalScrollVisible, scheduleVirtualHorizontalAlignment, tableRenderData, tableScrollX, virtualEditingCell]);');
|
||||
expect(source).toContain('tableInstance.scrollTo({ left: clampedOffset, top: holderEl.scrollTop });');
|
||||
expect(source).toContain('applyVirtualHorizontalOffset(tableContainer, latestExternalScroll.scrollLeft, { forceInternalScroll: true });');
|
||||
expect(source).toContain('if (externalSyncRafRef.current !== null)');
|
||||
expect(source).toContain('externalSyncRafRef.current = requestAnimationFrame');
|
||||
expect(source).toContain('const scheduleSyncExternalScrollFromTargets = useCallback');
|
||||
|
||||
@@ -1921,6 +1921,7 @@ const DataGrid: React.FC<DataGridProps> = ({
|
||||
const externalSyncRafRef = useRef<number | null>(null);
|
||||
const tableTargetSyncRafRef = useRef<number | null>(null);
|
||||
const tableHorizontalWheelRafRef = useRef<number | null>(null);
|
||||
const virtualHorizontalAlignmentRafRef = useRef<number | null>(null);
|
||||
const pendingTableHorizontalDeltaRef = useRef(0);
|
||||
const pendingTableTargetSyncSourceRef = useRef<HTMLElement | null>(null);
|
||||
const scrollSnapshotRafRef = useRef<number | null>(null);
|
||||
@@ -6311,7 +6312,7 @@ const DataGrid: React.FC<DataGridProps> = ({
|
||||
return { holderEl, clampedOffset, currentOffset };
|
||||
}, [resolveVirtualHorizontalElements, tableScrollX]);
|
||||
|
||||
const applyVirtualHorizontalOffset = useCallback((tableContainer: HTMLElement, nextOffset: number) => {
|
||||
const applyVirtualHorizontalOffset = useCallback((tableContainer: HTMLElement, nextOffset: number, options?: { forceInternalScroll?: boolean }) => {
|
||||
const synced = syncVirtualHorizontalVisualOffset(tableContainer, nextOffset);
|
||||
if (!synced) {
|
||||
return false;
|
||||
@@ -6319,7 +6320,7 @@ const DataGrid: React.FC<DataGridProps> = ({
|
||||
|
||||
const { holderEl, clampedOffset, currentOffset } = synced;
|
||||
const deltaX = clampedOffset - currentOffset;
|
||||
if (Math.abs(deltaX) < 0.5) return true;
|
||||
if (Math.abs(deltaX) < 0.5 && !options?.forceInternalScroll) return true;
|
||||
|
||||
const tableInstance = tableRef.current;
|
||||
if (tableInstance && typeof tableInstance.scrollTo === 'function') {
|
||||
@@ -6338,6 +6339,34 @@ const DataGrid: React.FC<DataGridProps> = ({
|
||||
return true;
|
||||
}, [syncVirtualHorizontalVisualOffset]);
|
||||
|
||||
const scheduleVirtualHorizontalAlignment = useCallback((preferredLeft?: number) => {
|
||||
if (!enableVirtual || !isTableSurfaceActive) return;
|
||||
if (virtualHorizontalAlignmentRafRef.current !== null) {
|
||||
cancelAnimationFrame(virtualHorizontalAlignmentRafRef.current);
|
||||
}
|
||||
virtualHorizontalAlignmentRafRef.current = requestAnimationFrame(() => {
|
||||
virtualHorizontalAlignmentRafRef.current = null;
|
||||
const tableContainer = tableContainerRef.current;
|
||||
if (!(tableContainer instanceof HTMLElement)) return;
|
||||
|
||||
virtualHorizontalElementsRef.current = { tableContainer: null, holderEl: null, innerEl: null, headerEl: null };
|
||||
const externalScroll = externalHorizontalScrollRef.current;
|
||||
const nextLeft = Math.max(0, preferredLeft ?? externalScroll?.scrollLeft ?? lastTableScrollLeftRef.current);
|
||||
const applied = applyVirtualHorizontalOffset(tableContainer, nextLeft, { forceInternalScroll: true });
|
||||
const resolvedLeft = applied ? readVirtualHorizontalOffset(tableContainer) : nextLeft;
|
||||
lastTableScrollLeftRef.current = resolvedLeft;
|
||||
if (externalScroll && Math.abs(externalScroll.scrollLeft - resolvedLeft) > 1) {
|
||||
externalScroll.scrollLeft = resolvedLeft;
|
||||
}
|
||||
lastExternalScrollLeftRef.current = externalScroll?.scrollLeft ?? resolvedLeft;
|
||||
requestAnimationFrame(() => {
|
||||
const latestContainer = tableContainerRef.current;
|
||||
if (!(latestContainer instanceof HTMLElement)) return;
|
||||
syncVirtualHorizontalVisualOffset(latestContainer, resolvedLeft);
|
||||
});
|
||||
});
|
||||
}, [applyVirtualHorizontalOffset, enableVirtual, isTableSurfaceActive, readVirtualHorizontalOffset, syncVirtualHorizontalVisualOffset]);
|
||||
|
||||
const flushVirtualHorizontalWheel = useCallback((tableContainer: HTMLElement) => {
|
||||
tableHorizontalWheelRafRef.current = null;
|
||||
const delta = pendingTableHorizontalDeltaRef.current;
|
||||
@@ -6586,7 +6615,7 @@ const DataGrid: React.FC<DataGridProps> = ({
|
||||
// 虚拟表格路径:通过合成 WheelEvent 驱动 rc-virtual-list 内部状态,
|
||||
// rc-table 自动同步 header scrollLeft。
|
||||
if (enableVirtual && tableContainer instanceof HTMLElement) {
|
||||
const applied = applyVirtualHorizontalOffset(tableContainer, latestExternalScroll.scrollLeft);
|
||||
const applied = applyVirtualHorizontalOffset(tableContainer, latestExternalScroll.scrollLeft, { forceInternalScroll: true });
|
||||
if (applied) {
|
||||
// WheelEvent 经 rc-virtual-list 处理后状态异步更新,延迟同步 ref
|
||||
requestAnimationFrame(() => {
|
||||
@@ -6875,6 +6904,17 @@ const DataGrid: React.FC<DataGridProps> = ({
|
||||
return () => cancelAnimationFrame(rafId);
|
||||
}, [isTableSurfaceActive, totalWidth, mergedDisplayData.length, pagination?.total, pagination?.pageSize, recalculateTableMetrics]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!horizontalScrollVisible) return;
|
||||
scheduleVirtualHorizontalAlignment();
|
||||
return () => {
|
||||
if (virtualHorizontalAlignmentRafRef.current !== null) {
|
||||
cancelAnimationFrame(virtualHorizontalAlignmentRafRef.current);
|
||||
virtualHorizontalAlignmentRafRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [horizontalScrollVisible, scheduleVirtualHorizontalAlignment, tableRenderData, tableScrollX, virtualEditingCell]);
|
||||
|
||||
// 虚拟表列对齐:antd 虚拟表 body 使用 <div>+<td>(非 <table>),
|
||||
// 不会自动拉伸列宽到视口。而 header <table> 会被 antd 的 CSS 或 JS
|
||||
// 设置为 width:100% 自动拉伸。强制 header table 宽度等于 scroll.x,
|
||||
|
||||
@@ -5,7 +5,12 @@ import { describe, expect, it, vi } from 'vitest';
|
||||
import DataGridColumnTitle from './DataGridColumnTitle';
|
||||
|
||||
vi.mock('antd', () => ({
|
||||
Tooltip: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
Tooltip: ({ children, title, rootClassName }: { children: React.ReactNode; title?: React.ReactNode; rootClassName?: string }) => (
|
||||
<>
|
||||
<div data-tooltip-root-class={rootClassName}>{title}</div>
|
||||
{children}
|
||||
</>
|
||||
),
|
||||
}));
|
||||
|
||||
describe('DataGridColumnTitle', () => {
|
||||
@@ -50,6 +55,43 @@ describe('DataGridColumnTitle', () => {
|
||||
expect(markup).toContain('align-items:flex-start');
|
||||
});
|
||||
|
||||
it('keeps column metadata tooltip readable in light theme', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<DataGridColumnTitle
|
||||
columnName="auth_type"
|
||||
columnMeta={{ type: 'tinyint(4)', comment: '认证类型:1企业,2个人' }}
|
||||
showColumnType
|
||||
showColumnComment
|
||||
metaFontSize={11}
|
||||
columnMetaHintColor="#595959"
|
||||
columnMetaTooltipColor="#262626"
|
||||
darkMode={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(markup).toContain('data-tooltip-root-class="gn-data-grid-column-meta-tooltip"');
|
||||
expect(markup).toContain('class="gn-data-grid-column-meta-tooltip-content"');
|
||||
expect(markup).toContain('color:var(--gn-fg-1, #fff)');
|
||||
expect(markup).not.toContain('color:#fff');
|
||||
});
|
||||
|
||||
it('keeps the configured warm metadata tooltip color in dark theme', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<DataGridColumnTitle
|
||||
columnName="auth_type"
|
||||
columnMeta={{ type: 'tinyint(4)', comment: '认证类型:1企业,2个人' }}
|
||||
showColumnType
|
||||
showColumnComment
|
||||
metaFontSize={11}
|
||||
columnMetaHintColor="rgba(255, 236, 179, 0.98)"
|
||||
columnMetaTooltipColor="rgba(255, 236, 179, 0.98)"
|
||||
darkMode
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(markup).toContain('color:rgba(255, 236, 179, 0.98)');
|
||||
});
|
||||
|
||||
it('renders foreign-key jump affordance when reference target exists', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<DataGridColumnTitle
|
||||
|
||||
@@ -150,22 +150,26 @@ const DataGridColumnTitle: React.FC<DataGridColumnTitleProps> = ({
|
||||
return titleNode;
|
||||
}
|
||||
|
||||
const tooltipTextColor = darkMode ? columnMetaTooltipColor : 'var(--gn-fg-1, #fff)';
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={(
|
||||
<pre
|
||||
className="gn-data-grid-column-meta-tooltip-content"
|
||||
style={{
|
||||
maxHeight: 260,
|
||||
overflow: 'auto',
|
||||
margin: 0,
|
||||
fontSize: 12,
|
||||
whiteSpace: 'pre-wrap',
|
||||
color: darkMode ? columnMetaTooltipColor : '#fff',
|
||||
color: tooltipTextColor,
|
||||
}}
|
||||
>
|
||||
{hoverLines.join('\n')}
|
||||
</pre>
|
||||
)}
|
||||
rootClassName="gn-data-grid-column-meta-tooltip"
|
||||
styles={{ root: { maxWidth: 640 } }}
|
||||
{...(!darkMode ? { color: 'rgba(0, 0, 0, 0.82)' } : {})}
|
||||
>
|
||||
|
||||
@@ -7,6 +7,7 @@ import Sidebar, {
|
||||
buildSidebarTableChildrenForUi,
|
||||
buildV2SidebarTableSectionedChildren,
|
||||
buildV2RailConnectionGroups,
|
||||
estimateV2TreeHorizontalScrollWidth,
|
||||
filterV2ExplorerTreeByKind,
|
||||
getV2RailConnectionGroupBadgeText,
|
||||
hasSidebarLazyChildren,
|
||||
@@ -176,6 +177,12 @@ vi.mock('../../wailsjs/go/app/App', () => ({
|
||||
SelectSQLDirectory: mocks.noop,
|
||||
ListSQLDirectory: mocks.noop,
|
||||
ReadSQLFile: mocks.noop,
|
||||
CreateSQLFile: mocks.noop,
|
||||
CreateSQLDirectory: mocks.noop,
|
||||
DeleteSQLFile: mocks.noop,
|
||||
DeleteSQLDirectory: mocks.noop,
|
||||
RenameSQLFile: mocks.noop,
|
||||
RenameSQLDirectory: mocks.noop,
|
||||
JVMProbeCapabilities: mocks.noop,
|
||||
GetDriverStatusList: mocks.noop,
|
||||
}));
|
||||
@@ -368,10 +375,47 @@ describe('Sidebar locate toolbar', () => {
|
||||
const locateActionIndex = markup.indexOf('data-sidebar-locate-current-tab-action="true"');
|
||||
|
||||
expect(markup).toContain('data-sidebar-locate-current-tab-action="true"');
|
||||
expect(markup).toContain('aria-label="定位当前打开表"');
|
||||
expect(markup).toContain('aria-label="定位当前标签页"');
|
||||
expect(locateActionIndex).toBeGreaterThan(externalSqlActionIndex);
|
||||
});
|
||||
|
||||
it('wires external SQL directory file actions to dedicated Wails APIs', () => {
|
||||
const source = readFileSync(new URL('./Sidebar.tsx', import.meta.url), 'utf8');
|
||||
const loadTablesSource = source.slice(
|
||||
source.indexOf('const loadTables = async'),
|
||||
source.indexOf('const locateObjectInSidebarRef'),
|
||||
);
|
||||
|
||||
expect(source).toContain('CreateSQLFile(directoryPath, name)');
|
||||
expect(source).toContain('RenameSQLFile(filePath, name)');
|
||||
expect(source).toContain('DeleteSQLFile(filePath)');
|
||||
expect(source).toContain('CreateSQLDirectory(directoryPath, name)');
|
||||
expect(source).toContain('RenameSQLDirectory(directoryPath, name)');
|
||||
expect(source).toContain('DeleteSQLDirectory(directoryPath)');
|
||||
expect(source).toContain('refreshGlobalExternalSQLRootNode(false)');
|
||||
expect(source).toContain("request.objectGroup === 'externalSqlFiles'");
|
||||
expect(source).toContain('SQL 文件未在外部 SQL 目录中找到');
|
||||
expect(source).toContain('filePath: data.filePath || undefined');
|
||||
expect(source).toContain("key: 'add-external-sql-directory'");
|
||||
expect(source).toContain("key: 'new-external-sql-file'");
|
||||
expect(source).toContain("key: 'rename-external-sql-file'");
|
||||
expect(source).toContain("key: 'delete-external-sql-file'");
|
||||
expect(source).toContain("key: 'new-external-sql-directory'");
|
||||
expect(source).toContain("key: 'rename-external-sql-directory'");
|
||||
expect(source).toContain("key: 'delete-external-sql-directory'");
|
||||
expect(source).toContain('新建 SQL 文件');
|
||||
expect(source).toContain('重命名 SQL 文件');
|
||||
expect(source).toContain('确认删除 SQL 文件');
|
||||
expect(source).toContain('新建目录');
|
||||
expect(source).toContain('重命名目录');
|
||||
expect(source).toContain('确认删除目录');
|
||||
expect(source).toContain('仅支持删除空目录');
|
||||
expect(source).toContain('文件名不能包含路径分隔符');
|
||||
expect(source).toContain('目录名不能包含路径分隔符');
|
||||
expect(loadTablesSource).not.toContain('externalSQLRootNode');
|
||||
expect(loadTablesSource).not.toContain('dbExternalSQLDirectories');
|
||||
});
|
||||
|
||||
it('keeps the legacy sidebar toolbar on a stable five-column grid layout', () => {
|
||||
const source = readFileSync(new URL('./Sidebar.tsx', import.meta.url), 'utf8');
|
||||
const markup = renderToStaticMarkup(<Sidebar />);
|
||||
@@ -509,14 +553,25 @@ describe('Sidebar locate toolbar', () => {
|
||||
expect(css).not.toContain('.gn-v2-active-connection-trigger:hover');
|
||||
});
|
||||
|
||||
it('keeps v2 tree status dots circular while truncating only the label text', () => {
|
||||
it('keeps v2 tree status dots circular while using virtual horizontal scroll for long labels', () => {
|
||||
const css = readFileSync(new URL('../v2-theme.css', import.meta.url), 'utf8');
|
||||
const source = readFileSync(new URL('./Sidebar.tsx', import.meta.url), 'utf8');
|
||||
|
||||
expect(source).toContain('gn-v2-tree-status is-${status}');
|
||||
expect(source).toContain('data-sidebar-tree-folder-icon="true"');
|
||||
expect(source).toContain("overflow: 'hidden'");
|
||||
expect(source).not.toContain("overflowX: isV2Ui ? 'auto' : 'hidden'");
|
||||
expect(source).toContain('scrollWidth={isV2Ui ? v2TreeHorizontalScrollWidth : undefined}');
|
||||
expect(css).toMatch(/\.gn-v2-explorer-tree-shell \{[^}]*overflow: hidden !important;/s);
|
||||
expect(css).toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree \{[^}]*width: 100%;[^}]*min-width: 0;/s);
|
||||
expect(css).toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-list-holder-inner \{[^}]*width: 100%;[^}]*min-width: 100%;/s);
|
||||
expect(css).not.toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-list-holder-inner \{[^}]*width: max-content;/s);
|
||||
expect(css).not.toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-list-holder \{[^}]*overflow-x: auto !important;/s);
|
||||
expect(css).toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-list-scrollbar-horizontal \{[^}]*height: 12px !important;/s);
|
||||
expect(css).toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-list-scrollbar-horizontal \.ant-tree-list-scrollbar-thumb \{[^}]*height: 8px !important;/s);
|
||||
expect(css).toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-node-content-wrapper \{[^}]*display: flex !important;/s);
|
||||
expect(css).toMatch(/\.gn-v2-tree-title\.is-connection \{[^}]*align-items:\s*center;/s);
|
||||
expect(css).toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-title \{[^}]*overflow: visible;/s);
|
||||
expect(css).toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-title \{[^}]*flex: 1 1 auto;[^}]*overflow: visible;/s);
|
||||
expect(css).toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-title > \.gn-v2-tree-title \{[^}]*overflow: visible;/s);
|
||||
expect(css).toMatch(/\.gn-v2-tree-status \{[^}]*width: 14px;[^}]*height: 14px;[^}]*flex: 0 0 14px;[^}]*overflow: visible;/s);
|
||||
expect(css).toMatch(/\.gn-v2-tree-status::before \{[^}]*width: 7px;[^}]*height: 7px;[^}]*border-radius: 50%;/s);
|
||||
@@ -526,6 +581,32 @@ describe('Sidebar locate toolbar', () => {
|
||||
expect(css).not.toContain('.gn-v2-tree-connection-meta');
|
||||
});
|
||||
|
||||
it('estimates a v2 tree scroll width only when content is wider than the viewport', () => {
|
||||
const narrowWidth = estimateV2TreeHorizontalScrollWidth([
|
||||
{
|
||||
title: 'front_end_sys',
|
||||
key: 'db-front-end',
|
||||
type: 'database',
|
||||
children: [{
|
||||
title: 'com_vod_error_file_tmp_with_a_very_long_table_name',
|
||||
key: 'table-long',
|
||||
type: 'table',
|
||||
}],
|
||||
},
|
||||
] as any, 260);
|
||||
const wideWidth = estimateV2TreeHorizontalScrollWidth([
|
||||
{
|
||||
title: 'users',
|
||||
key: 'table-users',
|
||||
type: 'table',
|
||||
},
|
||||
] as any, 900);
|
||||
|
||||
expect(narrowWidth).toBeGreaterThan(260);
|
||||
expect(narrowWidth).toBeLessThanOrEqual(960);
|
||||
expect(wideWidth).toBeUndefined();
|
||||
});
|
||||
|
||||
it('does not repeat the active connection as an object-tree root in v2', () => {
|
||||
mocks.state.connections = [{
|
||||
id: 'conn-local',
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2155,6 +2155,7 @@ body[data-ui-version="v2"] .gn-v2-explorer-tree-shell {
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .sidebar-tree-scroll-content {
|
||||
@@ -2162,10 +2163,37 @@ body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .sidebar-tree-scroll-conte
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
font-size: var(--gn-sidebar-tree-font-size, var(--gn-font-size-sm, 12px));
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-list-holder {
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-list-scrollbar-horizontal {
|
||||
height: 12px !important;
|
||||
bottom: 1px !important;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-list-scrollbar-horizontal .ant-tree-list-scrollbar-thumb {
|
||||
height: 8px !important;
|
||||
border-radius: 999px !important;
|
||||
background: color-mix(in srgb, var(--gn-fg-4) 52%, transparent) !important;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-list-scrollbar-horizontal .ant-tree-list-scrollbar-thumb:hover,
|
||||
body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-list-scrollbar-horizontal .ant-tree-list-scrollbar-thumb-moving {
|
||||
background: color-mix(in srgb, var(--gn-fg-3) 72%, transparent) !important;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-list-holder-inner {
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-treenode {
|
||||
min-height: 26px !important;
|
||||
padding: 0 6px !important;
|
||||
@@ -2189,7 +2217,7 @@ body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-node-content-wra
|
||||
min-width: 0;
|
||||
min-height: 26px !important;
|
||||
padding: 0 6px !important;
|
||||
display: inline-flex !important;
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
border-radius: 6px !important;
|
||||
color: var(--gn-fg-3) !important;
|
||||
@@ -2246,9 +2274,9 @@ body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .gn-v2-tree-folder-icon .a
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-title {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
@@ -2800,19 +2828,34 @@ body[data-ui-version="v2"] .gn-v2-main-tabs > .ant-tabs-nav {
|
||||
background: var(--gn-bg-panel-2);
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-main-tabs-double > .ant-tabs-nav {
|
||||
height: 46px;
|
||||
flex: 0 0 46px;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-main-tabs .ant-tabs-nav-wrap,
|
||||
body[data-ui-version="v2"] .gn-v2-main-tabs .ant-tabs-nav-list {
|
||||
min-height: 36px;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-main-tabs-double .ant-tabs-nav-wrap,
|
||||
body[data-ui-version="v2"] .gn-v2-main-tabs-double .ant-tabs-nav-list {
|
||||
min-height: 46px;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-main-tabs .ant-tabs-tab {
|
||||
min-width: 140px;
|
||||
width: 260px;
|
||||
min-width: 260px;
|
||||
max-width: 260px;
|
||||
height: 36px;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-main-tabs-double .ant-tabs-tab {
|
||||
height: 46px;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-main-tabs .ant-tabs-tab-btn {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -2822,10 +2865,94 @@ body[data-ui-version="v2"] .gn-v2-tab-label {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 0 8px 0 10px;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-tab-label-content {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-tab-label-double {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-tab-label-double .gn-v2-tab-label-content {
|
||||
align-items: stretch;
|
||||
justify-content: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-tab-label-double .gn-v2-tab-label-main {
|
||||
flex: 0 1 auto;
|
||||
gap: 5px;
|
||||
line-height: 17px;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-tab-label-main {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: var(--gn-fg-2);
|
||||
font-weight: 700;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-tab-label-secondary {
|
||||
min-width: 0;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
color: var(--gn-fg-5);
|
||||
font-family: var(--gn-font-mono);
|
||||
font-size: 10px;
|
||||
font-weight: 650;
|
||||
line-height: 12px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-tab-label-part {
|
||||
min-width: 0;
|
||||
flex: 0 0 auto;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-tab-label-part-object {
|
||||
flex: 1 1 auto;
|
||||
color: var(--gn-fg-2);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-tab-label-part-connection {
|
||||
flex: 0 1 92px;
|
||||
color: var(--gn-fg-2);
|
||||
font-family: var(--gn-font-mono);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-tab-label-part-host,
|
||||
body[data-ui-version="v2"] .gn-v2-tab-label-part-database,
|
||||
body[data-ui-version="v2"] .gn-v2-tab-label-part-schema {
|
||||
flex: 0 2 auto;
|
||||
max-width: 112px;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-tab-kind-icon {
|
||||
display: inline-flex;
|
||||
color: var(--gn-accent);
|
||||
@@ -2834,6 +2961,7 @@ body[data-ui-version="v2"] .gn-v2-tab-kind-icon {
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-tab-kind {
|
||||
height: 17px;
|
||||
flex: 0 0 auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0 5px;
|
||||
@@ -2858,7 +2986,6 @@ body[data-ui-version="v2"] .gn-v2-tab-conn {
|
||||
body[data-ui-version="v2"] .gn-v2-tab-close {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin-left: auto;
|
||||
flex: 0 0 18px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
@@ -2877,6 +3004,59 @@ body[data-ui-version="v2"] .gn-v2-tab-close:hover {
|
||||
color: var(--gn-fg-1);
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-tab-context-menu-popup .ant-dropdown-menu {
|
||||
min-width: 188px;
|
||||
padding: 6px !important;
|
||||
border: 0.5px solid var(--gn-br-2) !important;
|
||||
border-radius: 12px !important;
|
||||
background: color-mix(in srgb, var(--gn-bg-panel) 94%, transparent) !important;
|
||||
box-shadow: 0 18px 44px rgba(15, 23, 42, 0.18), 0 0 0 1px rgba(15, 23, 42, 0.04) !important;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-tab-context-menu-popup .ant-dropdown-menu-item {
|
||||
min-height: 34px !important;
|
||||
padding: 7px 10px !important;
|
||||
border-radius: 8px !important;
|
||||
color: var(--gn-fg-2) !important;
|
||||
font-size: 13px !important;
|
||||
font-weight: 650 !important;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-tab-context-menu-popup .ant-dropdown-menu-item:hover {
|
||||
background: var(--gn-bg-active) !important;
|
||||
color: var(--gn-fg-1) !important;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-tab-context-menu-popup .ant-dropdown-menu-item-disabled {
|
||||
color: var(--gn-fg-5) !important;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-tab-context-menu-popup .ant-dropdown-menu-item-icon {
|
||||
color: var(--gn-info);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-tab-context-menu-popup .ant-dropdown-menu-item-divider {
|
||||
margin: 5px 4px !important;
|
||||
background: var(--gn-br-1) !important;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-tab-context-menu-popup .ant-dropdown-menu-title-content {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-tab-context-menu-popup .ant-dropdown-menu-item:first-child {
|
||||
color: var(--gn-fg-1) !important;
|
||||
background: color-mix(in srgb, var(--gn-info) 8%, transparent) !important;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-tab-context-menu-popup .ant-dropdown-menu-item:first-child:hover {
|
||||
background: color-mix(in srgb, var(--gn-info) 14%, transparent) !important;
|
||||
}
|
||||
|
||||
/* ─── V2 DataGrid: toolbar, smart filters, table, statusbar ─ */
|
||||
body[data-ui-version="v2"] .gn-v2-data-viewer {
|
||||
background: var(--gn-bg-panel);
|
||||
@@ -4509,15 +4689,72 @@ body[data-ui-version="v2"] .gn-v2-query-results {
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-nav {
|
||||
background: var(--gn-bg-panel-2);
|
||||
padding-left: 8px;
|
||||
min-height: 38px;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-nav .ant-tabs-tab {
|
||||
min-height: 34px;
|
||||
padding: 4px 10px !important;
|
||||
width: auto !important;
|
||||
min-width: 0 !important;
|
||||
max-width: 148px !important;
|
||||
height: 30px !important;
|
||||
min-height: 30px;
|
||||
margin: 4px 6px 4px 0 !important;
|
||||
padding: 0 9px !important;
|
||||
border: 0.5px solid var(--gn-br-1) !important;
|
||||
border-radius: 999px !important;
|
||||
background: color-mix(in srgb, var(--gn-bg-panel) 72%, transparent) !important;
|
||||
color: var(--gn-fg-3) !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-nav .ant-tabs-nav-list {
|
||||
align-items: stretch;
|
||||
align-items: center;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-nav .ant-tabs-tab-btn {
|
||||
width: auto !important;
|
||||
height: 100%;
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
font-size: 14px !important;
|
||||
line-height: 1 !important;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-query-results .query-result-tab-label {
|
||||
height: 100%;
|
||||
line-height: 1;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-query-results .query-result-tab-text {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-nav .ant-tabs-tab:hover {
|
||||
background: var(--gn-bg-hover) !important;
|
||||
color: var(--gn-fg-1) !important;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-nav .ant-tabs-tab.ant-tabs-tab-active {
|
||||
border-color: color-mix(in srgb, var(--gn-accent) 38%, var(--gn-br-1)) !important;
|
||||
background: color-mix(in srgb, var(--gn-accent) 10%, var(--gn-bg-panel)) !important;
|
||||
color: var(--gn-fg-1) !important;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-nav .ant-tabs-tab.ant-tabs-tab-active::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-query-results .query-result-tab-count {
|
||||
background: color-mix(in srgb, var(--gn-accent) 12%, var(--gn-bg-active));
|
||||
color: var(--gn-fg-2);
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-query-empty,
|
||||
|
||||
Reference in New Issue
Block a user