feat: Implement upload modal event dispatching and enhance wizard modal navigation

This commit is contained in:
JB
2025-10-11 12:29:52 -07:00
parent 6266819670
commit 610091c0d9
6 changed files with 302 additions and 128 deletions
+11
View File
@@ -102,6 +102,15 @@ const initDashboard = () => {
let previewObjectUrl = null; let previewObjectUrl = null;
let suppressPauseStatus = false; let suppressPauseStatus = false;
const dispatchUploadModalEvent = (type, detail = {}) => {
const eventName = `upload-modal:${type}`;
if (uploadModal) {
uploadModal.dispatchEvent(new CustomEvent(eventName, { detail, bubbles: true }));
return;
}
document.dispatchEvent(new CustomEvent(eventName, { detail }));
};
const openUploadModal = (trigger) => { const openUploadModal = (trigger) => {
if (!uploadModal) return; if (!uploadModal) return;
lastTrigger = trigger || null; lastTrigger = trigger || null;
@@ -112,6 +121,7 @@ const initDashboard = () => {
if (focusTarget instanceof HTMLElement) { if (focusTarget instanceof HTMLElement) {
focusTarget.focus({ preventScroll: true }); focusTarget.focus({ preventScroll: true });
} }
dispatchUploadModalEvent("open", { trigger: lastTrigger });
}; };
const closeUploadModal = () => { const closeUploadModal = () => {
@@ -124,6 +134,7 @@ const initDashboard = () => {
if (lastTrigger && lastTrigger instanceof HTMLElement) { if (lastTrigger && lastTrigger instanceof HTMLElement) {
lastTrigger.focus({ preventScroll: true }); lastTrigger.focus({ preventScroll: true });
} }
dispatchUploadModalEvent("close", { trigger: lastTrigger });
}; };
openModalButtons.forEach((button) => { openModalButtons.forEach((button) => {
+94 -8
View File
@@ -2,6 +2,55 @@ document.addEventListener("DOMContentLoaded", () => {
const form = document.querySelector(".prepare-form"); const form = document.querySelector(".prepare-form");
if (!form) return; if (!form) return;
const wizardModal = document.querySelector('[data-role="wizard-modal"]');
const uploadModal = document.querySelector('[data-role="upload-modal"]');
const wizardPreviousButtons = Array.from(document.querySelectorAll('[data-role="wizard-previous"]'));
const openUploadTriggers = Array.from(document.querySelectorAll('[data-role="open-upload-modal"]'));
const showWizardModal = () => {
if (!wizardModal) return;
wizardModal.hidden = false;
wizardModal.dataset.open = "true";
wizardModal.removeAttribute("aria-hidden");
document.body.classList.add("modal-open");
};
const hideWizardModal = () => {
if (!wizardModal) return;
wizardModal.hidden = true;
delete wizardModal.dataset.open;
wizardModal.setAttribute("aria-hidden", "true");
};
const triggerUploadModal = () => {
const existingTrigger = openUploadTriggers.find((button) => button !== null);
if (existingTrigger) {
existingTrigger.dispatchEvent(new MouseEvent("click", { bubbles: true }));
return;
}
if (!uploadModal) return;
uploadModal.hidden = false;
uploadModal.dataset.open = "true";
document.body.classList.add("modal-open");
const focusTarget = uploadModal.querySelector("#source_file") || uploadModal.querySelector("#source_text") || uploadModal;
if (focusTarget instanceof HTMLElement) {
focusTarget.focus({ preventScroll: true });
}
};
showWizardModal();
document.addEventListener("upload-modal:open", hideWizardModal);
document.addEventListener("upload-modal:close", showWizardModal);
wizardPreviousButtons.forEach((button) => {
button.addEventListener("click", (event) => {
event.preventDefault();
hideWizardModal();
triggerUploadModal();
});
});
const parseJSONScript = (id) => { const parseJSONScript = (id) => {
const el = document.getElementById(id); const el = document.getElementById(id);
if (!el) return null; if (!el) return null;
@@ -184,12 +233,39 @@ document.addEventListener("DOMContentLoaded", () => {
const chapterRows = Array.from(form.querySelectorAll("[data-role=chapter-row]")); const chapterRows = Array.from(form.querySelectorAll("[data-role=chapter-row]"));
const setRowExpansion = (row, expanded) => {
if (!row) return;
const details = row.querySelector('[data-role="chapter-details"]');
const toggle = row.querySelector('[data-role="chapter-toggle"]');
const isExpanded = Boolean(expanded);
row.dataset.expanded = isExpanded ? "true" : "false";
if (details) {
details.hidden = !isExpanded;
details.setAttribute("aria-hidden", isExpanded ? "false" : "true");
}
if (toggle) {
toggle.setAttribute("aria-expanded", isExpanded ? "true" : "false");
toggle.setAttribute("aria-label", isExpanded ? "Hide chapter details" : "Show chapter details");
}
};
const toggleRowExpansion = (row, force) => {
if (!row) return;
const current = row.dataset.expanded === "true";
const next = typeof force === "boolean" ? force : !current;
setRowExpansion(row, next);
};
const isRowEnabled = (row) => {
const checkbox = row?.querySelector('[data-role="chapter-enabled"]');
return checkbox ? checkbox.checked : true;
};
const updateRowState = (row) => { const updateRowState = (row) => {
const enabled = row.querySelector('[data-role=chapter-enabled]'); const enabled = row.querySelector('[data-role=chapter-enabled]');
const inputs = Array.from(row.querySelectorAll("input[type=text], select, textarea")); const inputs = Array.from(row.querySelectorAll("input[type=text], select, textarea"));
const warning = row.querySelector('[data-role=chapter-warning]'); const warning = row.querySelector('[data-role=chapter-warning]');
const snippet = row.querySelector('[data-role="chapter-snippet"]'); const toggle = row.querySelector('[data-role="chapter-toggle"]');
const details = row.querySelector('[data-role="chapter-details"]');
const isChecked = enabled ? enabled.checked : true; const isChecked = enabled ? enabled.checked : true;
row.dataset.disabled = isChecked ? "false" : "true"; row.dataset.disabled = isChecked ? "false" : "true";
@@ -214,14 +290,13 @@ document.addEventListener("DOMContentLoaded", () => {
const select = row.querySelector("select[data-role=voice-select]"); const select = row.querySelector("select[data-role=voice-select]");
toggleFormula(select); toggleFormula(select);
if (snippet) { if (!isChecked) {
snippet.hidden = !isChecked; setRowExpansion(row, false);
snippet.setAttribute("aria-hidden", isChecked ? "false" : "true");
} }
if (details) { if (toggle) {
details.hidden = !isChecked; toggle.disabled = !isChecked;
details.setAttribute("aria-hidden", isChecked ? "false" : "true"); toggle.setAttribute("aria-disabled", isChecked ? "false" : "true");
} }
if (warning) { if (warning) {
@@ -248,6 +323,7 @@ document.addEventListener("DOMContentLoaded", () => {
}; };
chapterRows.forEach((row) => { chapterRows.forEach((row) => {
setRowExpansion(row, row.dataset.expanded === "true");
const enabled = row.querySelector('[data-role=chapter-enabled]'); const enabled = row.querySelector('[data-role=chapter-enabled]');
if (enabled) { if (enabled) {
enabled.addEventListener("change", () => updateRowState(row)); enabled.addEventListener("change", () => updateRowState(row));
@@ -258,6 +334,16 @@ document.addEventListener("DOMContentLoaded", () => {
select.addEventListener("change", () => toggleFormula(select)); select.addEventListener("change", () => toggleFormula(select));
toggleFormula(select); toggleFormula(select);
} }
const toggleButton = row.querySelector('[data-role="chapter-toggle"]');
if (toggleButton) {
toggleButton.addEventListener("click", () => {
if (!isRowEnabled(row)) {
setRowExpansion(row, false);
return;
}
toggleRowExpansion(row);
});
}
}); });
const updatePreviewVoice = (select) => { const updatePreviewVoice = (select) => {
+63 -1
View File
@@ -521,6 +521,11 @@ body {
padding: 3rem 0 5rem; padding: 3rem 0 5rem;
} }
.wizard-page--modal {
padding: 0;
min-height: 100vh;
}
.wizard-card { .wizard-card {
width: min(1100px, calc(100% - 2.5rem)); width: min(1100px, calc(100% - 2.5rem));
margin: 0 auto; margin: 0 auto;
@@ -1789,13 +1794,70 @@ button.step-indicator__item:focus-visible {
opacity: 0.5; opacity: 0.5;
} }
.chapter-card__header { .chapter-card__summary {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: flex-start; align-items: flex-start;
gap: 1.25rem; gap: 1.25rem;
} }
.chapter-card__summary:focus-within {
outline: none;
}
.chapter-card__toggle {
border: none;
background: rgba(148, 163, 184, 0.1);
color: var(--muted);
width: 32px;
height: 32px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background 0.2s ease, color 0.2s ease, transform 0.2s ease;
}
.chapter-card__toggle:hover,
.chapter-card__toggle:focus-visible {
background: rgba(148, 163, 184, 0.2);
color: #fff;
}
.chapter-card__toggle[disabled] {
opacity: 0.45;
cursor: not-allowed;
pointer-events: none;
}
.chapter-card__toggle:focus-visible {
outline: 2px solid rgba(56, 189, 248, 0.45);
outline-offset: 2px;
}
.chapter-card__toggle-icon {
display: inline-flex;
font-size: 1rem;
transition: transform 0.2s ease;
}
.chapter-card[data-expanded="true"] .chapter-card__toggle-icon {
transform: rotate(180deg);
}
.chapter-card__details {
display: grid;
gap: 1rem;
}
.chapter-card__snippet {
margin: 0;
font-size: 0.95rem;
line-height: 1.5;
color: var(--muted);
}
.chapter-card__title { .chapter-card__title {
font-weight: 600; font-weight: 600;
display: flex; display: flex;
@@ -8,6 +8,7 @@
{% elif settings_dict %} {% elif settings_dict %}
{% set is_multi = settings_dict.get('speaker_mode', 'single') == 'multi' %} {% set is_multi = settings_dict.get('speaker_mode', 'single') == 'multi' %}
{% endif %} {% endif %}
{% set total_steps = 3 if is_multi else 2 %}
{% set language_value = pending.language if pending else settings_dict.get('language', '') %} {% set language_value = pending.language if pending else settings_dict.get('language', '') %}
{% if not language_value %} {% if not language_value %}
{% set sorted_languages = options.languages|dictsort %} {% set sorted_languages = options.languages|dictsort %}
@@ -65,7 +66,7 @@
</span> </span>
{% endif %} {% endif %}
</nav> </nav>
<p class="modal__eyebrow">Step 1 of 3</p> <p class="modal__eyebrow">Step 1 of {{ total_steps }}</p>
<h2 class="modal__title" id="upload-modal-title">Upload &amp; settings</h2> <h2 class="modal__title" id="upload-modal-title">Upload &amp; settings</h2>
<p class="hint">Choose your source file or paste text, then set the defaults used for chapter analysis and speaker casting.</p> <p class="hint">Choose your source file or paste text, then set the defaults used for chapter analysis and speaker casting.</p>
</div> </div>
+80 -70
View File
@@ -3,46 +3,50 @@
{% block title %}Prepare · {{ pending.original_filename }}{% endblock %} {% block title %}Prepare · {{ pending.original_filename }}{% endblock %}
{% set is_multi_speaker = pending.speaker_mode == 'multi' %} {% set is_multi_speaker = pending.speaker_mode == 'multi' %}
{% set total_steps = 3 if is_multi_speaker else 2 %}
{% block content %} {% block content %}
<section class="wizard-page" data-step="chapters"> <section class="wizard-page wizard-page--modal" data-step="chapters">
<div class="wizard-card card card--modal"> <div class="modal modal--wizard" data-role="wizard-modal" data-open="true">
<header class="modal__header wizard-card__header"> <div class="modal__overlay" aria-hidden="true"></div>
<div class="wizard-card__headline"> <div class="modal__content card card--modal wizard-card" role="dialog" aria-modal="true" aria-labelledby="prepare-chapters-title">
<nav class="step-indicator" aria-label="Audiobook workflow"> <header class="modal__header wizard-card__header">
<button type="button" <div class="wizard-card__headline">
class="step-indicator__item is-complete" <nav class="step-indicator" aria-label="Audiobook workflow">
data-role="open-upload-modal"> <button type="button"
<span class="step-indicator__index">1</span> class="step-indicator__item is-complete"
<span class="step-indicator__label">Upload &amp; settings</span> data-role="open-upload-modal">
</button> <span class="step-indicator__index">1</span>
<a class="step-indicator__item is-active" <span class="step-indicator__label">Upload &amp; settings</span>
aria-current="step" </button>
href="{{ url_for('web.prepare_job', pending_id=pending.id, step='chapters') }}"> <a class="step-indicator__item is-active"
<span class="step-indicator__index">2</span> aria-current="step"
<span class="step-indicator__label">Chapters</span> href="{{ url_for('web.prepare_job', pending_id=pending.id, step='chapters') }}">
</a> <span class="step-indicator__index">2</span>
{% if is_multi_speaker %} <span class="step-indicator__label">Chapters</span>
<a class="step-indicator__item" </a>
href="{{ url_for('web.prepare_job', pending_id=pending.id, step='speakers') }}"> {% if is_multi_speaker %}
<span class="step-indicator__index">3</span> <a class="step-indicator__item"
<span class="step-indicator__label">Speakers</span> href="{{ url_for('web.prepare_job', pending_id=pending.id, step='speakers') }}">
</a> <span class="step-indicator__index">3</span>
{% endif %} <span class="step-indicator__label">Speakers</span>
</nav> </a>
<h2 class="modal__title">Select chapters</h2> {% endif %}
<p class="hint">Choose which chapters to convert. We'll analyse speakers automatically when you continue.</p> </nav>
</div> <p class="modal__eyebrow">Step 2 of {{ total_steps }}</p>
<div class="wizard-card__aside"> <h2 class="modal__title" id="prepare-chapters-title">Select chapters</h2>
<p class="wizard-card__filename" title="{{ pending.original_filename }}">{{ pending.original_filename }}</p> <p class="hint">Choose which chapters to convert. We'll analyse speakers automatically when you continue.</p>
</div> </div>
</header> <div class="wizard-card__aside">
<form method="post" <p class="wizard-card__filename" title="{{ pending.original_filename }}">{{ pending.original_filename }}</p>
action="{{ url_for('web.finalize_job', pending_id=pending.id) }}" </div>
class="prepare-form" </header>
id="prepare-form" <form method="post"
data-speaker-mode="{{ pending.speaker_mode }}" action="{{ url_for('web.finalize_job', pending_id=pending.id) }}"
data-analyze-url="{{ url_for('web.analyze_pending_job', pending_id=pending.id) }}"> class="prepare-form"
id="prepare-form"
data-speaker-mode="{{ pending.speaker_mode }}"
data-analyze-url="{{ url_for('web.analyze_pending_job', pending_id=pending.id) }}">
<input type="hidden" name="active_step" value="chapters" data-role="active-step-input"> <input type="hidden" name="active_step" value="chapters" data-role="active-step-input">
<div class="wizard-hidden-inputs" aria-hidden="true"> <div class="wizard-hidden-inputs" aria-hidden="true">
<input type="hidden" name="chunk_level" value="{{ pending.chunk_level }}"> <input type="hidden" name="chunk_level" value="{{ pending.chunk_level }}">
@@ -54,7 +58,7 @@
{% endif %} {% endif %}
</div> </div>
<div class="modal__body wizard-card__body"> <div class="modal__body wizard-card__body">
{% if error %} {% if error %}
<div class="alert alert--error">{{ error }}</div> <div class="alert alert--error">{{ error }}</div>
{% endif %} {% endif %}
@@ -83,8 +87,8 @@
<article class="chapter-card" <article class="chapter-card"
data-role="chapter-row" data-role="chapter-row"
data-disabled="{{ 'false' if is_enabled else 'true' }}" data-disabled="{{ 'false' if is_enabled else 'true' }}"
data-expanded="{{ 'true' if is_enabled else 'false' }}"> data-expanded="false">
<header class="chapter-card__summary"> <header class="chapter-card__summary" data-role="chapter-summary">
<label class="chapter-card__checkbox"> <label class="chapter-card__checkbox">
<input type="checkbox" <input type="checkbox"
name="chapter-{{ loop.index0 }}-enabled" name="chapter-{{ loop.index0 }}-enabled"
@@ -92,15 +96,20 @@
{% if is_enabled %}checked{% endif %}> {% if is_enabled %}checked{% endif %}>
<span>Chapter {{ loop.index }} · {{ chapter.title }}</span> <span>Chapter {{ loop.index }} · {{ chapter.title }}</span>
</label> </label>
<button type="button"
class="chapter-card__toggle"
data-role="chapter-toggle"
aria-expanded="false"
aria-label="Toggle chapter details">
<span class="chapter-card__toggle-icon" aria-hidden="true">&#9662;</span>
</button>
</header>
<div class="chapter-card__details"
data-role="chapter-details">
<p class="chapter-card__snippet" <p class="chapter-card__snippet"
data-role="chapter-snippet" data-role="chapter-snippet">
{% if not is_enabled %}hidden aria-hidden="true"{% else %}aria-hidden="false"{% endif %}>
{{ excerpt }}{% if raw_excerpt|length > 240 %}…{% endif %} {{ excerpt }}{% if raw_excerpt|length > 240 %}…{% endif %}
</p> </p>
</header>
<div class="chapter-card__details"
data-role="chapter-details"
{% if not is_enabled %}hidden aria-hidden="true"{% else %}aria-hidden="false"{% endif %}>
<div class="chapter-card__field"> <div class="chapter-card__field">
<label for="chapter-{{ loop.index0 }}-title">Title</label> <label for="chapter-{{ loop.index0 }}-title">Title</label>
<input type="text" id="chapter-{{ loop.index0 }}-title" name="chapter-{{ loop.index0 }}-title" value="{{ chapter.title }}"> <input type="text" id="chapter-{{ loop.index0 }}-title" name="chapter-{{ loop.index0 }}-title" value="{{ chapter.title }}">
@@ -145,30 +154,31 @@
{% endfor %} {% endfor %}
</div> </div>
</section> </section>
</div>
<footer class="modal__footer wizard-card__footer">
<div class="wizard-card__footer-actions">
<a class="button button--ghost" href="{{ url_for('web.index') }}">Previous</a>
<button type="submit" class="button button--ghost" form="cancel-form">Cancel</button>
</div> </div>
<div class="wizard-card__footer-actions"> <footer class="modal__footer wizard-card__footer">
{% if is_multi_speaker %} <div class="wizard-card__footer-actions">
<button type="submit" <button type="button" class="button button--ghost" data-role="wizard-previous">Previous</button>
class="button" <button type="submit" class="button button--ghost" form="cancel-form">Cancel</button>
data-role="submit-speaker-analysis" </div>
data-step-toggle="analysis" <div class="wizard-card__footer-actions">
formaction="{{ url_for('web.analyze_pending_job', pending_id=pending.id) }}" {% if is_multi_speaker %}
formmethod="post"> <button type="submit"
Continue to speakers class="button"
</button> data-role="submit-speaker-analysis"
{% endif %} data-step-toggle="analysis"
<button type="submit" class="button" data-step-toggle="finalize"{% if is_multi_speaker %} hidden aria-hidden="true"{% endif %}> formaction="{{ url_for('web.analyze_pending_job', pending_id=pending.id) }}"
Queue conversion formmethod="post">
</button> Continue to speakers
</div> </button>
</footer> {% endif %}
</form> <button type="submit" class="button" data-step-toggle="finalize"{% if is_multi_speaker %} hidden aria-hidden="true"{% endif %}>
<form method="post" action="{{ url_for('web.cancel_pending_job', pending_id=pending.id) }}" id="cancel-form"></form> Queue conversion
</button>
</div>
</footer>
</form>
<form method="post" action="{{ url_for('web.cancel_pending_job', pending_id=pending.id) }}" id="cancel-form"></form>
</div>
</div> </div>
</section> </section>
{% with pending=pending, readonly=True, active_step='chapters' %} {% with pending=pending, readonly=True, active_step='chapters' %}
+52 -48
View File
@@ -7,42 +7,46 @@
{% set analysis_speakers = analysis.get('speakers', {}) %} {% set analysis_speakers = analysis.get('speakers', {}) %}
{% set speaker_configs = options.speaker_configs or [] %} {% set speaker_configs = options.speaker_configs or [] %}
{% set recommended_template = options.speaker_pronunciation_sentence or "This is {{name}} speaking." %} {% set recommended_template = options.speaker_pronunciation_sentence or "This is {{name}} speaking." %}
{% set total_steps = 3 if is_multi_speaker else 2 %}
{% block content %} {% block content %}
<section class="wizard-page" data-step="speakers"> <section class="wizard-page wizard-page--modal" data-step="speakers">
<div class="wizard-card card card--modal"> <div class="modal modal--wizard" data-role="wizard-modal" data-open="true">
<header class="modal__header wizard-card__header"> <div class="modal__overlay" aria-hidden="true"></div>
<div class="wizard-card__headline"> <div class="modal__content card card--modal wizard-card" role="dialog" aria-modal="true" aria-labelledby="prepare-speakers-title">
<nav class="step-indicator" aria-label="Audiobook workflow"> <header class="modal__header wizard-card__header">
<button type="button" <div class="wizard-card__headline">
class="step-indicator__item is-complete" <nav class="step-indicator" aria-label="Audiobook workflow">
data-role="open-upload-modal"> <button type="button"
<span class="step-indicator__index">1</span> class="step-indicator__item is-complete"
<span class="step-indicator__label">Upload &amp; settings</span> data-role="open-upload-modal">
</button> <span class="step-indicator__index">1</span>
<a class="step-indicator__item is-complete" <span class="step-indicator__label">Upload &amp; settings</span>
href="{{ url_for('web.prepare_job', pending_id=pending.id, step='chapters') }}"> </button>
<span class="step-indicator__index">2</span> <a class="step-indicator__item is-complete"
<span class="step-indicator__label">Chapters</span> href="{{ url_for('web.prepare_job', pending_id=pending.id, step='chapters') }}">
</a> <span class="step-indicator__index">2</span>
{% if is_multi_speaker %} <span class="step-indicator__label">Chapters</span>
<a class="step-indicator__item is-active" </a>
aria-current="step" {% if is_multi_speaker %}
href="{{ url_for('web.prepare_job', pending_id=pending.id, step='speakers') }}"> <a class="step-indicator__item is-active"
<span class="step-indicator__index">3</span> aria-current="step"
<span class="step-indicator__label">Speakers</span> href="{{ url_for('web.prepare_job', pending_id=pending.id, step='speakers') }}">
</a> <span class="step-indicator__index">3</span>
{% endif %} <span class="step-indicator__label">Speakers</span>
</nav> </a>
<h2 class="modal__title">Select speakers</h2> {% endif %}
<p class="hint">Assign voices, audition samples, and finalise your roster before queueing the conversion.</p> </nav>
</div> <p class="modal__eyebrow">Step 3 of {{ total_steps }}</p>
<div class="wizard-card__aside"> <h2 class="modal__title" id="prepare-speakers-title">Select speakers</h2>
<p class="wizard-card__filename" title="{{ pending.original_filename }}">{{ pending.original_filename }}</p> <p class="hint">Assign voices, audition samples, and finalise your roster before queueing the conversion.</p>
</div> </div>
</header> <div class="wizard-card__aside">
<p class="wizard-card__filename" title="{{ pending.original_filename }}">{{ pending.original_filename }}</p>
</div>
</header>
<form method="post" <form method="post"
action="{{ url_for('web.finalize_job', pending_id=pending.id) }}" action="{{ url_for('web.finalize_job', pending_id=pending.id) }}"
class="prepare-form" class="prepare-form"
id="prepare-form" id="prepare-form"
@@ -78,7 +82,7 @@
{% endfor %} {% endfor %}
</div> </div>
<div class="modal__body wizard-card__body"> <div class="modal__body wizard-card__body">
{% if error %} {% if error %}
<div class="alert alert--error">{{ error }}</div> <div class="alert alert--error">{{ error }}</div>
{% endif %} {% endif %}
@@ -345,21 +349,21 @@
{% else %} {% else %}
<p class="hint">No additional speakers detected yet. The narrator voice will be used for all dialogue.</p> <p class="hint">No additional speakers detected yet. The narrator voice will be used for all dialogue.</p>
{% endif %} {% endif %}
</div>
<footer class="modal__footer wizard-card__footer">
<div class="wizard-card__footer-actions">
<a class="button button--ghost" href="{{ url_for('web.prepare_job', pending_id=pending.id, step='chapters') }}">Previous</a>
<button type="submit" class="button button--ghost" form="cancel-form">Cancel</button>
</div> </div>
<div class="wizard-card__footer-actions">
<button type="submit" class="button">Queue conversion</button>
</div>
</footer>
</form>
<form method="post" action="{{ url_for('web.cancel_pending_job', pending_id=pending.id) }}" id="cancel-form"></form>
<div class="modal" data-role="voice-modal" hidden> <footer class="modal__footer wizard-card__footer">
<div class="wizard-card__footer-actions">
<a class="button button--ghost" href="{{ url_for('web.prepare_job', pending_id=pending.id, step='chapters') }}">Previous</a>
<button type="submit" class="button button--ghost" form="cancel-form">Cancel</button>
</div>
<div class="wizard-card__footer-actions">
<button type="submit" class="button">Queue conversion</button>
</div>
</footer>
</form>
<form method="post" action="{{ url_for('web.cancel_pending_job', pending_id=pending.id) }}" id="cancel-form"></form>
<div class="modal" data-role="voice-modal" hidden>
<div class="modal__overlay" data-role="voice-modal-close" tabindex="-1"></div> <div class="modal__overlay" data-role="voice-modal-close" tabindex="-1"></div>
<div class="modal__content voice-browser" role="dialog" aria-modal="true" aria-labelledby="voice-modal-title"> <div class="modal__content voice-browser" role="dialog" aria-modal="true" aria-labelledby="voice-modal-title">
<header class="voice-browser__header"> <header class="voice-browser__header">
@@ -407,8 +411,8 @@
</section> </section>
</section> </section>
</div> </div>
</div>
</div> </div>
</div>
</div> </div>
</section> </section>
{% with pending=pending, readonly=True, active_step='speakers' %} {% with pending=pending, readonly=True, active_step='speakers' %}