feat: Enhance dashboard and prepare wizard functionality with speaker step handling and UI updates

This commit is contained in:
JB
2025-10-10 19:18:26 -07:00
parent 60fe7e7ffb
commit aed0df1b09
5 changed files with 112 additions and 28 deletions
+7 -2
View File
@@ -205,14 +205,19 @@ const initDashboard = () => {
});
document.addEventListener("click", (event) => {
const readerClose = event.target.closest('[data-role="reader-modal-close"]');
const target = event.target;
if (!(target instanceof Element)) {
return;
}
const readerClose = target.closest('[data-role="reader-modal-close"]');
if (readerClose) {
event.preventDefault();
closeReaderModal();
return;
}
const readerTriggerBtn = event.target.closest('[data-role="open-reader"]');
const readerTriggerBtn = target.closest('[data-role="open-reader"]');
if (readerTriggerBtn) {
event.preventDefault();
openReaderModal(readerTriggerBtn);
+70 -7
View File
@@ -244,6 +244,10 @@ document.addEventListener("DOMContentLoaded", () => {
const panels = new Map();
const initialStep = wizard.dataset.initialStep || "chapters";
const speakerModeSelect = form.querySelector("#speaker_mode");
const speakerIndicator = indicator ? indicator.querySelector('[data-step-key="speakers"]') : null;
const chapterActions = wizard.querySelector('[data-role="chapter-actions"]');
const continueButton = chapterActions ? chapterActions.querySelector('[data-chapter-action="continue"]') : null;
const finalizeButton = chapterActions ? chapterActions.querySelector('[data-chapter-action="finalize"]') : null;
stepOrder.forEach((step) => {
const panel = wizard.querySelector(`[data-step-panel="${step}"]`);
if (panel) {
@@ -311,23 +315,27 @@ document.addEventListener("DOMContentLoaded", () => {
};
const setStep = (step) => {
if (!panels.has(step)) {
let targetStep = step;
if (targetStep === "speakers" && shouldSkipSpeakersStep()) {
targetStep = "chapters";
}
if (!panels.has(targetStep)) {
return;
}
currentStep = step;
wizard.dataset.activeStep = step;
currentStep = targetStep;
wizard.dataset.activeStep = targetStep;
if (activeStepInput) {
activeStepInput.value = step;
activeStepInput.value = targetStep;
}
panels.forEach((panel, key) => {
const isActive = key === step;
const isActive = key === targetStep;
panel.hidden = !isActive;
panel.setAttribute("aria-hidden", isActive ? "false" : "true");
});
navButtons.forEach((button) => {
const target = button.dataset.stepTarget;
if (!target) return;
const isActive = target === step;
const isActive = target === targetStep;
button.classList.toggle("is-active", isActive);
if (button.dataset.state === "locked" && !unlockedSteps.has(target)) {
button.disabled = true;
@@ -337,7 +345,46 @@ document.addEventListener("DOMContentLoaded", () => {
button.removeAttribute("aria-disabled");
}
});
updateIndicator(step);
updateIndicator(targetStep);
};
const updateSpeakerControls = () => {
const skipSpeakers = shouldSkipSpeakersStep();
if (wizard) {
wizard.dataset.speakerStep = skipSpeakers ? "disabled" : "enabled";
}
if (speakerIndicator) {
if (skipSpeakers) {
speakerIndicator.hidden = true;
speakerIndicator.setAttribute("aria-hidden", "true");
} else {
speakerIndicator.hidden = false;
speakerIndicator.removeAttribute("aria-hidden");
}
}
if (continueButton) {
if (skipSpeakers) {
continueButton.hidden = true;
continueButton.setAttribute("aria-hidden", "true");
continueButton.setAttribute("tabindex", "-1");
} else {
continueButton.hidden = false;
continueButton.removeAttribute("aria-hidden");
continueButton.removeAttribute("tabindex");
}
}
if (finalizeButton) {
if (skipSpeakers) {
finalizeButton.hidden = false;
finalizeButton.removeAttribute("aria-hidden");
} else {
finalizeButton.hidden = true;
finalizeButton.setAttribute("aria-hidden", "true");
}
}
if (skipSpeakers && currentStep === "speakers") {
setStep("chapters");
}
};
const submitForAnalysis = () => {
@@ -399,7 +446,23 @@ document.addEventListener("DOMContentLoaded", () => {
});
});
if (speakerModeSelect) {
speakerModeSelect.addEventListener("change", () => {
updateSpeakerControls();
});
}
if (finalizeButton) {
finalizeButton.addEventListener("click", (event) => {
if (shouldSkipSpeakersStep()) {
event.preventDefault();
submitFinalize();
}
});
}
setStep(currentStep);
updateSpeakerControls();
}
const voiceModal = document.querySelector('[data-role="voice-modal"]');
+1 -1
View File
@@ -298,7 +298,7 @@ body {
display: flex;
justify-content: flex-end;
gap: 0.75rem;
padding: 0 2rem 2rem;
padding: 1.25rem 2rem 2.75rem;
border-top: 1px solid rgba(148, 163, 184, 0.12);
background: linear-gradient(180deg, transparent, rgba(15, 23, 42, 0.3));
}
+1 -1
View File
@@ -169,7 +169,7 @@
</div>
<footer class="modal__footer">
<button type="button" class="button button--ghost" data-role="upload-modal-close">Cancel</button>
<button type="submit" class="button">Queue Conversion</button>
<button type="submit" class="button">Chapter Selection</button>
</footer>
</form>
</div>
+23 -7
View File
@@ -4,9 +4,13 @@
{% block content %}
{% set wizard_step = (active_step or 'chapters') %}
{% set is_multi_speaker = pending.speaker_mode == 'multi' %}
{% if wizard_step == 'speakers' and not is_multi_speaker %}
{% set wizard_step = 'chapters' %}
{% endif %}
{% set is_chapters = wizard_step == 'chapters' %}
{% set is_speakers = wizard_step == 'speakers' %}
<section class="card" data-role="prepare-wizard" data-initial-step="{{ wizard_step }}">
<section class="card" data-role="prepare-wizard" data-initial-step="{{ wizard_step }}" data-speaker-step="{{ 'enabled' if is_multi_speaker else 'disabled' }}">
<div class="step-indicator" aria-label="Audiobook workflow" data-role="wizard-indicator">
<span class="step-indicator__item is-complete" data-role="wizard-step" data-step-key="setup">
<span class="step-indicator__index">1</span>
@@ -16,7 +20,7 @@
<span class="step-indicator__index">2</span>
<span class="step-indicator__label">Chapters</span>
</span>
<span class="step-indicator__item{% if is_speakers %} is-active{% endif %}" data-role="wizard-step" data-step-key="speakers">
<span class="step-indicator__item{% if is_speakers %} is-active{% endif %}" data-role="wizard-step" data-step-key="speakers" {% if not is_multi_speaker %}hidden aria-hidden="true"{% endif %}>
<span class="step-indicator__index">3</span>
<span class="step-indicator__label">Speakers</span>
</span>
@@ -148,12 +152,25 @@
</div>
</div>
<div class="prepare-step__actions">
<button type="button" class="button" data-role="step-next" data-step-target="speakers">Continue to speakers</button>
<div class="prepare-step__actions" data-role="chapter-actions">
<button type="button"
class="button"
data-role="step-next"
data-step-target="speakers"
data-chapter-action="continue"
{% if not is_multi_speaker %}hidden aria-hidden="true"{% endif %}>
Continue to speakers
</button>
<button type="submit"
class="button"
data-chapter-action="finalize"
{% if is_multi_speaker %}hidden aria-hidden="true"{% endif %}>
Queue conversion
</button>
</div>
</section>
<section class="prepare-step" data-step-panel="speakers" hidden>
<section class="prepare-step" data-step-panel="speakers" data-speaker-panel="true" hidden>
<header class="prepare-step__header">
<h2>Step 3 · Select speakers</h2>
<p class="hint">Assign voices, audition samples, and finalize your roster.</p>
@@ -408,9 +425,8 @@
{% endif %}
<div class="prepare-actions">
<button type="button" class="button button--ghost" data-role="step-prev" data-step-target="chapters">Back to chapters</button>
<button type="button" class="button button--ghost" data-role="step-prev" data-step-target="chapters">Back to upload &amp; settings</button>
<button type="submit" class="button">Queue conversion</button>
<button type="submit" class="button button--ghost" form="cancel-form">Cancel</button>
</div>
</section>
</div>