diff --git a/apps/desktop/src/components/structure/TableStructureEditor.vue b/apps/desktop/src/components/structure/TableStructureEditor.vue index 6c9c1d4bb..dccd31dc0 100644 --- a/apps/desktop/src/components/structure/TableStructureEditor.vue +++ b/apps/desktop/src/components/structure/TableStructureEditor.vue @@ -19,6 +19,7 @@ import { Plus, RefreshCw, Save, + SlidersHorizontal, Trash2, X, } from "lucide-vue-next"; @@ -33,6 +34,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip import { SearchableSelect } from "@/components/ui/searchable-select"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { useConnectionStore } from "@/stores/connectionStore"; +import { useSettingsStore, type StructureEditorDensity } from "@/stores/settingsStore"; import { useTheme } from "@/composables/useTheme"; import { useToast } from "@/composables/useToast"; import { type SqlHighlighter, createShikiSqlHighlighter } from "@/lib/sqlHighlighter"; @@ -54,6 +56,7 @@ import * as api from "@/lib/api"; const { t } = useI18n(); const { isDark } = useTheme(); const store = useConnectionStore(); +const settingsStore = useSettingsStore(); const { toast } = useToast(); const rootRef = ref(); @@ -94,18 +97,149 @@ const warnings = ref([]); const foreignKeys = ref([]); const triggers = ref([]); -const colWidths = ref([28, 128, 144, 96, 64, 56, 112, 128, 128]); +const structureDensityValues: StructureEditorDensity[] = ["compact", "standard", "comfortable"]; +const structureDensityMetrics: Record< + StructureEditorDensity, + { + columns: number[]; + indexes: number[]; + minColumnWidth: number; + minIndexColumnWidth: number; + fontSize: number; + shellPadding: number; + cellPaddingX: number; + cellPaddingY: number; + headerPaddingY: number; + controlHeight: number; + controlPaddingX: number; + iconSize: number; + checkboxSize: number; + lineHeight: number; + } +> = { + compact: { + columns: [24, 104, 120, 72, 54, 46, 92, 104, 104, 96], + indexes: [104, 156, 54, 78, 108, 128, 104, 62], + minColumnWidth: 24, + minIndexColumnWidth: 48, + fontSize: 10, + shellPadding: 8, + cellPaddingX: 4, + cellPaddingY: 3, + headerPaddingY: 4, + controlHeight: 20, + controlPaddingX: 6, + iconSize: 12, + checkboxSize: 12, + lineHeight: 1.25, + }, + standard: { + columns: [28, 128, 144, 96, 64, 56, 112, 128, 128, 128], + indexes: [132, 200, 64, 96, 132, 160, 132, 76], + minColumnWidth: 28, + minIndexColumnWidth: 60, + fontSize: 11, + shellPadding: 12, + cellPaddingX: 6, + cellPaddingY: 4, + headerPaddingY: 6, + controlHeight: 24, + controlPaddingX: 8, + iconSize: 14, + checkboxSize: 14, + lineHeight: 1.35, + }, + comfortable: { + columns: [32, 152, 168, 104, 76, 68, 136, 168, 152, 132], + indexes: [156, 232, 76, 112, 156, 192, 156, 92], + minColumnWidth: 32, + minIndexColumnWidth: 64, + fontSize: 12, + shellPadding: 14, + cellPaddingX: 8, + cellPaddingY: 6, + headerPaddingY: 8, + controlHeight: 28, + controlPaddingX: 10, + iconSize: 15, + checkboxSize: 15, + lineHeight: 1.45, + }, +}; + +function isStructureEditorDensity(value: unknown): value is StructureEditorDensity { + return structureDensityValues.includes(value as StructureEditorDensity); +} + +function metricsForDensity(density: StructureEditorDensity) { + return structureDensityMetrics[density]; +} + +const structureDensity = computed(() => settingsStore.editorSettings.structureEditorDensity); +const structureDensityMetric = computed(() => metricsForDensity(structureDensity.value)); +const structureDensityOptions = computed(() => [ + { value: "compact", label: t("structureEditor.densityCompact") }, + { value: "standard", label: t("structureEditor.densityStandard") }, + { value: "comfortable", label: t("structureEditor.densityComfortable") }, +]); +const structureDensityStyle = computed(() => { + const metric = structureDensityMetric.value; + return { + "--structure-font-size": `${metric.fontSize}px`, + "--structure-shell-padding": `${metric.shellPadding}px`, + "--structure-cell-px": `${metric.cellPaddingX}px`, + "--structure-cell-py": `${metric.cellPaddingY}px`, + "--structure-header-py": `${metric.headerPaddingY}px`, + "--structure-control-height": `${metric.controlHeight}px`, + "--structure-control-px": `${metric.controlPaddingX}px`, + "--structure-icon-size": `${metric.iconSize}px`, + "--structure-checkbox-size": `${metric.checkboxSize}px`, + "--structure-line-height": String(metric.lineHeight), + }; +}); +const structureControlClass = + "h-[var(--structure-control-height)] min-w-0 px-[var(--structure-control-px)] py-0 text-[length:var(--structure-font-size)]"; +const structureMonoControlClass = `${structureControlClass} font-mono`; +const structureToolbarButtonClass = + "h-[var(--structure-control-height)] gap-1 px-[var(--structure-control-px)] text-[length:var(--structure-font-size)]"; +const structureIconButtonClass = "h-[var(--structure-control-height)] w-[var(--structure-control-height)]"; +const structureIconClass = "h-[var(--structure-icon-size)] w-[var(--structure-icon-size)]"; +const structureCheckboxClass = "h-[var(--structure-checkbox-size)] w-[var(--structure-checkbox-size)]"; +const structureHeaderCellClass = + "relative border-b border-r px-[var(--structure-cell-px)] py-[var(--structure-header-py)] text-left"; +const structureCellClass = "border-b border-r px-[var(--structure-cell-px)] py-[var(--structure-cell-py)]"; +const structureLastCellClass = "border-b px-[var(--structure-cell-px)] py-[var(--structure-cell-py)]"; + +function applyStructureDensityWidths(density: StructureEditorDensity) { + const metric = metricsForDensity(density); + colWidths.value = [...metric.columns]; + indexColWidths.value = [...metric.indexes]; +} + +function setStructureDensity(value: unknown) { + if (!isStructureEditorDensity(value)) return; + settingsStore.updateEditorSettings({ structureEditorDensity: value }); +} + +const initialDensityMetric = metricsForDensity(structureDensity.value); +const colWidths = ref([...initialDensityMetric.columns]); const colResizing = ref<{ col: number; startX: number; startW: number } | null>(null); -const indexColWidths = ref([132, 200, 64, 96, 132, 160, 132, 76]); +const indexColWidths = ref([...initialDensityMetric.indexes]); const resizing = ref<{ col: number; startX: number; startW: number } | null>(null); +watch(structureDensity, (density, previousDensity) => { + if (density === previousDensity) return; + applyStructureDensityWidths(density); +}); + function onColResize(e: MouseEvent, col: number) { e.preventDefault(); - colResizing.value = { col, startX: e.clientX, startW: colWidths.value[col] }; + const widthIndex = columnWidthIndex(col); + colResizing.value = { col: widthIndex, startX: e.clientX, startW: colWidths.value[widthIndex] }; const onMove = (ev: MouseEvent) => { if (!colResizing.value) return; const delta = ev.clientX - colResizing.value.startX; - colWidths.value[col] = Math.max(28, colResizing.value.startW + delta); + colWidths.value[col] = Math.max(structureDensityMetric.value.minColumnWidth, colResizing.value.startW + delta); }; const onUp = () => { colResizing.value = null; @@ -122,7 +256,10 @@ function onIndexColResize(e: MouseEvent, col: number) { const onMove = (ev: MouseEvent) => { if (!resizing.value) return; const delta = ev.clientX - resizing.value.startX; - indexColWidths.value[col] = Math.max(60, resizing.value.startW + delta); + indexColWidths.value[col] = Math.max( + structureDensityMetric.value.minIndexColumnWidth, + resizing.value.startW + delta, + ); }; const onUp = () => { resizing.value = null; @@ -154,6 +291,18 @@ const showExtendedProperties = computed(() => { const dt = databaseType.value; return dt === "mysql" || dt === "postgres" || dt === "sqlserver"; }); +const extendedPropertiesColumnIndex = 8; +const visibleColWidths = computed(() => + showExtendedProperties.value + ? colWidths.value + : colWidths.value.filter((_, index) => index !== extendedPropertiesColumnIndex), +); + +function columnWidthIndex(visibleIndex: number) { + return !showExtendedProperties.value && visibleIndex >= extendedPropertiesColumnIndex + ? visibleIndex + 1 + : visibleIndex; +} const colLabels = computed(() => { const labels = [ @@ -477,52 +626,58 @@ watch(