diff --git a/BarWidget.qml b/BarWidget.qml index 8423f89..b200e98 100644 --- a/BarWidget.qml +++ b/BarWidget.qml @@ -7,11 +7,13 @@ import qs.Ui import "Model.js" as Model // Bar pill + popup player for thesinging.bible. Mirrors the site's own -// control set (Book, Chapter, Style, Voice, Language) and transport (loop, -// prev, -10s, play/pause, +10s, next, shuffle). Audio comes straight from -// the site's public R2 bucket (https://audio.thesinging.bible/...), no -// auth needed. Playback goes through a single long-lived `mpv --idle` process -// controlled over its JSON IPC unix socket. +// control set (Book, Chapter, Style, Voice) and transport (loop, prev, -10s, +// play/pause, +10s, next, shuffle). Language is hard-coded to English — +// no Language dropdown, see Model.js FIXED_TRANSLATION. Audio comes +// straight from the site's public R2 bucket +// (https://audio.thesinging.bible/...), no auth needed. Playback goes +// through a single long-lived `mpv --idle` process controlled over its +// JSON IPC unix socket. BarWidget { id: root moduleName: "dotjuice.thesinging.bible" @@ -44,7 +46,6 @@ BarWidget { readonly property var bookOptions: manifest ? Model.optionsForBooks(manifest) : [] readonly property var styleOptions: manifest ? Model.optionsForStyles(manifest) : [] readonly property var voiceOptions: manifest ? Model.optionsForVoices(manifest) : [] - readonly property var translationOptions: manifest ? Model.optionsForTranslations(manifest) : [] readonly property var chapterOptions: (manifest && selection && !unavailable) ? Model.optionsForChapters(manifest, selection) : [] // PopupCard routes owner.close() back here; without this it would set its @@ -437,6 +438,7 @@ BarWidget { spacing: Style.spacing.rowGap Dropdown { + id: bookDropdown width: parent.width label: "Book" options: root.bookOptions @@ -444,6 +446,7 @@ BarWidget { onChanged: function(v) { root.applyDesired(Object.assign({}, root.selection, { book: v, chapter: 1 })) } } Dropdown { + id: chapterDropdown width: parent.width label: "Chapter" options: root.chapterOptions @@ -451,6 +454,7 @@ BarWidget { onChanged: function(v) { root.applyDesired(Object.assign({}, root.selection, { chapter: parseInt(v, 10) })) } } Dropdown { + id: styleDropdown width: parent.width label: "Style" options: root.styleOptions @@ -458,18 +462,26 @@ BarWidget { onChanged: function(v) { root.applyDesired(Object.assign({}, root.selection, { style: v })) } } Dropdown { + id: voiceDropdown width: parent.width label: "Voice" options: root.voiceOptions value: root.selection ? root.selection.voice : "" onChanged: function(v) { root.applyDesired(Object.assign({}, root.selection, { voice: v })) } } - Dropdown { - width: parent.width - label: "Language" - options: root.translationOptions - value: root.selection ? root.selection.translation : "" - onChanged: function(v) { root.applyDesired(Object.assign({}, root.selection, { translation: v })) } + // The card's window is sized to popupColumn.implicitHeight, which + // only covers the collapsed rows — Dropdown's own popup is an + // overlay that isn't counted, so with nothing after it the last + // dropdown's open list renders past the window's bottom edge and + // is clipped away. Reserve room for the tallest possible popup + // (Dropdown.qml caps at 8 rows) only while one of these four is + // actually open, so the panel stays compact the rest of the time + // instead of always carrying that dead space at the bottom. + Item { + width: 1 + height: (bookDropdown.popupOpen || chapterDropdown.popupOpen || styleDropdown.popupOpen || voiceDropdown.popupOpen) + ? (Style.spacing.popupRowHeight * 8 + 7 * Style.spacing.labelGap + Style.spacing.xxs) + : 0 } } } diff --git a/Model.js b/Model.js index 1b471d7..590e649 100644 --- a/Model.js +++ b/Model.js @@ -56,12 +56,15 @@ function enabledFirst(list) { return list.length ? list[0] : null } +// Language is hard-coded to English (sbe) — no Language dropdown, no +// per-user Spanish selection. +var FIXED_TRANSLATION = "sbe" + function defaultDesired(manifest) { - var t = enabledFirst(manifest.translations) var v = enabledFirst(manifest.voices) var s = enabledFirst(manifest.styles) return { - translation: t ? t.id : "", + translation: FIXED_TRANSLATION, voice: v ? v.id : "", style: s ? s.id : "", book: manifest.books && manifest.books.length ? bookKey(manifest.books[0]) : "01-genesis", @@ -203,10 +206,12 @@ function optionsForBooks(manifest) { return out } -function optionsForStyles(manifest) { return toOptions(manifest.styles) } +// Alabanza only has Spanish recordings (no sbe/*/alabanza in availability), +// so it's excluded here to avoid a style that silently plays nothing. +function optionsForStyles(manifest) { + return toOptions(manifest.styles).filter(function(o) { return o.value !== "alabanza" }) +} function optionsForVoices(manifest) { return toOptions(manifest.voices) } -function optionsForTranslations(manifest) { return toOptions(manifest.translations) } - function optionsForChapters(manifest, sel) { var chapters = chaptersFor(manifest, sel.translation, sel.voice, sel.age, sel.style, sel.book) var out = [] @@ -218,9 +223,11 @@ function parseStateFile(raw) { try { var data = JSON.parse(String(raw || "")) if (!data || typeof data !== "object") return null - if (!data.translation || !data.voice || !data.style || !data.book || !data.chapter) return null + if (!data.voice || !data.style || !data.book || !data.chapter) return null return { - translation: String(data.translation), + // Forced regardless of what an older state.json saved (from before + // Language was hard-coded to English). + translation: FIXED_TRANSLATION, voice: String(data.voice), style: String(data.style), book: String(data.book),