mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 13:40:27 +02:00
feat: Improve UI consistency and accessibility across dashboard and settings pages
This commit is contained in:
@@ -22,6 +22,19 @@ const initDashboard = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectFirstProfileIfAvailable = () => {
|
||||||
|
if (!profileSelect) return false;
|
||||||
|
const saved = Array.from(profileSelect.options).filter(
|
||||||
|
(option) => option.value && option.value !== "__standard" && option.value !== "__formula",
|
||||||
|
);
|
||||||
|
if (!saved.length) {
|
||||||
|
profileSelect.value = "__standard";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
saved[0].selected = true;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
const updateVoiceControls = () => {
|
const updateVoiceControls = () => {
|
||||||
if (!profileSelect) {
|
if (!profileSelect) {
|
||||||
return;
|
return;
|
||||||
@@ -32,15 +45,18 @@ const initDashboard = () => {
|
|||||||
const isSavedProfile = Boolean(value && !isStandard && !isFormula);
|
const isSavedProfile = Boolean(value && !isStandard && !isFormula);
|
||||||
|
|
||||||
if (voiceField) {
|
if (voiceField) {
|
||||||
voiceField.hidden = false;
|
const showVoice = isStandard;
|
||||||
voiceField.setAttribute("aria-hidden", "false");
|
voiceField.hidden = !showVoice;
|
||||||
|
voiceField.setAttribute("aria-hidden", showVoice ? "false" : "true");
|
||||||
}
|
}
|
||||||
if (voiceSelect) {
|
if (voiceSelect) {
|
||||||
voiceSelect.disabled = !isStandard;
|
voiceSelect.disabled = !isStandard;
|
||||||
voiceSelect.dataset.state = isStandard ? "editable" : "locked";
|
voiceSelect.dataset.state = isStandard ? "editable" : "locked";
|
||||||
|
if (isStandard) {
|
||||||
|
hydrateDefaultVoice();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let showFormula = isFormula || isSavedProfile;
|
|
||||||
let presetFormula = "";
|
let presetFormula = "";
|
||||||
if (isSavedProfile) {
|
if (isSavedProfile) {
|
||||||
const option = profileSelect.selectedOptions[0];
|
const option = profileSelect.selectedOptions[0];
|
||||||
@@ -53,6 +69,7 @@ const initDashboard = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showFormula = isFormula;
|
||||||
if (formulaField) {
|
if (formulaField) {
|
||||||
formulaField.hidden = !showFormula;
|
formulaField.hidden = !showFormula;
|
||||||
formulaField.setAttribute("aria-hidden", showFormula ? "false" : "true");
|
formulaField.setAttribute("aria-hidden", showFormula ? "false" : "true");
|
||||||
@@ -60,14 +77,16 @@ const initDashboard = () => {
|
|||||||
if (formulaInput) {
|
if (formulaInput) {
|
||||||
formulaInput.disabled = !showFormula;
|
formulaInput.disabled = !showFormula;
|
||||||
if (showFormula) {
|
if (showFormula) {
|
||||||
if (presetFormula) {
|
if (presetFormula && !formulaInput.value) {
|
||||||
formulaInput.value = presetFormula;
|
formulaInput.value = presetFormula;
|
||||||
}
|
}
|
||||||
|
formulaInput.readOnly = false;
|
||||||
|
formulaInput.dataset.state = "editable";
|
||||||
} else {
|
} else {
|
||||||
formulaInput.value = formulaInput.value.trim();
|
formulaInput.value = formulaInput.value.trim();
|
||||||
}
|
formulaInput.readOnly = true;
|
||||||
formulaInput.dataset.state = isSavedProfile ? "locked" : "editable";
|
formulaInput.dataset.state = isSavedProfile ? "locked" : "editable";
|
||||||
formulaInput.readOnly = isSavedProfile;
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -102,11 +121,15 @@ const initDashboard = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (profileSelect) {
|
if (profileSelect) {
|
||||||
|
const hasSaved = selectFirstProfileIfAvailable();
|
||||||
profileSelect.addEventListener("change", updateVoiceControls);
|
profileSelect.addEventListener("change", updateVoiceControls);
|
||||||
updateVoiceControls();
|
updateVoiceControls();
|
||||||
}
|
if (!hasSaved) {
|
||||||
|
|
||||||
hydrateDefaultVoice();
|
hydrateDefaultVoice();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hydrateDefaultVoice();
|
||||||
|
}
|
||||||
|
|
||||||
if (sourceText) {
|
if (sourceText) {
|
||||||
sourceText.addEventListener("input", updatePreview);
|
sourceText.addEventListener("input", updatePreview);
|
||||||
|
|||||||
@@ -1030,13 +1030,17 @@ progress.progress::-moz-progress-bar {
|
|||||||
|
|
||||||
.voice-preview {
|
.voice-preview {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0.75rem;
|
gap: 0.9rem;
|
||||||
|
padding: 1rem 1.25rem;
|
||||||
|
border-radius: 18px;
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.18);
|
||||||
|
background: rgba(15, 23, 42, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
.field--slider {
|
.field--slider {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.5rem;
|
gap: 0.65rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field--slider label {
|
.field--slider label {
|
||||||
@@ -1088,6 +1092,7 @@ progress.progress::-moz-progress-bar {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
background: rgba(15, 23, 42, 0.45);
|
background: rgba(15, 23, 42, 0.45);
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.18);
|
||||||
}
|
}
|
||||||
|
|
||||||
.voice-status {
|
.voice-status {
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="card">
|
<section class="card">
|
||||||
<h1 class="card__title">Create a new audiobook</h1>
|
<h1 class="card__title">Create a New Audiobook</h1>
|
||||||
<form action="{{ url_for('web.enqueue_job') }}" method="post" enctype="multipart/form-data" class="grid grid--two form-grid">
|
<form action="{{ url_for('web.enqueue_job') }}" method="post" enctype="multipart/form-data" class="grid grid--two form-grid">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="source_file">Source file</label>
|
<label for="source_file">Source File</label>
|
||||||
<input type="file" id="source_file" name="source_file" accept=".txt,.pdf,.epub,.md,.markdown">
|
<input type="file" id="source_file" name="source_file" accept=".txt,.pdf,.epub,.md,.markdown">
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
@@ -19,7 +19,21 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="field" data-conditional="standard" data-role="voice-field">
|
<div class="field">
|
||||||
|
<label for="voice_profile">Voice Profile</label>
|
||||||
|
<select id="voice_profile" name="voice_profile" data-role="voice-profile">
|
||||||
|
<option value="__standard" {% if not options.voice_profile_options %}selected{% endif %}>Standard Voice</option>
|
||||||
|
<option value="__formula">Custom Voice Formula</option>
|
||||||
|
{% if options.voice_profile_options %}
|
||||||
|
<optgroup label="Saved mixes">
|
||||||
|
{% for profile in options.voice_profile_options %}
|
||||||
|
<option value="{{ profile.name }}" data-language="{{ profile.language }}" data-formula="{{ profile.formula|e }}" {% if loop.first %}selected{% endif %}>{{ profile.name }}{% if profile.language %} ({{ profile.language|upper }}){% endif %}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</optgroup>
|
||||||
|
{% endif %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="field" data-role="voice-field" {% if options.voice_profile_options %}hidden aria-hidden="true"{% endif %}>
|
||||||
<label for="voice">Voice</label>
|
<label for="voice">Voice</label>
|
||||||
<select id="voice" name="voice" data-role="voice-select" data-default="{{ settings.default_voice }}">
|
<select id="voice" name="voice" data-role="voice-select" data-default="{{ settings.default_voice }}">
|
||||||
{% for voice in options.voices %}
|
{% for voice in options.voices %}
|
||||||
@@ -27,23 +41,8 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field" data-conditional="formula" data-role="formula-field" hidden aria-hidden="true">
|
||||||
<label for="voice_profile">Voice profile</label>
|
<label for="voice_formula">Custom Voice Formula</label>
|
||||||
<select id="voice_profile" name="voice_profile" data-role="voice-profile">
|
|
||||||
<option value="__standard" selected>Standard voice</option>
|
|
||||||
<option value="__formula">Formula</option>
|
|
||||||
{% if options.voice_profile_options %}
|
|
||||||
<optgroup label="Saved mixes">
|
|
||||||
{% for profile in options.voice_profile_options %}
|
|
||||||
<option value="{{ profile.name }}" data-language="{{ profile.language }}" data-formula="{{ profile.formula|e }}">{{ profile.name }} {% if profile.language %}({{ profile.language|upper }}){% endif %}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</optgroup>
|
|
||||||
{% endif %}
|
|
||||||
</select>
|
|
||||||
<p class="tag">Manage mixes in the <a href="{{ url_for('web.voice_profiles_page') }}">voice mixer</a>.</p>
|
|
||||||
</div>
|
|
||||||
<div class="field" data-conditional="formula" data-role="formula-field" hidden>
|
|
||||||
<label for="voice_formula">Custom voice formula</label>
|
|
||||||
<input type="text" id="voice_formula" name="voice_formula" placeholder="af_nova*0.4+am_liam*0.6" data-role="voice-formula">
|
<input type="text" id="voice_formula" name="voice_formula" placeholder="af_nova*0.4+am_liam*0.6" data-role="voice-formula">
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
@@ -65,7 +64,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="field field--full">
|
<div class="field field--full">
|
||||||
<label for="source_text">Or paste text directly</label>
|
<label for="source_text">Or Paste Text Directly</label>
|
||||||
<textarea id="source_text" name="source_text" rows="12" placeholder="Drop some text here if you don't have a file handy..." data-role="source-text"></textarea>
|
<textarea id="source_text" name="source_text" rows="12" placeholder="Drop some text here if you don't have a file handy..." data-role="source-text"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-preview" data-role="text-preview" aria-live="polite">
|
<div class="text-preview" data-role="text-preview" aria-live="polite">
|
||||||
@@ -80,7 +79,7 @@
|
|||||||
<pre class="text-preview__body" data-role="preview-body">Paste text to see a live preview and character count.</pre>
|
<pre class="text-preview__body" data-role="preview-body">Paste text to see a live preview and character count.</pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="field field--actions">
|
<div class="field field--actions">
|
||||||
<button type="submit" class="button">Queue conversion</button>
|
<button type="submit" class="button">Queue Conversion</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="card settings">
|
<section class="card settings">
|
||||||
<h1 class="card__title">Application settings</h1>
|
<h1 class="card__title">Application Settings</h1>
|
||||||
<p class="tag">Settings apply to new jobs you queue from the dashboard.</p>
|
<p class="tag">Settings apply to new jobs you queue from the dashboard.</p>
|
||||||
|
|
||||||
{% if saved %}
|
{% if saved %}
|
||||||
@@ -13,9 +13,9 @@
|
|||||||
|
|
||||||
<form action="{{ url_for('web.settings_page') }}" method="post" class="settings__form">
|
<form action="{{ url_for('web.settings_page') }}" method="post" class="settings__form">
|
||||||
<fieldset class="settings__section">
|
<fieldset class="settings__section">
|
||||||
<legend>Output defaults</legend>
|
<legend>Output Defaults</legend>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="output_format">Audio format</label>
|
<label for="output_format">Audio Format</label>
|
||||||
<select id="output_format" name="output_format">
|
<select id="output_format" name="output_format">
|
||||||
{% for fmt in options.output_formats %}
|
{% for fmt in options.output_formats %}
|
||||||
<option value="{{ fmt }}" {% if settings.output_format == fmt %}selected{% endif %}>{{ fmt }}</option>
|
<option value="{{ fmt }}" {% if settings.output_format == fmt %}selected{% endif %}>{{ fmt }}</option>
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="subtitle_format">Subtitle file format</label>
|
<label for="subtitle_format">Subtitle File Format</label>
|
||||||
<select id="subtitle_format" name="subtitle_format">
|
<select id="subtitle_format" name="subtitle_format">
|
||||||
{% for value, text in options.subtitle_formats %}
|
{% for value, text in options.subtitle_formats %}
|
||||||
<option value="{{ value }}" {% if settings.subtitle_format == value %}selected{% endif %}>{{ text }}</option>
|
<option value="{{ value }}" {% if settings.subtitle_format == value %}selected{% endif %}>{{ text }}</option>
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="default_voice">Default voice</label>
|
<label for="default_voice">Default Voice</label>
|
||||||
<select id="default_voice" name="default_voice">
|
<select id="default_voice" name="default_voice">
|
||||||
{% for voice in options.voices %}
|
{% for voice in options.voices %}
|
||||||
<option value="{{ voice }}" {% if settings.default_voice == voice %}selected{% endif %}>{{ voice }}</option>
|
<option value="{{ voice }}" {% if settings.default_voice == voice %}selected{% endif %}>{{ voice }}</option>
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
<p class="hint">Used when “Standard voice” is selected on the dashboard.</p>
|
<p class="hint">Used when “Standard voice” is selected on the dashboard.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="save_mode">Save location</label>
|
<label for="save_mode">Save Location</label>
|
||||||
<select id="save_mode" name="save_mode">
|
<select id="save_mode" name="save_mode">
|
||||||
{% for location in save_locations %}
|
{% for location in save_locations %}
|
||||||
<option value="{{ location.value }}" {% if settings.save_mode == location.value %}selected{% endif %}>{{ location.label }}</option>
|
<option value="{{ location.value }}" {% if settings.save_mode == location.value %}selected{% endif %}>{{ location.label }}</option>
|
||||||
@@ -55,27 +55,27 @@
|
|||||||
<div class="field field--choices">
|
<div class="field field--choices">
|
||||||
<label class="toggle-pill">
|
<label class="toggle-pill">
|
||||||
<input type="checkbox" name="replace_single_newlines" value="true" {% if settings.replace_single_newlines %}checked{% endif %}>
|
<input type="checkbox" name="replace_single_newlines" value="true" {% if settings.replace_single_newlines %}checked{% endif %}>
|
||||||
<span>Replace single newlines</span>
|
<span>Replace Single Newlines</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="toggle-pill">
|
<label class="toggle-pill">
|
||||||
<input type="checkbox" name="use_gpu" value="true" {% if settings.use_gpu %}checked{% endif %}>
|
<input type="checkbox" name="use_gpu" value="true" {% if settings.use_gpu %}checked{% endif %}>
|
||||||
<span>Use GPU (when available)</span>
|
<span>Use GPU (When Available)</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="toggle-pill">
|
<label class="toggle-pill">
|
||||||
<input type="checkbox" name="save_chapters_separately" value="true" {% if settings.save_chapters_separately %}checked{% endif %}>
|
<input type="checkbox" name="save_chapters_separately" value="true" {% if settings.save_chapters_separately %}checked{% endif %}>
|
||||||
<span>Save each chapter separately</span>
|
<span>Save Each Chapter Separately</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="toggle-pill">
|
<label class="toggle-pill">
|
||||||
<input type="checkbox" name="merge_chapters_at_end" value="true" {% if settings.merge_chapters_at_end %}checked{% endif %}>
|
<input type="checkbox" name="merge_chapters_at_end" value="true" {% if settings.merge_chapters_at_end %}checked{% endif %}>
|
||||||
<span>Also create merged audiobook</span>
|
<span>Also Create Merged Audiobook</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="toggle-pill">
|
<label class="toggle-pill">
|
||||||
<input type="checkbox" name="save_as_project" value="true" {% if settings.save_as_project %}checked{% endif %}>
|
<input type="checkbox" name="save_as_project" value="true" {% if settings.save_as_project %}checked{% endif %}>
|
||||||
<span>Save as project with metadata</span>
|
<span>Save as Project With Metadata</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="separate_chapters_format">Separate chapter format</label>
|
<label for="separate_chapters_format">Separate Chapter Format</label>
|
||||||
<select id="separate_chapters_format" name="separate_chapters_format">
|
<select id="separate_chapters_format" name="separate_chapters_format">
|
||||||
{% for fmt in options.separate_formats %}
|
{% for fmt in options.separate_formats %}
|
||||||
<option value="{{ fmt }}" {% if settings.separate_chapters_format == fmt %}selected{% endif %}>{{ fmt|upper }}</option>
|
<option value="{{ fmt }}" {% if settings.separate_chapters_format == fmt %}selected{% endif %}>{{ fmt|upper }}</option>
|
||||||
@@ -83,17 +83,17 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="silence_between_chapters">Silence between chapters (seconds)</label>
|
<label for="silence_between_chapters">Silence Between Chapters (Seconds)</label>
|
||||||
<input type="number" step="0.5" min="0" id="silence_between_chapters" name="silence_between_chapters" value="{{ settings.silence_between_chapters }}">
|
<input type="number" step="0.5" min="0" id="silence_between_chapters" name="silence_between_chapters" value="{{ settings.silence_between_chapters }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="max_subtitle_words">Max words per subtitle entry</label>
|
<label for="max_subtitle_words">Max Words Per Subtitle Entry</label>
|
||||||
<input type="number" min="1" max="200" id="max_subtitle_words" name="max_subtitle_words" value="{{ settings.max_subtitle_words }}">
|
<input type="number" min="1" max="200" id="max_subtitle_words" name="max_subtitle_words" value="{{ settings.max_subtitle_words }}">
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<div class="settings__actions">
|
<div class="settings__actions">
|
||||||
<button type="submit" class="button">Save settings</button>
|
<button type="submit" class="button">Save Settings</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user