mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
@@ -125,6 +125,9 @@ import {
|
||||
SIDEBAR_INDENT_MAX,
|
||||
SIDEBAR_FONT_SIZE_MIN,
|
||||
SIDEBAR_FONT_SIZE_MAX,
|
||||
SIDEBAR_DENSITIES,
|
||||
isSidebarDensity,
|
||||
type SidebarDensity,
|
||||
} from "@/stores/settingsStore";
|
||||
import { EDITOR_FONT_FAMILY_CSS_VAR, EDITOR_FONT_SIZE_CSS_VAR, createRunStatementButtonDom, loadEditorTheme, editorFontTheme } from "@/lib/editor/editorThemes";
|
||||
import { orderAiConfigsForDisplay } from "@/lib/ai/aiConfigOrdering";
|
||||
@@ -910,6 +913,7 @@ const editSidebarAllowHorizontalScroll = ref(settingsStore.editorSettings.sideba
|
||||
const editSidebarShowTooltips = ref(settingsStore.editorSettings.sidebarShowTooltips);
|
||||
const editSidebarIndent = ref(settingsStore.editorSettings.sidebarIndent);
|
||||
const editSidebarFontSize = ref(settingsStore.editorSettings.sidebarFontSize);
|
||||
const editSidebarDensity = ref<SidebarDensity>(settingsStore.editorSettings.sidebarDensity);
|
||||
const editExportBatchSize = ref(settingsStore.editorSettings.exportBatchSize);
|
||||
const editPreferredExportPath = ref(settingsStore.editorSettings.preferredExportPath);
|
||||
const editAutoOpenExportFolder = ref(settingsStore.editorSettings.autoOpenExportFolder);
|
||||
@@ -1192,6 +1196,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
|
||||
sidebarShowTooltips: editSidebarShowTooltips.value,
|
||||
sidebarIndent: editSidebarIndent.value,
|
||||
sidebarFontSize: editSidebarFontSize.value,
|
||||
sidebarDensity: editSidebarDensity.value,
|
||||
sidebarHiddenTablePrefixes: normalizeSidebarHiddenTablePrefixes(editSidebarHiddenTablePrefixes.value),
|
||||
sidebarCopyTableNameSeparator: editSidebarCopyTableNameSeparator.value,
|
||||
sidebarCopyTableNameIncludeSchema: editSidebarCopyTableNameIncludeSchema.value,
|
||||
@@ -1883,6 +1888,7 @@ function syncEditorSettingsDraftFromStore() {
|
||||
editSidebarShowTooltips.value = settingsStore.editorSettings.sidebarShowTooltips;
|
||||
editSidebarIndent.value = settingsStore.editorSettings.sidebarIndent;
|
||||
editSidebarFontSize.value = settingsStore.editorSettings.sidebarFontSize;
|
||||
editSidebarDensity.value = settingsStore.editorSettings.sidebarDensity;
|
||||
editExportBatchSize.value = settingsStore.editorSettings.exportBatchSize;
|
||||
editPreferredExportPath.value = settingsStore.editorSettings.preferredExportPath;
|
||||
editAutoOpenExportFolder.value = settingsStore.editorSettings.autoOpenExportFolder;
|
||||
@@ -2037,6 +2043,7 @@ const editorSettingsDraftRefs: EditorSettingsDraftRefMap = {
|
||||
sidebarShowTooltips: editSidebarShowTooltips,
|
||||
sidebarIndent: editSidebarIndent,
|
||||
sidebarFontSize: editSidebarFontSize,
|
||||
sidebarDensity: editSidebarDensity,
|
||||
sidebarHiddenTablePrefixes: editSidebarHiddenTablePrefixes,
|
||||
sidebarCopyTableNameSeparator: editSidebarCopyTableNameSeparator,
|
||||
sidebarCopyTableNameIncludeSchema: editSidebarCopyTableNameIncludeSchema,
|
||||
@@ -2553,6 +2560,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
|
||||
editSidebarShowTooltips.value = DEFAULT_EDITOR_SETTINGS.sidebarShowTooltips;
|
||||
editSidebarIndent.value = DEFAULT_EDITOR_SETTINGS.sidebarIndent;
|
||||
editSidebarFontSize.value = DEFAULT_EDITOR_SETTINGS.sidebarFontSize;
|
||||
editSidebarDensity.value = DEFAULT_EDITOR_SETTINGS.sidebarDensity;
|
||||
editSidebarHiddenTablePrefixes.value = DEFAULT_EDITOR_SETTINGS.sidebarHiddenTablePrefixes.join("\n");
|
||||
editSidebarCopyTableNameSeparator.value = DEFAULT_EDITOR_SETTINGS.sidebarCopyTableNameSeparator;
|
||||
editSidebarCopyTableNameIncludeSchema.value = DEFAULT_EDITOR_SETTINGS.sidebarCopyTableNameIncludeSchema;
|
||||
@@ -2765,6 +2773,7 @@ function resetAllDefaults() {
|
||||
editSidebarShowTooltips.value = DEFAULT_EDITOR_SETTINGS.sidebarShowTooltips;
|
||||
editSidebarIndent.value = DEFAULT_EDITOR_SETTINGS.sidebarIndent;
|
||||
editSidebarFontSize.value = DEFAULT_EDITOR_SETTINGS.sidebarFontSize;
|
||||
editSidebarDensity.value = DEFAULT_EDITOR_SETTINGS.sidebarDensity;
|
||||
editSidebarHiddenTablePrefixes.value = DEFAULT_EDITOR_SETTINGS.sidebarHiddenTablePrefixes.join("\n");
|
||||
editSidebarCopyTableNameSeparator.value = DEFAULT_EDITOR_SETTINGS.sidebarCopyTableNameSeparator;
|
||||
editSidebarCopyTableNameIncludeSchema.value = DEFAULT_EDITOR_SETTINGS.sidebarCopyTableNameIncludeSchema;
|
||||
@@ -8305,6 +8314,31 @@ onUnmounted(() => {
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="settings-item flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
|
||||
<div class="space-y-1">
|
||||
<Label for="sidebar-density">{{ t("settings.sidebarDensity") }}</Label>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ t("settings.sidebarDensityDescription") }}
|
||||
</p>
|
||||
</div>
|
||||
<Select
|
||||
:model-value="editSidebarDensity"
|
||||
@update:model-value="
|
||||
(value) => {
|
||||
if (isSidebarDensity(value)) editSidebarDensity = value;
|
||||
}
|
||||
"
|
||||
>
|
||||
<SelectTrigger id="sidebar-density" class="h-8 w-36 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper" align="end">
|
||||
<SelectItem v-for="density in SIDEBAR_DENSITIES" :key="density" :value="density" class="text-xs">
|
||||
{{ t(`settings.sidebarDensity_${density}`) }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="sidebar-hidden-table-prefixes">{{ t("settings.sidebarHiddenTablePrefixes") }}</Label>
|
||||
<textarea
|
||||
|
||||
@@ -53,7 +53,7 @@ import {
|
||||
type SidebarNodeScrollAlign,
|
||||
} from "@/lib/sidebar/sidebarActiveTabTarget";
|
||||
import { findLoadedTableTargetForCandidate, queryContextTargetFromCandidate, queryCursorTableCandidate, type QueryCursorTableCandidate } from "@/lib/sql/queryCursorTableTarget";
|
||||
import { createFlatTreeIndex, flatTreeRowsChanged, SIDEBAR_TREE_ROW_HEIGHT, SIDEBAR_TREE_PRERENDER_COUNT, SIDEBAR_TREE_SCROLL_BUFFER, SIDEBAR_TREE_VIRTUALIZE_HYSTERESIS, SIDEBAR_TREE_VIRTUALIZE_THRESHOLD, flattenTree, shouldVirtualizeFlatTree, type FlatTreeNode } from "@/composables/useFlatTree";
|
||||
import { createFlatTreeIndex, flatTreeRowsChanged, getSidebarTreeRowHeight, SIDEBAR_TREE_PRERENDER_COUNT, SIDEBAR_TREE_SCROLL_BUFFER, SIDEBAR_TREE_VIRTUALIZE_HYSTERESIS, SIDEBAR_TREE_VIRTUALIZE_THRESHOLD, flattenTree, shouldVirtualizeFlatTree, type FlatTreeNode } from "@/composables/useFlatTree";
|
||||
import { sidebarTreeContextKey } from "@/lib/sidebar/sidebarTreeContext";
|
||||
import { createSidebarTreeRuntime, sidebarTreeRuntimeKey, type SidebarTreeRuntimeHostInstance } from "@/lib/sidebar/sidebarTreeRuntime";
|
||||
import { createSidebarPasteHandlerRegistry } from "@/lib/sidebar/sidebarPasteHandlerRegistry";
|
||||
@@ -111,7 +111,11 @@ const showConnectedConnectionsOnly = ref(false);
|
||||
const isDisconnectingAllActiveConnections = ref(false);
|
||||
const searchInputRef = ref<HTMLInputElement>();
|
||||
const rootRef = ref<HTMLElement>();
|
||||
const sidebarRootStyle = computed<Record<string, string>>(() => ({ fontSize: `${settingsStore.editorSettings.sidebarFontSize}px` }));
|
||||
const sidebarTreeRowHeight = computed(() => getSidebarTreeRowHeight(settingsStore.editorSettings.sidebarDensity));
|
||||
const sidebarRootStyle = computed<Record<string, string>>(() => ({
|
||||
fontSize: `${settingsStore.editorSettings.sidebarFontSize}px`,
|
||||
"--sidebar-tree-row-height": `${sidebarTreeRowHeight.value}px`,
|
||||
}));
|
||||
const pointerInsideTree = ref(false);
|
||||
const treeScrollerRef = ref<InstanceType<typeof RecycleScroller> | null>(null);
|
||||
const plainTreeScrollerRef = ref<HTMLElement | null>(null);
|
||||
@@ -855,7 +859,7 @@ const sidebarLayoutMonitor = createSidebarLayoutMonitor({
|
||||
readContext: () => ({
|
||||
flatNodeCount: flatNodes.value.length,
|
||||
useVirtualTree: useVirtualTree.value,
|
||||
virtualItemSize: SIDEBAR_TREE_ROW_HEIGHT,
|
||||
virtualItemSize: sidebarTreeRowHeight.value,
|
||||
scrollerEl: currentTreeScroller(),
|
||||
shellEl: treeScrollShellRef.value,
|
||||
rootEl: rootRef.value ?? null,
|
||||
@@ -970,10 +974,14 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
watch([sidebarTreeNaturalWidthItems, () => settingsStore.editorSettings.uiFontFamily, () => settingsStore.editorSettings.uiScale, () => settingsStore.editorSettings.sidebarIndent, () => settingsStore.editorSettings.sidebarFontSize], scheduleSidebarTreeContentWidthMeasure, {
|
||||
flush: "post",
|
||||
immediate: true,
|
||||
});
|
||||
watch(
|
||||
[sidebarTreeNaturalWidthItems, () => settingsStore.editorSettings.uiFontFamily, () => settingsStore.editorSettings.uiScale, () => settingsStore.editorSettings.sidebarIndent, () => settingsStore.editorSettings.sidebarFontSize, () => settingsStore.editorSettings.sidebarDensity],
|
||||
scheduleSidebarTreeContentWidthMeasure,
|
||||
{
|
||||
flush: "post",
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
const visibleSidebarTableStorageScopes = computed(() => {
|
||||
if (settingsStore.editorSettings.sidebarObjectInfoMode !== "size") return [];
|
||||
@@ -1143,10 +1151,10 @@ const stickyContainerIndex = computed(() => {
|
||||
const len = nodes.length;
|
||||
if (len === 0) return -1;
|
||||
|
||||
const topIndex = Math.min(Math.floor(stickyScrollTop.value / SIDEBAR_TREE_ROW_HEIGHT), len - 1);
|
||||
const topIndex = Math.min(Math.floor(stickyScrollTop.value / sidebarTreeRowHeight.value), len - 1);
|
||||
const containerIndex = flatTreeIndex.value.stickyContainerIndexByIndex[topIndex] ?? -1;
|
||||
if (containerIndex < 0) return -1;
|
||||
return stickyScrollTop.value > containerIndex * SIDEBAR_TREE_ROW_HEIGHT ? containerIndex : -1;
|
||||
return stickyScrollTop.value > containerIndex * sidebarTreeRowHeight.value ? containerIndex : -1;
|
||||
});
|
||||
|
||||
const stickyNode = computed<FlatTreeNode | null>(() => flatNodes.value[stickyContainerIndex.value] ?? null);
|
||||
@@ -1160,10 +1168,10 @@ const stickyHeaderStyle = computed<CSSProperties>(() => {
|
||||
const nextBoundaryIndex = flatTreeIndex.value.nextBoundaryIndexByIndex[currentIndex] ?? -1;
|
||||
const nextCollisionIndex = nextContainerIndex < 0 ? nextBoundaryIndex : nextBoundaryIndex < 0 ? nextContainerIndex : Math.min(nextContainerIndex, nextBoundaryIndex);
|
||||
if (nextCollisionIndex < 0) return {};
|
||||
const distanceToNext = nextCollisionIndex * SIDEBAR_TREE_ROW_HEIGHT - stickyScrollTop.value;
|
||||
if (distanceToNext >= SIDEBAR_TREE_ROW_HEIGHT) return {};
|
||||
const distanceToNext = nextCollisionIndex * sidebarTreeRowHeight.value - stickyScrollTop.value;
|
||||
if (distanceToNext >= sidebarTreeRowHeight.value) return {};
|
||||
return {
|
||||
transform: `translateY(${Math.min(0, distanceToNext - SIDEBAR_TREE_ROW_HEIGHT)}px)`,
|
||||
transform: `translateY(${Math.min(0, distanceToNext - sidebarTreeRowHeight.value)}px)`,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1494,7 +1502,7 @@ async function flashSidebarNode(nodeId: string) {
|
||||
function topOcclusionHeightForSidebarNode(nodeId: string): number {
|
||||
const sticky = stickyNode.value;
|
||||
if (!sticky || sticky.id === nodeId) return 0;
|
||||
return SIDEBAR_TREE_ROW_HEIGHT;
|
||||
return sidebarTreeRowHeight.value;
|
||||
}
|
||||
|
||||
/** Select and reveal a freshly created table group. */
|
||||
@@ -1515,6 +1523,7 @@ async function scrollToSidebarNode(nodeId: string, options?: { align?: SidebarNo
|
||||
currentScrollTop: scroller.scrollTop,
|
||||
viewportHeight: scroller.clientHeight,
|
||||
scrollHeight: scroller.scrollHeight,
|
||||
rowHeight: sidebarTreeRowHeight.value,
|
||||
topOcclusionHeight: topOcclusionHeightForSidebarNode(nodeId),
|
||||
...(options?.align ? { align: options.align } : {}),
|
||||
});
|
||||
@@ -2259,6 +2268,7 @@ async function selectActiveTabSidebarNode(options: { scroll: boolean }) {
|
||||
currentScrollTop: scroller.scrollTop,
|
||||
viewportHeight: scroller.clientHeight,
|
||||
scrollHeight: scroller.scrollHeight,
|
||||
rowHeight: sidebarTreeRowHeight.value,
|
||||
topOcclusionHeight: topOcclusionHeightForSidebarNode(match.id),
|
||||
});
|
||||
if (nextScrollTop !== scroller.scrollTop) {
|
||||
@@ -2626,7 +2636,7 @@ defineExpose({ focusSearch, createNewGroup, collapseAllTreeNodes, locateTabInSid
|
||||
:style="sidebarTreeScrollerStyle"
|
||||
@click="clearSidebarSelection"
|
||||
:items="flatNodes"
|
||||
:item-size="SIDEBAR_TREE_ROW_HEIGHT"
|
||||
:item-size="sidebarTreeRowHeight"
|
||||
:buffer="SIDEBAR_TREE_SCROLL_BUFFER"
|
||||
:prerender="SIDEBAR_TREE_PRERENDER_COUNT"
|
||||
:skip-hover="true"
|
||||
@@ -2871,12 +2881,12 @@ defineExpose({ focusSearch, createNewGroup, collapseAllTreeNodes, locateTabInSid
|
||||
.connection-tree-scroller :deep(.vue-recycle-scroller__item-view) {
|
||||
min-width: 100%;
|
||||
contain: style;
|
||||
/* The virtual renderer positions rows at fixed item-size offsets (28px, see
|
||||
SIDEBAR_TREE_ROW_HEIGHT). TreeItem rows only guarantee min-h-7, so rename
|
||||
inputs or larger sidebar fonts could grow a row beyond 28px and overlap
|
||||
the next row. Pin every materialized row to the fixed height and clip any
|
||||
overflow instead of letting the layout drift. */
|
||||
height: 28px;
|
||||
/* The virtual renderer positions rows at fixed item-size offsets (see
|
||||
getSidebarTreeRowHeight). TreeItem rows only guarantee min-h, so rename
|
||||
inputs or larger sidebar fonts could grow a row beyond the row height and
|
||||
overlap the next row. Pin every materialized row to the fixed height and clip
|
||||
any overflow instead of letting the layout drift. */
|
||||
height: var(--sidebar-tree-row-height, 28px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
@@ -945,6 +945,8 @@ function hasTrailingMetadata(): boolean {
|
||||
|
||||
const usesFullWidthLabel = computed(() => usesFullWidthTreeLabel(activeNode.value.type, settingsStore.editorSettings.sidebarAllowHorizontalScroll, hasTrailingMetadata()));
|
||||
|
||||
const isCompactSidebar = computed(() => settingsStore.editorSettings.sidebarDensity === "compact");
|
||||
|
||||
const rowWidthClass = computed(() => (usesFullWidthLabel.value ? "w-max min-w-full" : "w-full min-w-0"));
|
||||
|
||||
const labelWidthClass = computed(() => {
|
||||
@@ -1583,7 +1585,17 @@ function onKeydown(event: KeyboardEvent) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="node.type === 'table-search-control'" data-sidebar-table-search-control class="tree-table-search-control flex h-7 items-center gap-1.5 py-0.5 pr-2" :style="tableSearchStyle" @click.stop @dblclick.stop @mousedown.stop @keydown="onTableSearchControlKeydown">
|
||||
<div
|
||||
v-if="node.type === 'table-search-control'"
|
||||
data-sidebar-table-search-control
|
||||
class="tree-table-search-control flex items-center gap-1.5 pr-2"
|
||||
:class="isCompactSidebar ? 'h-6 py-0' : 'h-7 py-0.5'"
|
||||
:style="tableSearchStyle"
|
||||
@click.stop
|
||||
@dblclick.stop
|
||||
@mousedown.stop
|
||||
@keydown="onTableSearchControlKeydown"
|
||||
>
|
||||
<div class="relative min-w-0 flex-1">
|
||||
<Search class="pointer-events-none absolute left-2 top-1/2 h-3 w-3 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input
|
||||
@@ -1591,7 +1603,8 @@ function onKeydown(event: KeyboardEvent) {
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
class="h-6 w-full rounded border pl-7 pr-6 text-xs shadow-none focus-visible:ring-1"
|
||||
class="w-full rounded border pl-7 pr-6 text-xs shadow-none focus-visible:ring-1"
|
||||
:class="isCompactSidebar ? 'h-5' : 'h-6'"
|
||||
:style="{ backgroundColor: 'var(--tree-table-search-input-bg)', borderColor: 'var(--tree-table-search-border)' }"
|
||||
:placeholder="t(node.label)"
|
||||
:aria-label="t(node.label)"
|
||||
@@ -1616,9 +1629,10 @@ function onKeydown(event: KeyboardEvent) {
|
||||
<LightTooltip :text="visibleLabel(node)" :disabled="isTooltipDisabled()" side="right" :side-offset="8" :delay="0" :close-delay="30" :surface="detailTooltip ? 'popover' : 'foreground'">
|
||||
<div
|
||||
ref="rowRef"
|
||||
class="group flex cursor-default items-center gap-2 min-h-7 py-1 px-2 relative outline-none"
|
||||
class="group flex cursor-default items-center gap-2 px-2 relative outline-none"
|
||||
style="contain: layout style"
|
||||
:class="[
|
||||
isCompactSidebar ? 'min-h-6 py-0.5' : 'min-h-7 py-1',
|
||||
rowWidthClass,
|
||||
{
|
||||
'group/sidebar-row': true,
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { createApp, defineComponent, h, nextTick, reactive, type App } from "vue";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import i18n from "@/i18n";
|
||||
import TreeItem from "@/components/sidebar/TreeItem.vue";
|
||||
import { createSidebarTreeRuntime, sidebarTreeRuntimeKey, type SidebarTreeRuntimeHost } from "@/lib/sidebar/sidebarTreeRuntime";
|
||||
import type { TreeNode } from "@/types/database";
|
||||
import type { SidebarDensity } from "@/stores/settingsStore";
|
||||
|
||||
const mockSettings = reactive({
|
||||
editorSettings: {
|
||||
shortcuts: { openDataInNewTab: "" },
|
||||
sidebarActivation: "double",
|
||||
sidebarAllowHorizontalScroll: false,
|
||||
sidebarHiddenTablePrefixes: [],
|
||||
sidebarObjectInfoMode: "none",
|
||||
sidebarDensity: "default" as SidebarDensity,
|
||||
sidebarShowTooltips: false,
|
||||
sidebarIndent: 16,
|
||||
sidebarTableSearchLocal: true,
|
||||
},
|
||||
});
|
||||
|
||||
const connectionStore = {
|
||||
activeConnectionId: "connection-1",
|
||||
connectedIds: new Set(["connection-1"]),
|
||||
connectingIds: new Set<string>(),
|
||||
connectionErrors: {},
|
||||
connectionMultiSelectActive: false,
|
||||
connections: [],
|
||||
getConfig: () => undefined,
|
||||
getSidebarVisibleFilterSummary: () => null,
|
||||
clearConnectionError: vi.fn(),
|
||||
isDefaultDatabase: () => false,
|
||||
isDefaultSchema: () => false,
|
||||
isPinnedTreeNodeReorderTarget: () => false,
|
||||
isTreeNodeChildrenLoaded: () => false,
|
||||
isTreeNodePinned: () => false,
|
||||
selectedTreeNodeId: null as string | null,
|
||||
selectedTreeNodeIds: [] as string[],
|
||||
selectedTreeNodeIdsSet: new Set<string>(),
|
||||
sidebarTableSearchQueries: {},
|
||||
tableNameFilterForScope: () => undefined,
|
||||
treeNodes: [],
|
||||
treeSelectionAnchorId: null as string | null,
|
||||
refreshSidebarTableSearch: vi.fn(),
|
||||
};
|
||||
|
||||
vi.mock("@/stores/connectionStore", () => ({
|
||||
useConnectionStore: () => connectionStore,
|
||||
}));
|
||||
|
||||
vi.mock("@/stores/queryStore", () => ({
|
||||
useQueryStore: () => ({ openDatabaseKeys: new Set<string>() }),
|
||||
}));
|
||||
|
||||
vi.mock("@/stores/settingsStore", () => ({
|
||||
useSettingsStore: () => mockSettings,
|
||||
}));
|
||||
|
||||
vi.mock("@/composables/useToast", () => ({
|
||||
useToast: () => ({ toast: vi.fn() }),
|
||||
}));
|
||||
|
||||
const mountedApps: App[] = [];
|
||||
|
||||
afterEach(() => {
|
||||
while (mountedApps.length > 0) {
|
||||
mountedApps.pop()?.unmount();
|
||||
}
|
||||
document.body.innerHTML = "";
|
||||
mockSettings.editorSettings.sidebarDensity = "default";
|
||||
});
|
||||
|
||||
function createTreeRuntimeHost(): SidebarTreeRuntimeHost {
|
||||
return {
|
||||
handleRowClick: vi.fn(),
|
||||
handleRowDoubleClick: vi.fn(),
|
||||
handleRowKeydown: vi.fn(),
|
||||
handleToggleExpand: vi.fn(),
|
||||
isSelected: () => false,
|
||||
isMultiSelected: () => false,
|
||||
isAnchor: () => false,
|
||||
usesSelectionSetHighlight: () => false,
|
||||
isSelectionLead: () => false,
|
||||
isDragging: () => false,
|
||||
dragVisualState: () => ({
|
||||
dragging: false,
|
||||
showBefore: false,
|
||||
showAfter: false,
|
||||
showInside: false,
|
||||
dropTargetNodeId: null,
|
||||
}),
|
||||
tableVGroupDropTargetNodeId: () => null,
|
||||
updateTreeDragTarget: vi.fn(),
|
||||
clearTreeDragTarget: vi.fn(),
|
||||
canDragPinnedOrder: () => false,
|
||||
startPinnedOrderDrag: vi.fn(),
|
||||
isNodeHighlighted: () => false,
|
||||
onRowContextMenu: vi.fn(),
|
||||
executeDefaultNodeAction: vi.fn(),
|
||||
};
|
||||
}
|
||||
|
||||
function mountTreeItem(node: TreeNode) {
|
||||
const container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
|
||||
const runtime = createSidebarTreeRuntime(createTreeRuntimeHost());
|
||||
const rootComponent = defineComponent({
|
||||
setup() {
|
||||
return () => h(TreeItem, { node, depth: 0 });
|
||||
},
|
||||
});
|
||||
|
||||
const app = createApp(rootComponent);
|
||||
app.use(i18n);
|
||||
app.provide(sidebarTreeRuntimeKey, runtime);
|
||||
app.mount(container);
|
||||
mountedApps.push(app);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
describe("TreeItem sidebarDensity layout", () => {
|
||||
it("renders default min-h-7 and py-1 when sidebarDensity is default", async () => {
|
||||
mockSettings.editorSettings.sidebarDensity = "default";
|
||||
const node: TreeNode = {
|
||||
id: "table-1",
|
||||
label: "users",
|
||||
type: "table",
|
||||
connectionId: "connection-1",
|
||||
};
|
||||
|
||||
const container = mountTreeItem(node);
|
||||
await nextTick();
|
||||
|
||||
const row = container.querySelector('[data-node-id="table-1"]');
|
||||
expect(row).not.toBeNull();
|
||||
expect(row?.classList.contains("min-h-7")).toBe(true);
|
||||
expect(row?.classList.contains("py-1")).toBe(true);
|
||||
expect(row?.classList.contains("min-h-6")).toBe(false);
|
||||
expect(row?.classList.contains("py-0.5")).toBe(false);
|
||||
});
|
||||
|
||||
it("renders compact min-h-6 and py-0.5 when sidebarDensity is compact", async () => {
|
||||
mockSettings.editorSettings.sidebarDensity = "compact";
|
||||
const node: TreeNode = {
|
||||
id: "table-1",
|
||||
label: "users",
|
||||
type: "table",
|
||||
connectionId: "connection-1",
|
||||
};
|
||||
|
||||
const container = mountTreeItem(node);
|
||||
await nextTick();
|
||||
|
||||
const row = container.querySelector('[data-node-id="table-1"]');
|
||||
expect(row).not.toBeNull();
|
||||
expect(row?.classList.contains("min-h-6")).toBe(true);
|
||||
expect(row?.classList.contains("py-0.5")).toBe(true);
|
||||
expect(row?.classList.contains("min-h-7")).toBe(false);
|
||||
expect(row?.classList.contains("py-1")).toBe(false);
|
||||
});
|
||||
|
||||
it("adjusts search control row and input height in compact mode", async () => {
|
||||
mockSettings.editorSettings.sidebarDensity = "compact";
|
||||
const searchNode: TreeNode = {
|
||||
id: "search-1",
|
||||
label: "sidebar.searchTablesInCurrentScope",
|
||||
type: "table-search-control",
|
||||
tableSearchParentId: "db-1",
|
||||
};
|
||||
|
||||
const container = mountTreeItem(searchNode);
|
||||
await nextTick();
|
||||
|
||||
const control = container.querySelector("[data-sidebar-table-search-control]");
|
||||
expect(control).not.toBeNull();
|
||||
expect(control?.classList.contains("h-6")).toBe(true);
|
||||
expect(control?.classList.contains("h-7")).toBe(false);
|
||||
|
||||
const input = container.querySelector("input");
|
||||
expect(input).not.toBeNull();
|
||||
expect(input?.classList.contains("h-5")).toBe(true);
|
||||
expect(input?.classList.contains("h-6")).toBe(false);
|
||||
});
|
||||
});
|
||||
+16
-1
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createFlatTreeIndex, flatTreeRowsChanged, flattenTree, mutateFlatTreeExpansion, replaceFlatTreeChildren, type FlatTreeNode } from "@/composables/useFlatTree";
|
||||
import { createFlatTreeIndex, flatTreeRowsChanged, flattenTree, getSidebarTreeRowHeight, mutateFlatTreeExpansion, replaceFlatTreeChildren, SIDEBAR_TREE_ROW_HEIGHT, SIDEBAR_TREE_ROW_HEIGHT_COMPACT, SIDEBAR_TREE_ROW_HEIGHT_DEFAULT, type FlatTreeNode } from "@/composables/useFlatTree";
|
||||
import type { TreeNode, TreeNodeType } from "@/types/database";
|
||||
|
||||
function item(id: string, type: TreeNodeType, depth: number, children?: TreeNode[]): FlatTreeNode {
|
||||
@@ -194,3 +194,18 @@ describe("flat-tree render identities", () => {
|
||||
expect(new Set(flattened.map((entry) => entry.renderKey))).toHaveLength(flattened.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getSidebarTreeRowHeight", () => {
|
||||
it("returns default row height (28px) when unset or default", () => {
|
||||
expect(SIDEBAR_TREE_ROW_HEIGHT).toBe(28);
|
||||
expect(SIDEBAR_TREE_ROW_HEIGHT_DEFAULT).toBe(28);
|
||||
expect(getSidebarTreeRowHeight()).toBe(28);
|
||||
expect(getSidebarTreeRowHeight("default")).toBe(28);
|
||||
expect(getSidebarTreeRowHeight("unknown" as any)).toBe(28);
|
||||
});
|
||||
|
||||
it("returns compact row height (24px) when density is compact", () => {
|
||||
expect(SIDEBAR_TREE_ROW_HEIGHT_COMPACT).toBe(24);
|
||||
expect(getSidebarTreeRowHeight("compact")).toBe(24);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import type { TreeNode, TreeNodeType } from "@/types/database";
|
||||
|
||||
export const SIDEBAR_TREE_ROW_HEIGHT = 28;
|
||||
export const SIDEBAR_TREE_ROW_HEIGHT_DEFAULT = 28;
|
||||
export const SIDEBAR_TREE_ROW_HEIGHT_COMPACT = 24;
|
||||
export const SIDEBAR_TREE_ROW_HEIGHT = SIDEBAR_TREE_ROW_HEIGHT_DEFAULT;
|
||||
|
||||
export function getSidebarTreeRowHeight(density?: string): number {
|
||||
return density === "compact" ? SIDEBAR_TREE_ROW_HEIGHT_COMPACT : SIDEBAR_TREE_ROW_HEIGHT_DEFAULT;
|
||||
}
|
||||
export const SIDEBAR_TREE_SCROLL_BUFFER = 600;
|
||||
export const SIDEBAR_TREE_PRERENDER_COUNT = 48;
|
||||
|
||||
|
||||
@@ -7512,6 +7512,10 @@ export default withEnglishFallback({
|
||||
sidebarIndentDescription: "Ağacın hər dərinlik səviyyəsi üçün əlavə olunan üfüqi məsafə, piksellə.",
|
||||
sidebarFontSize: "Yan panel şriftinin ölçüsü",
|
||||
sidebarFontSizeDescription: "Yan paneldəki obyekt ağacının şrift ölçüsü, piksellə. Redaktorun şrift ölçüsündən asılı deyil.",
|
||||
sidebarDensity: "Yan panel sıxlığı",
|
||||
sidebarDensityDescription: "Yan panel naviqasiya ağacı üçün sətir hündürlüyü sıxlığı.",
|
||||
sidebarDensity_default: "Standart",
|
||||
sidebarDensity_compact: "Kompakt",
|
||||
snippetsDescription: "Redaktorda işə salınan SQL kod parçası şablonlarını fərdiləşdirin.",
|
||||
snippetTriggerKey: "Kod parçası işə salma düyməsi",
|
||||
snippetTriggerKeyDescription: "Redaktorda kod parçasının genişləndirilməsini işə salmaq üçün istifadə olunan düyməni seçin (Tab, Boşluq və ya Hər ikisi).",
|
||||
|
||||
@@ -8532,6 +8532,10 @@ export default {
|
||||
sidebarIndentDescription: "Horizontal offset added per tree depth level, in pixels.",
|
||||
sidebarFontSize: "Sidebar font size",
|
||||
sidebarFontSizeDescription: "Font size for the sidebar object tree, in pixels. Independent of the editor font size.",
|
||||
sidebarDensity: "Sidebar density",
|
||||
sidebarDensityDescription: "Row height density for the sidebar navigation tree.",
|
||||
sidebarDensity_default: "Default",
|
||||
sidebarDensity_compact: "Compact",
|
||||
snippetsDescription: "Customize SQL snippet templates triggered in the editor.",
|
||||
snippetTriggerKey: "Snippet trigger key",
|
||||
snippetTriggerKeyDescription: "Choose the key used to trigger snippet expansion in the editor (Tab, Space, or Both).",
|
||||
|
||||
@@ -8712,6 +8712,10 @@ export default withEnglishFallback({
|
||||
sidebarIndentDescription: "Desplazamiento en píxeles de la sangría para cada nivel del árbol.",
|
||||
sidebarFontSize: "Tamaño de fuente de la barra lateral",
|
||||
sidebarFontSizeDescription: "Tamaño de fuente (en píxeles) del árbol de objetos de la barra lateral, independiente del tamaño de fuente del editor.",
|
||||
sidebarDensity: "Densidad de la barra lateral",
|
||||
sidebarDensityDescription: "Densidad de altura de fila para el árbol de navegación de la barra lateral.",
|
||||
sidebarDensity_default: "Predeterminado",
|
||||
sidebarDensity_compact: "Compacto",
|
||||
themePaletteCustom: "Personalizado",
|
||||
customUiReset: "Restablecer predeterminados",
|
||||
customUiEditingLight: "Editando: Colores claros",
|
||||
|
||||
@@ -8260,6 +8260,10 @@ export default withEnglishFallback({
|
||||
sidebarIndentDescription: "Offset horizontal yang ditambahkan per tingkat kedalaman pohon, dalam piksel.",
|
||||
sidebarFontSize: "Ukuran font bilah sisi",
|
||||
sidebarFontSizeDescription: "Ukuran font untuk pohon objek bilah sisi, dalam piksel. Independen dari ukuran font editor.",
|
||||
sidebarDensity: "Kepadatan bilah sisi",
|
||||
sidebarDensityDescription: "Kepadatan tinggi baris untuk pohon navigasi bilah sisi.",
|
||||
sidebarDensity_default: "Default",
|
||||
sidebarDensity_compact: "Ringkas",
|
||||
snippetsDescription: "Sesuaikan template snippet SQL yang dipicu di editor.",
|
||||
snippetTriggerKey: "Tombol pemicu snippet",
|
||||
snippetTriggerKeyDescription: "Pilih tombol yang digunakan untuk memicu perluasan snippet di editor (Tab, Spasi, atau Keduanya).",
|
||||
|
||||
@@ -8458,6 +8458,10 @@ export default withEnglishFallback({
|
||||
sidebarIndentDescription: "Offset in pixel per l'indentazione di ogni livello dell'albero.",
|
||||
sidebarFontSize: "Dimensione del carattere della barra laterale",
|
||||
sidebarFontSizeDescription: "Dimensione del carattere (in pixel) per l'albero degli oggetti della barra laterale, indipendente dalla dimensione del carattere dell'editor.",
|
||||
sidebarDensity: "Densità della barra laterale",
|
||||
sidebarDensityDescription: "Densità dell'altezza della riga per l'albero di navigazione della barra laterale.",
|
||||
sidebarDensity_default: "Predefinito",
|
||||
sidebarDensity_compact: "Compatto",
|
||||
themePaletteCustom: "Personalizzato",
|
||||
customUiReset: "Ripristina predefiniti",
|
||||
customUiEditingLight: "Modifica in corso: tema chiaro",
|
||||
|
||||
@@ -8499,6 +8499,10 @@ export default withEnglishFallback({
|
||||
sidebarIndentDescription: "各ツリー階層のインデントのピクセルオフセット。",
|
||||
sidebarFontSize: "サイドバーのフォントサイズ",
|
||||
sidebarFontSizeDescription: "サイドバーのオブジェクトツリーのフォントサイズ(ピクセル)。エディターのフォントサイズとは独立しています。",
|
||||
sidebarDensity: "サイドバーの表示密度",
|
||||
sidebarDensityDescription: "サイドバーのオブジェクトツリーの行の高さと表示密度。",
|
||||
sidebarDensity_default: "デフォルト",
|
||||
sidebarDensity_compact: "コンパクト",
|
||||
themePaletteCustom: "カスタム",
|
||||
customUiReset: "デフォルトに戻す",
|
||||
customUiEditingLight: "編集中:ライト配色",
|
||||
|
||||
@@ -7413,6 +7413,10 @@ export default withEnglishFallback({
|
||||
sidebarAllowHorizontalScrollDescription: "사이드바 가로 스크롤을 허용하여 긴 테이블, 뷰, 컬렉션 이름을 전체로 표시합니다.",
|
||||
sidebarShowTooltips: "사이드바 호버 툴팁",
|
||||
sidebarShowTooltipsDescription: "사이드바에서 연결 및 데이터베이스 개체에 마우스를 올리면 상세 툴팁을 표시합니다. 끄면 표시되지 않습니다.",
|
||||
sidebarDensity: "사이드바 표시 밀도",
|
||||
sidebarDensityDescription: "사이드바 탐색 트리의 행 높이 및 표시 밀도입니다.",
|
||||
sidebarDensity_default: "기본",
|
||||
sidebarDensity_compact: "컴팩트",
|
||||
snippetsDescription: "편집기에서 트리거되는 SQL 스니펫 템플릿을 사용자 정의합니다.",
|
||||
snippetTriggerKey: "스니펫 트리거 키",
|
||||
snippetTriggerKeyDescription: "편집기에서 스니펫 확장을 트리거하는 키를 설정합니다(Tab, 스페이스 또는 둘 다).",
|
||||
|
||||
@@ -8458,6 +8458,10 @@ export default withEnglishFallback({
|
||||
sidebarIndentDescription: "O deslocamento em pixels da indentação para cada nível da árvore.",
|
||||
sidebarFontSize: "Tamanho da fonte da barra lateral",
|
||||
sidebarFontSizeDescription: "Tamanho da fonte (em pixels) da árvore de objetos da barra lateral, independente do tamanho da fonte do editor.",
|
||||
sidebarDensity: "Densidade da barra lateral",
|
||||
sidebarDensityDescription: "Densidade da altura da linha para a árvore de navegação da barra lateral.",
|
||||
sidebarDensity_default: "Padrão",
|
||||
sidebarDensity_compact: "Compacto",
|
||||
themePaletteCustom: "Personalizado",
|
||||
customUiReset: "Restaurar padrão",
|
||||
customUiEditingLight: "Editando: paleta de cores clara",
|
||||
|
||||
@@ -8827,6 +8827,10 @@ export default withEnglishFallback({
|
||||
sidebarIndentDescription: "Горизонтальный отступ, добавляемый на каждый уровень глубины дерева, в пикселях.",
|
||||
sidebarFontSize: "Размер шрифта боковой панели",
|
||||
sidebarFontSizeDescription: "Размер шрифта дерева объектов боковой панели в пикселях. Не зависит от размера шрифта редактора.",
|
||||
sidebarDensity: "Плотность боковой панели",
|
||||
sidebarDensityDescription: "Плотность высоты строк дерева навигации боковой панели.",
|
||||
sidebarDensity_default: "По умолчанию",
|
||||
sidebarDensity_compact: "Компактная",
|
||||
snippetsDescription: "Настройка шаблонов SQL-сниппетов, вызываемых в редакторе.",
|
||||
snippetTriggerKey: "Клавиша вызова сниппетов",
|
||||
snippetTriggerKeyDescription: "Выберите клавишу для вставки сниппетов в редакторе (Tab, пробел или оба).",
|
||||
|
||||
@@ -7411,6 +7411,10 @@ export default withEnglishFallback({
|
||||
sidebarIndentDescription: "Ağaç derinliğinin her düzeyi için piksel cinsinden eklenen yatay kaydırma.",
|
||||
sidebarFontSize: "Kenar çubuğu yazı tipi boyutu",
|
||||
sidebarFontSizeDescription: "Kenar çubuğu nesne ağacı için piksel cinsinden yazı tipi boyutu. Düzenleyici yazı tipi boyutundan bağımsızdır.",
|
||||
sidebarDensity: "Kenar çubuğu yoğunluğu",
|
||||
sidebarDensityDescription: "Kenar çubuğu gezinti ağacı için satır yüksekliği yoğunluğu.",
|
||||
sidebarDensity_default: "Varsayılan",
|
||||
sidebarDensity_compact: "Sıkışık",
|
||||
snippetsDescription: "Düzenleyicide tetiklenen SQL parçacığı şablonlarını özelleştirin.",
|
||||
snippetTriggerKey: "Kod parçacığı tetikleme tuşu",
|
||||
snippetTriggerKeyDescription: "Düzenleyicide kod parçacığı genişletmesini tetiklemek için kullanılan tuşu seçin (Tab, Boşluk veya Her İkisi).",
|
||||
|
||||
@@ -8517,6 +8517,10 @@ export default withEnglishFallback({
|
||||
sidebarIndentDescription: "每个树层级缩进的像素偏移量。",
|
||||
sidebarFontSize: "侧边栏字体大小",
|
||||
sidebarFontSizeDescription: "侧边栏对象树的字体大小(像素),独立于编辑器字体大小。",
|
||||
sidebarDensity: "侧边栏行高密度",
|
||||
sidebarDensityDescription: "侧边栏对象导航树的行高密度。",
|
||||
sidebarDensity_default: "默认",
|
||||
sidebarDensity_compact: "紧凑",
|
||||
snippetsDescription: "自定义编辑器中触发的 SQL 代码片段模板。",
|
||||
snippetTriggerKey: "代码片段触发键",
|
||||
snippetTriggerKeyDescription: "设置在编辑器中触发插入代码片段的按键(Tab、空格或两者均可)。",
|
||||
|
||||
@@ -7814,6 +7814,10 @@ export default withEnglishFallback({
|
||||
sidebarIndentDescription: "每個樹層級縮排的像素位移量。",
|
||||
sidebarFontSize: "側邊欄字型大小",
|
||||
sidebarFontSizeDescription: "側邊欄物件樹的字型大小(像素),獨立於編輯器字型大小。",
|
||||
sidebarDensity: "側邊欄行高密度",
|
||||
sidebarDensityDescription: "側邊欄物件導覽樹的行高密度。",
|
||||
sidebarDensity_default: "預設",
|
||||
sidebarDensity_compact: "緊湊",
|
||||
themePaletteCustom: "自訂",
|
||||
customUiReset: "恢復預設",
|
||||
customUiEditingLight: "正在編輯:淺色配色",
|
||||
|
||||
@@ -169,6 +169,19 @@ describe("settingsTransfer", () => {
|
||||
expect(result.error.detail).toContain("appLayout");
|
||||
});
|
||||
|
||||
it("round-trips sidebarDensity in navigation category and rejects invalid values", () => {
|
||||
const valid = parseSettingsTransferFile(fileWith({ sidebarDensity: "compact" }));
|
||||
expect(valid.ok).toBe(true);
|
||||
if (!valid.ok) return;
|
||||
expect(valid.value.editorSettings.sidebarDensity).toBe("compact");
|
||||
expect(valid.value.categories).toContain("navigation");
|
||||
|
||||
const invalid = parseSettingsTransferFile(fileWith({ sidebarDensity: "ultra-compact" }));
|
||||
expect(invalid.ok).toBe(false);
|
||||
if (!invalid.ok) return;
|
||||
expect(invalid.error.detail).toContain("sidebarDensity");
|
||||
});
|
||||
|
||||
it("rejects pass-through boolean flags with non-boolean values", () => {
|
||||
const result = parseSettingsTransferFile(fileWith({ wordWrap: "yes" }));
|
||||
expect(result.ok).toBe(false);
|
||||
|
||||
@@ -135,6 +135,7 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [
|
||||
"sidebarShowTooltips",
|
||||
"sidebarIndent",
|
||||
"sidebarFontSize",
|
||||
"sidebarDensity",
|
||||
"sidebarHiddenTablePrefixes",
|
||||
"sidebarCopyTableNameSeparator",
|
||||
"sidebarCopyTableNameIncludeSchema",
|
||||
|
||||
@@ -219,6 +219,7 @@ export const SETTINGS_SEARCH_DEFINITIONS: readonly SettingsSearchDefinition[] =
|
||||
{ id: "navigation-sidebar-tooltips", category: "navigation", titleKey: "settings.sidebarShowTooltips", descriptionKey: "settings.sidebarShowTooltipsDescription", targetId: "navigation" },
|
||||
{ id: "navigation-sidebar-indent", category: "navigation", titleKey: "settings.sidebarIndent", descriptionKey: "settings.sidebarIndentDescription", targetId: "navigation" },
|
||||
{ id: "navigation-sidebar-font-size", category: "navigation", titleKey: "settings.sidebarFontSize", descriptionKey: "settings.sidebarFontSizeDescription", targetId: "navigation" },
|
||||
{ id: "navigation-sidebar-density", category: "navigation", titleKey: "settings.sidebarDensity", descriptionKey: "settings.sidebarDensityDescription", targetId: "navigation" },
|
||||
{ id: "navigation-hidden-tables", category: "navigation", titleKey: "settings.sidebarHiddenTablePrefixes", descriptionKey: "settings.sidebarHiddenTablePrefixesDescription", targetId: "navigation" },
|
||||
{ id: "history-retention", category: "data", titleKey: "settings.historyRetentionLimit", descriptionKey: "settings.historyRetentionDescription", targetId: "history-retention" },
|
||||
{ id: "mcp-history-retention", category: "data", titleKey: "settings.mcpHistoryRetentionLimit", descriptionKey: "settings.mcpHistoryRetentionDescription", targetId: "mcp-history-retention" },
|
||||
|
||||
@@ -144,6 +144,7 @@ const SETTINGS_TRANSFER_CATEGORY_KEYS: Record<SettingsTransferCategoryId, readon
|
||||
"sidebarShowTooltips",
|
||||
"sidebarIndent",
|
||||
"sidebarFontSize",
|
||||
"sidebarDensity",
|
||||
"sidebarHiddenTablePrefixes",
|
||||
"sidebarCopyTableNameSeparator",
|
||||
"sidebarCopyTableNameIncludeSchema",
|
||||
@@ -327,6 +328,7 @@ const PASS_THROUGH_FIELD_VALIDATORS: Partial<Record<EditorSettingsDraftKey, (val
|
||||
webLogoPosition: (value) => value === "left" || value === "right" || value === "hidden",
|
||||
resultTabNamingMode: (value) => value === "source" || value === "ordinal" || value === "comment",
|
||||
activeCustomThemeId: (value) => typeof value === "string" && value.trim().length > 0,
|
||||
sidebarDensity: (value) => value === "default" || value === "compact",
|
||||
// normalizeToolbarItems keeps unknown/typed values for every known key, so
|
||||
// each one must already be the boolean the UI writes, and no extra key may
|
||||
// appear.
|
||||
|
||||
@@ -299,6 +299,11 @@ describe("normalizeEditorSettings", () => {
|
||||
expect(normalizeEditorSettings({ sidebarIndent: 999, sidebarFontSize: 1 } as any).sidebarFontSize).toBe(9);
|
||||
expect(normalizeEditorSettings({ sidebarIndent: 1.4, sidebarFontSize: 13.6 } as any).sidebarIndent).toBe(4);
|
||||
expect(normalizeEditorSettings({ sidebarIndent: 1.4, sidebarFontSize: 13.6 } as any).sidebarFontSize).toBe(14);
|
||||
expect(normalizeEditorSettings({}).sidebarDensity).toBe("default");
|
||||
expect(normalizeEditorSettings({ sidebarDensity: "compact" }).sidebarDensity).toBe("compact");
|
||||
expect(normalizeEditorSettings({ sidebarDensity: "default" }).sidebarDensity).toBe("default");
|
||||
expect(normalizeEditorSettings({ sidebarDensity: "comfortable" } as any).sidebarDensity).toBe("default");
|
||||
expect(normalizeEditorSettings({ sidebarDensity: 123 } as any).sidebarDensity).toBe("default");
|
||||
});
|
||||
|
||||
it("uses inline comments by default and preserves legacy comment visibility", () => {
|
||||
|
||||
@@ -723,6 +723,12 @@ const STRUCTURE_EDITOR_DENSITIES = ["compact", "standard", "comfortable"] as con
|
||||
export type StructureEditorDensity = (typeof STRUCTURE_EDITOR_DENSITIES)[number];
|
||||
const COLUMN_WIDTH_DENSITIES = ["compact", "standard", "comfortable"] as const;
|
||||
export type ColumnWidthDensity = (typeof COLUMN_WIDTH_DENSITIES)[number];
|
||||
export const SIDEBAR_DENSITIES = ["default", "compact"] as const;
|
||||
export type SidebarDensity = (typeof SIDEBAR_DENSITIES)[number];
|
||||
|
||||
export function isSidebarDensity(value: unknown): value is SidebarDensity {
|
||||
return typeof value === "string" && (SIDEBAR_DENSITIES as readonly string[]).includes(value);
|
||||
}
|
||||
const DATA_GRID_COLUMN_WIDTH_MODES = ["fill", "content"] as const;
|
||||
export type DataGridColumnWidthMode = (typeof DATA_GRID_COLUMN_WIDTH_MODES)[number];
|
||||
const CELL_DETAIL_PANEL_LAYOUTS = ["bottom", "right"] as const;
|
||||
@@ -1087,6 +1093,7 @@ export interface EditorSettings {
|
||||
sidebarAllowHorizontalScroll: boolean;
|
||||
sidebarIndent: number;
|
||||
sidebarFontSize: number;
|
||||
sidebarDensity: SidebarDensity;
|
||||
columnFormatters: Record<string, ColumnFormatterConfig>;
|
||||
customColumnFormatters: Record<string, CustomColumnFormatterConfig>;
|
||||
globalDateTimeDisplayFormat: string;
|
||||
@@ -1404,6 +1411,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
|
||||
sidebarAllowHorizontalScroll: false,
|
||||
sidebarIndent: SIDEBAR_INDENT_DEFAULT,
|
||||
sidebarFontSize: SIDEBAR_FONT_SIZE_DEFAULT,
|
||||
sidebarDensity: "default",
|
||||
columnFormatters: {},
|
||||
customColumnFormatters: {},
|
||||
globalDateTimeDisplayFormat: "",
|
||||
@@ -1586,6 +1594,10 @@ function normalizeSidebarIndent(value: unknown): number {
|
||||
return Math.min(SIDEBAR_INDENT_MAX, Math.max(SIDEBAR_INDENT_MIN, Math.round(value)));
|
||||
}
|
||||
|
||||
function normalizeSidebarDensity(value: unknown): SidebarDensity {
|
||||
return isSidebarDensity(value) ? value : DEFAULT_EDITOR_SETTINGS.sidebarDensity;
|
||||
}
|
||||
|
||||
function normalizeUpdateDownloadSource(value: unknown): UpdateDownloadSource {
|
||||
// Preserve the intent of users who previously selected the mainland-China mirror.
|
||||
if (value === "atomgit") return "cnb";
|
||||
@@ -2087,6 +2099,7 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
|
||||
sidebarAllowHorizontalScroll: settings.sidebarAllowHorizontalScroll ?? DEFAULT_EDITOR_SETTINGS.sidebarAllowHorizontalScroll,
|
||||
sidebarIndent: normalizeSidebarIndent(settings.sidebarIndent),
|
||||
sidebarFontSize: normalizeSidebarFontSize(settings.sidebarFontSize),
|
||||
sidebarDensity: normalizeSidebarDensity(settings.sidebarDensity),
|
||||
columnFormatters: normalizeColumnFormatters(settings.columnFormatters),
|
||||
customColumnFormatters: normalizeCustomColumnFormatters(settings.customColumnFormatters),
|
||||
globalDateTimeDisplayFormat: normalizeGlobalDateTimePattern(settings.globalDateTimeDisplayFormat),
|
||||
@@ -2930,6 +2943,7 @@ export const useSettingsStore = defineStore("settings", () => {
|
||||
if (partial.sidebarAllowHorizontalScroll !== undefined) editorSettings.value.sidebarAllowHorizontalScroll = partial.sidebarAllowHorizontalScroll;
|
||||
if (partial.sidebarIndent !== undefined) editorSettings.value.sidebarIndent = normalizeSidebarIndent(partial.sidebarIndent);
|
||||
if (partial.sidebarFontSize !== undefined) editorSettings.value.sidebarFontSize = normalizeSidebarFontSize(partial.sidebarFontSize);
|
||||
if (partial.sidebarDensity !== undefined) editorSettings.value.sidebarDensity = normalizeSidebarDensity(partial.sidebarDensity);
|
||||
if (partial.columnFormatters !== undefined) editorSettings.value.columnFormatters = partial.columnFormatters;
|
||||
if (partial.customColumnFormatters !== undefined) editorSettings.value.customColumnFormatters = partial.customColumnFormatters;
|
||||
if (partial.globalDateTimeDisplayFormat !== undefined) editorSettings.value.globalDateTimeDisplayFormat = normalizeGlobalDateTimePattern(partial.globalDateTimeDisplayFormat);
|
||||
|
||||
Reference in New Issue
Block a user