feat(sidebar): hide configured table prefixes

This commit is contained in:
t8y2 2026-05-21 22:56:42 +08:00
parent 390e244c85
commit 2569d54f68
11 changed files with 127 additions and 3 deletions

View File

@ -32,6 +32,7 @@ import {
normalizeShortcutSettings,
type ShortcutActionId,
} from "@/lib/shortcutRegistry";
import { normalizeSidebarHiddenTablePrefixes } from "@/lib/sidebarTableNameDisplay";
import AiProviderLogo from "@/components/icons/AiProviderLogo.vue";
const { t } = useI18n();
@ -57,6 +58,7 @@ const editAppLayout = ref(settingsStore.editorSettings.appLayout);
const editRedisScanPageSize = ref(settingsStore.editorSettings.redisScanPageSize);
const editShortcuts = ref(normalizeShortcutSettings(settingsStore.editorSettings.shortcuts));
const editSidebarActivation = ref(settingsStore.editorSettings.sidebarActivation);
const editSidebarHiddenTablePrefixes = ref(settingsStore.editorSettings.sidebarHiddenTablePrefixes.join("\n"));
const redisScanPageSizeOptions = [200, 1000, 5000, 10000];
const systemFonts = ref<string[]>([]);
const systemFontsLoading = ref(false);
@ -118,6 +120,7 @@ watch(
editRedisScanPageSize.value = settingsStore.editorSettings.redisScanPageSize;
editShortcuts.value = normalizeShortcutSettings(settingsStore.editorSettings.shortcuts);
editSidebarActivation.value = settingsStore.editorSettings.sidebarActivation;
editSidebarHiddenTablePrefixes.value = settingsStore.editorSettings.sidebarHiddenTablePrefixes.join("\n");
void loadSystemFontOptions();
}
},
@ -141,7 +144,9 @@ function hasChanges(): boolean {
editAppLayout.value !== settingsStore.editorSettings.appLayout ||
editRedisScanPageSize.value !== settingsStore.editorSettings.redisScanPageSize ||
JSON.stringify(editShortcuts.value) !== JSON.stringify(settingsStore.editorSettings.shortcuts) ||
editSidebarActivation.value !== settingsStore.editorSettings.sidebarActivation
editSidebarActivation.value !== settingsStore.editorSettings.sidebarActivation ||
JSON.stringify(normalizeSidebarHiddenTablePrefixes(editSidebarHiddenTablePrefixes.value)) !==
JSON.stringify(settingsStore.editorSettings.sidebarHiddenTablePrefixes)
);
}
@ -157,6 +162,7 @@ function applySettings() {
redisScanPageSize: editRedisScanPageSize.value,
shortcuts: editShortcuts.value,
sidebarActivation: editSidebarActivation.value,
sidebarHiddenTablePrefixes: normalizeSidebarHiddenTablePrefixes(editSidebarHiddenTablePrefixes.value),
});
emit("update:open", false);
}
@ -171,6 +177,7 @@ function resetDefaults() {
editRedisScanPageSize.value = DEFAULT_EDITOR_SETTINGS.redisScanPageSize;
editShortcuts.value = normalizeShortcutSettings(DEFAULT_EDITOR_SETTINGS.shortcuts);
editSidebarActivation.value = DEFAULT_EDITOR_SETTINGS.sidebarActivation;
editSidebarHiddenTablePrefixes.value = DEFAULT_EDITOR_SETTINGS.sidebarHiddenTablePrefixes.join("\n");
}
function onExecuteModeChange(v: any) {
@ -835,6 +842,18 @@ watch(
</Button>
</div>
</div>
<div class="space-y-2">
<Label for="sidebar-hidden-table-prefixes">{{ t("settings.sidebarHiddenTablePrefixes") }}</Label>
<textarea
id="sidebar-hidden-table-prefixes"
v-model="editSidebarHiddenTablePrefixes"
class="min-h-24 w-full rounded-md border border-input bg-background px-3 py-2 text-sm outline-none transition-colors placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
:placeholder="t('settings.sidebarHiddenTablePrefixesPlaceholder')"
/>
<p class="text-xs text-muted-foreground">
{{ t("settings.sidebarHiddenTablePrefixesDescription") }}
</p>
</div>
</section>
<section v-else-if="activeSettingsTab === 'redis'" class="flex flex-col gap-5 py-2">

View File

@ -107,6 +107,7 @@ import { buildViewDdl } from "@/lib/viewDdl";
import { hexToRgba } from "@/lib/color";
import { focusSidebarRenameInput, shouldPreventRenameCloseAutoFocus } from "@/lib/sidebarRenameFocus";
import { hasTreeNodeDatabaseContext } from "@/lib/treeNodeContext";
import { sidebarDisplayTableName } from "@/lib/sidebarTableNameDisplay";
import DangerConfirmDialog from "@/components/editor/DangerConfirmDialog.vue";
import { isTauriRuntime } from "@/lib/tauriRuntime";
import DatabaseIcon from "@/components/icons/DatabaseIcon.vue";
@ -256,6 +257,17 @@ function displayLabel(node: TreeNode): string {
return isGroupLabel(node) ? t(node.label) : node.label;
}
function visibleLabel(node: TreeNode): string {
if (node.type === "table" || node.type === "view" || node.type === "mongo-collection") {
return sidebarDisplayTableName(node.label, settingsStore.editorSettings.sidebarHiddenTablePrefixes);
}
return displayLabel(node);
}
function isTooltipDisabled(node: TreeNode): boolean {
return !isTruncated.value && visibleLabel(node) === displayLabel(node);
}
async function toggle() {
const node = props.node;
if (node.isLoading) return;
@ -1797,9 +1809,9 @@ const isDragging = computed(() => dragState.active && dragState.draggedId === pr
@keydown.escape.prevent="isRenamingGroup = false"
@click.stop
/>
<Tooltip v-else :disabled="!isTruncated">
<Tooltip v-else :disabled="isTooltipDisabled(node)">
<TooltipTrigger as-child>
<span ref="labelRef" class="min-w-0 flex-1 truncate">{{ displayLabel(node) }}</span>
<span ref="labelRef" class="min-w-0 flex-1 truncate">{{ visibleLabel(node) }}</span>
</TooltipTrigger>
<TooltipContent side="right" :side-offset="8">{{ displayLabel(node) }}</TooltipContent>
</Tooltip>

View File

@ -1198,6 +1198,10 @@ export default {
sidebarActivationSingleDescription: "Open actionable sidebar items with one click.",
sidebarActivationDouble: "Double click",
sidebarActivationDoubleDescription: "Single click selects rows; double click opens items.",
sidebarHiddenTablePrefixes: "Hidden table name prefixes",
sidebarHiddenTablePrefixesDescription:
"One prefix per line. Only sidebar table, view, and collection labels are shortened; tooltips and actions still use the full name.",
sidebarHiddenTablePrefixesPlaceholder: "Example:\nODS_\nT8Y2_LONG_",
apply: "Apply",
reset: "Reset",
resetDefaults: "Reset Defaults",

View File

@ -1096,6 +1096,10 @@ export default {
sidebarActivationSingleDescription: "Abrir elementos accionables de la barra lateral con un clic.",
sidebarActivationDouble: "Doble clic",
sidebarActivationDoubleDescription: "Un clic selecciona la fila; doble clic abre elementos.",
sidebarHiddenTablePrefixes: "Prefijos ocultos de tablas",
sidebarHiddenTablePrefixesDescription:
"Un prefijo por linea. Solo acorta etiquetas de tablas, vistas y colecciones en la barra lateral; las acciones y ayudas usan el nombre completo.",
sidebarHiddenTablePrefixesPlaceholder: "Ejemplo:\nODS_\nT8Y2_LONG_",
apply: "Aplicar",
reset: "Restablecer",
resetDefaults: "Restablecer valores por defecto",

View File

@ -1176,6 +1176,10 @@ export default {
sidebarActivationSingleDescription: "单击即可打开侧边栏中的可操作项目。",
sidebarActivationDouble: "双击打开",
sidebarActivationDoubleDescription: "单击只选中高亮,双击打开项目。",
sidebarHiddenTablePrefixes: "隐藏表名前缀",
sidebarHiddenTablePrefixesDescription:
"每行一个前缀,仅影响侧边栏表、视图和集合的显示名称,悬浮提示和实际操作仍使用完整名称。",
sidebarHiddenTablePrefixesPlaceholder: "例如:\nODS_\nT8Y2_LONG_",
apply: "应用",
reset: "重置",
resetDefaults: "恢复默认",

View File

@ -0,0 +1,26 @@
export function normalizeSidebarHiddenTablePrefixes(value: unknown): string[] {
const rawPrefixes =
typeof value === "string"
? value.split(/\r?\n/)
: Array.isArray(value)
? value.filter((item) => typeof item === "string")
: [];
const seen = new Set<string>();
const prefixes: string[] = [];
for (const rawPrefix of rawPrefixes) {
const prefix = rawPrefix.trim();
if (!prefix || seen.has(prefix)) continue;
seen.add(prefix);
prefixes.push(prefix);
}
return prefixes.sort((a, b) => b.length - a.length);
}
export function sidebarDisplayTableName(name: string, prefixes: readonly string[]): string {
const prefix = [...prefixes]
.sort((a, b) => b.length - a.length)
.find((item) => name.startsWith(item) && name.length > item.length);
return prefix ? `...${name.slice(prefix.length)}` : name;
}

View File

@ -9,6 +9,7 @@ import {
} from "@/lib/columnFormatter";
import { normalizeShortcutSettings, type ShortcutSettings } from "@/lib/shortcutRegistry";
import { normalizeResultPageSize } from "@/lib/paginationPageSize";
import { normalizeSidebarHiddenTablePrefixes } from "@/lib/sidebarTableNameDisplay";
import type { SidebarActivation } from "@/lib/treeNodeClick";
export type AiProvider =
@ -170,6 +171,7 @@ export interface EditorSettings {
mongoViewMode: "document" | "table";
shortcuts: ShortcutSettings;
sidebarActivation: SidebarActivation;
sidebarHiddenTablePrefixes: string[];
columnFormatters: Record<string, ColumnFormatterConfig>;
customColumnFormatters: Record<string, CustomColumnFormatterConfig>;
}
@ -209,6 +211,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
mongoViewMode: "document",
shortcuts: normalizeShortcutSettings(),
sidebarActivation: "single",
sidebarHiddenTablePrefixes: [],
columnFormatters: {},
customColumnFormatters: {},
};
@ -253,6 +256,7 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>): Edit
settings.sidebarActivation === "single" || settings.sidebarActivation === "double"
? settings.sidebarActivation
: DEFAULT_EDITOR_SETTINGS.sidebarActivation,
sidebarHiddenTablePrefixes: normalizeSidebarHiddenTablePrefixes(settings.sidebarHiddenTablePrefixes),
columnFormatters: normalizeColumnFormatters(settings.columnFormatters),
customColumnFormatters: normalizeCustomColumnFormatters(settings.customColumnFormatters),
};
@ -334,6 +338,9 @@ export const useSettingsStore = defineStore("settings", () => {
const normalizedPartial = {
...partial,
...(partial.pageSize !== undefined ? { pageSize: normalizeResultPageSize(partial.pageSize) } : {}),
...(partial.sidebarHiddenTablePrefixes !== undefined
? { sidebarHiddenTablePrefixes: normalizeSidebarHiddenTablePrefixes(partial.sidebarHiddenTablePrefixes) }
: {}),
};
Object.assign(editorSettings.value, normalizedPartial);
saveEditorSettings(editorSettings.value);

View File

@ -46,6 +46,8 @@ test("settings dialog exposes sidebar activation in navigation settings", () =>
assert.match(source, /value: "navigation"/);
assert.match(source, /activeSettingsTab === ['"]navigation['"]/);
assert.match(source, /settings\.sidebarActivation/);
assert.match(source, /settings\.sidebarHiddenTablePrefixes/);
assert.match(source, /editSidebarHiddenTablePrefixes/);
});
test("AI settings can browse provider model names while keeping manual input", () => {

View File

@ -51,6 +51,15 @@ test("keeps saved sidebar activation", () => {
assert.equal(normalizeEditorSettings({ sidebarActivation: "invalid" } as any).sidebarActivation, "single");
});
test("normalizes saved sidebar hidden table prefixes", () => {
assert.deepEqual(DEFAULT_EDITOR_SETTINGS.sidebarHiddenTablePrefixes, []);
assert.deepEqual(
normalizeEditorSettings({ sidebarHiddenTablePrefixes: [" app_", "app_", "", "ods."] } as any)
.sidebarHiddenTablePrefixes,
["app_", "ods."],
);
});
test("defaults column formatters to an empty record", () => {
assert.deepEqual(DEFAULT_EDITOR_SETTINGS.columnFormatters, {});
assert.deepEqual(normalizeEditorSettings({}).columnFormatters, {});

View File

@ -0,0 +1,24 @@
import test from "node:test";
import assert from "node:assert/strict";
import {
normalizeSidebarHiddenTablePrefixes,
sidebarDisplayTableName,
} from "../../apps/desktop/src/lib/sidebarTableNameDisplay.ts";
test("normalizes sidebar hidden table prefixes", () => {
assert.deepEqual(normalizeSidebarHiddenTablePrefixes([" app_", "app_", "", "ods."]), ["app_", "ods."]);
assert.deepEqual(normalizeSidebarHiddenTablePrefixes("app_\nods.\n app_ "), ["app_", "ods."]);
assert.deepEqual(normalizeSidebarHiddenTablePrefixes(null), []);
});
test("hides a configured table prefix from the sidebar label", () => {
assert.equal(sidebarDisplayTableName("t8y2_long_customer_order", ["t8y2_long_"]), "...customer_order");
});
test("uses the longest matching sidebar table prefix", () => {
assert.equal(sidebarDisplayTableName("app_sales_orders", ["app_", "app_sales_"]), "...orders");
});
test("keeps the full name when hiding would leave an empty label", () => {
assert.equal(sidebarDisplayTableName("app_", ["app_"]), "app_");
});

View File

@ -0,0 +1,13 @@
import { readFileSync } from "node:fs";
import { strict as assert } from "node:assert";
import test from "node:test";
const source = readFileSync("apps/desktop/src/components/sidebar/TreeItem.vue", "utf8");
test("sidebar table prefix hiding is display-only", () => {
assert.match(source, /sidebarDisplayTableName/);
assert.match(source, /sidebarHiddenTablePrefixes/);
assert.match(source, /node\.type === "table" \|\| node\.type === "view" \|\| node\.type === "mongo-collection"/);
assert.match(source, /{{ visibleLabel\(node\) }}/);
assert.match(source, /{{ displayLabel\(node\) }}/);
});