diff --git a/data/units.json b/data/units.json index f76b989..5ad4bd7 100644 --- a/data/units.json +++ b/data/units.json @@ -7,7 +7,7 @@ "symbol": "dl", "group": "dickloads", "toBase": 1, - "description": "See {pound:description}. Let's also look at {us-oil-bbl}." + "description": "The basic unit of measurement for all weights and masses worldwide." }, { "id": "pound", @@ -16,25 +16,26 @@ "symbol": "lb", "group": "dickloads", "toBase": 0.038, - "description": "My description is {dickload:description}" + "description": "Avoirdupois pounds, as used in the USA." }, { "id": "us-oil-bbl", - "label": "oil barrel (US)", - "labelPlural": "oil barrels (US)", - "symbol": "us oil bbl", + "label": "barrel", + "labelPlural": "barrels", + "symbol": "bbl us oil", "group": "dickloads", "toBase": 13.3192584, "description": "Weight is based on pure water at 4 degrees celsius.", "hidden": false }, { - "id": "hogshead", + "id": "imperial-hogshead", "label": "hogshead", "labelPlural": "hogsheads", - "symbol": "hhd", + "symbol": "hhd imperial", "group": "dickloads", - "toBase": 38 + "toBase": 27.420633296, + "description": "The hogshead as understood by residents of Old Blighty." }, { "id": "short-ton", @@ -108,26 +109,34 @@ "id": "us-fluid-bbl", "label": "barrel", "labelPlural": "barrels", - "symbol": "us fluid bbl", + "symbol": "bbl us fluid", "group": "dickloads", "toBase": 9.9894438 }, { "id": "us-dry-bbl", - "label": "dry barrel", - "labelPlural": "dry barrels", - "symbol": "us dry bbl", + "label": "barrel", + "labelPlural": "barrels", + "symbol": "bbl us dry", "group": "dickloads", - "toBase": 1 + "toBase": 9.6864954123 }, { "id": "us-hogshead", - "label": "hogshead (US)", - "labelPlural": "hogsheads (US)", - "symbol": "us hhd", + "label": "hogshead", + "labelPlural": "hogsheads", + "symbol": "hhd us", "group": "dickloads", "toBase": 19.9788876, "description": "Equal to two US fluid barrels." + }, + { + "id": "imperial-barrel", + "label": "barrel", + "labelPlural": "barrels", + "symbol": "bbl imperial", + "group": "dickloads", + "toBase": 13.710316648 } ], "groups": [ diff --git a/src/lib/types.ts b/src/lib/types.ts index 96a8db1..ba72e46 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -30,6 +30,14 @@ export interface Unit { /** If true, this unit is excluded from automatic rendering in the converter. */ hidden?: boolean; + + /** + * Optional fixed display position (1-based) within the group's rendered list. + * Pinned units are inserted at their requested slot after floating units are ordered. + * If position > total units, the unit is appended at the end in ascending sortPosition order. + * Ties are broken by the group's natural sort order. + */ + sortPosition?: number; } /** A group of units sharing a base unit and a universal scale factor. */ diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 676e1cd..0909715 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -152,13 +152,29 @@ for (const group of groups) { const items = byGroup.get(group.id); if (items?.length) { - const sorted = group.sortOrder === 'alpha' + // Base order: alpha or defined + const base = group.sortOrder === 'alpha' ? [...items].sort((a, b) => { const labelCmp = a.unit.labelPlural.localeCompare(b.unit.labelPlural); return labelCmp !== 0 ? labelCmp : a.unit.symbol.localeCompare(b.unit.symbol); }) - : items; - sections.push({ groupLabel: group.label, alwaysShowLabel: group.alwaysShowLabel ?? false, items: sorted }); + : [...items]; + + // Partition into pinned (sortPosition set) and floating + const pinned = base.filter((r) => r.unit.sortPosition != null) + .sort((a, b) => (a.unit.sortPosition ?? 0) - (b.unit.sortPosition ?? 0)); + const floating = base.filter((r) => r.unit.sortPosition == null); + + // Insert pinned units at their requested 1-based positions + // Position > length β append at end in sortPosition order + const result: typeof base = [...floating]; + for (const p of pinned) { + const pos = Math.max(1, p.unit.sortPosition ?? 1); + const insertAt = Math.min(pos - 1, result.length); + result.splice(insertAt, 0, p); + } + + sections.push({ groupLabel: group.label, alwaysShowLabel: group.alwaysShowLabel ?? false, items: result }); } } const orphaned = byGroup.get(null); diff --git a/src/routes/admin/+page.svelte b/src/routes/admin/+page.svelte index 0cf17a9..63db937 100644 --- a/src/routes/admin/+page.svelte +++ b/src/routes/admin/+page.svelte @@ -638,6 +638,20 @@ formError = null; } + async function sortGroupAlpha(group: Group) { + const units = unitsData.units.filter((u) => u.group === group.id); + const sorted = [...units].sort((a, b) => { + const cmp = a.labelPlural.localeCompare(b.labelPlural); + return cmp !== 0 ? cmp : a.symbol.localeCompare(b.symbol); + }); + const res = await fetch(`${api}/order`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ units: { [group.id]: sorted.map((u) => u.id) } }) + }); + if (res.ok) await loadData(); + } + function closeDeleteGroup() { deleteGroupTarget = null; } async function confirmDeleteGroup() { @@ -683,7 +697,10 @@