feat: dynamic field labels, optionally overriding field names per species
This commit is contained in:
parent
a0060ca4bb
commit
a2b904811a
21 changed files with 322 additions and 73 deletions
|
|
@ -1,8 +1,10 @@
|
|||
import type { Character } from '../types';
|
||||
import { FLAGS_KEY } from './field-flags';
|
||||
|
||||
export function isBlankCharacter(char: Character): boolean {
|
||||
if (!char.data) return true;
|
||||
for (const value of Object.values(char.data)) {
|
||||
for (const [key, value] of Object.entries(char.data)) {
|
||||
if (key === FLAGS_KEY) continue;
|
||||
if (value === '' || value === undefined || value === null || value === 0) continue;
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length === 0) continue;
|
||||
|
|
|
|||
44
src/lib/utils/field-flags.ts
Normal file
44
src/lib/utils/field-flags.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
export const FLAGS_KEY = '__flags';
|
||||
|
||||
export interface FieldFlags {
|
||||
useSpeciesLabel?: boolean;
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
const DEFAULTS: Required<FieldFlags> = {
|
||||
useSpeciesLabel: true,
|
||||
hidden: false
|
||||
};
|
||||
|
||||
export function getFlag(
|
||||
data: Record<string, unknown>,
|
||||
fieldKey: string,
|
||||
flag: keyof FieldFlags
|
||||
): boolean {
|
||||
const all = data[FLAGS_KEY] as Record<string, FieldFlags> | undefined;
|
||||
return all?.[fieldKey]?.[flag] ?? DEFAULTS[flag];
|
||||
}
|
||||
|
||||
export function setFlag(
|
||||
data: Record<string, unknown>,
|
||||
fieldKey: string,
|
||||
flag: keyof FieldFlags,
|
||||
value: boolean
|
||||
): void {
|
||||
if (!data[FLAGS_KEY]) data[FLAGS_KEY] = {};
|
||||
const all = data[FLAGS_KEY] as Record<string, FieldFlags>;
|
||||
if (!all[fieldKey]) all[fieldKey] = {};
|
||||
if (value === DEFAULTS[flag]) {
|
||||
delete all[fieldKey][flag];
|
||||
if (Object.keys(all[fieldKey]).length === 0) delete all[fieldKey];
|
||||
if (Object.keys(all).length === 0) delete data[FLAGS_KEY];
|
||||
} else {
|
||||
all[fieldKey][flag] = value;
|
||||
}
|
||||
}
|
||||
|
||||
export function getAllFlags(
|
||||
data: Record<string, unknown>
|
||||
): Record<string, FieldFlags> {
|
||||
return (data[FLAGS_KEY] as Record<string, FieldFlags>) ?? {};
|
||||
}
|
||||
37
src/lib/utils/resolve-label.test.ts
Normal file
37
src/lib/utils/resolve-label.test.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { resolveFieldLabel } from './resolve-label';
|
||||
import type { FieldDef } from '$lib/types';
|
||||
import type { SpeciesData } from '$lib/data/types';
|
||||
|
||||
const stubSpecies: SpeciesData[] = [
|
||||
{
|
||||
id: 'tajara',
|
||||
name: 'Tajara',
|
||||
labels: { subspecies: 'Ethnicity', 'skin-color': 'Fur Colour' },
|
||||
subspecies: [{ id: 'hharar', name: 'Hharar' }],
|
||||
languages: [],
|
||||
citizenships: []
|
||||
}
|
||||
];
|
||||
|
||||
describe('resolveFieldLabel', () => {
|
||||
it('returns species override when available', () => {
|
||||
const field: FieldDef = { label: 'Skin Color', type: 'text' };
|
||||
expect(resolveFieldLabel(field, stubSpecies, 'tajara')).toBe('Fur Colour');
|
||||
});
|
||||
|
||||
it('returns null when no species is selected', () => {
|
||||
const field: FieldDef = { label: 'Skin Color', type: 'text' };
|
||||
expect(resolveFieldLabel(field, stubSpecies, undefined)).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null when species has no label for the field', () => {
|
||||
const field: FieldDef = { label: 'Height', type: 'height' };
|
||||
expect(resolveFieldLabel(field, stubSpecies, 'tajara')).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null for separator fields', () => {
|
||||
const field: FieldDef = { label: 'Appearance', type: 'separator' };
|
||||
expect(resolveFieldLabel(field, stubSpecies, 'tajara')).toBeNull();
|
||||
});
|
||||
});
|
||||
17
src/lib/utils/resolve-label.ts
Normal file
17
src/lib/utils/resolve-label.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import type { FieldDef } from '$lib/types';
|
||||
import type { SpeciesData } from '$lib/data/types';
|
||||
import { slugify } from './slugify';
|
||||
|
||||
export function resolveFieldLabel(
|
||||
field: FieldDef,
|
||||
speciesData: SpeciesData[],
|
||||
currentSpeciesId: string | undefined
|
||||
): string | null {
|
||||
if (field.type === 'separator') return null;
|
||||
if (!currentSpeciesId) return null;
|
||||
|
||||
const sp = speciesData.find((s) => s.id === currentSpeciesId);
|
||||
if (!sp) return null;
|
||||
|
||||
return sp.labels[slugify(field.label)] ?? null;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue