mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 21:50:28 +02:00
feat: Refactor reader modal implementation and enhance dashboard event handling
This commit is contained in:
@@ -1,36 +1,13 @@
|
|||||||
|
import { initReaderUI } from "./reader.js";
|
||||||
|
|
||||||
const initDashboard = () => {
|
const initDashboard = () => {
|
||||||
const uploadModal = document.querySelector('[data-role="upload-modal"]');
|
const uploadModal = document.querySelector('[data-role="upload-modal"]');
|
||||||
const openModalButtons = document.querySelectorAll('[data-role="open-upload-modal"]');
|
const openModalButtons = document.querySelectorAll('[data-role="open-upload-modal"]');
|
||||||
const readerModal = document.querySelector('[data-role="reader-modal"]');
|
|
||||||
const readerFrame = readerModal?.querySelector('[data-role="reader-frame"]') || null;
|
|
||||||
const readerHint = readerModal?.querySelector('[data-role="reader-modal-hint"]') || null;
|
|
||||||
const readerTitle = readerModal?.querySelector('#reader-modal-title') || null;
|
|
||||||
const defaultReaderHint = readerHint?.textContent || "";
|
|
||||||
const scope = uploadModal || document;
|
const scope = uploadModal || document;
|
||||||
const sourceFileInput = scope.querySelector('#source_file');
|
const sourceFileInput = scope.querySelector('#source_file');
|
||||||
const dropzone = document.querySelector('[data-role="upload-dropzone"]');
|
const dropzone = document.querySelector('[data-role="upload-dropzone"]');
|
||||||
const dropzoneFilename = document.querySelector('[data-role="upload-dropzone-filename"]');
|
const dropzoneFilename = document.querySelector('[data-role="upload-dropzone-filename"]');
|
||||||
|
|
||||||
const readerButtonRegistry = new WeakSet();
|
|
||||||
|
|
||||||
const bindReaderButtons = (root) => {
|
|
||||||
const context = root instanceof Element ? root : document;
|
|
||||||
const buttons = context.querySelectorAll('[data-role="open-reader"]');
|
|
||||||
buttons.forEach((button) => {
|
|
||||||
if (!(button instanceof HTMLElement)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (readerButtonRegistry.has(button)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
button.addEventListener("click", (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
openReaderModal(button);
|
|
||||||
});
|
|
||||||
readerButtonRegistry.add(button);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const parseJSONScript = (id) => {
|
const parseJSONScript = (id) => {
|
||||||
const element = document.getElementById(id);
|
const element = document.getElementById(id);
|
||||||
if (!element) return null;
|
if (!element) return null;
|
||||||
@@ -121,7 +98,6 @@ const initDashboard = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let lastTrigger = null;
|
let lastTrigger = null;
|
||||||
let readerTrigger = null;
|
|
||||||
let previewAbortController = null;
|
let previewAbortController = null;
|
||||||
let previewObjectUrl = null;
|
let previewObjectUrl = null;
|
||||||
let suppressPauseStatus = false;
|
let suppressPauseStatus = false;
|
||||||
@@ -150,54 +126,6 @@ const initDashboard = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const openReaderModal = (trigger) => {
|
|
||||||
const url = trigger?.dataset.readerUrl || "";
|
|
||||||
if (!url) return;
|
|
||||||
if (!readerModal || !readerFrame) {
|
|
||||||
window.open(url, "_blank", "noopener,noreferrer");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
readerTrigger = trigger || null;
|
|
||||||
const bookTitle = trigger?.dataset.bookTitle || "";
|
|
||||||
if (readerTitle) {
|
|
||||||
readerTitle.textContent = bookTitle ? `${bookTitle} · reader` : "Read & listen";
|
|
||||||
}
|
|
||||||
if (readerHint) {
|
|
||||||
readerHint.textContent = bookTitle ? `Preview ${bookTitle} directly in your browser.` : defaultReaderHint;
|
|
||||||
}
|
|
||||||
closeUploadModal();
|
|
||||||
readerModal.hidden = false;
|
|
||||||
readerModal.dataset.open = "true";
|
|
||||||
document.body.classList.add("modal-open");
|
|
||||||
readerFrame.src = url;
|
|
||||||
try {
|
|
||||||
readerFrame.focus({ preventScroll: true });
|
|
||||||
} catch (error) {
|
|
||||||
// Ignore focus errors when the browser blocks iframe focus
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeReaderModal = () => {
|
|
||||||
if (!readerModal) return;
|
|
||||||
if (readerModal.hidden) return;
|
|
||||||
readerModal.hidden = true;
|
|
||||||
delete readerModal.dataset.open;
|
|
||||||
document.body.classList.remove("modal-open");
|
|
||||||
if (readerFrame) {
|
|
||||||
readerFrame.src = "about:blank";
|
|
||||||
}
|
|
||||||
if (readerHint) {
|
|
||||||
readerHint.textContent = defaultReaderHint;
|
|
||||||
}
|
|
||||||
if (readerTitle) {
|
|
||||||
readerTitle.textContent = "Read & listen";
|
|
||||||
}
|
|
||||||
if (readerTrigger && readerTrigger instanceof HTMLElement) {
|
|
||||||
readerTrigger.focus({ preventScroll: true });
|
|
||||||
}
|
|
||||||
readerTrigger = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
openModalButtons.forEach((button) => {
|
openModalButtons.forEach((button) => {
|
||||||
button.addEventListener("click", (event) => {
|
button.addEventListener("click", (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -221,55 +149,10 @@ const initDashboard = () => {
|
|||||||
closeUploadModal();
|
closeUploadModal();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (readerModal && !readerModal.hidden) {
|
|
||||||
closeReaderModal();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const resolveEventMatch = (event, selector) => {
|
initReaderUI({ onBeforeOpen: closeUploadModal });
|
||||||
const target = event.target;
|
|
||||||
if (target instanceof Element) {
|
|
||||||
const match = target.closest(selector);
|
|
||||||
if (match) {
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (typeof event.composedPath === "function") {
|
|
||||||
const path = event.composedPath();
|
|
||||||
for (const node of path) {
|
|
||||||
if (node instanceof Element) {
|
|
||||||
if (node.matches(selector)) {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
const match = node.closest(selector);
|
|
||||||
if (match) {
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const fallback = target?.parentElement;
|
|
||||||
if (fallback instanceof Element) {
|
|
||||||
return fallback.closest(selector);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener("click", (event) => {
|
|
||||||
const readerClose = resolveEventMatch(event, '[data-role="reader-modal-close"]');
|
|
||||||
if (readerClose) {
|
|
||||||
event.preventDefault();
|
|
||||||
closeReaderModal();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const readerTriggerBtn = resolveEventMatch(event, '[data-role="open-reader"]');
|
|
||||||
if (readerTriggerBtn instanceof HTMLElement) {
|
|
||||||
event.preventDefault();
|
|
||||||
openReaderModal(readerTriggerBtn);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (sourceFileInput) {
|
if (sourceFileInput) {
|
||||||
sourceFileInput.addEventListener("change", updateDropzoneFilename);
|
sourceFileInput.addEventListener("change", updateDropzoneFilename);
|
||||||
@@ -655,17 +538,6 @@ const initDashboard = () => {
|
|||||||
cancelPreviewRequest();
|
cancelPreviewRequest();
|
||||||
stopPreviewAudio();
|
stopPreviewAudio();
|
||||||
});
|
});
|
||||||
|
|
||||||
bindReaderButtons();
|
|
||||||
|
|
||||||
document.addEventListener("htmx:afterSwap", (event) => {
|
|
||||||
const fragment = event?.detail?.target;
|
|
||||||
if (fragment instanceof Element) {
|
|
||||||
bindReaderButtons(fragment);
|
|
||||||
} else {
|
|
||||||
bindReaderButtons();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (document.readyState === "loading") {
|
if (document.readyState === "loading") {
|
||||||
|
|||||||
@@ -355,22 +355,18 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
wizard.dataset.speakerStep = skipSpeakers ? "disabled" : "enabled";
|
wizard.dataset.speakerStep = skipSpeakers ? "disabled" : "enabled";
|
||||||
}
|
}
|
||||||
if (speakerIndicator) {
|
if (speakerIndicator) {
|
||||||
|
speakerIndicator.hidden = skipSpeakers;
|
||||||
if (skipSpeakers) {
|
if (skipSpeakers) {
|
||||||
speakerIndicator.hidden = true;
|
|
||||||
speakerIndicator.setAttribute("aria-hidden", "true");
|
speakerIndicator.setAttribute("aria-hidden", "true");
|
||||||
speakerIndicator.style.display = "none";
|
|
||||||
} else {
|
} else {
|
||||||
speakerIndicator.hidden = false;
|
|
||||||
speakerIndicator.removeAttribute("aria-hidden");
|
speakerIndicator.removeAttribute("aria-hidden");
|
||||||
speakerIndicator.style.removeProperty("display");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!skipSpeakers) {
|
||||||
|
unlockStep("speakers");
|
||||||
|
}
|
||||||
if (speakerPanel) {
|
if (speakerPanel) {
|
||||||
if (skipSpeakers) {
|
speakerPanel.dataset.speakerEnabled = skipSpeakers ? "false" : "true";
|
||||||
speakerPanel.style.display = "none";
|
|
||||||
} else {
|
|
||||||
speakerPanel.style.removeProperty("display");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (continueButton) {
|
if (continueButton) {
|
||||||
if (skipSpeakers) {
|
if (skipSpeakers) {
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { initReaderUI } from "./reader.js";
|
||||||
|
|
||||||
|
const initQueuePage = () => {
|
||||||
|
initReaderUI();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (document.readyState === "loading") {
|
||||||
|
document.addEventListener("DOMContentLoaded", initQueuePage, { once: true });
|
||||||
|
} else {
|
||||||
|
initQueuePage();
|
||||||
|
}
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
const readerButtonRegistry = new WeakSet();
|
||||||
|
let initialized = false;
|
||||||
|
let readerModal = null;
|
||||||
|
let readerFrame = null;
|
||||||
|
let readerHint = null;
|
||||||
|
let readerTitle = null;
|
||||||
|
let readerTrigger = null;
|
||||||
|
let defaultReaderHint = "";
|
||||||
|
|
||||||
|
const resolveEventMatch = (event, selector) => {
|
||||||
|
const target = event.target;
|
||||||
|
if (target instanceof Element) {
|
||||||
|
const match = target.closest(selector);
|
||||||
|
if (match) {
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const path = typeof event.composedPath === "function" ? event.composedPath() : [];
|
||||||
|
for (const node of path) {
|
||||||
|
if (node instanceof Element) {
|
||||||
|
if (node.matches(selector)) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
const match = node.closest(selector);
|
||||||
|
if (match) {
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeReaderModal = () => {
|
||||||
|
if (!readerModal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (readerModal.hidden) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
readerModal.hidden = true;
|
||||||
|
readerModal.removeAttribute("data-open");
|
||||||
|
document.body.classList.remove("modal-open");
|
||||||
|
if (readerFrame) {
|
||||||
|
readerFrame.src = "about:blank";
|
||||||
|
}
|
||||||
|
if (readerHint && defaultReaderHint) {
|
||||||
|
readerHint.textContent = defaultReaderHint;
|
||||||
|
}
|
||||||
|
if (readerTitle) {
|
||||||
|
readerTitle.textContent = "Read & listen";
|
||||||
|
}
|
||||||
|
if (readerTrigger instanceof HTMLElement) {
|
||||||
|
try {
|
||||||
|
readerTrigger.focus({ preventScroll: true });
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore focus errors.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
readerTrigger = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createBindReaderButtons = (openReaderModal) => {
|
||||||
|
return (root) => {
|
||||||
|
const context = root instanceof Element ? root : document;
|
||||||
|
const buttons = context.querySelectorAll('[data-role="open-reader"]');
|
||||||
|
buttons.forEach((button) => {
|
||||||
|
if (!(button instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (readerButtonRegistry.has(button)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
button.addEventListener("click", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
openReaderModal(button);
|
||||||
|
});
|
||||||
|
readerButtonRegistry.add(button);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initReaderUI = (options = {}) => {
|
||||||
|
if (initialized) {
|
||||||
|
return {
|
||||||
|
bindReaderButtons: createBindReaderButtons((trigger) => {
|
||||||
|
if (typeof options.onBeforeOpen === "function") {
|
||||||
|
options.onBeforeOpen();
|
||||||
|
}
|
||||||
|
openReader(trigger, options);
|
||||||
|
}),
|
||||||
|
closeReaderModal,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
readerModal = document.querySelector('[data-role="reader-modal"]');
|
||||||
|
readerFrame = readerModal?.querySelector('[data-role="reader-frame"]') || null;
|
||||||
|
readerHint = readerModal?.querySelector('[data-role="reader-modal-hint"]') || null;
|
||||||
|
readerTitle = readerModal?.querySelector('#reader-modal-title') || null;
|
||||||
|
defaultReaderHint = readerHint?.textContent || "";
|
||||||
|
|
||||||
|
const openReaderModal = (trigger) => {
|
||||||
|
if (typeof options.onBeforeOpen === "function") {
|
||||||
|
options.onBeforeOpen();
|
||||||
|
}
|
||||||
|
if (!(trigger instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const url = trigger.dataset.readerUrl || "";
|
||||||
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!readerModal || !readerFrame) {
|
||||||
|
window.open(url, "_blank", "noopener,noreferrer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
readerTrigger = trigger;
|
||||||
|
const bookTitle = trigger.dataset.bookTitle || "";
|
||||||
|
if (readerTitle) {
|
||||||
|
readerTitle.textContent = bookTitle ? `${bookTitle} · reader` : "Read & listen";
|
||||||
|
}
|
||||||
|
if (readerHint) {
|
||||||
|
readerHint.textContent = bookTitle
|
||||||
|
? `Preview ${bookTitle} directly in your browser.`
|
||||||
|
: defaultReaderHint;
|
||||||
|
}
|
||||||
|
readerModal.hidden = false;
|
||||||
|
readerModal.dataset.open = "true";
|
||||||
|
document.body.classList.add("modal-open");
|
||||||
|
readerFrame.src = url;
|
||||||
|
try {
|
||||||
|
readerFrame.focus({ preventScroll: true });
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore focus errors.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const bindReaderButtons = createBindReaderButtons(openReaderModal);
|
||||||
|
bindReaderButtons();
|
||||||
|
|
||||||
|
document.addEventListener("click", (event) => {
|
||||||
|
const closeButton = resolveEventMatch(event, '[data-role="reader-modal-close"]');
|
||||||
|
if (closeButton) {
|
||||||
|
event.preventDefault();
|
||||||
|
closeReaderModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const trigger = resolveEventMatch(event, '[data-role="open-reader"]');
|
||||||
|
if (trigger instanceof HTMLElement) {
|
||||||
|
event.preventDefault();
|
||||||
|
openReaderModal(trigger);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("keydown", (event) => {
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
closeReaderModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("htmx:afterSwap", (event) => {
|
||||||
|
const fragment = event?.detail?.target;
|
||||||
|
if (fragment instanceof Element) {
|
||||||
|
bindReaderButtons(fragment);
|
||||||
|
} else {
|
||||||
|
bindReaderButtons();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
return {
|
||||||
|
bindReaderButtons,
|
||||||
|
closeReaderModal,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const openReader = (trigger, options) => {
|
||||||
|
if (typeof options.onBeforeOpen === "function") {
|
||||||
|
options.onBeforeOpen();
|
||||||
|
}
|
||||||
|
if (!(trigger instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const url = trigger.dataset.readerUrl || "";
|
||||||
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!readerModal || !readerFrame) {
|
||||||
|
window.open(url, "_blank", "noopener,noreferrer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
readerTrigger = trigger;
|
||||||
|
const bookTitle = trigger.dataset.bookTitle || "";
|
||||||
|
if (readerTitle) {
|
||||||
|
readerTitle.textContent = bookTitle ? `${bookTitle} · reader` : "Read & listen";
|
||||||
|
}
|
||||||
|
if (readerHint) {
|
||||||
|
readerHint.textContent = bookTitle
|
||||||
|
? `Preview ${bookTitle} directly in your browser.`
|
||||||
|
: defaultReaderHint;
|
||||||
|
}
|
||||||
|
readerModal.hidden = false;
|
||||||
|
readerModal.dataset.open = "true";
|
||||||
|
document.body.classList.add("modal-open");
|
||||||
|
readerFrame.src = url;
|
||||||
|
try {
|
||||||
|
readerFrame.focus({ preventScroll: true });
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore focus errors.
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -175,25 +175,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal" data-role="reader-modal" hidden>
|
{% include "partials/reader_modal.html" %}
|
||||||
<div class="modal__overlay" data-role="reader-modal-close" tabindex="-1"></div>
|
|
||||||
<div class="modal__content card card--modal reader-modal" role="dialog" aria-modal="true" aria-labelledby="reader-modal-title">
|
|
||||||
<header class="modal__header">
|
|
||||||
<div class="modal__heading">
|
|
||||||
<h2 class="modal__title" id="reader-modal-title">Read & listen</h2>
|
|
||||||
<p class="hint" data-role="reader-modal-hint">Preview synchronized narration directly in your browser.</p>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="icon-button" data-role="reader-modal-close" aria-label="Close reader">✕</button>
|
|
||||||
</header>
|
|
||||||
<div class="modal__body reader-modal__body">
|
|
||||||
<iframe title="EPUB reader"
|
|
||||||
class="reader-modal__frame"
|
|
||||||
data-role="reader-frame"
|
|
||||||
allow="autoplay"
|
|
||||||
loading="lazy"></iframe>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<div class="modal" data-role="reader-modal" hidden>
|
||||||
|
<div class="modal__overlay" data-role="reader-modal-close" tabindex="-1"></div>
|
||||||
|
<div class="modal__content card card--modal reader-modal" role="dialog" aria-modal="true" aria-labelledby="reader-modal-title">
|
||||||
|
<header class="modal__header">
|
||||||
|
<div class="modal__heading">
|
||||||
|
<h2 class="modal__title" id="reader-modal-title">Read & listen</h2>
|
||||||
|
<p class="hint" data-role="reader-modal-hint">Preview synchronized narration directly in your browser.</p>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="icon-button" data-role="reader-modal-close" aria-label="Close reader">✕</button>
|
||||||
|
</header>
|
||||||
|
<div class="modal__body reader-modal__body">
|
||||||
|
<iframe title="EPUB reader"
|
||||||
|
class="reader-modal__frame"
|
||||||
|
data-role="reader-frame"
|
||||||
|
allow="autoplay"
|
||||||
|
loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% set is_chapters = wizard_step == 'chapters' %}
|
{% set is_chapters = wizard_step == 'chapters' %}
|
||||||
{% set is_speakers = wizard_step == 'speakers' %}
|
{% set is_speakers = wizard_step == 'speakers' %}
|
||||||
|
{% set hide_speakers_panel = not is_speakers %}
|
||||||
<section class="card" data-role="prepare-wizard" data-initial-step="{{ wizard_step }}" data-speaker-step="{{ 'enabled' if is_multi_speaker else 'disabled' }}">
|
<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">
|
<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__item is-complete" data-role="wizard-step" data-step-key="setup">
|
||||||
@@ -20,7 +21,7 @@
|
|||||||
<span class="step-indicator__index">2</span>
|
<span class="step-indicator__index">2</span>
|
||||||
<span class="step-indicator__label">Chapters</span>
|
<span class="step-indicator__label">Chapters</span>
|
||||||
</span>
|
</span>
|
||||||
<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" style="display:none;"{% endif %}>
|
<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__index">3</span>
|
||||||
<span class="step-indicator__label">Speakers</span>
|
<span class="step-indicator__label">Speakers</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -170,7 +171,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="prepare-step" data-step-panel="speakers" data-speaker-panel="true" {% if not is_multi_speaker %}style="display:none;"{% endif %} hidden>
|
<section class="prepare-step"
|
||||||
|
data-step-panel="speakers"
|
||||||
|
data-speaker-panel="true"
|
||||||
|
{% if hide_speakers_panel %}hidden aria-hidden="true"{% endif %}
|
||||||
|
data-speaker-enabled="{{ 'true' if is_multi_speaker else 'false' }}">
|
||||||
<header class="prepare-step__header">
|
<header class="prepare-step__header">
|
||||||
<h2>Step 3 · Select speakers</h2>
|
<h2>Step 3 · Select speakers</h2>
|
||||||
<p class="hint">Assign voices, audition samples, and finalize your roster.</p>
|
<p class="hint">Assign voices, audition samples, and finalize your roster.</p>
|
||||||
|
|||||||
@@ -4,20 +4,6 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="card">
|
<section class="card">
|
||||||
<div class="step-indicator" aria-label="Audiobook workflow">
|
|
||||||
<span class="step-indicator__item is-complete">
|
|
||||||
<span class="step-indicator__index">1</span>
|
|
||||||
<span class="step-indicator__label">Upload</span>
|
|
||||||
</span>
|
|
||||||
<span class="step-indicator__item is-complete">
|
|
||||||
<span class="step-indicator__index">2</span>
|
|
||||||
<span class="step-indicator__label">Speakers</span>
|
|
||||||
</span>
|
|
||||||
<span class="step-indicator__item is-active">
|
|
||||||
<span class="step-indicator__index">3</span>
|
|
||||||
<span class="step-indicator__label">Queue</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div id="jobs-panel"
|
<div id="jobs-panel"
|
||||||
hx-get="{{ url_for('web.jobs_partial') }}"
|
hx-get="{{ url_for('web.jobs_partial') }}"
|
||||||
hx-trigger="load, every 3s"
|
hx-trigger="load, every 3s"
|
||||||
@@ -26,4 +12,10 @@
|
|||||||
{{ jobs_panel|safe }}
|
{{ jobs_panel|safe }}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
{% include "partials/reader_modal.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{{ super() }}
|
||||||
|
<script type="module" src="{{ url_for('static', filename='queue.js') }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user