Skip to content

Commit 29f9189

Browse files
committed
Refine Audio SFX JSON actions and name controls - PR_26144_006-audio-sfx-json-actions-and-name-controls
1 parent f3f2de4 commit 29f9189

8 files changed

Lines changed: 302 additions & 9 deletions

File tree

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Audio / SFX Playground V2 JSON Actions And Name Controls Validation
2+
3+
PR: `PR_26144_006-audio-sfx-json-actions-and-name-controls`
4+
5+
## Scope
6+
7+
- Updated only `tools/audio-sfx-playground-v2` plus required reports.
8+
- Renamed the tool action from `Export toolState` to `Export JSON`.
9+
- Added `Import JSON` immediately to the left of `Copy JSON`.
10+
- Added a hidden external-file input for JSON import; event wiring remains in external JavaScript.
11+
- Changed the Name-row Add button text to `+`.
12+
- Added a trash-can delete button immediately to the right of `+`.
13+
- Kept Name input, `+`, and delete button on one row.
14+
- Let range sliders use the flexible remaining column between label and value output.
15+
- Did not modify `start_of_day` folders.
16+
17+
## Static Validation
18+
19+
PASS:
20+
21+
- HTML/CSS restriction check:
22+
- no `<style>` blocks
23+
- no inline event handlers
24+
- no script tags without `src`
25+
- linked stylesheets resolve
26+
- CSS braces are balanced
27+
- `Import JSON`, `Copy JSON`, and `Export JSON` controls are present
28+
- old `Export toolState` label is absent
29+
- delete SFX button is present
30+
- Audio / SFX Playground V2 JavaScript syntax check:
31+
- `node --check` over `tools/audio-sfx-playground-v2/js/**/*.js`
32+
- Changed runtime module import check:
33+
- `AudioSfxPlaygroundV2App.js`
34+
- `ActionNavControl.js`
35+
- `SfxControlPanel.js`
36+
- `ToolStateSerializer.js`
37+
- JSON import/export serializer check:
38+
- `ToolStateSerializer.createToolState(...)` and `readToolState(...)` round-trip a saved SFX collection.
39+
- Whitespace validation:
40+
- `git diff --check -- tools/audio-sfx-playground-v2`
41+
- JSON file validation:
42+
- No JSON files changed in this PR.
43+
44+
## Playwright Impact
45+
46+
Playwright impacted: Yes.
47+
48+
Expected validation:
49+
50+
- Workspace V2 launches Audio / SFX Playground V2 with no console errors.
51+
- Tool actions show `Import JSON`, `Copy JSON`, and `Export JSON` in that order.
52+
- `Export JSON` replaces the old `Export toolState` label.
53+
- Name input, `+`, and trash-can delete button remain on one row.
54+
- `+` creates a saved SFX tile.
55+
- Trash-can delete removes the selected saved SFX tile and disables when no tile is selected.
56+
- Range sliders fill the remaining space between their label and value display.
57+
- Import JSON accepts the tool's exported JSON and restores the editor plus saved tile list.
58+
- Invalid JSON import logs an actionable error and does not partially render.
59+
60+
Local command results:
61+
62+
- `npm run test:workspace-v2`
63+
- FAIL: PowerShell blocked `npm.ps1` because script execution is disabled on this system.
64+
- `npm.cmd run test:workspace-v2`
65+
- FAIL: package script started, but `playwright` is not installed or not available on PATH.
66+
67+
Because Playwright is unavailable in this local environment, browser launch validation and V8 coverage could not be completed here.
68+
69+
## Coverage
70+
71+
WARN: Runtime JavaScript changed, but Playwright V8 coverage could not be generated because the local Playwright command is unavailable.
72+
73+
- `(WARN) tools/audio-sfx-playground-v2/js/AudioSfxPlaygroundV2App.js - Playwright unavailable`
74+
- `(WARN) tools/audio-sfx-playground-v2/js/bootstrap.js - Playwright unavailable`
75+
- `(WARN) tools/audio-sfx-playground-v2/js/controls/ActionNavControl.js - Playwright unavailable`
76+
- `(WARN) tools/audio-sfx-playground-v2/js/controls/SfxControlPanel.js - Playwright unavailable`
77+
- `(WARN) tools/audio-sfx-playground-v2/js/services/ToolStateSerializer.js - Playwright unavailable`
78+
79+
## Full Samples Smoke Test
80+
81+
Skipped. This PR only impacts Audio / SFX Playground V2 UI controls.

tools/audio-sfx-playground-v2/index.html

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
3838
</details>
3939

4040
<nav class="tool-starter__menu tool-starter__tool__menu audio-sfx__tool__menu" aria-label="Tool actions" data-launch-mode-nav="tool">
41+
<button id="toolImportJsonButton" type="button">Import JSON</button>
4142
<button id="toolCopyJsonButton" type="button">Copy JSON</button>
42-
<button id="toolExportToolStateButton" type="button">Export toolState</button>
43+
<button id="toolExportToolStateButton" type="button">Export JSON</button>
44+
<input id="toolImportJsonInput" type="file" accept="application/json,.json" hidden>
4345
</nav>
4446

