mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
47 lines
2.0 KiB
TypeScript
47 lines
2.0 KiB
TypeScript
/**
|
|
* Name uniqueness for datasets (spec §05 → Naming & Uniqueness;
|
|
* docs/architecture/07 §2).
|
|
*
|
|
* Portable core: no browser APIs, no React, no store access — plain data in,
|
|
* plain data out. Dataset names are the key snippets reference via
|
|
* `datasetRefs` and the contract a Vega-Lite spec uses through
|
|
* `{ "data": { "name": "..." } }`, so they must be unique. Comparisons are
|
|
* **case-insensitive** throughout (`Sales` and `sales` collide) so a single
|
|
* display name maps to a single dataset regardless of how a reference is typed.
|
|
*
|
|
* - `isNameTaken` — reject duplicate create/rename in the UI. `excludeId` lets
|
|
* a rename ignore the record being renamed (renaming `Sales` to `Sales`, or a
|
|
* case-only edit, is not a self-collision).
|
|
* - `makeUniqueName` — for non-interactive paths (import, extract, build chart)
|
|
* where blocking the user is worse than a silent, reported rename: derive the
|
|
* next free `${base} ${n}` (n ≥ 2). We never parse meaning out of a name; a
|
|
* base already ending in a number still just gets a suffix (`Q1 2024 2`).
|
|
*/
|
|
|
|
/** Case-insensitive: is `desired` already used by another dataset (minus `excludeId`)? */
|
|
export function isNameTaken(
|
|
desired: string,
|
|
datasets: ReadonlyArray<{ id: number; name: string }>,
|
|
excludeId?: number,
|
|
): boolean {
|
|
const lower = desired.trim().toLowerCase();
|
|
return datasets.some((d) => d.id !== excludeId && d.name.toLowerCase() === lower);
|
|
}
|
|
|
|
/**
|
|
* Returns `desired` (trimmed) if free, else the first available `${desired} ${n}`
|
|
* (n ≥ 2). `existingNames` is the set of names already in the collection.
|
|
* Comparison is case-insensitive; the returned name preserves `desired`'s casing.
|
|
*/
|
|
export function makeUniqueName(desired: string, existingNames: Iterable<string>): string {
|
|
const taken = new Set<string>();
|
|
for (const n of existingNames) taken.add(n.toLowerCase());
|
|
|
|
const base = desired.trim();
|
|
if (!taken.has(base.toLowerCase())) return base;
|
|
|
|
let n = 2;
|
|
while (taken.has(`${base} ${n}`.toLowerCase())) n++;
|
|
return `${base} ${n}`;
|
|
}
|