feat(structure): persist local column order

This commit is contained in:
zipg authored and GitHub committed 2026-07-12 12:06:49 +08:00
1 parent c3ec7fceef
commit 0650428d1c
13 files changed
+278 -9

No files matched your search

+48 -3
View File
@@ -159,7 +159,19 @@ import { MAX_RESULT_PAGE_SIZE, MIN_RESULT_PAGE_SIZE, normalizeResultPageSize, re
import { allNullColumnIndexes, filterColumnVisibilityOptions, hiddenColumnIndexesWithAllNullColumns, invertedHiddenColumnIndexes, nextHiddenColumnIndexes, removeAutoHiddenColumnIndexes, visibleColumnIndexesForFilter } from "@/lib/dataGrid/dataGridColumnVisibility";
import { buildDataGridColumnLookupItems, filterDataGridColumnLookupItems } from "@/lib/dataGrid/dataGridColumnLookup";
import { columnOrderKeysForIndexes, isDefaultColumnOrder, moveVisibleColumnIndex, orderedColumnIndexes, uniqueDataGridColumnOrderKeys } from "@/lib/dataGrid/dataGridColumnOrder";
import { dataGridColumnLayoutScopeKey, loadDataGridColumnOrder, removeDataGridColumnOrder, saveDataGridColumnOrder } from "@/lib/dataGrid/dataGridColumnLayoutStorage";
import {
dataGridColumnLayoutScopeKey,
loadDataGridColumnOrder,
loadTableDataGridColumnOrder,
notifyTableDataGridColumnOrderChanged,
removeDataGridColumnOrder,
removeTableDataGridColumnOrder,
saveDataGridColumnOrder,
saveTableDataGridColumnOrder,
TABLE_DATA_GRID_COLUMN_ORDER_CHANGED_EVENT,
tableDataGridColumnOrderScopeKey,
type TableDataGridColumnOrderChangedDetail,
} from "@/lib/dataGrid/dataGridColumnLayoutStorage";
import { parseClipboardTable, summarizeSelection } from "@/lib/dataGrid/gridSelection";
import { columnHeaderCanvasPointerDisabled, columnHeaderClickShouldBeSuppressed, columnHeaderPreviewOffsetForColumn, columnHeaderTooltipDisabled } from "@/lib/dataGrid/dataGridColumnHeaderInteraction";
@@ -2554,6 +2566,15 @@ const columnLayoutScopeKey = computed(() =>
sourceColumns: props.sourceColumns,
}),
);
const tableColumnOrderScopeKey = computed(() => {
if (props.context !== "table-data" || !props.connectionId || !props.database || !props.tableMeta?.tableName) return "";
return tableDataGridColumnOrderScopeKey({
connectionId: props.connectionId,
database: props.database,
schema: props.tableMeta.schema,
tableName: props.tableMeta.tableName,
});
});
const persistedColumnOrderKeys = ref<string[]>([]);
const displayableColumnIndexes = computed(() =>
props.result.columns
@@ -2633,20 +2654,42 @@ function invertColumnVisibility() {
hiddenColumnIndexes.value = invertedHiddenColumnIndexes(displayableColumnIndexes.value, hiddenColumnIndexes.value);
}
function loadColumnOrder() {
persistedColumnOrderKeys.value = loadDataGridColumnOrder(columnLayoutScopeKey.value, columnOrderKeys.value);
const tableOrder = tableColumnOrderScopeKey.value ? loadTableDataGridColumnOrder(tableColumnOrderScopeKey.value) : [];
persistedColumnOrderKeys.value = tableOrder.length ? tableOrder : loadDataGridColumnOrder(columnLayoutScopeKey.value, columnOrderKeys.value);
}
function onTableDataGridColumnOrderChanged(event: Event) {
if (!(event instanceof CustomEvent)) return;
const detail = event.detail as TableDataGridColumnOrderChangedDetail | undefined;
if (!detail || detail.scopeKey !== tableColumnOrderScopeKey.value) return;
loadColumnOrder();
nextTick(refreshGridScrollerMetrics);
}
function persistColumnOrder(indexes: number[]) {
const tableScopeKey = tableColumnOrderScopeKey.value;
if (isDefaultColumnOrder(displayableColumnIndexes.value, indexes)) {
removeDataGridColumnOrder(columnLayoutScopeKey.value);
if (tableScopeKey) {
removeTableDataGridColumnOrder(tableScopeKey);
notifyTableDataGridColumnOrderChanged(tableScopeKey);
}
persistedColumnOrderKeys.value = [];
return;
}
const keys = columnOrderKeysForIndexes(indexes, columnOrderKeys.value);
persistedColumnOrderKeys.value = keys;
saveDataGridColumnOrder(columnLayoutScopeKey.value, columnOrderKeys.value, keys);
if (tableScopeKey) {
saveTableDataGridColumnOrder(tableScopeKey, keys);
notifyTableDataGridColumnOrderChanged(tableScopeKey);
}
}
function resetColumnOrder() {
removeDataGridColumnOrder(columnLayoutScopeKey.value);
const tableScopeKey = tableColumnOrderScopeKey.value;
if (tableScopeKey) {
removeTableDataGridColumnOrder(tableScopeKey);
notifyTableDataGridColumnOrderChanged(tableScopeKey);
}
persistedColumnOrderKeys.value = [];
nextTick(refreshGridScrollerMetrics);
}
@@ -2678,7 +2721,7 @@ watch(allNullColumnIndexesForResult, () => {
autoHiddenNullColumnIndexes.value = new Set();
hideAllNullColumns();
});
watch(() => columnLayoutScopeKey.value, loadColumnOrder, { immediate: true });
watch([columnLayoutScopeKey, tableColumnOrderScopeKey], loadColumnOrder, { immediate: true });
const firstVisibleColumnIndex = computed(() => visibleColumnIndexes.value[0] ?? 0);
function actualColumnIndex(visibleColumnIndex: number): number {
return visibleColumnIndexes.value[visibleColumnIndex] ?? visibleColumnIndex;
@@ -6266,6 +6309,7 @@ onMounted(() => {
window.visualViewport?.addEventListener("resize", resizeFocusedConditionInputs);
window.addEventListener("dbx:ui-scale-applied", scheduleCanvasPixelRatioRefresh);
window.addEventListener("dbx:ui-scale-applied", resizeFocusedConditionInputs);
window.addEventListener(TABLE_DATA_GRID_COLUMN_ORDER_CHANGED_EVENT, onTableDataGridColumnOrderChanged);
document.addEventListener("pointerdown", onConditionSuggestionDocumentPointerDown, true);
});
onDeactivated(pauseCanvasGridWork);
@@ -6292,6 +6336,7 @@ onUnmounted(() => {
window.visualViewport?.removeEventListener("resize", resizeFocusedConditionInputs);
window.removeEventListener("dbx:ui-scale-applied", scheduleCanvasPixelRatioRefresh);
window.removeEventListener("dbx:ui-scale-applied", resizeFocusedConditionInputs);
window.removeEventListener(TABLE_DATA_GRID_COLUMN_ORDER_CHANGED_EVENT, onTableDataGridColumnOrderChanged);
document.removeEventListener("pointerdown", onConditionSuggestionDocumentPointerDown, true);
});
@@ -28,7 +28,9 @@ import { safeLocalStorageGet, safeLocalStorageSet } from "@/lib/backend/safeStor
import { type BuildTableStructureChangeSqlOptions, type EditableStructureColumn, type EditableStructureForeignKey, type EditableStructureIndex, type EditableStructureTrigger } from "@/lib/table/tableStructureEditorSql";
import { PRESET_FIELDS_TEMPLATE_ID, createTableColumnTemplateDrafts } from "@/lib/table/tableColumnTemplates";
import { getTableMetadataCapabilities, firstStructureMetadataTab, isStructureMetadataTabSupported } from "@/lib/table/tableMetadataCapabilities";
import { canAddTableStructureColumn, getTableStructureCapabilities } from "@/lib/table/tableStructureCapabilities";
import { canAddTableStructureColumn, getTableStructureCapabilities, hasLocalTableColumnOrderChange, isPhysicalTableColumnOrderChange, supportsLocalTableColumnReorder } from "@/lib/table/tableStructureCapabilities";
import { orderedColumnIndexes, uniqueDataGridColumnOrderKeys } from "@/lib/dataGrid/dataGridColumnOrder";
import { loadTableDataGridColumnOrder, notifyTableDataGridColumnOrderChanged, removeTableDataGridColumnOrder, saveTableDataGridColumnOrder, tableDataGridColumnOrderScopeKey } from "@/lib/dataGrid/dataGridColumnLayoutStorage";
import { connectionObjectTreeQuerySchema, tableStructureDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
import type { TableInfoTab, TableStructureEditorDraft, TableStructureEditorTarget, TableStructureEditorViewport } from "@/types/database";
import {
@@ -191,7 +193,7 @@ function columnChanged(column: EditableStructureColumn, index: number): boolean
if (!column.original || column.markedForDrop) return true;
const original = column.original;
return (
column.originalPosition !== index ||
isPhysicalTableColumnOrderChange(databaseType.value, connection.value?.db_type, column.originalPosition, index) ||
column.name !== original.name ||
column.dataType !== original.data_type ||
column.isNullable !== original.is_nullable ||
@@ -1152,6 +1154,7 @@ function resetState() {
highlightedIndexId.value = null;
appliedInitialTargetSearchKey = "";
appliedInitialTargetScrollKey = "";
localColumnOrderNoticeShown.value = false;
}
async function reloadStructureFromDatabase() {
@@ -1216,7 +1219,7 @@ async function loadStructure(silent = false, scope: StructureRefreshScope = FULL
// Load live charset/collation metadata from the MySQL server so the column
// editor shows the correct options for the server version.
void loadCharsetMetadata();
columns.value = createColumnDrafts(nextColumns, databaseType.value);
columns.value = applyStoredLocalColumnOrder(createColumnDrafts(nextColumns, databaseType.value));
}
const nextTableComment = await tableCommentPromise;
@@ -1325,6 +1328,7 @@ type ColumnDragState = {
};
const columnDragState = ref<ColumnDragState | null>(null);
const localColumnOrderNoticeShown = ref(false);
let columnDragPreviousBodyUserSelect = "";
let columnDragPreviousBodyCursor = "";
let columnDragTracking = false;
@@ -1350,7 +1354,53 @@ function canDropColumnAt(sourceIndex: number, insertionIndex: number): boolean {
return crossedColumns.every((column) => !column.original);
}
const canShowColumnDragControls = computed(() => isCreateMode.value || structureCapabilities.value.reorderColumn);
const usesLocalTableColumnOrder = computed(() => !isCreateMode.value && supportsLocalTableColumnReorder(databaseType.value, connection.value?.db_type));
const canShowColumnDragControls = computed(() => isCreateMode.value || structureCapabilities.value.reorderColumn || usesLocalTableColumnOrder.value);
function localTableColumnOrderScopeKey(): string {
return tableDataGridColumnOrderScopeKey({
connectionId: props.connectionId,
database: props.database,
schema: props.schema,
tableName: props.tableName,
});
}
function localColumnOrderKeys(items: readonly EditableStructureColumn[]): string[] {
return uniqueDataGridColumnOrderKeys(items.map((column) => column.name));
}
const hasLocalColumnOrderChange = computed(() => {
if (!usesLocalTableColumnOrder.value) return false;
return hasLocalTableColumnOrderChange(columns.value);
});
function applyStoredLocalColumnOrder(items: EditableStructureColumn[]): EditableStructureColumn[] {
if (!usesLocalTableColumnOrder.value) return items;
const orderedKeys = loadTableDataGridColumnOrder(localTableColumnOrderScopeKey());
if (!orderedKeys.length) return items;
const columnKeys = uniqueDataGridColumnOrderKeys(items.map((column) => column.name));
const indexes = orderedColumnIndexes({
availableIndexes: items.map((_, index) => index),
columnKeys,
orderedKeys,
});
return indexes.map((index) => items[index]).filter((column): column is EditableStructureColumn => !!column);
}
function persistLocalColumnOrder(showNotice = true) {
if (!usesLocalTableColumnOrder.value) return;
const scopeKey = localTableColumnOrderScopeKey();
if (hasLocalColumnOrderChange.value) {
saveTableDataGridColumnOrder(scopeKey, localColumnOrderKeys(columns.value));
} else {
removeTableDataGridColumnOrder(scopeKey);
}
notifyTableDataGridColumnOrderChanged(scopeKey);
if (!showNotice || localColumnOrderNoticeShown.value) return;
localColumnOrderNoticeShown.value = true;
toast(t("structureEditor.localColumnOrderNotice"), 4000);
}
function isSqlServerIdentityChecked(column: EditableStructureColumn): boolean {
return !!column.extra.autoIncrement || !!column.extra.identity;
@@ -1510,6 +1560,7 @@ function moveColumnTo(index: number, insertionIndex: number) {
const adjustedInsertionIndex = insertionIndex > index ? insertionIndex - 1 : insertionIndex;
nextColumns.splice(adjustedInsertionIndex, 0, column);
columns.value = nextColumns;
persistLocalColumnOrder();
}
function onColumnDragPointerDown(index: number, event: PointerEvent) {
@@ -2097,6 +2148,8 @@ async function applyChanges() {
emit("saved", tableComment.value !== originalTableComment.value);
emit("close");
} else {
// Refresh persisted keys after successful renames/additions before metadata reloads.
persistLocalColumnOrder(false);
saving.value = false;
postSaveRefreshing.value = true;
skipNextRefreshVersion = true;
@@ -2788,7 +2841,7 @@ watch([activeTab, ddlLoading], ([tab, loading]) => {
type="button"
variant="ghost"
size="icon"
:class="[structureActionButtonClass, canDragColumn(index) ? 'cursor-grab active:cursor-grabbing' : 'cursor-not-allowed']"
:class="[structureActionButtonClass, canDragColumn(index) ? 'cursor-grab active:cursor-grabbing' : 'cursor-not-allowed', hasLocalColumnOrderChange ? 'border-primary/30 bg-primary/10 text-primary hover:bg-primary/15 hover:text-primary' : '']"
:disabled="!canDragColumn(index)"
:title="t('structureEditor.dragColumn')"
:aria-label="t('structureEditor.dragColumn')"