mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 03:59:03 +00:00
@@ -369,6 +369,7 @@ const editDataGridQuickEntry = ref(settingsStore.editorSettings.dataGridQuickEnt
|
||||
const editDataGridFilterEditorView = ref<DataGridFilterEditorView>(settingsStore.editorSettings.dataGridFilterEditorView);
|
||||
const editDataGridTextFilterPanelHeight = ref(settingsStore.editorSettings.dataGridTextFilterPanelHeight);
|
||||
const editDataGridAutoTransposeSingleRow = ref(settingsStore.editorSettings.dataGridAutoTransposeSingleRow);
|
||||
const editDataGridCellDetailButtonVisible = ref(settingsStore.editorSettings.dataGridCellDetailButtonVisible);
|
||||
const editPageSize = ref(settingsStore.editorSettings.pageSize);
|
||||
const editTableOpenPageSize = ref(settingsStore.editorSettings.tableOpenPageSize);
|
||||
const editQueryResultMaxRowsEnabled = ref(settingsStore.editorSettings.queryResultMaxRowsEnabled);
|
||||
@@ -557,6 +558,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
|
||||
dataGridFilterEditorView: editDataGridFilterEditorView.value,
|
||||
dataGridTextFilterPanelHeight: editDataGridTextFilterPanelHeight.value,
|
||||
dataGridAutoTransposeSingleRow: editDataGridAutoTransposeSingleRow.value,
|
||||
dataGridCellDetailButtonVisible: editDataGridCellDetailButtonVisible.value,
|
||||
flatteningMultiLineText: editFlatteningMultiLineText.value,
|
||||
pageSize: editPageSize.value,
|
||||
tableOpenPageSize: editTableOpenPageSize.value,
|
||||
@@ -845,6 +847,7 @@ function syncEditorSettingsDraftFromStore() {
|
||||
editDataGridFilterEditorView.value = settingsStore.editorSettings.dataGridFilterEditorView;
|
||||
editDataGridTextFilterPanelHeight.value = settingsStore.editorSettings.dataGridTextFilterPanelHeight;
|
||||
editDataGridAutoTransposeSingleRow.value = settingsStore.editorSettings.dataGridAutoTransposeSingleRow;
|
||||
editDataGridCellDetailButtonVisible.value = settingsStore.editorSettings.dataGridCellDetailButtonVisible;
|
||||
editFlatteningMultiLineText.value = settingsStore.editorSettings.flatteningMultiLineText;
|
||||
editPageSize.value = settingsStore.editorSettings.pageSize;
|
||||
editTableOpenPageSize.value = settingsStore.editorSettings.tableOpenPageSize;
|
||||
@@ -1139,6 +1142,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
|
||||
editDataGridFilterEditorView.value = DEFAULT_EDITOR_SETTINGS.dataGridFilterEditorView;
|
||||
editDataGridTextFilterPanelHeight.value = DEFAULT_EDITOR_SETTINGS.dataGridTextFilterPanelHeight;
|
||||
editDataGridAutoTransposeSingleRow.value = DEFAULT_EDITOR_SETTINGS.dataGridAutoTransposeSingleRow;
|
||||
editDataGridCellDetailButtonVisible.value = DEFAULT_EDITOR_SETTINGS.dataGridCellDetailButtonVisible;
|
||||
editFlatteningMultiLineText.value = DEFAULT_EDITOR_SETTINGS.flatteningMultiLineText;
|
||||
editPageSize.value = DEFAULT_EDITOR_SETTINGS.pageSize;
|
||||
editTableOpenPageSize.value = DEFAULT_EDITOR_SETTINGS.tableOpenPageSize;
|
||||
@@ -1218,6 +1222,7 @@ function resetAllDefaults() {
|
||||
editDataGridFilterEditorView.value = DEFAULT_EDITOR_SETTINGS.dataGridFilterEditorView;
|
||||
editDataGridTextFilterPanelHeight.value = DEFAULT_EDITOR_SETTINGS.dataGridTextFilterPanelHeight;
|
||||
editDataGridAutoTransposeSingleRow.value = DEFAULT_EDITOR_SETTINGS.dataGridAutoTransposeSingleRow;
|
||||
editDataGridCellDetailButtonVisible.value = DEFAULT_EDITOR_SETTINGS.dataGridCellDetailButtonVisible;
|
||||
editFlatteningMultiLineText.value = DEFAULT_EDITOR_SETTINGS.flatteningMultiLineText;
|
||||
editPageSize.value = DEFAULT_EDITOR_SETTINGS.pageSize;
|
||||
editTableOpenPageSize.value = DEFAULT_EDITOR_SETTINGS.tableOpenPageSize;
|
||||
@@ -5305,6 +5310,17 @@ onUnmounted(() => {
|
||||
</div>
|
||||
<Switch id="data-grid-auto-transpose-single-row" v-model="editDataGridAutoTransposeSingleRow" />
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
|
||||
<div class="space-y-1">
|
||||
<Label for="data-grid-cell-detail-button-visible">
|
||||
{{ t("settings.dataGridCellDetailButtonVisible") }}
|
||||
</Label>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ t("settings.dataGridCellDetailButtonVisibleDescription") }}
|
||||
</p>
|
||||
</div>
|
||||
<Switch id="data-grid-cell-detail-button-visible" v-model="editDataGridCellDetailButtonVisible" />
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
|
||||
<div class="space-y-1">
|
||||
<Label for="flattening-multi-line">
|
||||
|
||||
@@ -362,6 +362,7 @@ const slots = useSlots();
|
||||
const connectionStore = useConnectionStore();
|
||||
const queryStore = useQueryStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const cellDetailButtonEnabled = computed(() => settingsStore.editorSettings.dataGridCellDetailButtonVisible);
|
||||
const tableFontSize = computed(() => settingsStore.editorSettings.tableFontSize);
|
||||
const rowNumberWidth = ref(DATA_GRID_ROW_NUM_WIDTH);
|
||||
const largeValueResolutionVersion = ref(0);
|
||||
@@ -7296,7 +7297,8 @@ const canvasDetailButtonCell = computed(() => {
|
||||
const visibleRight = viewportWidth > 0 ? Math.min(viewportWidth, rect.left + rect.width) : rect.left + rect.width;
|
||||
const canQuickDownload = canQuickDownloadCellValue(target.rowIndex, target.col);
|
||||
const foreignKey = canvasCellForeignKey(target.rowIndex, target.col);
|
||||
const minWidth = canvasDataGridActionOverlayWidth(canQuickDownload, !!foreignKey) + 2;
|
||||
if (!cellDetailButtonEnabled.value && !canQuickDownload && !foreignKey) return null;
|
||||
const minWidth = canvasDataGridActionOverlayWidth(canQuickDownload, !!foreignKey, cellDetailButtonEnabled.value) + 2;
|
||||
if (rect.top < 0 || rect.top > viewportHeight - 1 || visibleRight - visibleLeft < minWidth) return null;
|
||||
return {
|
||||
rowIndex: target.rowIndex,
|
||||
@@ -7311,7 +7313,7 @@ const canvasDetailButtonCell = computed(() => {
|
||||
const canvasDetailButtonStyle = computed(() => {
|
||||
const cell = canvasDetailButtonCell.value;
|
||||
if (!cell) return {};
|
||||
const actionWidth = canvasDataGridActionOverlayWidth(cell.canQuickDownload, !!cell.foreignKey);
|
||||
const actionWidth = canvasDataGridActionOverlayWidth(cell.canQuickDownload, !!cell.foreignKey, cellDetailButtonEnabled.value);
|
||||
const edgeGap = 6;
|
||||
return {
|
||||
left: `${Math.max(rowNumberWidth.value, cell.rect.left + cell.rect.width - actionWidth - edgeGap)}px`,
|
||||
@@ -7325,7 +7327,7 @@ const canvasRightAlignedActionCell = computed(() => {
|
||||
return {
|
||||
rowIndex: cell.rowIndex,
|
||||
visibleColIdx: cell.visibleColIdx,
|
||||
reservedWidth: canvasDataGridActionReservedWidth(cell.canQuickDownload, !!cell.foreignKey),
|
||||
reservedWidth: canvasDataGridActionReservedWidth(cell.canQuickDownload, !!cell.foreignKey, cellDetailButtonEnabled.value),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -7412,6 +7414,7 @@ watch(
|
||||
hoveredDetailCell,
|
||||
detailCell,
|
||||
showCellDetail,
|
||||
cellDetailButtonEnabled,
|
||||
editingCell,
|
||||
frozenColumnCount,
|
||||
saveError,
|
||||
@@ -12083,6 +12086,7 @@ function currentGridContextMenuItems(): ContextMenuItem[] {
|
||||
</template>
|
||||
</LightDropdownMenu>
|
||||
<button
|
||||
v-if="cellDetailButtonEnabled"
|
||||
class="flex h-5 w-5 items-center justify-center rounded bg-background/90 text-muted-foreground shadow-sm ring-1 ring-border hover:text-foreground"
|
||||
:title="t('grid.cellDetails')"
|
||||
@mousedown.stop
|
||||
@@ -12816,6 +12820,7 @@ function currentGridContextMenuItems(): ContextMenuItem[] {
|
||||
<ArrowUpRight class="h-3 w-3" />
|
||||
</button>
|
||||
<button
|
||||
v-if="cellDetailButtonEnabled"
|
||||
class="flex h-5 w-5 items-center justify-center rounded bg-background/90 text-muted-foreground shadow-sm ring-1 ring-border hover:text-foreground"
|
||||
:title="t('grid.cellDetails')"
|
||||
@mousedown.stop
|
||||
@@ -13031,6 +13036,7 @@ function currentGridContextMenuItems(): ContextMenuItem[] {
|
||||
</template>
|
||||
</LightDropdownMenu>
|
||||
<button
|
||||
v-if="cellDetailButtonEnabled"
|
||||
class="flex h-5 w-5 items-center justify-center rounded bg-background/90 text-muted-foreground shadow-sm ring-1 ring-border hover:text-foreground"
|
||||
:title="t('grid.cellDetails')"
|
||||
@mousedown.stop
|
||||
|
||||
+23
@@ -4,6 +4,29 @@ import { describe, expect, it } from "vitest";
|
||||
const dataGridSource = readFileSync(new URL("../DataGrid.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("DataGrid cell detail selection", () => {
|
||||
it("gates only the three hover Info buttons with the visibility setting", () => {
|
||||
expect(dataGridSource).toContain("const cellDetailButtonEnabled = computed(() => settingsStore.editorSettings.dataGridCellDetailButtonVisible);");
|
||||
expect(dataGridSource.match(/v-if="cellDetailButtonEnabled"/g)).toHaveLength(3);
|
||||
expect(dataGridSource.match(/v-if="cellDetailButtonVisible\(/g)).toHaveLength(2);
|
||||
|
||||
const hoverVisibility = dataGridSource.match(/function cellDetailButtonVisible[\s\S]*?\n\}/)?.[0];
|
||||
expect(hoverVisibility).toBeDefined();
|
||||
expect(hoverVisibility).not.toContain("dataGridCellDetailButtonVisible");
|
||||
});
|
||||
|
||||
it("removes the Canvas action overlay and reservation only when no action remains", () => {
|
||||
expect(dataGridSource).toContain("if (!cellDetailButtonEnabled.value && !canQuickDownload && !foreignKey) return null;");
|
||||
expect(dataGridSource).toContain("canvasDataGridActionOverlayWidth(canQuickDownload, !!foreignKey, cellDetailButtonEnabled.value)");
|
||||
expect(dataGridSource).toContain("canvasDataGridActionOverlayWidth(cell.canQuickDownload, !!cell.foreignKey, cellDetailButtonEnabled.value)");
|
||||
expect(dataGridSource).toContain("canvasDataGridActionReservedWidth(cell.canQuickDownload, !!cell.foreignKey, cellDetailButtonEnabled.value)");
|
||||
expect(dataGridSource).toMatch(/watch\([\s\S]*?cellDetailButtonEnabled,[\s\S]*?scheduleCanvasDraw/);
|
||||
});
|
||||
|
||||
it("keeps context-menu details and refresh restoration independent of the hover setting", () => {
|
||||
expect(dataGridSource).toContain("cellDetails: openContextCellDetailDialog");
|
||||
expect(dataGridSource).toContain("if (cellDialog) openCellDetailDialog(cellDialog.rowIndex, cellDialog.col);");
|
||||
});
|
||||
|
||||
it("resynchronizes the open detail after a mouse selection gesture finishes", () => {
|
||||
expect(dataGridSource).toContain("watch([selectedRange, showCellDetail, isEditingDetail, isSelectingCells]");
|
||||
expect(dataGridSource).toContain("if (isSelectingCells.value) return;");
|
||||
|
||||
@@ -6227,6 +6227,8 @@ export default {
|
||||
dataGridQuickEntryDescription: "When enabled, editing a cell or filling the bottom blank row saves to the database as soon as focus leaves. Useful for frequent entry, but it can cause accidental writes.",
|
||||
dataGridAutoTransposeSingleRow: "Auto-transpose single-row query results",
|
||||
dataGridAutoTransposeSingleRowDescription: "When enabled, SQL query results with exactly one row and multiple columns automatically switch to transpose view.",
|
||||
dataGridCellDetailButtonVisible: "Show cell detail button",
|
||||
dataGridCellDetailButtonVisibleDescription: "Show the cell detail button when hovering over a cell.",
|
||||
infiniteScroll: "Infinite scroll loading",
|
||||
autoCalculateTotalRows: "Auto-calculate total row count",
|
||||
autoCalculateTotalRowsDescription: "Run COUNT(*) automatically after each query to show the total matching rows. Off by default to keep large queries fast — calculate it on demand from the result footer.",
|
||||
|
||||
@@ -5947,6 +5947,8 @@ export default withEnglishFallback({
|
||||
dataGridQuickEntryDescription: "Al activarlo, editar una celda o llenar la fila vacía inferior se guarda en la base de datos al perder el foco. Agiliza la captura frecuente, pero puede causar escrituras accidentales.",
|
||||
dataGridAutoTransposeSingleRow: "Transponer automáticamente los resultados de consulta de una sola fila",
|
||||
dataGridAutoTransposeSingleRowDescription: "Al activarlo, los resultados de consultas SQL con exactamente una fila y varias columnas cambian automáticamente a la vista transpuesta.",
|
||||
dataGridCellDetailButtonVisible: "Mostrar botón de detalles de celda",
|
||||
dataGridCellDetailButtonVisibleDescription: "Mostrar el botón de detalles de celda al pasar el cursor sobre una celda.",
|
||||
infiniteScroll: "Carga de desplazamiento infinito",
|
||||
autoCalculateTotalRows: "Calcular automáticamente el total de filas",
|
||||
autoCalculateTotalRowsDescription: "Ejecuta COUNT(*) automáticamente tras cada consulta para mostrar el total de filas coincidentes. Desactivado por defecto para mantener rápidas las consultas grandes; puedes calcularlo cuando quieras desde el pie de resultados.",
|
||||
|
||||
@@ -5947,6 +5947,8 @@ export default withEnglishFallback({
|
||||
dataGridQuickEntryDescription: "Se attivo, la modifica di una cella o la compilazione della riga vuota in fondo viene salvata nel database appena il focus esce. Utile per inserimenti frequenti, ma può causare scritture accidentali.",
|
||||
dataGridAutoTransposeSingleRow: "Trasponi automaticamente i risultati delle query a riga singola",
|
||||
dataGridAutoTransposeSingleRowDescription: "Quando è attivata, i risultati delle query SQL con esattamente una riga e più colonne passano automaticamente alla vista trasposta.",
|
||||
dataGridCellDetailButtonVisible: "Mostra il pulsante dei dettagli della cella",
|
||||
dataGridCellDetailButtonVisibleDescription: "Mostra il pulsante dei dettagli della cella al passaggio del mouse su una cella.",
|
||||
infiniteScroll: "Caricamento a scorrimento infinito",
|
||||
autoCalculateTotalRows: "Calcola automaticamente il totale delle righe",
|
||||
autoCalculateTotalRowsDescription: "Esegue COUNT(*) automaticamente dopo ogni query per mostrare il totale delle righe corrispondenti. Disattivato per impostazione predefinita per mantenere veloci le query grandi; puoi calcolarlo all'occorrenza dal piè di pagina dei risultati.",
|
||||
|
||||
@@ -5981,6 +5981,8 @@ export default withEnglishFallback({
|
||||
dataGridQuickEntryDescription: "有効にすると、セル編集や下部の空行入力はフォーカスが外れた時点でデータベースに保存されます。頻繁な入力に便利ですが、誤操作による書き込みのリスクがあります。",
|
||||
dataGridAutoTransposeSingleRow: "1 行のクエリ結果を自動で転置表示",
|
||||
dataGridAutoTransposeSingleRowDescription: "有効にすると、1 行かつ複数列の SQL クエリ結果が自動的に転置表示に切り替わります。",
|
||||
dataGridCellDetailButtonVisible: "セル詳細ボタンを表示",
|
||||
dataGridCellDetailButtonVisibleDescription: "セルにカーソルを合わせたときにセル詳細ボタンを表示します。",
|
||||
tableColumnTemplateFields: "新規テーブルのプリセット列",
|
||||
tableColumnTemplateFieldsDescription: "データベース種別を選択し、新規テーブル作成時に使うプリセット列の型を設定します。",
|
||||
tableColumnTemplateAdd: "列を追加",
|
||||
|
||||
@@ -5630,6 +5630,8 @@ export default withEnglishFallback({
|
||||
dataGridQuickEntryDescription: "활성화하면 셀을 편집하거나 아래 빈 행을 채운 후 포커스가 벗어나는 즉시 데이터베이스에 저장합니다. 빈번한 입력에 유용하지만 실수로 쓰기를 유발할 수 있습니다.",
|
||||
dataGridAutoTransposeSingleRow: "단일 행 쿼리 결과 자동 전치",
|
||||
dataGridAutoTransposeSingleRowDescription: "활성화하면 정확히 한 행과 여러 열이 있는 SQL 쿼리 결과가 자동으로 전치 보기로 전환됩니다.",
|
||||
dataGridCellDetailButtonVisible: "셀 세부 정보 버튼 표시",
|
||||
dataGridCellDetailButtonVisibleDescription: "셀 위에 마우스를 올리면 셀 세부 정보 버튼을 표시합니다.",
|
||||
infiniteScroll: "무한 스크롤 로딩",
|
||||
autoCalculateTotalRows: "전체 행 수 자동 집계",
|
||||
autoCalculateTotalRowsDescription: "각 쿼리 후 자동으로 COUNT(*)를 실행하여 일치하는 전체 행 수를 표시합니다. 대규모 쿼리를 빠르게 유지하기 위해 기본적으로 꺼져 있으며, 결과 푸터에서 요청 시 집계할 수 있습니다.",
|
||||
|
||||
@@ -5949,6 +5949,8 @@ export default withEnglishFallback({
|
||||
dataGridQuickEntryDescription: "Quando ativado, editar uma célula ou preencher a linha vazia inferior salva no banco de dados assim que o foco sai. Ajuda na entrada frequente, mas pode causar gravações acidentais.",
|
||||
dataGridAutoTransposeSingleRow: "Transpor automaticamente resultados de consulta com uma única linha",
|
||||
dataGridAutoTransposeSingleRowDescription: "Quando ativado, os resultados de consultas SQL com exatamente uma linha e várias colunas alternam automaticamente para a visualização transposta.",
|
||||
dataGridCellDetailButtonVisible: "Mostrar botão de detalhes da célula",
|
||||
dataGridCellDetailButtonVisibleDescription: "Mostrar o botão de detalhes da célula ao passar o mouse sobre uma célula.",
|
||||
infiniteScroll: "Carregamento por rolagem infinita",
|
||||
autoCalculateTotalRows: "Calcular automaticamente o total de linhas",
|
||||
autoCalculateTotalRowsDescription: "Executa COUNT(*) automaticamente após cada consulta para mostrar o total de linhas correspondentes. Desativado por padrão para manter consultas grandes rápidas; você pode calculá-lo quando quiser no rodapé dos resultados.",
|
||||
|
||||
@@ -6211,6 +6211,8 @@ export default withEnglishFallback({
|
||||
dataGridQuickEntryDescription: "开启后,编辑单元格或填写底部空行并离焦会立即保存到数据库。便于高频录入,但存在误操作风险。",
|
||||
dataGridAutoTransposeSingleRow: "自动转置单行查询结果",
|
||||
dataGridAutoTransposeSingleRowDescription: "开启后,SQL 查询结果只有一行且包含多列时,自动切换为转置视图。",
|
||||
dataGridCellDetailButtonVisible: "显示单元格详情按钮",
|
||||
dataGridCellDetailButtonVisibleDescription: "鼠标悬停在单元格上时显示单元格详情按钮。",
|
||||
infiniteScroll: "无限滚动加载",
|
||||
autoCalculateTotalRows: "自动统计总行数",
|
||||
autoCalculateTotalRowsDescription: "每次查询后自动执行 COUNT(*) 显示匹配的总行数。默认关闭以保证大查询速度 —— 可在结果栏按需手动统计。",
|
||||
|
||||
@@ -5266,6 +5266,8 @@ export default withEnglishFallback({
|
||||
dataGridQuickEntryDescription: "開啟後,編輯儲存格或填寫底部空白列並離焦會立即儲存到資料庫。適合高頻輸入,但有誤操作風險。",
|
||||
dataGridAutoTransposeSingleRow: "自動轉置單行查詢結果",
|
||||
dataGridAutoTransposeSingleRowDescription: "開啟後,SQL 查詢結果只有一行且包含多個欄位時,會自動切換為轉置檢視。",
|
||||
dataGridCellDetailButtonVisible: "顯示儲存格詳細資料按鈕",
|
||||
dataGridCellDetailButtonVisibleDescription: "游標懸停在儲存格上時顯示儲存格詳細資料按鈕。",
|
||||
infiniteScroll: "無限滾動載入",
|
||||
autoCalculateTotalRows: "自動統計總筆數",
|
||||
autoCalculateTotalRowsDescription: "每次查詢後自動執行 COUNT(*) 顯示符合的總筆數。預設關閉以確保大型查詢速度 —— 可在結果列按需手動統計。",
|
||||
|
||||
+13
-1
@@ -1,7 +1,7 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { drawCanvasDataGrid, resolveCanvasBackingStoreMetrics, type DrawCanvasDataGridOptions } from "@/lib/dataGrid/canvasDataGridRenderer";
|
||||
import { canvasDataGridActionOverlayWidth, canvasDataGridActionReservedWidth, drawCanvasDataGrid, resolveCanvasBackingStoreMetrics, type DrawCanvasDataGridOptions } from "@/lib/dataGrid/canvasDataGridRenderer";
|
||||
|
||||
function createMockCanvas(width = 800, height = 400) {
|
||||
const canvas = document.createElement("canvas");
|
||||
@@ -80,6 +80,18 @@ describe("drawCanvasDataGrid with frozen columns", () => {
|
||||
} as CSSStyleDeclaration);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[false, false, true, 22, 28],
|
||||
[false, false, false, 0, 0],
|
||||
[true, false, false, 22, 28],
|
||||
[false, true, false, 22, 28],
|
||||
[true, true, false, 44, 50],
|
||||
[true, true, true, 66, 72],
|
||||
])("reserves only enabled Canvas actions (download=%s, foreign key=%s, detail=%s)", (canQuickDownload, canNavigateForeignKey, showCellDetail, overlayWidth, reservedWidth) => {
|
||||
expect(canvasDataGridActionOverlayWidth(canQuickDownload, canNavigateForeignKey, showCellDetail)).toBe(overlayWidth);
|
||||
expect(canvasDataGridActionReservedWidth(canQuickDownload, canNavigateForeignKey, showCellDetail)).toBe(reservedWidth);
|
||||
});
|
||||
|
||||
it("uses the exact observed device-pixel size instead of a nominal DPR", () => {
|
||||
expect(
|
||||
resolveCanvasBackingStoreMetrics({
|
||||
|
||||
@@ -195,13 +195,14 @@ export function fitCanvasText(ctx: CanvasRenderingContext2D, text: string, maxWi
|
||||
return result;
|
||||
}
|
||||
|
||||
export function canvasDataGridActionReservedWidth(canQuickDownload: boolean, canNavigateForeignKey = false): number {
|
||||
return canvasDataGridActionOverlayWidth(canQuickDownload, canNavigateForeignKey) + 6;
|
||||
export function canvasDataGridActionReservedWidth(canQuickDownload: boolean, canNavigateForeignKey = false, showCellDetail = true): number {
|
||||
const overlayWidth = canvasDataGridActionOverlayWidth(canQuickDownload, canNavigateForeignKey, showCellDetail);
|
||||
return overlayWidth > 0 ? overlayWidth + 6 : 0;
|
||||
}
|
||||
|
||||
/** 悬浮按钮组宽度:每个按钮 20px + 2px 间距(detail 按钮始终存在) */
|
||||
export function canvasDataGridActionOverlayWidth(canQuickDownload: boolean, canNavigateForeignKey = false): number {
|
||||
return 22 + (canQuickDownload ? 22 : 0) + (canNavigateForeignKey ? 22 : 0);
|
||||
/** 悬浮按钮组宽度:每个已启用按钮 20px + 2px 间距。 */
|
||||
export function canvasDataGridActionOverlayWidth(canQuickDownload: boolean, canNavigateForeignKey = false, showCellDetail = true): number {
|
||||
return (showCellDetail ? 22 : 0) + (canQuickDownload ? 22 : 0) + (canNavigateForeignKey ? 22 : 0);
|
||||
}
|
||||
|
||||
export function resolveCanvasCellTextLayout(options: { drawX: number; colWidth: number; dpr: number; isRightAlign: boolean; reservedWidth?: number }): { textAnchorX: number; maxWidth: number } {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { EDITOR_SETTINGS_DRAFT_KEYS, editorSettingsDraftFromSettings, editorSettingsDraftChanged, editorSettingsPatchFromDraft, normalizeQueryResultMaxRowsDraft, normalizeTableOpenPageSizeDraft, shouldConfirmEditorSettingsDialogClose } from "../editorSettingsDraft";
|
||||
import type { EditorSettings } from "@/stores/settingsStore";
|
||||
|
||||
const settingsDialogSource = readFileSync(new URL("../../../components/editor/EditorSettingsDialog.vue", import.meta.url), "utf8");
|
||||
|
||||
function makeSettings(overrides: Partial<EditorSettings> = {}): EditorSettings {
|
||||
return {
|
||||
autoCalculateTotalRows: false,
|
||||
@@ -97,6 +100,10 @@ describe("EDITOR_SETTINGS_DRAFT_KEYS", () => {
|
||||
expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("dataGridTextFilterPanelHeight");
|
||||
});
|
||||
|
||||
it("includes the cell detail button visibility", () => {
|
||||
expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("dataGridCellDetailButtonVisible");
|
||||
});
|
||||
|
||||
it("includes completionTriggerMode", () => {
|
||||
expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("completionTriggerMode");
|
||||
});
|
||||
@@ -106,6 +113,16 @@ describe("EDITOR_SETTINGS_DRAFT_KEYS", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("cell detail button settings control", () => {
|
||||
it("binds the switch through apply and both reset paths", () => {
|
||||
expect(settingsDialogSource).toContain("const editDataGridCellDetailButtonVisible = ref(settingsStore.editorSettings.dataGridCellDetailButtonVisible)");
|
||||
expect(settingsDialogSource).toContain("dataGridCellDetailButtonVisible: editDataGridCellDetailButtonVisible.value");
|
||||
expect(settingsDialogSource).toContain("editDataGridCellDetailButtonVisible.value = settingsStore.editorSettings.dataGridCellDetailButtonVisible");
|
||||
expect(settingsDialogSource.match(/editDataGridCellDetailButtonVisible\.value = DEFAULT_EDITOR_SETTINGS\.dataGridCellDetailButtonVisible/g)).toHaveLength(2);
|
||||
expect(settingsDialogSource).toContain('id="data-grid-cell-detail-button-visible" v-model="editDataGridCellDetailButtonVisible"');
|
||||
});
|
||||
});
|
||||
|
||||
describe("editorSettingsDraftFromSettings", () => {
|
||||
it("round-trips a hidden line-number preference through the draft patch", () => {
|
||||
const settings = makeSettings({ showLineNumbers: true });
|
||||
@@ -281,6 +298,15 @@ describe("editorSettingsDraftChanged", () => {
|
||||
});
|
||||
|
||||
describe("editorSettingsPatchFromDraft", () => {
|
||||
it("applies, cancels, and re-enables the cell detail button visibility", () => {
|
||||
const visible = editorSettingsDraftFromSettings(makeSettings({ dataGridCellDetailButtonVisible: true }));
|
||||
const hidden = editorSettingsDraftFromSettings(makeSettings({ dataGridCellDetailButtonVisible: false }));
|
||||
|
||||
expect(editorSettingsPatchFromDraft(hidden, visible)).toEqual({ dataGridCellDetailButtonVisible: false });
|
||||
expect(editorSettingsPatchFromDraft(visible, visible)).toEqual({});
|
||||
expect(editorSettingsPatchFromDraft(visible, hidden)).toEqual({ dataGridCellDetailButtonVisible: true });
|
||||
});
|
||||
|
||||
it("includes continueOnErrorOnBatch in patch when changed", () => {
|
||||
const settings = makeSettings({ continueOnErrorOnBatch: false });
|
||||
const draft = editorSettingsDraftFromSettings(settings);
|
||||
|
||||
@@ -44,6 +44,7 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [
|
||||
"dataGridFilterEditorView",
|
||||
"dataGridTextFilterPanelHeight",
|
||||
"dataGridAutoTransposeSingleRow",
|
||||
"dataGridCellDetailButtonVisible",
|
||||
"pageSize",
|
||||
"tableOpenPageSize",
|
||||
"queryResultMaxRowsEnabled",
|
||||
|
||||
@@ -279,6 +279,16 @@ describe("normalizeEditorSettings", () => {
|
||||
expect(invalid.dataGridBooleanDisplayMode).toBe("dropdown");
|
||||
});
|
||||
|
||||
it("defaults the cell detail hover button on and preserves only boolean values", () => {
|
||||
expect(normalizeEditorSettings({}).dataGridCellDetailButtonVisible).toBe(true);
|
||||
expect(normalizeEditorSettings({ dataGridCellDetailButtonVisible: true }).dataGridCellDetailButtonVisible).toBe(true);
|
||||
expect(normalizeEditorSettings({ dataGridCellDetailButtonVisible: false }).dataGridCellDetailButtonVisible).toBe(false);
|
||||
|
||||
for (const invalidValue of [0, 1, "false", null]) {
|
||||
expect(normalizeEditorSettings({ dataGridCellDetailButtonVisible: invalidValue as never }).dataGridCellDetailButtonVisible).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("defaults the data grid font and preserves a custom font family", () => {
|
||||
const defaultFontFamily = `"Geist Variable Tabular", "Geist Variable", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif`;
|
||||
expect(normalizeEditorSettings({}).tableFontFamily).toBe(defaultFontFamily);
|
||||
@@ -732,6 +742,28 @@ describe("settingsStore persisted settings initialization", () => {
|
||||
expect(saveEditorSettings).toHaveBeenLastCalledWith(expect.objectContaining({ openDataTabsNextToActive: false }));
|
||||
});
|
||||
|
||||
it("loads, persists, and reloads the cell detail button visibility", async () => {
|
||||
let persistedSettings: Record<string, unknown> = { dataGridCellDetailButtonVisible: false };
|
||||
const loadEditorSettings = vi.fn(async () => JSON.parse(JSON.stringify(persistedSettings)));
|
||||
const saveEditorSettings = vi.fn(async (settings: Record<string, unknown>) => {
|
||||
persistedSettings = JSON.parse(JSON.stringify(settings));
|
||||
});
|
||||
vi.doMock("@/lib/backend/api", () => ({ loadEditorSettings, saveEditorSettings }));
|
||||
|
||||
const { useSettingsStore } = await import("@/stores/settingsStore");
|
||||
const store = useSettingsStore();
|
||||
await store.initEditorSettings();
|
||||
|
||||
expect(store.editorSettings.dataGridCellDetailButtonVisible).toBe(false);
|
||||
await store.updateEditorSettingsAndPersist({ dataGridCellDetailButtonVisible: true });
|
||||
expect(saveEditorSettings).toHaveBeenLastCalledWith(expect.objectContaining({ dataGridCellDetailButtonVisible: true }));
|
||||
|
||||
setActivePinia(createPinia());
|
||||
const restartedStore = useSettingsStore();
|
||||
await restartedStore.initEditorSettings();
|
||||
expect(restartedStore.editorSettings.dataGridCellDetailButtonVisible).toBe(true);
|
||||
});
|
||||
|
||||
it("loads, persists, and reloads hidden query editor line numbers", async () => {
|
||||
let persistedSettings: Record<string, unknown> = { showLineNumbers: true };
|
||||
const loadEditorSettings = vi.fn(async () => JSON.parse(JSON.stringify(persistedSettings)));
|
||||
|
||||
@@ -573,6 +573,7 @@ export interface EditorSettings {
|
||||
dataGridExtractorOptions: DataGridExtractorOptions;
|
||||
resultRunDisplayMode: ResultRunDisplayMode;
|
||||
dataGridAutoTransposeSingleRow: boolean;
|
||||
dataGridCellDetailButtonVisible: boolean;
|
||||
dataGridMultiRowTranspose: boolean;
|
||||
dataGridHideNullColumns: boolean;
|
||||
dataGridBooleanDisplayMode: "dropdown" | "checkbox";
|
||||
@@ -781,6 +782,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
|
||||
dataGridExtractorOptions: normalizeDataGridExtractorOptions(DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS),
|
||||
resultRunDisplayMode: "tabs",
|
||||
dataGridAutoTransposeSingleRow: false,
|
||||
dataGridCellDetailButtonVisible: true,
|
||||
dataGridMultiRowTranspose: false,
|
||||
dataGridHideNullColumns: false,
|
||||
dataGridBooleanDisplayMode: "dropdown",
|
||||
@@ -1154,6 +1156,7 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
|
||||
dataGridExtractorOptions: normalizeDataGridExtractorOptions(settings.dataGridExtractorOptions),
|
||||
resultRunDisplayMode: normalizeResultRunDisplayMode(settings.resultRunDisplayMode),
|
||||
dataGridAutoTransposeSingleRow: settings.dataGridAutoTransposeSingleRow === true,
|
||||
dataGridCellDetailButtonVisible: typeof settings.dataGridCellDetailButtonVisible === "boolean" ? settings.dataGridCellDetailButtonVisible : DEFAULT_EDITOR_SETTINGS.dataGridCellDetailButtonVisible,
|
||||
dataGridMultiRowTranspose: settings.dataGridMultiRowTranspose === true,
|
||||
dataGridHideNullColumns: settings.dataGridHideNullColumns === true,
|
||||
dataGridBooleanDisplayMode: settings.dataGridBooleanDisplayMode === "checkbox" ? "checkbox" : "dropdown",
|
||||
@@ -1766,6 +1769,7 @@ export const useSettingsStore = defineStore("settings", () => {
|
||||
if (partial.dataGridExtractorOptions !== undefined) editorSettings.value.dataGridExtractorOptions = normalizeDataGridExtractorOptions(partial.dataGridExtractorOptions);
|
||||
if (partial.resultRunDisplayMode !== undefined) editorSettings.value.resultRunDisplayMode = normalizeResultRunDisplayMode(partial.resultRunDisplayMode);
|
||||
if (partial.dataGridAutoTransposeSingleRow !== undefined) editorSettings.value.dataGridAutoTransposeSingleRow = partial.dataGridAutoTransposeSingleRow === true;
|
||||
if (partial.dataGridCellDetailButtonVisible !== undefined) editorSettings.value.dataGridCellDetailButtonVisible = typeof partial.dataGridCellDetailButtonVisible === "boolean" ? partial.dataGridCellDetailButtonVisible : DEFAULT_EDITOR_SETTINGS.dataGridCellDetailButtonVisible;
|
||||
if (partial.dataGridMultiRowTranspose !== undefined) editorSettings.value.dataGridMultiRowTranspose = partial.dataGridMultiRowTranspose === true;
|
||||
if (partial.dataGridHideNullColumns !== undefined) editorSettings.value.dataGridHideNullColumns = partial.dataGridHideNullColumns === true;
|
||||
if (partial.dataGridBooleanDisplayMode !== undefined) editorSettings.value.dataGridBooleanDisplayMode = partial.dataGridBooleanDisplayMode === "dropdown" ? "dropdown" : "checkbox";
|
||||
|
||||
Reference in New Issue
Block a user