mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-09 08:13:29 +08:00
🐛 fix(query-editor): 优化工具栏长名称提示与片段弹窗布局
- SQL 编辑器连接和数据库选择器支持悬浮延迟显示完整名称 - SQL 片段管理嵌入工具中心时隐藏重复标题 - 缩小片段操作按钮并拆分滚动区域,避免底部内容被遮挡 - 补充工具栏和片段弹窗源码级回归断言
This commit is contained in:
@@ -7478,6 +7478,10 @@ describe('QueryEditor external SQL save', () => {
|
||||
expect(toolbarSource).toContain('gn-v2-query-toolbar-actions');
|
||||
expect(toolbarSource).toContain('gn-v2-query-toolbar-connection-select');
|
||||
expect(toolbarSource).toContain('gn-v2-query-toolbar-database-select');
|
||||
expect(toolbarSource).toContain('FULL_NAME_TOOLTIP_DELAY_SECONDS = 1');
|
||||
expect(toolbarSource).toContain('mouseEnterDelay={FULL_NAME_TOOLTIP_DELAY_SECONDS}');
|
||||
expect(toolbarSource).toContain('optionRender={(option) => renderFullNameSelectTooltip(option.data.fullName)}');
|
||||
expect(toolbarSource).toContain('labelRender={(option) => renderFullNameSelectTooltip(option.label ?? option.value)}');
|
||||
expect(toolbarSource).toContain('gn-v2-query-toolbar-max-rows-select');
|
||||
expect(toolbarSource).toContain('QueryEditorTransactionSettings');
|
||||
expect(transactionSettingsSource).toContain('gn-v2-query-toolbar-transaction-mode-select');
|
||||
@@ -7556,10 +7560,12 @@ describe('QueryEditor external SQL save', () => {
|
||||
expect(modalSource).toContain('data-sql-snippet-action-row="true"');
|
||||
expect(modalSource).toContain('data-sql-snippet-content-region="true"');
|
||||
expect(modalSource).toContain('data-sql-snippet-editor-scroll-region="true"');
|
||||
expect(modalSource).toContain("maxHeight: snippetModalBodyMaxHeight");
|
||||
expect(modalSource).toContain('maxHeight: embedded ? snippetModalEmbeddedBodyMaxHeight : snippetModalBodyMaxHeight');
|
||||
expect(modalSource).toContain('data-sql-snippet-syntax-reference-scroll-region="true"');
|
||||
expect(modalSource).toContain('data-sql-snippet-editor-panel-scroll-region="true"');
|
||||
expect(modalSource).toContain("flex: '0 0 auto'");
|
||||
expect(modalSource).toContain("size=\"large\"");
|
||||
expect(modalSource).toContain('minWidth: 96');
|
||||
expect(modalSource).toContain("size=\"middle\"");
|
||||
expect(modalSource).toContain('minWidth: 84');
|
||||
expect(modalSource).toContain('syntaxHelp');
|
||||
expect(modalSource).toContain("t('snippet_settings.syntax_reference.label')");
|
||||
expect(source).toContain('s.syntaxHelp || s.description || s.body');
|
||||
|
||||
@@ -2712,6 +2712,10 @@ describe('QueryEditor external SQL save', () => {
|
||||
expect(toolbarSource).toContain('gn-v2-query-toolbar-actions');
|
||||
expect(toolbarSource).toContain('gn-v2-query-toolbar-connection-select');
|
||||
expect(toolbarSource).toContain('gn-v2-query-toolbar-database-select');
|
||||
expect(toolbarSource).toContain('FULL_NAME_TOOLTIP_DELAY_SECONDS = 1');
|
||||
expect(toolbarSource).toContain('mouseEnterDelay={FULL_NAME_TOOLTIP_DELAY_SECONDS}');
|
||||
expect(toolbarSource).toContain('optionRender={(option) => renderFullNameSelectTooltip(option.data.fullName)}');
|
||||
expect(toolbarSource).toContain('labelRender={(option) => renderFullNameSelectTooltip(option.label ?? option.value)}');
|
||||
expect(toolbarSource).toContain('gn-v2-query-toolbar-max-rows-select');
|
||||
expect(toolbarSource).toContain('QueryEditorTransactionSettings');
|
||||
expect(transactionSettingsSource).toContain('gn-v2-query-toolbar-transaction-mode-select');
|
||||
@@ -2789,10 +2793,12 @@ describe('QueryEditor external SQL save', () => {
|
||||
expect(modalSource).toContain('data-sql-snippet-action-row="true"');
|
||||
expect(modalSource).toContain('data-sql-snippet-content-region="true"');
|
||||
expect(modalSource).toContain('data-sql-snippet-editor-scroll-region="true"');
|
||||
expect(modalSource).toContain("maxHeight: snippetModalBodyMaxHeight");
|
||||
expect(modalSource).toContain('maxHeight: embedded ? snippetModalEmbeddedBodyMaxHeight : snippetModalBodyMaxHeight');
|
||||
expect(modalSource).toContain('data-sql-snippet-syntax-reference-scroll-region="true"');
|
||||
expect(modalSource).toContain('data-sql-snippet-editor-panel-scroll-region="true"');
|
||||
expect(modalSource).toContain("flex: '0 0 auto'");
|
||||
expect(modalSource).toContain("size=\"large\"");
|
||||
expect(modalSource).toContain('minWidth: 96');
|
||||
expect(modalSource).toContain("size=\"middle\"");
|
||||
expect(modalSource).toContain('minWidth: 84');
|
||||
expect(modalSource).toContain('syntaxHelp');
|
||||
expect(modalSource).toContain("t('snippet_settings.syntax_reference.label')");
|
||||
expect(source).toContain('s.syntaxHelp || s.description || s.body');
|
||||
|
||||
@@ -79,4 +79,29 @@ describe('QueryEditorToolbar layout', () => {
|
||||
expect(transactionDelayCss).toContain('width: 104px !important;');
|
||||
expect(transactionDelayCss).toContain('flex: 0 0 104px !important;');
|
||||
});
|
||||
|
||||
it('shows delayed full-name tooltips for truncated connection and database selectors', () => {
|
||||
const toolbarSource = readFileSync(new URL('./QueryEditorToolbar.tsx', import.meta.url), 'utf8');
|
||||
const css = readV2ThemeCss();
|
||||
const connectionSelectSource = toolbarSource.slice(
|
||||
toolbarSource.indexOf('gn-v2-query-toolbar-connection-select'),
|
||||
toolbarSource.indexOf('gn-v2-query-toolbar-database-select'),
|
||||
);
|
||||
const databaseSelectSource = toolbarSource.slice(
|
||||
toolbarSource.indexOf('gn-v2-query-toolbar-database-select'),
|
||||
toolbarSource.indexOf('gn-v2-query-toolbar-max-rows-select'),
|
||||
);
|
||||
|
||||
expect(toolbarSource).toContain('FULL_NAME_TOOLTIP_DELAY_SECONDS = 1');
|
||||
expect(toolbarSource).toContain('mouseEnterDelay={FULL_NAME_TOOLTIP_DELAY_SECONDS}');
|
||||
expect(toolbarSource).toContain('renderFullNameSelectTooltip');
|
||||
expect(toolbarSource).toContain('gn-query-toolbar-select-full-name');
|
||||
expect(connectionSelectSource).toContain('optionRender={(option) => renderFullNameSelectTooltip(option.data.fullName)}');
|
||||
expect(connectionSelectSource).toContain('labelRender={(option) => renderFullNameSelectTooltip(option.label ?? option.value)}');
|
||||
expect(databaseSelectSource).toContain('optionRender={(option) => renderFullNameSelectTooltip(option.data.fullName)}');
|
||||
expect(databaseSelectSource).toContain('labelRender={(option) => renderFullNameSelectTooltip(option.label ?? option.value)}');
|
||||
expect(css).toContain('.gn-query-toolbar-select-full-name {');
|
||||
expect(css).toContain('text-overflow: ellipsis;');
|
||||
expect(css).toContain('white-space: nowrap;');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -55,6 +55,33 @@ type QueryEditorToolbarProps = {
|
||||
onAIAction: (action: "generate" | "explain" | "optimize" | "schema") => void;
|
||||
};
|
||||
|
||||
const FULL_NAME_TOOLTIP_DELAY_SECONDS = 1;
|
||||
|
||||
type FullNameSelectOption = {
|
||||
label: string;
|
||||
value: string;
|
||||
fullName: string;
|
||||
};
|
||||
|
||||
const renderFullNameSelectTooltip = (fullName: React.ReactNode) => {
|
||||
const fullNameText = String(fullName ?? "");
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={fullNameText}
|
||||
mouseEnterDelay={FULL_NAME_TOOLTIP_DELAY_SECONDS}
|
||||
placement="topLeft"
|
||||
>
|
||||
<span
|
||||
className="gn-query-toolbar-select-full-name"
|
||||
aria-label={fullNameText}
|
||||
>
|
||||
{fullNameText}
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const QueryEditorToolbar: React.FC<QueryEditorToolbarProps> = ({
|
||||
isV2Ui,
|
||||
currentConnectionId,
|
||||
@@ -89,6 +116,17 @@ const QueryEditorToolbar: React.FC<QueryEditorToolbarProps> = ({
|
||||
const i18n = useOptionalI18n();
|
||||
const t = i18n?.t ?? defaultTranslate;
|
||||
const baseMoreMenuItems = saveMoreMenuItems ?? [];
|
||||
const connectionSelectOptions: FullNameSelectOption[] =
|
||||
queryCapableConnections.map((connection) => ({
|
||||
label: connection.name,
|
||||
value: connection.id,
|
||||
fullName: connection.name,
|
||||
}));
|
||||
const databaseSelectOptions: FullNameSelectOption[] = dbList.map((db) => ({
|
||||
label: db,
|
||||
value: db,
|
||||
fullName: db,
|
||||
}));
|
||||
const toggleResultPanelShortcutLabel =
|
||||
toggleQueryResultsPanelShortcutBinding.enabled &&
|
||||
toggleQueryResultsPanelShortcutBinding.combo
|
||||
@@ -172,10 +210,10 @@ const QueryEditorToolbar: React.FC<QueryEditorToolbarProps> = ({
|
||||
placeholder={t("query_editor.placeholder.connection")}
|
||||
value={currentConnectionId}
|
||||
onChange={onConnectionChange}
|
||||
options={queryCapableConnections.map((c) => ({
|
||||
label: c.name,
|
||||
value: c.id,
|
||||
}))}
|
||||
options={connectionSelectOptions}
|
||||
optionFilterProp="label"
|
||||
optionRender={(option) => renderFullNameSelectTooltip(option.data.fullName)}
|
||||
labelRender={(option) => renderFullNameSelectTooltip(option.label ?? option.value)}
|
||||
showSearch
|
||||
/>
|
||||
<Select
|
||||
@@ -188,7 +226,10 @@ const QueryEditorToolbar: React.FC<QueryEditorToolbarProps> = ({
|
||||
placeholder={t("query_editor.placeholder.database")}
|
||||
value={currentDb}
|
||||
onChange={onDatabaseChange}
|
||||
options={dbList.map((db) => ({ label: db, value: db }))}
|
||||
options={databaseSelectOptions}
|
||||
optionFilterProp="label"
|
||||
optionRender={(option) => renderFullNameSelectTooltip(option.data.fullName)}
|
||||
labelRender={(option) => renderFullNameSelectTooltip(option.label ?? option.value)}
|
||||
showSearch
|
||||
/>
|
||||
<Tooltip title={t("query_editor.max_rows.tooltip")}>
|
||||
|
||||
@@ -225,7 +225,7 @@ const overlayTheme: OverlayWorkbenchTheme = {
|
||||
divider: '#eee',
|
||||
};
|
||||
|
||||
const renderModal = async () => {
|
||||
const renderModal = async (props: Partial<React.ComponentProps<typeof SnippetSettingsModal>> = {}) => {
|
||||
let renderer: ReturnType<typeof create>;
|
||||
|
||||
await act(async () => {
|
||||
@@ -240,6 +240,7 @@ const renderModal = async () => {
|
||||
onClose={() => undefined}
|
||||
darkMode={false}
|
||||
overlayTheme={overlayTheme}
|
||||
{...props}
|
||||
/>
|
||||
</I18nProvider>,
|
||||
);
|
||||
@@ -322,11 +323,30 @@ describe('SnippetSettingsModal i18n', () => {
|
||||
|
||||
it('keeps snippet editor content scrollable without clipping the action row', () => {
|
||||
expect(source).toContain("const snippetModalBodyMaxHeight = 'calc(100vh - 128px)';");
|
||||
expect(source).toContain("maxHeight: snippetModalBodyMaxHeight");
|
||||
expect(source).toContain("const snippetModalEmbeddedBodyMaxHeight = '100%';");
|
||||
expect(source).toContain("const snippetSyntaxReferenceMaxHeight = 'min(220px, 32vh)';");
|
||||
expect(source).toContain('maxHeight: embedded ? snippetModalEmbeddedBodyMaxHeight : snippetModalBodyMaxHeight');
|
||||
expect(source).toContain('data-sql-snippet-syntax-reference-scroll-region="true"');
|
||||
expect(source).toContain('data-sql-snippet-editor-panel-scroll-region="true"');
|
||||
expect(source).toContain('data-sql-snippet-content-region="true"');
|
||||
expect(source).toContain('data-sql-snippet-editor-scroll-region="true"');
|
||||
expect(source).toContain("overflowY: 'auto'");
|
||||
expect(source).toContain("flex: '0 0 auto'");
|
||||
expect(source).toContain('height: 220');
|
||||
expect(source).toContain('maxHeight: 260');
|
||||
});
|
||||
|
||||
it('lets the tool center provide the title when embedded', async () => {
|
||||
const renderer = await renderModal({ embedded: true });
|
||||
const root = renderer.root;
|
||||
|
||||
expect(() => root.findByProps({ className: 'gn-embedded-modal-header' })).toThrow();
|
||||
expect(getJsonText(renderer.toJSON())).toContain('Select a snippet on the left to edit, or click "New Snippet"');
|
||||
|
||||
const standaloneRenderer = await renderModal();
|
||||
const standaloneText = getJsonText(standaloneRenderer.toJSON());
|
||||
expect(standaloneText).toContain('Snippet Management');
|
||||
expect(standaloneText).toContain('Manage SQL snippets and prefix completion.');
|
||||
});
|
||||
|
||||
it('keeps the shell and feedback keys available in every locale', () => {
|
||||
@@ -384,7 +404,9 @@ describe('SnippetSettingsModal i18n', () => {
|
||||
});
|
||||
|
||||
const contentRegion = renderer.root.findByProps({ 'data-sql-snippet-content-region': 'true' });
|
||||
const editorPanelScrollRegion = renderer.root.findByProps({ 'data-sql-snippet-editor-panel-scroll-region': 'true' });
|
||||
const editorScrollRegion = renderer.root.findByProps({ 'data-sql-snippet-editor-scroll-region': 'true' });
|
||||
const syntaxReferenceScrollRegion = renderer.root.findByProps({ 'data-sql-snippet-syntax-reference-scroll-region': 'true' });
|
||||
const actionRow = renderer.root.findByProps({ 'data-sql-snippet-action-row': 'true' });
|
||||
|
||||
expect(contentRegion.props.style).toMatchObject({
|
||||
@@ -392,14 +414,83 @@ describe('SnippetSettingsModal i18n', () => {
|
||||
minHeight: 0,
|
||||
overflow: 'hidden',
|
||||
});
|
||||
expect(editorScrollRegion.props.style).toMatchObject({
|
||||
flex: 1,
|
||||
expect(editorPanelScrollRegion.props.style).toMatchObject({
|
||||
height: '100%',
|
||||
minHeight: 0,
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
overscrollBehavior: 'contain',
|
||||
});
|
||||
expect(editorScrollRegion.props.style).toMatchObject({
|
||||
flex: '0 0 auto',
|
||||
minHeight: 0,
|
||||
marginTop: 10,
|
||||
});
|
||||
expect(syntaxReferenceScrollRegion.props.style).toMatchObject({
|
||||
maxHeight: 'min(220px, 32vh)',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
overscrollBehavior: 'contain',
|
||||
});
|
||||
expect(actionRow.props.style).toMatchObject({
|
||||
flex: '0 0 auto',
|
||||
gap: 10,
|
||||
justifyContent: 'flex-end',
|
||||
paddingTop: 8,
|
||||
marginTop: 8,
|
||||
});
|
||||
});
|
||||
|
||||
it('uses the full embedded body height and keeps the action row outside the scrollable content', async () => {
|
||||
const renderer = await renderModal({ embedded: true });
|
||||
|
||||
const newButton = renderer.root.findAll((node: any) => node.type === 'button' && getText(node).includes('New Snippet'))[0];
|
||||
|
||||
await act(async () => {
|
||||
newButton.props.onClick();
|
||||
});
|
||||
|
||||
const embeddedBody = renderer.root.findByProps({ className: 'gn-embedded-modal-body' });
|
||||
const contentRegion = renderer.root.findByProps({ 'data-sql-snippet-content-region': 'true' });
|
||||
const actionRow = renderer.root.findByProps({ 'data-sql-snippet-action-row': 'true' });
|
||||
|
||||
expect(embeddedBody.props.style).toMatchObject({
|
||||
height: '100%',
|
||||
maxHeight: '100%',
|
||||
minHeight: 0,
|
||||
overflow: 'hidden',
|
||||
});
|
||||
expect(contentRegion.props.style).toMatchObject({
|
||||
flex: '1 1 0',
|
||||
minHeight: 0,
|
||||
overflow: 'hidden',
|
||||
});
|
||||
expect(actionRow.props.style).toMatchObject({
|
||||
flex: '0 0 auto',
|
||||
gap: 10,
|
||||
paddingTop: 8,
|
||||
marginTop: 8,
|
||||
});
|
||||
});
|
||||
|
||||
it('uses compact action buttons so the footer does not consume editor height', async () => {
|
||||
const renderer = await renderModal({ embedded: true, onBack: () => undefined });
|
||||
|
||||
const newButton = renderer.root.findAll((node: any) => node.type === 'button' && getText(node).includes('New Snippet'))[0];
|
||||
|
||||
await act(async () => {
|
||||
newButton.props.onClick();
|
||||
});
|
||||
|
||||
const saveButton = renderer.root.findAll((node: any) => node.type === 'button' && getText(node).includes('Save'))[0];
|
||||
const closeButton = renderer.root.findAll((node: any) => node.type === 'button' && getText(node).includes('Close'))[0];
|
||||
const backButton = renderer.root.findAll((node: any) => node.type === 'button' && getText(node).includes('Back'))[0];
|
||||
|
||||
expect(saveButton.props.size).toBe('middle');
|
||||
expect(saveButton.props.style).toMatchObject({ minWidth: 84 });
|
||||
expect(closeButton.props.size).toBe('middle');
|
||||
expect(closeButton.props.style).toMatchObject({ minWidth: 84 });
|
||||
expect(backButton.props.size).toBe('middle');
|
||||
expect(backButton.props.style).toMatchObject({ minWidth: 104 });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -78,6 +78,8 @@ export default function SnippetSettingsModal({
|
||||
const selectedBg = darkMode ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.04)';
|
||||
const newSnippetAction = t('snippet_settings.action.new');
|
||||
const snippetModalBodyMaxHeight = 'calc(100vh - 128px)';
|
||||
const snippetModalEmbeddedBodyMaxHeight = '100%';
|
||||
const snippetSyntaxReferenceMaxHeight = 'min(220px, 32vh)';
|
||||
|
||||
const sortedSnippets = useMemo(
|
||||
() => [...sqlSnippets].sort((a, b) => a.prefix.localeCompare(b.prefix)),
|
||||
@@ -193,7 +195,20 @@ export default function SnippetSettingsModal({
|
||||
key: 'syntax',
|
||||
label: t('snippet_settings.syntax_reference.label'),
|
||||
children: (
|
||||
<div style={{ fontSize: 12, lineHeight: 1.8, color: mutedColor, fontFamily: 'var(--gn-font-mono)' }}>
|
||||
<div
|
||||
data-sql-snippet-syntax-reference-scroll-region="true"
|
||||
style={{
|
||||
maxHeight: snippetSyntaxReferenceMaxHeight,
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
overscrollBehavior: 'contain',
|
||||
paddingRight: 6,
|
||||
fontSize: 12,
|
||||
lineHeight: 1.8,
|
||||
color: mutedColor,
|
||||
fontFamily: 'var(--gn-font-mono)',
|
||||
}}
|
||||
>
|
||||
<div>{t('snippet_settings.syntax_reference.first_tabstop')}</div>
|
||||
<div>{t('snippet_settings.syntax_reference.second_tabstop')}</div>
|
||||
<div>{t('snippet_settings.syntax_reference.final_cursor')}</div>
|
||||
@@ -211,14 +226,14 @@ export default function SnippetSettingsModal({
|
||||
),
|
||||
},
|
||||
],
|
||||
[draft.syntaxHelp, mutedColor, t, textColor],
|
||||
[draft.syntaxHelp, mutedColor, snippetSyntaxReferenceMaxHeight, t, textColor],
|
||||
);
|
||||
|
||||
const showEditor = isCreating || selectedSnippet;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={
|
||||
title={embedded ? null : (
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 12, minWidth: 0 }}>
|
||||
<div
|
||||
style={{
|
||||
@@ -241,9 +256,10 @@ export default function SnippetSettingsModal({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
open={open}
|
||||
embedded={embedded}
|
||||
closable={embedded ? false : undefined}
|
||||
onCancel={onClose}
|
||||
width={820}
|
||||
styles={{
|
||||
@@ -254,7 +270,8 @@ export default function SnippetSettingsModal({
|
||||
paddingBottom: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
maxHeight: snippetModalBodyMaxHeight,
|
||||
height: embedded ? '100%' : undefined,
|
||||
maxHeight: embedded ? snippetModalEmbeddedBodyMaxHeight : snippetModalBodyMaxHeight,
|
||||
minHeight: 0,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
@@ -266,7 +283,7 @@ export default function SnippetSettingsModal({
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: 16,
|
||||
flex: '1 1 420px',
|
||||
flex: embedded ? '1 1 0' : '1 1 420px',
|
||||
minHeight: 0,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
@@ -351,20 +368,23 @@ export default function SnippetSettingsModal({
|
||||
</div>
|
||||
|
||||
{/* Right: editor */}
|
||||
<div style={{ flex: 1, minWidth: 0, minHeight: 0, display: 'flex' }}>
|
||||
<div style={{ flex: 1, minWidth: 0, minHeight: 0, display: 'flex', overflow: 'hidden' }}>
|
||||
{showEditor ? (
|
||||
<div
|
||||
style={{
|
||||
...panelStyle,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 12,
|
||||
height: '100%',
|
||||
minHeight: 0,
|
||||
overflow: 'hidden',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
overscrollBehavior: 'contain',
|
||||
paddingRight: 12,
|
||||
}}
|
||||
data-sql-snippet-editor-panel-scroll-region="true"
|
||||
>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<div style={{ display: 'flex', gap: 12, flex: '0 0 auto' }}>
|
||||
<div style={{ flex: 0.4 }}>
|
||||
<div style={{ fontSize: 12, color: mutedColor, marginBottom: 4 }}>{t('snippet_settings.field.prefix.label')}</div>
|
||||
<Input
|
||||
@@ -389,7 +409,7 @@ export default function SnippetSettingsModal({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div style={{ flex: '0 0 auto', marginTop: 10 }}>
|
||||
<div style={{ fontSize: 12, color: mutedColor, marginBottom: 4 }}>{t('snippet_settings.field.description.label')}</div>
|
||||
<Input
|
||||
value={draft.description || ''}
|
||||
@@ -403,12 +423,11 @@ export default function SnippetSettingsModal({
|
||||
<div
|
||||
data-sql-snippet-editor-scroll-region="true"
|
||||
style={{
|
||||
flex: 1,
|
||||
flex: '0 0 auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
minHeight: 0,
|
||||
overflowY: 'auto',
|
||||
paddingRight: 4,
|
||||
marginTop: 10,
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: 12, color: mutedColor, marginBottom: 4 }}>{t('snippet_settings.field.body.label')}</div>
|
||||
@@ -417,8 +436,9 @@ export default function SnippetSettingsModal({
|
||||
onChange={(e) => setDraft((d) => ({ ...d, body: e.target.value }))}
|
||||
placeholder={'SELECT ${1:columns} FROM ${2:table_name}$0;'}
|
||||
style={{
|
||||
flex: 1,
|
||||
minHeight: 120,
|
||||
height: 220,
|
||||
minHeight: 160,
|
||||
maxHeight: 260,
|
||||
fontFamily: 'var(--gn-font-mono)',
|
||||
fontSize: 13,
|
||||
resize: 'none',
|
||||
@@ -428,7 +448,7 @@ export default function SnippetSettingsModal({
|
||||
size="small"
|
||||
defaultActiveKey={['snippet-help']}
|
||||
items={syntaxHelpItems}
|
||||
style={{ marginTop: 8, background: 'transparent' }}
|
||||
style={{ marginTop: 8, background: 'transparent', flex: '0 0 auto' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -454,11 +474,11 @@ export default function SnippetSettingsModal({
|
||||
style={{
|
||||
display: 'flex',
|
||||
flex: '0 0 auto',
|
||||
gap: 12,
|
||||
gap: 10,
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
paddingTop: 12,
|
||||
marginTop: 12,
|
||||
paddingTop: 8,
|
||||
marginTop: 8,
|
||||
borderTop: overlayTheme.sectionBorder,
|
||||
}}
|
||||
>
|
||||
@@ -468,7 +488,7 @@ export default function SnippetSettingsModal({
|
||||
description={t('snippet_settings.confirm.reset.description')}
|
||||
onConfirm={() => handleReset(draft.id)}
|
||||
>
|
||||
<Button icon={<UndoOutlined />} size="large" style={{ minWidth: 118 }}>
|
||||
<Button icon={<UndoOutlined />} size="middle" style={{ minWidth: 104 }}>
|
||||
{t('snippet_settings.action.reset')}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
@@ -479,21 +499,21 @@ export default function SnippetSettingsModal({
|
||||
description={t('snippet_settings.confirm.delete.description')}
|
||||
onConfirm={() => handleDelete(draft.id)}
|
||||
>
|
||||
<Button danger icon={<DeleteOutlined />} size="large" style={{ minWidth: 96 }}>
|
||||
<Button danger icon={<DeleteOutlined />} size="middle" style={{ minWidth: 84 }}>
|
||||
{t('snippet_settings.action.delete')}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
)}
|
||||
{showEditor && (
|
||||
<Button type="primary" icon={<SaveOutlined />} size="large" style={{ minWidth: 96 }} onClick={handleSave}>
|
||||
<Button type="primary" icon={<SaveOutlined />} size="middle" style={{ minWidth: 84 }} onClick={handleSave}>
|
||||
{t('snippet_settings.action.save')}
|
||||
</Button>
|
||||
)}
|
||||
<Button size="large" style={{ minWidth: 96 }} onClick={onClose}>
|
||||
<Button size="middle" style={{ minWidth: 84 }} onClick={onClose}>
|
||||
{t('snippet_settings.action.close')}
|
||||
</Button>
|
||||
{onBack ? (
|
||||
<Button size="large" style={{ minWidth: 124 }} onClick={onBack}>
|
||||
<Button size="middle" style={{ minWidth: 104 }} onClick={onBack}>
|
||||
{t('common.back_to_previous')}
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
@@ -148,6 +148,14 @@ body[data-ui-version="v2"] .gn-v2-query-toolbar-database-select {
|
||||
max-width: 220px;
|
||||
}
|
||||
|
||||
.gn-query-toolbar-select-full-name {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-query-toolbar-max-rows-select {
|
||||
width: 132px !important;
|
||||
flex: 0 0 132px !important;
|
||||
|
||||
Reference in New Issue
Block a user