From bda52e43900e9b81f5be91890a354cf977105ff5 Mon Sep 17 00:00:00 2001 From: Talon Date: Thu, 12 Mar 2026 16:14:19 +0100 Subject: [PATCH] Add sound selector to widgets --- client/Dockerfile | 1 + client/public/sounds/widgets/.gitkeep | 0 client/src/items/itemPropertyEditor.ts | 22 +++++++++- client/src/main.ts | 56 ++++++++++++++++++++++++++ deploy/php/sounds_list.php | 41 +++++++++++++++++++ 5 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 client/public/sounds/widgets/.gitkeep create mode 100644 deploy/php/sounds_list.php diff --git a/client/Dockerfile b/client/Dockerfile index 3e8aa9f..84e62db 100644 --- a/client/Dockerfile +++ b/client/Dockerfile @@ -10,6 +10,7 @@ RUN apk add --no-cache php83 php83-fpm php83-curl php83-ctype RUN echo 'clear_env = no' >> /etc/php83/php-fpm.d/www.conf COPY --from=build /app/dist /usr/share/nginx/html COPY deploy/php/media_proxy.php /usr/share/nginx/html/media_proxy.php +COPY deploy/php/sounds_list.php /usr/share/nginx/html/sounds_list.php COPY client/nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["sh", "-c", "php-fpm83 && exec nginx -g 'daemon off;'"] diff --git a/client/public/sounds/widgets/.gitkeep b/client/public/sounds/widgets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/client/src/items/itemPropertyEditor.ts b/client/src/items/itemPropertyEditor.ts index 63607b2..ea21b32 100644 --- a/client/src/items/itemPropertyEditor.ts +++ b/client/src/items/itemPropertyEditor.ts @@ -47,6 +47,8 @@ type EditorDeps = { updateStatus: (message: string) => void; sfxUiBlip: () => void; sfxUiCancel: () => void; + openSoundPropertyPicker?: (item: WorldItem, key: string) => void; + previewSound?: (soundPath: string) => void; }; /** @@ -187,9 +189,13 @@ export function createItemPropertyEditor(deps: EditorDeps): { deps.openItemPropertyOptionSelect(item, selectedKey); return; } + if (metadata?.valueType === 'sound' && deps.openSoundPropertyPicker) { + deps.openSoundPropertyPicker(item, selectedKey); + return; + } deps.state.mode = 'itemPropertyEdit'; deps.state.editingPropertyKey = selectedKey; - const selectedMetadata = deps.getItemPropertyMetadata(item.type, selectedKey); + const selectedMetadata = metadata; deps.state.nicknameInput = selectedKey === 'title' ? item.title @@ -369,6 +375,13 @@ export function createItemPropertyEditor(deps: EditorDeps): { const nextIndex = (deps.state.itemPropertyOptionIndex + delta + length * 1000) % length; deps.state.itemPropertyOptionIndex = nextIndex; deps.updateStatus(deps.state.itemPropertyOptionValues[nextIndex]); + const pageItem = deps.state.items.get(itemId!); + if (pageItem) { + const pageMeta = deps.getItemPropertyMetadata(pageItem.type, propertyKey); + if (pageMeta?.valueType === 'sound') { + deps.previewSound?.(deps.state.itemPropertyOptionValues[nextIndex]); + } + } deps.sfxUiBlip(); return; } @@ -383,6 +396,13 @@ export function createItemPropertyEditor(deps: EditorDeps): { if (control.type === 'move') { deps.state.itemPropertyOptionIndex = control.index; deps.updateStatus(deps.state.itemPropertyOptionValues[deps.state.itemPropertyOptionIndex]); + const moveItem = deps.state.items.get(itemId!); + if (moveItem) { + const moveMeta = deps.getItemPropertyMetadata(moveItem.type, propertyKey); + if (moveMeta?.valueType === 'sound') { + deps.previewSound?.(deps.state.itemPropertyOptionValues[deps.state.itemPropertyOptionIndex]); + } + } deps.sfxUiBlip(); return; } diff --git a/client/src/main.ts b/client/src/main.ts index 381ee25..0f13d2b 100644 --- a/client/src/main.ts +++ b/client/src/main.ts @@ -1010,6 +1010,60 @@ function openItemPropertyOptionSelect(item: WorldItem, key: string): void { audio.sfxUiBlip(); } +/** Fetches the list of widget sounds from the server. Returns [] on error. */ +async function fetchWidgetSounds(): Promise { + try { + const response = await fetch('/sounds_list.php'); + if (!response.ok) return []; + const names = (await response.json()) as string[]; + if (!Array.isArray(names)) return []; + return names.map((name: string) => `sounds/widgets/${name}`); + } catch { + return []; + } +} + +let previewDebounceTimer: ReturnType | null = null; + +/** Plays a sound preview with debounce, for use while navigating sound picker. */ +function previewSound(soundPath: string): void { + if (previewDebounceTimer !== null) { + clearTimeout(previewDebounceTimer); + } + previewDebounceTimer = setTimeout(() => { + previewDebounceTimer = null; + if (soundPath) { + void audio.playSample(soundPath, 0.7); + } + }, 200); +} + +/** Opens the sound picker for a sound-typed item property, falling back to text edit if no sounds are found. */ +async function openSoundPropertyPicker(item: WorldItem, key: string): Promise { + updateStatus('Loading sounds...'); + const sounds = await fetchWidgetSounds(); + if (sounds.length === 0) { + state.mode = 'itemPropertyEdit'; + state.editingPropertyKey = key; + const currentValue = String(item.params[key] ?? ''); + state.nicknameInput = currentValue; + state.cursorPos = currentValue.length; + replaceTextOnNextType = true; + updateStatus(`Edit ${itemPropertyLabel(key)}: ${currentValue}`); + audio.sfxUiBlip(); + return; + } + const options = ['', ...sounds]; + state.mode = 'itemPropertyOptionSelect'; + state.editingPropertyKey = key; + state.itemPropertyOptionValues = options; + const currentValue = String(item.params[key] ?? '').trim(); + const currentIndex = options.indexOf(currentValue); + state.itemPropertyOptionIndex = currentIndex >= 0 ? currentIndex : 0; + updateStatus(`Select ${itemPropertyLabel(key)}: ${options[state.itemPropertyOptionIndex] || 'none'}`); + audio.sfxUiBlip(); +} + /** Returns the active text-input max length for the current UI mode, if applicable. */ function textInputMaxLengthForMode(mode: typeof state.mode): number | null { if (mode === 'nickname') return NICKNAME_MAX_LENGTH; @@ -2573,6 +2627,8 @@ const itemPropertyEditor = createItemPropertyEditor({ updateStatus, sfxUiBlip: () => audio.sfxUiBlip(), sfxUiCancel: () => audio.sfxUiCancel(), + openSoundPropertyPicker: (item, key) => { void openSoundPropertyPicker(item, key); }, + previewSound, }); /** Handles nickname edit mode submission/cancel and text editing keys. */ diff --git a/deploy/php/sounds_list.php b/deploy/php/sounds_list.php new file mode 100644 index 0000000..86466e8 --- /dev/null +++ b/deploy/php/sounds_list.php @@ -0,0 +1,41 @@ +