mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 13:40:27 +02:00
feat: Implement upload modal event dispatching and enhance wizard modal navigation
This commit is contained in:
@@ -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) => {
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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 & settings</h2>
|
<h2 class="modal__title" id="upload-modal-title">Upload & 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>
|
||||||
|
|||||||
@@ -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 & 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 & 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">▾</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' %}
|
||||||
|
|||||||
@@ -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 & 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 & 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' %}
|
||||||
|
|||||||
Reference in New Issue
Block a user