diff --git a/frontend/src/components/DataGrid.layout.test.tsx b/frontend/src/components/DataGrid.layout.test.tsx index 196fb6fb..681746db 100644 --- a/frontend/src/components/DataGrid.layout.test.tsx +++ b/frontend/src/components/DataGrid.layout.test.tsx @@ -933,6 +933,7 @@ describe('DataGrid layout', () => { 'data_grid.record_view.empty': 'No rows label', 'data_grid.record_view.json_record_count': `${params?.count} JSON rows label`, 'data_grid.record_view.edit_json': 'Edit JSON label', + 'data_grid.record_view.back_to_table': 'Back to table label', 'data_grid.record_view.previous': 'Previous label', 'data_grid.record_view.next': 'Next label', 'data_grid.record_view.record_position': `Record label ${params?.current} of ${params?.total}`, @@ -1054,10 +1055,12 @@ describe('DataGrid layout', () => { jsonViewText="[]" translate={translate} onOpenJsonEditor={() => {}} + onReturnToTable={() => {}} />, ); expect(jsonRecordMarkup).toContain('5 JSON rows label'); expect(jsonRecordMarkup).toContain('Edit JSON label'); + expect(jsonRecordMarkup).toContain('Back to table label'); expect(jsonRecordMarkup).not.toContain('data_grid.record_view'); const textRecordMarkup = renderToStaticMarkup( @@ -1076,6 +1079,7 @@ describe('DataGrid layout', () => { onPrev={() => {}} onNext={() => {}} onEditCurrent={() => {}} + onReturnToTable={() => {}} formatTextViewValue={(value) => String(value)} />, ); @@ -1083,6 +1087,7 @@ describe('DataGrid layout', () => { expect(textRecordMarkup).toContain('Next label'); expect(textRecordMarkup).toContain('Record label 1 of 2'); expect(textRecordMarkup).toContain('Edit current label'); + expect(textRecordMarkup).toContain('Back to table label'); expect(textRecordMarkup).toContain('raw_sql'); expect(textRecordMarkup).toContain('TYPE varchar(128)'); expect(textRecordMarkup).toContain('COMMENT SQL text payload'); @@ -1105,6 +1110,7 @@ describe('DataGrid layout', () => { onPrev={() => {}} onNext={() => {}} onEditCurrent={() => {}} + onReturnToTable={() => {}} formatTextViewValue={(value) => String(value)} />, ); @@ -1415,9 +1421,13 @@ describe('DataGrid layout', () => { expect(markup).toContain('align-items:center'); expect(markup).toContain('min-height:var(--gonavi-header-min-height, 40px)'); expect(markup).toContain('text-align:center'); - expect(markup).toContain('padding-inline:2'); + expect(markup).toContain('padding:0'); expect(markup).toContain('vertical-align:middle'); expect(markup).toContain('data-grid-row-number="true"'); + expect(markup).toContain('data-grid-row-number-action="true"'); + expect(markup).toContain('display:flex'); + expect(markup).toContain('width:100%'); + expect(markup).toContain('height:100%'); expect(markup).toContain('width:36'); // ant Table fixed 列会渲染 fix 相关 class expect(markup.includes('ant-table-cell-fix') || markup.includes('fixed')).toBe(true); diff --git a/frontend/src/components/DataGrid.tsx b/frontend/src/components/DataGrid.tsx index 5022637b..539f12db 100644 --- a/frontend/src/components/DataGrid.tsx +++ b/frontend/src/components/DataGrid.tsx @@ -3322,6 +3322,10 @@ const DataGrid: React.FC = ({ return ROW_NUMBER_COLUMN_WIDTH; }, [columnWidths]); + const handleRowNumberDoubleClick = useCallback((index: number) => { + handleViewModeChange('text', { textRecordIndex: index }); + }, [handleViewModeChange]); + const rowNumberColumn = useMemo>(() => ({ title: (
= ({ minWidth: 28, }, }), - onCell: () => ({ + onCell: (_record: Item, index?: number) => ({ + 'data-grid-row-number-action': 'true', style: { width: rowNumberColumnWidth, minWidth: 28, - paddingInline: 2, + padding: 0, textAlign: 'center' as const, + }, + onDoubleClick: (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + handleRowNumberDoubleClick(index ?? 0); }, }), render: (_value: unknown, _record: Item, index: number) => { @@ -3381,12 +3391,31 @@ const DataGrid: React.FC = ({ const pageSize = Math.max(1, Number(pagination?.pageSize) || 0); const offset = pageSize > 0 ? (currentPage - 1) * pageSize : 0; return ( - - {offset + index + 1} - + + { + event.preventDefault(); + event.stopPropagation(); + handleRowNumberDoubleClick(index); + }} + > + {offset + index + 1} + + ); }, - }), [handleResizeAutoFit, handleResizeStart, pagination?.current, pagination?.pageSize, rowNumberColumnWidth]); + }), [handleResizeAutoFit, handleResizeStart, handleRowNumberDoubleClick, pagination?.current, pagination?.pageSize, rowNumberColumnWidth, translateDataGrid]); const tableColumns = useMemo(() => { const baseColumns = resolvedShowRowNumberColumn diff --git a/frontend/src/components/DataGridRecordViews.tsx b/frontend/src/components/DataGridRecordViews.tsx index 2b05f2a7..c3cf848b 100644 --- a/frontend/src/components/DataGridRecordViews.tsx +++ b/frontend/src/components/DataGridRecordViews.tsx @@ -12,6 +12,7 @@ interface DataGridJsonViewProps { jsonViewText: string; translate?: DataGridRecordViewTranslate; onOpenJsonEditor: () => void; + onReturnToTable: () => void; } export const DataGridJsonView: React.FC = ({ @@ -21,6 +22,7 @@ export const DataGridJsonView: React.FC = ({ jsonViewText, translate = defaultTranslate, onOpenJsonEditor, + onReturnToTable, }) => (
@@ -34,6 +36,9 @@ export const DataGridJsonView: React.FC = ({ {translate('data_grid.record_view.edit_json')} )} +
void; onNext: () => void; onEditCurrent: () => void; + onReturnToTable: () => void; formatTextViewValue: (value: any, columnName?: string) => string; } @@ -90,6 +96,7 @@ export const DataGridTextView: React.FC = ({ onPrev, onNext, onEditCurrent, + onReturnToTable, formatTextViewValue, }) => { const metaTextColor = darkMode ? 'rgba(255,255,255,0.52)' : 'rgba(0,0,0,0.48)'; @@ -113,6 +120,9 @@ export const DataGridTextView: React.FC = ({ {translate('data_grid.record_view.edit_current')} )} +
{currentTextRow ? displayOutputColumnNames.map((col) => { diff --git a/frontend/src/components/DataGridShell.tsx b/frontend/src/components/DataGridShell.tsx index 804ddba7..dd7032cb 100644 --- a/frontend/src/components/DataGridShell.tsx +++ b/frontend/src/components/DataGridShell.tsx @@ -852,6 +852,7 @@ const renderDataTableView = () => ( canModifyData={canModifyData} jsonViewText={jsonViewText} translate={translateDataGrid} + onReturnToTable={() => handleViewModeChange('table')} onOpenJsonEditor={handleOpenJsonEditor} /> ) : ( @@ -867,6 +868,7 @@ const renderDataTableView = () => ( showColumnType={showColumnType} showColumnComment={showColumnComment} translate={translateDataGrid} + onReturnToTable={() => handleViewModeChange('table')} onPrev={() => setTextRecordIndex((i: number) => Math.max(0, i - 1))} onNext={() => setTextRecordIndex((i: number) => Math.min(textViewRows.length - 1, i + 1))} onEditCurrent={openCurrentViewRowEditor} diff --git a/frontend/src/components/dataGridStyles.ts b/frontend/src/components/dataGridStyles.ts index 8e2d3ce7..f615a38d 100644 --- a/frontend/src/components/dataGridStyles.ts +++ b/frontend/src/components/dataGridStyles.ts @@ -386,6 +386,14 @@ export const buildDataGridCssText = ({ .${gridId} .data-grid-row-number-cell { text-align: center !important; overflow: hidden !important; + padding: 0 !important; + } + .${gridId} .ant-table-tbody-virtual-holder .ant-table-row > .ant-table-cell.data-grid-row-number-cell, + .${gridId} .ant-table-tbody .ant-table-row > .ant-table-cell.data-grid-row-number-cell, + .${gridId} .ant-table-header th.data-grid-row-number-cell, + body[data-ui-version="v2"] .${gridId}.gn-v2-data-grid .ant-table-tbody-virtual-holder .ant-table-row > .ant-table-cell.data-grid-row-number-cell, + body[data-ui-version="v2"] .${gridId}.gn-v2-data-grid .ant-table-tbody .ant-table-row > .ant-table-cell.data-grid-row-number-cell { + padding: 0 !important; } .${gridId} .ant-table-thead > tr:first-child > th:first-child, diff --git a/frontend/src/components/useDataGridDdlView.i18n.test.tsx b/frontend/src/components/useDataGridDdlView.i18n.test.tsx index c0b88810..2571b0f9 100644 --- a/frontend/src/components/useDataGridDdlView.i18n.test.tsx +++ b/frontend/src/components/useDataGridDdlView.i18n.test.tsx @@ -99,4 +99,24 @@ describe('useDataGridDdlView i18n', () => { expect(messageApi.error).toHaveBeenCalledWith('ORA-31603: object "USERS" not found'); }); + + it('honors an explicit text row when opening from the row number', () => { + const setTextRecordIndex = vi.fn(); + renderHook({ + selectedRowKeys: ['row-10'], + mergedDisplayDataRef: { + current: [ + { __gonavi_row_key__: 'row-8' }, + { __gonavi_row_key__: 'row-10' }, + ], + }, + setTextRecordIndex, + }); + + act(() => { + controller?.handleViewModeChange('text', { textRecordIndex: 0 }); + }); + + expect(setTextRecordIndex).toHaveBeenCalledWith(0); + }); }); diff --git a/frontend/src/components/useDataGridDdlView.ts b/frontend/src/components/useDataGridDdlView.ts index 3190d69d..1d4ab283 100644 --- a/frontend/src/components/useDataGridDdlView.ts +++ b/frontend/src/components/useDataGridDdlView.ts @@ -122,7 +122,7 @@ export interface UseDataGridDdlViewResult { ddlRequestSeqRef: React.MutableRefObject; isTableSurfaceActive: boolean; handleOpenTableDdl: (options?: { asView?: boolean }) => Promise; - handleViewModeChange: (nextMode: GridViewMode) => void; + handleViewModeChange: (nextMode: GridViewMode, options?: { textRecordIndex?: number }) => void; handleDdlSidebarResizeStart: (event: React.MouseEvent) => void; resetDdlViewState: () => void; closeDdlView: () => void; @@ -291,7 +291,7 @@ export const useDataGridDdlView = ({ void handleOpenTableDdl({ asView: true }); }, [canRestoreSharedDdlView, ddlContextKey, handleOpenTableDdl, hasPendingLocalTableViewRequest, isActive]); - const handleViewModeChange = React.useCallback((nextMode: GridViewMode) => { + const handleViewModeChange = React.useCallback((nextMode: GridViewMode, options?: { textRecordIndex?: number }) => { setSuppressSharedDdlView(false); if ((nextMode === 'fields' || nextMode === 'ddl' || nextMode === 'er' || nextMode === 'sqlLog') && !isV2Ui) { setSharedDdlViewOpen(false); @@ -317,11 +317,17 @@ export const useDataGridDdlView = ({ } if (nextMode === 'text') { - const selectedKey = selectedRowKeys[0]; - if (selectedKey !== undefined) { - const idx = mergedDisplayDataRef.current.findIndex((row) => rowKeyStr(row?.__gonavi_row_key__) === rowKeyStr(selectedKey)); - if (idx >= 0) { - setTextRecordIndex(idx); + const explicitIndex = options?.textRecordIndex; + if (explicitIndex !== undefined && Number.isFinite(explicitIndex)) { + const maxIndex = Math.max(0, mergedDisplayDataRef.current.length - 1); + setTextRecordIndex(Math.max(0, Math.min(Math.trunc(explicitIndex), maxIndex))); + } else { + const selectedKey = selectedRowKeys[0]; + if (selectedKey !== undefined) { + const idx = mergedDisplayDataRef.current.findIndex((row) => rowKeyStr(row?.__gonavi_row_key__) === rowKeyStr(selectedKey)); + if (idx >= 0) { + setTextRecordIndex(idx); + } } } } diff --git a/shared/i18n/de-DE.json b/shared/i18n/de-DE.json index bdf81e4a..fe9fd97a 100644 --- a/shared/i18n/de-DE.json +++ b/shared/i18n/de-DE.json @@ -3997,6 +3997,7 @@ "data_grid.ai_insight.prompt": "Analysiere bitte die folgenden Abfrageergebnisse (die ersten {{count}} Beispieldatensätze):\n```json\n{{json}}\n```\n\nAnalysiere Datenmerkmale, erkenne Muster oder liefere geschäftliche Erkenntnisse.", "data_grid.ai_prompt.analyze_page": "Analysiere bitte die folgenden Abfrageergebnisse (die ersten {{count}} Beispielzeilen):\n```json\n{{json}}\n```\n\nAnalysiere Datenmerkmale, erkenne Muster oder liefere geschäftliche Erkenntnisse.", "data_grid.aria.row_number": "Zeilennummer", + "data_grid.row_number.double_click_to_view": "Doppelklicken, um diese Zeile als Text anzuzeigen", "data_grid.batch_fill.set_null": "Auf NULL setzen", "data_grid.batch_fill.title": "Stapelfüllung ({{count}} Zellen)", "data_grid.batch_fill.value_placeholder": "Einzufüllenden Wert eingeben", @@ -4315,6 +4316,7 @@ "data_grid.preview_sql.title": "Änderungsvorschau", "data_grid.record_view.edit_current": "Aktuellen Datensatz bearbeiten", "data_grid.record_view.edit_json": "JSON bearbeiten", + "data_grid.record_view.back_to_table": "Zur Tabelle", "data_grid.record_view.empty": "Keine Daten in der aktuellen Ergebnismenge", "data_grid.record_view.json_record_count": "Aktuelle Ergebnismenge: {{count}} Datensätze", "data_grid.record_view.next": "Weiter", diff --git a/shared/i18n/en-US.json b/shared/i18n/en-US.json index b80b0161..e527e6d1 100644 --- a/shared/i18n/en-US.json +++ b/shared/i18n/en-US.json @@ -3997,6 +3997,7 @@ "data_grid.ai_insight.prompt": "Please analyze the following query result data (first {{count}} sample rows):\n```json\n{{json}}\n```\n\nAnalyze data characteristics, find patterns, or provide business insights.", "data_grid.ai_prompt.analyze_page": "Please analyze the following query result data (first {{count}} sample rows):\n```json\n{{json}}\n```\n\nPlease analyze data characteristics, find patterns, or provide business insights.", "data_grid.aria.row_number": "Row number", + "data_grid.row_number.double_click_to_view": "Double-click to view this row as text", "data_grid.batch_fill.set_null": "Set to NULL", "data_grid.batch_fill.title": "Batch fill ({{count}} cells)", "data_grid.batch_fill.value_placeholder": "Enter the value to fill", @@ -4315,6 +4316,7 @@ "data_grid.preview_sql.title": "Change preview", "data_grid.record_view.edit_current": "Edit current record", "data_grid.record_view.edit_json": "Edit JSON", + "data_grid.record_view.back_to_table": "Back to table", "data_grid.record_view.empty": "No data in the current result set", "data_grid.record_view.json_record_count": "Current result set: {{count}} records", "data_grid.record_view.next": "Next", diff --git a/shared/i18n/ja-JP.json b/shared/i18n/ja-JP.json index 3c779539..7fb1c26c 100644 --- a/shared/i18n/ja-JP.json +++ b/shared/i18n/ja-JP.json @@ -3997,6 +3997,7 @@ "data_grid.ai_insight.prompt": "次のクエリ結果データを分析してください(先頭 {{count}} 件のサンプル):\n```json\n{{json}}\n```\n\nデータの特徴、パターン、または業務上の洞察を分析してください。", "data_grid.ai_prompt.analyze_page": "次のクエリ結果データ(先頭 {{count}} 件のサンプル行)を分析してください:\n```json\n{{json}}\n```\n\nデータの特徴、傾向、またはビジネス上の示唆を分析してください。", "data_grid.aria.row_number": "行番号", + "data_grid.row_number.double_click_to_view": "ダブルクリックしてこの行をテキストで表示", "data_grid.batch_fill.set_null": "NULL に設定", "data_grid.batch_fill.title": "一括入力({{count}} セル)", "data_grid.batch_fill.value_placeholder": "入力する値を指定", @@ -4315,6 +4316,7 @@ "data_grid.preview_sql.title": "変更プレビュー", "data_grid.record_view.edit_current": "現在のレコードを編集", "data_grid.record_view.edit_json": "JSON を編集", + "data_grid.record_view.back_to_table": "テーブルに戻る", "data_grid.record_view.empty": "現在の結果セットにデータはありません", "data_grid.record_view.json_record_count": "現在の結果セット: {{count}} 件のレコード", "data_grid.record_view.next": "次のレコード", diff --git a/shared/i18n/ru-RU.json b/shared/i18n/ru-RU.json index a861e0af..a1abc4ff 100644 --- a/shared/i18n/ru-RU.json +++ b/shared/i18n/ru-RU.json @@ -3997,6 +3997,7 @@ "data_grid.ai_insight.prompt": "Проанализируй следующие результаты запроса (первые {{count}} строк примера):\n```json\n{{json}}\n```\n\nПроанализируй характеристики данных, найди закономерности или предложи бизнес-инсайты.", "data_grid.ai_prompt.analyze_page": "Проанализируй следующие данные результата запроса (первые {{count}} строк-образцов):\n```json\n{{json}}\n```\n\nОпредели особенности данных, найди закономерности или дай бизнес-выводы.", "data_grid.aria.row_number": "Номер строки", + "data_grid.row_number.double_click_to_view": "Дважды щёлкните, чтобы просмотреть строку как текст", "data_grid.batch_fill.set_null": "Установить NULL", "data_grid.batch_fill.title": "Массовое заполнение ({{count}} ячеек)", "data_grid.batch_fill.value_placeholder": "Введите значение для заполнения", @@ -4315,6 +4316,7 @@ "data_grid.preview_sql.title": "Предпросмотр изменений", "data_grid.record_view.edit_current": "Редактировать текущую запись", "data_grid.record_view.edit_json": "Редактировать JSON", + "data_grid.record_view.back_to_table": "Вернуться к таблице", "data_grid.record_view.empty": "В текущем наборе результатов нет данных", "data_grid.record_view.json_record_count": "Текущий набор результатов: {{count}} записей", "data_grid.record_view.next": "Следующая", diff --git a/shared/i18n/zh-CN.json b/shared/i18n/zh-CN.json index 0fd45584..64845898 100644 --- a/shared/i18n/zh-CN.json +++ b/shared/i18n/zh-CN.json @@ -3997,6 +3997,7 @@ "data_grid.ai_insight.prompt": "请帮我分析以下查询结果数据(取前 {{count}} 条示例):\n```json\n{{json}}\n```\n\n请分析数据特征、发现规律,或者给出一些业务上的洞察。", "data_grid.ai_prompt.analyze_page": "请分析以下查询结果数据(前 {{count}} 行样本):\n```json\n{{json}}\n```\n\n请分析数据特征、发现规律,或提供业务洞察。", "data_grid.aria.row_number": "行号", + "data_grid.row_number.double_click_to_view": "双击以文本查看此行", "data_grid.batch_fill.set_null": "设置为 NULL", "data_grid.batch_fill.title": "批量填充({{count}} 个单元格)", "data_grid.batch_fill.value_placeholder": "输入要填充的值", @@ -4315,6 +4316,7 @@ "data_grid.preview_sql.title": "变更预览", "data_grid.record_view.edit_current": "编辑当前记录", "data_grid.record_view.edit_json": "编辑 JSON", + "data_grid.record_view.back_to_table": "返回表格", "data_grid.record_view.empty": "当前结果集无数据", "data_grid.record_view.json_record_count": "当前结果集 {{count}} 条记录", "data_grid.record_view.next": "下一条", diff --git a/shared/i18n/zh-TW.json b/shared/i18n/zh-TW.json index 43c31d7d..8c8ea882 100644 --- a/shared/i18n/zh-TW.json +++ b/shared/i18n/zh-TW.json @@ -3997,6 +3997,7 @@ "data_grid.ai_insight.prompt": "請幫我分析以下查詢結果資料(取前 {{count}} 筆範例):\n```json\n{{json}}\n```\n\n請分析資料特徵、找出規律,或提供一些業務洞察。", "data_grid.ai_prompt.analyze_page": "請分析以下查詢結果資料(前 {{count}} 列樣本):\n```json\n{{json}}\n```\n\n請分析資料特徵、發現規律,或提供業務洞察。", "data_grid.aria.row_number": "行號", + "data_grid.row_number.double_click_to_view": "雙擊以文字檢視此列", "data_grid.batch_fill.set_null": "設為 NULL", "data_grid.batch_fill.title": "批次填入({{count}} 個儲存格)", "data_grid.batch_fill.value_placeholder": "輸入要填入的值", @@ -4315,6 +4316,7 @@ "data_grid.preview_sql.title": "變更預覽", "data_grid.record_view.edit_current": "編輯目前記錄", "data_grid.record_view.edit_json": "編輯 JSON", + "data_grid.record_view.back_to_table": "返回表格", "data_grid.record_view.empty": "目前結果集沒有資料", "data_grid.record_view.json_record_count": "目前結果集 {{count}} 筆記錄", "data_grid.record_view.next": "下一筆",