From 43cf9316c0a8e3c4f7da79941f1c7a285bf4f204 Mon Sep 17 00:00:00 2001 From: Jage9 Date: Tue, 24 Feb 2026 01:58:53 -0500 Subject: [PATCH] Modularize client item type definitions by item module --- client/public/version.js | 2 +- client/src/items/itemRegistry.ts | 138 +++++-------------------- client/src/items/types/clock.ts | 64 ++++++++++++ client/src/items/types/dice.ts | 18 ++++ client/src/items/types/index.ts | 22 ++++ client/src/items/types/piano.ts | 23 +++++ client/src/items/types/radioStation.ts | 24 +++++ client/src/items/types/shared.ts | 14 +++ client/src/items/types/wheel.ts | 18 ++++ client/src/items/types/widget.ts | 35 +++++++ 10 files changed, 246 insertions(+), 112 deletions(-) create mode 100644 client/src/items/types/clock.ts create mode 100644 client/src/items/types/dice.ts create mode 100644 client/src/items/types/index.ts create mode 100644 client/src/items/types/piano.ts create mode 100644 client/src/items/types/radioStation.ts create mode 100644 client/src/items/types/shared.ts create mode 100644 client/src/items/types/wheel.ts create mode 100644 client/src/items/types/widget.ts diff --git a/client/public/version.js b/client/public/version.js index fc581fa..04a4493 100644 --- a/client/public/version.js +++ b/client/public/version.js @@ -1,5 +1,5 @@ // Maintainer-controlled web client version. // Format: YYYY.MM.DD Rn (example: 2026.02.20 R2) -window.CHGRID_WEB_VERSION = "2026.02.23 R223"; +window.CHGRID_WEB_VERSION = "2026.02.24 R224"; // Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid. window.CHGRID_TIME_ZONE = "America/Detroit"; diff --git a/client/src/items/itemRegistry.ts b/client/src/items/itemRegistry.ts index 8a96183..54b321b 100644 --- a/client/src/items/itemRegistry.ts +++ b/client/src/items/itemRegistry.ts @@ -1,82 +1,6 @@ -import { EFFECT_SEQUENCE } from '../audio/effects'; -import { RADIO_CHANNEL_OPTIONS } from '../audio/radioStationRuntime'; import { type ItemType, type WorldItem } from '../state/gameState'; - -const DEFAULT_CLOCK_TIME_ZONE_OPTIONS = [ - 'America/Anchorage', - 'America/Argentina/Buenos_Aires', - 'America/Chicago', - 'America/Detroit', - 'America/Halifax', - 'America/Indiana/Indianapolis', - 'America/Kentucky/Louisville', - 'America/Los_Angeles', - 'America/St_Johns', - 'Asia/Bangkok', - 'Asia/Dhaka', - 'Asia/Dubai', - 'Asia/Hong_Kong', - 'Asia/Kabul', - 'Asia/Karachi', - 'Asia/Kathmandu', - 'Asia/Kolkata', - 'Asia/Seoul', - 'Asia/Singapore', - 'Asia/Tehran', - 'Asia/Tokyo', - 'Asia/Yangon', - 'Atlantic/Azores', - 'Atlantic/South_Georgia', - 'Australia/Brisbane', - 'Australia/Darwin', - 'Australia/Eucla', - 'Australia/Lord_Howe', - 'Europe/Berlin', - 'Europe/Helsinki', - 'Europe/London', - 'Europe/Moscow', - 'Pacific/Apia', - 'Pacific/Auckland', - 'Pacific/Chatham', - 'Pacific/Honolulu', - 'Pacific/Kiritimati', - 'Pacific/Noumea', - 'Pacific/Pago_Pago', - 'UTC', -] as const; - -const DEFAULT_ITEM_TYPE_SEQUENCE: ItemType[] = ['clock', 'dice', 'piano', 'radio_station', 'wheel', 'widget']; -const DEFAULT_PIANO_INSTRUMENT_OPTIONS = [ - 'piano', - 'electric_piano', - 'guitar', - 'organ', - 'bass', - 'violin', - 'synth_lead', - 'brass', - 'nintendo', - 'drum_kit', -] as const; -const DEFAULT_PIANO_VOICE_MODE_OPTIONS = ['poly', 'mono'] as const; - -const DEFAULT_ITEM_TYPE_EDITABLE_PROPERTIES: Record = { - radio_station: ['title', 'streamUrl', 'enabled', 'mediaVolume', 'mediaChannel', 'mediaEffect', 'mediaEffectValue', 'facing', 'emitRange'], - dice: ['title', 'sides', 'number'], - piano: ['title', 'instrument', 'voiceMode', 'octave', 'attack', 'decay', 'release', 'brightness', 'emitRange'], - wheel: ['title', 'spaces'], - clock: ['title', 'timeZone', 'use24Hour'], - widget: ['title', 'enabled', 'directional', 'facing', 'emitRange', 'emitVolume', 'emitSoundSpeed', 'emitSoundTempo', 'emitEffect', 'emitEffectValue', 'useSound', 'emitSound'], -}; - -const DEFAULT_ITEM_TYPE_GLOBAL_PROPERTIES: Record> = { - radio_station: { useSound: 'none', emitSound: 'none', useCooldownMs: 1000, emitRange: 20, directional: true, emitSoundSpeed: 50, emitSoundTempo: 50 }, - dice: { useSound: 'sounds/roll.ogg', emitSound: 'none', useCooldownMs: 1000, emitRange: 15, directional: false, emitSoundSpeed: 50, emitSoundTempo: 50 }, - piano: { useSound: 'none', emitSound: 'none', useCooldownMs: 1000, emitRange: 15, directional: false, emitSoundSpeed: 50, emitSoundTempo: 50 }, - wheel: { useSound: 'sounds/spin.ogg', emitSound: 'none', useCooldownMs: 4000, emitRange: 15, directional: false, emitSoundSpeed: 50, emitSoundTempo: 50 }, - clock: { useSound: 'none', emitSound: 'sounds/clock.ogg', useCooldownMs: 1000, emitRange: 10, directional: false, emitSoundSpeed: 50, emitSoundTempo: 50 }, - widget: { useSound: 'none', emitSound: 'none', useCooldownMs: 1000, emitRange: 15, directional: false, emitSoundSpeed: 50, emitSoundTempo: 50 }, -}; +import { CLOCK_TIME_ZONE_OPTIONS } from './types/clock'; +import { DEFAULT_ITEM_TYPE_DEFINITIONS, DEFAULT_ITEM_TYPE_SEQUENCE } from './types'; export type ItemPropertyValueType = 'boolean' | 'text' | 'number' | 'list' | 'sound'; @@ -105,41 +29,33 @@ type UiDefinitionsPayload = { }; let itemTypeSequence: ItemType[] = [...DEFAULT_ITEM_TYPE_SEQUENCE]; -let itemTypeLabels: Record = { - radio_station: 'radio', - dice: 'dice', - piano: 'piano', - wheel: 'wheel', - clock: 'clock', - widget: 'widget', -}; +let itemTypeLabels: Record = {} as Record; let itemTypeTooltips: Partial> = {}; -let itemTypeEditableProperties: Record = { - radio_station: [...DEFAULT_ITEM_TYPE_EDITABLE_PROPERTIES.radio_station], - dice: [...DEFAULT_ITEM_TYPE_EDITABLE_PROPERTIES.dice], - piano: [...DEFAULT_ITEM_TYPE_EDITABLE_PROPERTIES.piano], - wheel: [...DEFAULT_ITEM_TYPE_EDITABLE_PROPERTIES.wheel], - clock: [...DEFAULT_ITEM_TYPE_EDITABLE_PROPERTIES.clock], - widget: [...DEFAULT_ITEM_TYPE_EDITABLE_PROPERTIES.widget], -}; -let itemTypeGlobalProperties: Record> = { - radio_station: { ...DEFAULT_ITEM_TYPE_GLOBAL_PROPERTIES.radio_station }, - dice: { ...DEFAULT_ITEM_TYPE_GLOBAL_PROPERTIES.dice }, - piano: { ...DEFAULT_ITEM_TYPE_GLOBAL_PROPERTIES.piano }, - wheel: { ...DEFAULT_ITEM_TYPE_GLOBAL_PROPERTIES.wheel }, - clock: { ...DEFAULT_ITEM_TYPE_GLOBAL_PROPERTIES.clock }, - widget: { ...DEFAULT_ITEM_TYPE_GLOBAL_PROPERTIES.widget }, -}; -let optionItemPropertyValues: Partial> = { - mediaEffect: EFFECT_SEQUENCE.map((effect) => effect.id), - emitEffect: EFFECT_SEQUENCE.map((effect) => effect.id), - mediaChannel: [...RADIO_CHANNEL_OPTIONS], - instrument: [...DEFAULT_PIANO_INSTRUMENT_OPTIONS], - voiceMode: [...DEFAULT_PIANO_VOICE_MODE_OPTIONS], - timeZone: [...DEFAULT_CLOCK_TIME_ZONE_OPTIONS], -}; +let itemTypeEditableProperties: Record = {} as Record; +let itemTypeGlobalProperties: Record> = {} as Record< + ItemType, + Record +>; +let optionItemPropertyValues: Partial> = {}; let itemTypePropertyMetadata: Partial>> = {}; +for (const definition of DEFAULT_ITEM_TYPE_DEFINITIONS) { + itemTypeLabels[definition.type] = definition.label; + if (definition.tooltip) { + itemTypeTooltips[definition.type] = definition.tooltip; + } + itemTypeEditableProperties[definition.type] = [...definition.editableProperties]; + itemTypeGlobalProperties[definition.type] = { ...definition.globalProperties }; + if (definition.propertyMetadata) { + itemTypePropertyMetadata[definition.type] = { ...definition.propertyMetadata }; + } + if (definition.propertyOptions) { + for (const [key, values] of Object.entries(definition.propertyOptions)) { + optionItemPropertyValues[key] = [...values]; + } + } +} + export let EDITABLE_ITEM_PROPERTY_KEYS = new Set( Object.values(itemTypeEditableProperties).flatMap((keys) => keys), ); @@ -190,7 +106,7 @@ function normalizePropertyMetadataRecord(raw: Record | undefine /** Returns current timezone option list used by clock item properties. */ export function getClockTimeZoneOptions(): string[] { - return [...(optionItemPropertyValues.timeZone ?? DEFAULT_CLOCK_TIME_ZONE_OPTIONS)]; + return [...(optionItemPropertyValues.timeZone ?? CLOCK_TIME_ZONE_OPTIONS)]; } /** Returns default timezone used by clock items when no override is set. */ diff --git a/client/src/items/types/clock.ts b/client/src/items/types/clock.ts new file mode 100644 index 0000000..916ab2f --- /dev/null +++ b/client/src/items/types/clock.ts @@ -0,0 +1,64 @@ +import { type ItemTypeClientDefinition } from './shared'; + +export const CLOCK_TIME_ZONE_OPTIONS = [ + 'America/Anchorage', + 'America/Argentina/Buenos_Aires', + 'America/Chicago', + 'America/Detroit', + 'America/Halifax', + 'America/Indiana/Indianapolis', + 'America/Kentucky/Louisville', + 'America/Los_Angeles', + 'America/St_Johns', + 'Asia/Bangkok', + 'Asia/Dhaka', + 'Asia/Dubai', + 'Asia/Hong_Kong', + 'Asia/Kabul', + 'Asia/Karachi', + 'Asia/Kathmandu', + 'Asia/Kolkata', + 'Asia/Seoul', + 'Asia/Singapore', + 'Asia/Tehran', + 'Asia/Tokyo', + 'Asia/Yangon', + 'Atlantic/Azores', + 'Atlantic/South_Georgia', + 'Australia/Brisbane', + 'Australia/Darwin', + 'Australia/Eucla', + 'Australia/Lord_Howe', + 'Europe/Berlin', + 'Europe/Helsinki', + 'Europe/London', + 'Europe/Moscow', + 'Pacific/Apia', + 'Pacific/Auckland', + 'Pacific/Chatham', + 'Pacific/Honolulu', + 'Pacific/Kiritimati', + 'Pacific/Noumea', + 'Pacific/Pago_Pago', + 'UTC', +] as const; + +/** Default client-side UI definition for clock items. */ +export const clockDefinition: ItemTypeClientDefinition = { + type: 'clock', + label: 'clock', + editableProperties: ['title', 'timeZone', 'use24Hour'], + globalProperties: { + useSound: 'none', + emitSound: 'sounds/clock.ogg', + useCooldownMs: 1000, + emitRange: 10, + directional: false, + emitSoundSpeed: 50, + emitSoundTempo: 50, + }, + propertyOptions: { + timeZone: [...CLOCK_TIME_ZONE_OPTIONS], + }, +}; + diff --git a/client/src/items/types/dice.ts b/client/src/items/types/dice.ts new file mode 100644 index 0000000..400ec9e --- /dev/null +++ b/client/src/items/types/dice.ts @@ -0,0 +1,18 @@ +import { type ItemTypeClientDefinition } from './shared'; + +/** Default client-side UI definition for dice items. */ +export const diceDefinition: ItemTypeClientDefinition = { + type: 'dice', + label: 'dice', + editableProperties: ['title', 'sides', 'number'], + globalProperties: { + useSound: 'sounds/roll.ogg', + emitSound: 'none', + useCooldownMs: 1000, + emitRange: 15, + directional: false, + emitSoundSpeed: 50, + emitSoundTempo: 50, + }, +}; + diff --git a/client/src/items/types/index.ts b/client/src/items/types/index.ts new file mode 100644 index 0000000..b4e6157 --- /dev/null +++ b/client/src/items/types/index.ts @@ -0,0 +1,22 @@ +import { type ItemType } from '../../state/gameState'; +import { clockDefinition } from './clock'; +import { diceDefinition } from './dice'; +import { pianoDefinition } from './piano'; +import { radioStationDefinition } from './radioStation'; +import { wheelDefinition } from './wheel'; +import { widgetDefinition } from './widget'; +import { type ItemTypeClientDefinition } from './shared'; + +/** Ordered default client item definitions used before server UI definitions arrive. */ +export const DEFAULT_ITEM_TYPE_DEFINITIONS: ItemTypeClientDefinition[] = [ + clockDefinition, + diceDefinition, + pianoDefinition, + radioStationDefinition, + wheelDefinition, + widgetDefinition, +]; + +/** Default add-item menu ordering derived from local item definitions. */ +export const DEFAULT_ITEM_TYPE_SEQUENCE: ItemType[] = DEFAULT_ITEM_TYPE_DEFINITIONS.map((definition) => definition.type); + diff --git a/client/src/items/types/piano.ts b/client/src/items/types/piano.ts new file mode 100644 index 0000000..9de4c06 --- /dev/null +++ b/client/src/items/types/piano.ts @@ -0,0 +1,23 @@ +import { PIANO_INSTRUMENT_OPTIONS } from '../../audio/pianoSynth'; +import { type ItemTypeClientDefinition } from './shared'; + +/** Default client-side UI definition for piano items. */ +export const pianoDefinition: ItemTypeClientDefinition = { + type: 'piano', + label: 'piano', + editableProperties: ['title', 'instrument', 'voiceMode', 'octave', 'attack', 'decay', 'release', 'brightness', 'emitRange'], + globalProperties: { + useSound: 'none', + emitSound: 'none', + useCooldownMs: 1000, + emitRange: 15, + directional: false, + emitSoundSpeed: 50, + emitSoundTempo: 50, + }, + propertyOptions: { + instrument: [...PIANO_INSTRUMENT_OPTIONS], + voiceMode: ['poly', 'mono'], + }, +}; + diff --git a/client/src/items/types/radioStation.ts b/client/src/items/types/radioStation.ts new file mode 100644 index 0000000..2729156 --- /dev/null +++ b/client/src/items/types/radioStation.ts @@ -0,0 +1,24 @@ +import { RADIO_CHANNEL_OPTIONS } from '../../audio/radioStationRuntime'; +import { EFFECT_SEQUENCE } from '../../audio/effects'; +import { type ItemTypeClientDefinition } from './shared'; + +/** Default client-side UI definition for radio_station items. */ +export const radioStationDefinition: ItemTypeClientDefinition = { + type: 'radio_station', + label: 'radio', + editableProperties: ['title', 'streamUrl', 'enabled', 'mediaVolume', 'mediaChannel', 'mediaEffect', 'mediaEffectValue', 'facing', 'emitRange'], + globalProperties: { + useSound: 'none', + emitSound: 'none', + useCooldownMs: 1000, + emitRange: 20, + directional: true, + emitSoundSpeed: 50, + emitSoundTempo: 50, + }, + propertyOptions: { + mediaEffect: EFFECT_SEQUENCE.map((effect) => effect.id), + mediaChannel: [...RADIO_CHANNEL_OPTIONS], + }, +}; + diff --git a/client/src/items/types/shared.ts b/client/src/items/types/shared.ts new file mode 100644 index 0000000..d58bc7a --- /dev/null +++ b/client/src/items/types/shared.ts @@ -0,0 +1,14 @@ +import { type ItemType } from '../../state/gameState'; +import { type ItemPropertyMetadata } from '../itemRegistry'; + +/** Static client-side definition for one item type's UI/config defaults. */ +export type ItemTypeClientDefinition = { + type: ItemType; + label: string; + tooltip?: string; + editableProperties: string[]; + globalProperties: Record; + propertyOptions?: Record; + propertyMetadata?: Record; +}; + diff --git a/client/src/items/types/wheel.ts b/client/src/items/types/wheel.ts new file mode 100644 index 0000000..06c9dc5 --- /dev/null +++ b/client/src/items/types/wheel.ts @@ -0,0 +1,18 @@ +import { type ItemTypeClientDefinition } from './shared'; + +/** Default client-side UI definition for wheel items. */ +export const wheelDefinition: ItemTypeClientDefinition = { + type: 'wheel', + label: 'wheel', + editableProperties: ['title', 'spaces'], + globalProperties: { + useSound: 'sounds/spin.ogg', + emitSound: 'none', + useCooldownMs: 4000, + emitRange: 15, + directional: false, + emitSoundSpeed: 50, + emitSoundTempo: 50, + }, +}; + diff --git a/client/src/items/types/widget.ts b/client/src/items/types/widget.ts new file mode 100644 index 0000000..958bd79 --- /dev/null +++ b/client/src/items/types/widget.ts @@ -0,0 +1,35 @@ +import { EFFECT_SEQUENCE } from '../../audio/effects'; +import { type ItemTypeClientDefinition } from './shared'; + +/** Default client-side UI definition for widget items. */ +export const widgetDefinition: ItemTypeClientDefinition = { + type: 'widget', + label: 'widget', + editableProperties: [ + 'title', + 'enabled', + 'directional', + 'facing', + 'emitRange', + 'emitVolume', + 'emitSoundSpeed', + 'emitSoundTempo', + 'emitEffect', + 'emitEffectValue', + 'useSound', + 'emitSound', + ], + globalProperties: { + useSound: 'none', + emitSound: 'none', + useCooldownMs: 1000, + emitRange: 15, + directional: false, + emitSoundSpeed: 50, + emitSoundTempo: 50, + }, + propertyOptions: { + emitEffect: EFFECT_SEQUENCE.map((effect) => effect.id), + }, +}; +