Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/flo-bit/shapecraft. flo-bit.dev/shapecraft
Something went wrong. Try again.
4.5 kB · 133 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134export interface RangeOption { type: 'range' default: number | [number, number] min: number max: number step?: number label?: string /** Optional UI grouping (e.g. 'Trunk', 'Canopy'); ungrouped params land in 'General' */ group?: string}
export interface IntegerOption { type: 'integer' default: number | [number, number] min: number max: number label?: string /** Optional UI grouping (e.g. 'Trunk', 'Canopy'); ungrouped params land in 'General' */ group?: string}
export interface ColorOption { type: 'color' default: string label?: string /** Optional UI grouping (e.g. 'Trunk', 'Canopy'); ungrouped params land in 'General' */ group?: string /** Semantic color role ('bark', 'leaf', …) — lets a StyleProfile supply the default. */ role?: string}
export interface ColorArrayOption { type: 'color-array' default: string[] min?: number max?: number label?: string /** Optional UI grouping (e.g. 'Trunk', 'Canopy'); ungrouped params land in 'General' */ group?: string /** Semantic color role ('bark', 'leaf', …) — lets a StyleProfile supply the default. */ role?: string}
export interface BooleanOption { type: 'boolean' default: boolean label?: string /** Optional UI grouping (e.g. 'Trunk', 'Canopy'); ungrouped params land in 'General' */ group?: string}
export interface SelectOption { type: 'select' default: string options: string[] label?: string /** Optional UI grouping (e.g. 'Trunk', 'Canopy'); ungrouped params land in 'General' */ group?: string}
export type OptionDef = RangeOption | IntegerOption | ColorOption | ColorArrayOption | BooleanOption | SelectOption
export type OptionSchema = Record<string, OptionDef>
export type OptionValues<S extends OptionSchema> = { [K in keyof S]: S[K] extends RangeOption ? number : S[K] extends IntegerOption ? number : S[K] extends ColorOption ? string : S[K] extends ColorArrayOption ? string[] : S[K] extends BooleanOption ? boolean : S[K] extends SelectOption ? string : never}
/** A value that can be fixed or a [min, max] range resolved at generation time */export type Randomizable<T> = T | [T, T]
/** * The *input* shape for a schema: like {@link OptionValues}, but numeric options also * accept a `[min, max]` tuple (resolved to a number at generation time). Use this for a * generator's public options type; use {@link OptionValues} for the resolved result. */export type OptionInput<S extends OptionSchema> = { [K in keyof S]: S[K] extends RangeOption ? Randomizable<number> : S[K] extends IntegerOption ? Randomizable<number> : S[K] extends ColorOption ? string : S[K] extends ColorArrayOption ? string[] : S[K] extends BooleanOption ? boolean : S[K] extends SelectOption ? string : never}
/** * Resolve options for a generator: apply preset, then overrides, then fill schema defaults. * Numeric values can be [min, max] tuples — resolved using rand() if provided. * * Precedence: explicit overrides > preset > style palette (for `role`-tagged * color options) > schema default. A style re-colors everything it has a role * for, but never beats an author's or preset's explicit choice. */export function resolveOptions<S extends OptionSchema>( schema: S, options: Record<string, any>, presets?: Record<string, Record<string, any>>, rand?: () => number, style?: { palettes: Record<string, string[]> },): OptionValues<S> { const presetName = options.preset ?? 'default' const preset = presets?.[presetName] ?? {} const { preset: _, style: _s, ...overrides } = options
const resolved: Record<string, any> = {} for (const [key, def] of Object.entries(schema)) { const role = (def.type === 'color' || def.type === 'color-array') ? def.role : undefined const stylePal = role && style ? style.palettes[role] : undefined const styleVal = stylePal === undefined ? undefined : def.type === 'color' ? stylePal[0] : [...stylePal] let val = overrides[key] ?? preset[key] ?? styleVal ?? structuredClone(def.default)
// Resolve [min, max] ranges for numeric types if (Array.isArray(val) && val.length === 2 && typeof val[0] === 'number' && typeof val[1] === 'number' && (def.type === 'range' || def.type === 'integer')) { const r = rand ? rand() : Math.random() val = val[0] + (val[1] - val[0]) * r if (def.type === 'integer') val = Math.round(val) }
resolved[key] = val }
return resolved as OptionValues<S>}