4547
<nav class="tool-starter__menu tool-starter__workspace__menu audio-sfx__workspace__menu" aria-label="Workspace actions" data-launch-mode-nav="workspace" hidden>
@@ -62,7 +64,8 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
6264
<span class="audio-sfx__label-tip" tabindex="0" data-tooltip="Names this sound in the tile list and exported toolState.">Name</span>
6365
</label>
6466
<input id="nameInput" type="text" maxlength="48" autocomplete="off">
65-
<button id="addSfxButton" type="button">Add</button>
67+
<button id="addSfxButton" type="button" aria-label="Add SFX">+</button>
68+
<button id="deleteSfxButton" type="button" aria-label="Delete selected SFX">&#128465;</button>
6669
</div>
6770
<label class="tool-starter__field" for="waveformSelect">
6871
<span class="audio-sfx__label-tip" tabindex="0" data-tooltip="Chooses the oscillator shape that defines the sound character.">Wave</span>

tools/audio-sfx-playground-v2/js/AudioSfxPlaygroundV2App.js

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@ function cloneSound(sound) {
1212
};
1313
}
1414

15+
function nextSoundNumberAfter(soundEntries) {
16+
let highest = 0;
17+
soundEntries.forEach((entry) => {
18+
const match = /^sfx-(\d+)$/.exec(entry.id);
19+
if (match) {
20+
highest = Math.max(highest, Number.parseInt(match[1], 10));
21+
}
22+
});
23+
return highest + 1;
24+
}
25+
1526
export class AudioSfxPlaygroundV2App {
1627
constructor({ accordions, actionNav, audioEngine, controls, inspector, preview, serializer, shell, statusLog, tileList, windowRef = window }) {
1728
this.accordions = accordions;
@@ -37,7 +48,10 @@ export class AudioSfxPlaygroundV2App {
3748
onToolCopyJson: () => {
3849
void this.copyJson();
3950
},
40-
onToolExportToolState: () => this.exportToolState(),
51+
onToolExportToolState: () => this.exportJson(),
52+
onToolImportJson: (file) => {
53+
void this.importJson(file);
54+
},
4155
onToolPlay: () => {
4256
void this.play();
4357
},
@@ -47,7 +61,8 @@ export class AudioSfxPlaygroundV2App {
4761
});
4862
this.controls.mount({
4963
onAdd: () => this.addCurrentSound(),
50-
onChange: () => this.handleEditorChange()
64+
onChange: () => this.handleEditorChange(),
65+
onDelete: () => this.deleteCurrentSound()
5166
});
5267
this.tileList.mount({
5368
onSelect: (soundId) => this.selectSound(soundId)
@@ -132,6 +147,7 @@ export class AudioSfxPlaygroundV2App {
132147
activeSoundId: this.activeSoundId,
133148
soundEntries: this.soundEntries
134149
});
150+
this.controls.setDeleteEnabled(Boolean(this.activeSoundId));
135151
}
136152

137153
async play() {
@@ -150,15 +166,53 @@ export class AudioSfxPlaygroundV2App {
150166
this.refreshPreview();
151167
}
152168

153-
exportToolState() {
169+
deleteCurrentSound() {
170+
const entryIndex = this.soundEntries.findIndex((entry) => entry.id === this.activeSoundId);
171+
if (entryIndex === -1) {
172+
this.statusLog.error("Select a saved SFX tile before deleting.");
173+
this.controls.setDeleteEnabled(false);
174+
return;
175+
}
176+
const [entry] = this.soundEntries.splice(entryIndex, 1);
177+
this.activeSoundId = "";
178+
this.renderSoundList();
179+
this.refreshPreview();
180+
this.statusLog.write(`Deleted ${entry.sound.name}.`);
181+
}
182+
183+
exportJson() {
154184
const { toolState, validation } = this.currentToolState();
155185
if (!validation.valid) {
156186
this.statusLog.error(validation.message);
157187
this.refreshPreview();
158188
return;
159189
}
160190
this.inspector.showObject(toolState);
161-
this.statusLog.write("toolState preview written to Output Summary.");
191+
this.statusLog.write("JSON preview written to Output Summary.");
192+
}
193+
194+
async importJson(file) {
195+
try {
196+
const text = await file.text();
197+
const parsed = JSON.parse(text);
198+
const result = this.serializer.readToolState(parsed);
199+
if (!result.valid) {
200+
this.statusLog.error(`Import JSON failed: ${result.message}`);
201+
return;
202+
}
203+
this.activeSoundId = result.value.activeSoundId;
204+
this.soundEntries = result.value.soundEntries.map((entry) => ({
205+
id: entry.id,
206+
sound: cloneSound(entry.sound)
207+
}));
208+
this.nextSoundNumber = nextSoundNumberAfter(this.soundEntries);
209+
this.controls.loadSound(result.value.sound);
210+
this.renderSoundList();
211+
this.refreshPreview();
212+
this.statusLog.write(`Imported JSON from ${file.name}.`);
213+
} catch (error) {
214+
this.statusLog.error(`Import JSON failed: ${error.message}`);
215+
}
162216
}
163217

164218
async copyJson() {

tools/audio-sfx-playground-v2/js/bootstrap.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ window.addEventListener("DOMContentLoaded", () => {
3030
returnToWorkspaceButton: requireElement("#returnToWorkspaceButton"),
3131
toolCopyJsonButton: requireElement("#toolCopyJsonButton"),
3232
toolExportToolStateButton: requireElement("#toolExportToolStateButton"),
33+
toolImportJsonButton: requireElement("#toolImportJsonButton"),
34+
toolImportJsonInput: requireElement("#toolImportJsonInput"),
3335
toolNav: requireElement(".tool-starter__tool__menu"),
3436
toolPlayButton: requireElement("#toolPlayButton"),
3537
workspaceCopyManifestButton: requireElement("#workspaceCopyManifestButton"),
@@ -46,6 +48,7 @@ window.addEventListener("DOMContentLoaded", () => {
4648
durationValue: requireElement("#durationValue"),
4749
frequencyInput: requireElement("#frequencyInput"),
4850
frequencyValue: requireElement("#frequencyValue"),
51+
deleteButton: requireElement("#deleteSfxButton"),
4952
nameInput: requireElement("#nameInput"),
5053
noiseInput: requireElement("#noiseInput"),
5154
pitchSweepInput: requireElement("#pitchSweepInput"),

tools/audio-sfx-playground-v2/js/controls/ActionNavControl.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ export class ActionNavControl {
55
windowRef = window,
66
toolCopyJsonButton,
77
toolExportToolStateButton,
8+
toolImportJsonButton,
9+
toolImportJsonInput,
810
toolNav,
911
toolPlayButton,
1012
workspaceCopyManifestButton,
@@ -16,6 +18,8 @@ export class ActionNavControl {
1618
this.returnToWorkspaceButton = returnToWorkspaceButton;
1719
this.toolCopyJsonButton = toolCopyJsonButton;
1820
this.toolExportToolStateButton = toolExportToolStateButton;
21+
this.toolImportJsonButton = toolImportJsonButton;
22+
this.toolImportJsonInput = toolImportJsonInput;
1923
this.toolNav = toolNav;
2024
this.toolPlayButton = toolPlayButton;
2125
this.workspaceCopyManifestButton = workspaceCopyManifestButton;
@@ -28,13 +32,24 @@ export class ActionNavControl {
2832
mount({
2933
onToolCopyJson,
3034
onToolExportToolState,
35+
onToolImportJson,
3136
onToolPlay,
3237
onWorkspaceCopyManifest,
3338
onWorkspaceExportManifest,
3439
onWorkspaceImportManifest
3540
}) {
3641
this.applyLaunchMode();
3742
this.toolPlayButton.addEventListener("click", onToolPlay);
43+
this.toolImportJsonButton.addEventListener("click", () => {
44+
this.toolImportJsonInput.value = "";
45+
this.toolImportJsonInput.click();
46+
});
47+
this.toolImportJsonInput.addEventListener("change", () => {
48+
const [file] = this.toolImportJsonInput.files;
49+
if (file) {
50+
onToolImportJson(file);
51+
}
52+
});
3853
this.toolCopyJsonButton.addEventListener("click", onToolCopyJson);
3954
this.toolExportToolStateButton.addEventListener("click", onToolExportToolState);
4055
this.workspaceImportManifestButton.addEventListener("click", onWorkspaceImportManifest);

tools/audio-sfx-playground-v2/js/controls/SfxControlPanel.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export class SfxControlPanel {
3131
addButton,
3232
attackInput,
3333
attackValue,
34+
deleteButton,
3435
durationInput,
3536
durationValue,
3637
frequencyInput,
@@ -49,6 +50,7 @@ export class SfxControlPanel {
4950
this.addButton = addButton;
5051
this.attackInput = attackInput;
5152
this.attackValue = attackValue;
53+
this.deleteButton = deleteButton;
5254
this.durationInput = durationInput;
5355
this.durationValue = durationValue;
5456
this.frequencyInput = frequencyInput;
@@ -65,9 +67,11 @@ export class SfxControlPanel {
6567
this.waveformSelect = waveformSelect;
6668
}
6769

68-
mount({ onAdd, onChange }) {
70+
mount({ onAdd, onChange, onDelete }) {
6971
this.loadSound(DEFAULT_SOUND);
7072
this.addButton.addEventListener("click", onAdd);
73+
this.deleteButton.addEventListener("click", onDelete);
74+
this.setDeleteEnabled(false);
7175
[
7276
this.attackInput,
7377
this.durationInput,
@@ -153,4 +157,8 @@ export class SfxControlPanel {
153157
this.validationMessage.textContent = message;
154158
this.validationMessage.classList.toggle("is-error", isError);
155159
}
160+
161+
setDeleteEnabled(isEnabled) {
162+
this.deleteButton.disabled = !isEnabled;
163+
}
156164
}

0 commit comments

Comments
 (0)