Remove Alabanza/Language, fix dropdown popup clipping
- Alabanza style removed: it only has Spanish recordings, so it always played nothing under the (now-removed) English default. - Language dropdown removed entirely; translation is hard-coded to English (sbe) in Model.js, overriding any legacy saved state too. - Dropdown popups were rendering past the popup window's bottom edge and getting clipped, since the window is sized only to the collapsed rows. Reserve room for the open popup, but only while a dropdown is actually expanded, so the panel doesn't carry permanent dead space. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LYvjTEDeh9T6NqBDdCALMq
This commit is contained in:
@@ -7,11 +7,13 @@ import qs.Ui
|
|||||||
import "Model.js" as Model
|
import "Model.js" as Model
|
||||||
|
|
||||||
// Bar pill + popup player for thesinging.bible. Mirrors the site's own
|
// Bar pill + popup player for thesinging.bible. Mirrors the site's own
|
||||||
// control set (Book, Chapter, Style, Voice, Language) and transport (loop,
|
// control set (Book, Chapter, Style, Voice) and transport (loop, prev, -10s,
|
||||||
// prev, -10s, play/pause, +10s, next, shuffle). Audio comes straight from
|
// play/pause, +10s, next, shuffle). Language is hard-coded to English —
|
||||||
// the site's public R2 bucket (https://audio.thesinging.bible/...), no
|
// no Language dropdown, see Model.js FIXED_TRANSLATION. Audio comes
|
||||||
// auth needed. Playback goes through a single long-lived `mpv --idle` process
|
// straight from the site's public R2 bucket
|
||||||
// controlled over its JSON IPC unix socket.
|
// (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 {
|
BarWidget {
|
||||||
id: root
|
id: root
|
||||||
moduleName: "dotjuice.thesinging.bible"
|
moduleName: "dotjuice.thesinging.bible"
|
||||||
@@ -44,7 +46,6 @@ BarWidget {
|
|||||||
readonly property var bookOptions: manifest ? Model.optionsForBooks(manifest) : []
|
readonly property var bookOptions: manifest ? Model.optionsForBooks(manifest) : []
|
||||||
readonly property var styleOptions: manifest ? Model.optionsForStyles(manifest) : []
|
readonly property var styleOptions: manifest ? Model.optionsForStyles(manifest) : []
|
||||||
readonly property var voiceOptions: manifest ? Model.optionsForVoices(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) : []
|
readonly property var chapterOptions: (manifest && selection && !unavailable) ? Model.optionsForChapters(manifest, selection) : []
|
||||||
|
|
||||||
// PopupCard routes owner.close() back here; without this it would set its
|
// PopupCard routes owner.close() back here; without this it would set its
|
||||||
@@ -437,6 +438,7 @@ BarWidget {
|
|||||||
spacing: Style.spacing.rowGap
|
spacing: Style.spacing.rowGap
|
||||||
|
|
||||||
Dropdown {
|
Dropdown {
|
||||||
|
id: bookDropdown
|
||||||
width: parent.width
|
width: parent.width
|
||||||
label: "Book"
|
label: "Book"
|
||||||
options: root.bookOptions
|
options: root.bookOptions
|
||||||
@@ -444,6 +446,7 @@ BarWidget {
|
|||||||
onChanged: function(v) { root.applyDesired(Object.assign({}, root.selection, { book: v, chapter: 1 })) }
|
onChanged: function(v) { root.applyDesired(Object.assign({}, root.selection, { book: v, chapter: 1 })) }
|
||||||
}
|
}
|
||||||
Dropdown {
|
Dropdown {
|
||||||
|
id: chapterDropdown
|
||||||
width: parent.width
|
width: parent.width
|
||||||
label: "Chapter"
|
label: "Chapter"
|
||||||
options: root.chapterOptions
|
options: root.chapterOptions
|
||||||
@@ -451,6 +454,7 @@ BarWidget {
|
|||||||
onChanged: function(v) { root.applyDesired(Object.assign({}, root.selection, { chapter: parseInt(v, 10) })) }
|
onChanged: function(v) { root.applyDesired(Object.assign({}, root.selection, { chapter: parseInt(v, 10) })) }
|
||||||
}
|
}
|
||||||
Dropdown {
|
Dropdown {
|
||||||
|
id: styleDropdown
|
||||||
width: parent.width
|
width: parent.width
|
||||||
label: "Style"
|
label: "Style"
|
||||||
options: root.styleOptions
|
options: root.styleOptions
|
||||||
@@ -458,18 +462,26 @@ BarWidget {
|
|||||||
onChanged: function(v) { root.applyDesired(Object.assign({}, root.selection, { style: v })) }
|
onChanged: function(v) { root.applyDesired(Object.assign({}, root.selection, { style: v })) }
|
||||||
}
|
}
|
||||||
Dropdown {
|
Dropdown {
|
||||||
|
id: voiceDropdown
|
||||||
width: parent.width
|
width: parent.width
|
||||||
label: "Voice"
|
label: "Voice"
|
||||||
options: root.voiceOptions
|
options: root.voiceOptions
|
||||||
value: root.selection ? root.selection.voice : ""
|
value: root.selection ? root.selection.voice : ""
|
||||||
onChanged: function(v) { root.applyDesired(Object.assign({}, root.selection, { voice: v })) }
|
onChanged: function(v) { root.applyDesired(Object.assign({}, root.selection, { voice: v })) }
|
||||||
}
|
}
|
||||||
Dropdown {
|
// The card's window is sized to popupColumn.implicitHeight, which
|
||||||
width: parent.width
|
// only covers the collapsed rows — Dropdown's own popup is an
|
||||||
label: "Language"
|
// overlay that isn't counted, so with nothing after it the last
|
||||||
options: root.translationOptions
|
// dropdown's open list renders past the window's bottom edge and
|
||||||
value: root.selection ? root.selection.translation : ""
|
// is clipped away. Reserve room for the tallest possible popup
|
||||||
onChanged: function(v) { root.applyDesired(Object.assign({}, root.selection, { translation: v })) }
|
// (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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
21
Model.js
21
Model.js
@@ -56,12 +56,15 @@ function enabledFirst(list) {
|
|||||||
return list.length ? list[0] : null
|
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) {
|
function defaultDesired(manifest) {
|
||||||
var t = enabledFirst(manifest.translations)
|
|
||||||
var v = enabledFirst(manifest.voices)
|
var v = enabledFirst(manifest.voices)
|
||||||
var s = enabledFirst(manifest.styles)
|
var s = enabledFirst(manifest.styles)
|
||||||
return {
|
return {
|
||||||
translation: t ? t.id : "",
|
translation: FIXED_TRANSLATION,
|
||||||
voice: v ? v.id : "",
|
voice: v ? v.id : "",
|
||||||
style: s ? s.id : "",
|
style: s ? s.id : "",
|
||||||
book: manifest.books && manifest.books.length ? bookKey(manifest.books[0]) : "01-genesis",
|
book: manifest.books && manifest.books.length ? bookKey(manifest.books[0]) : "01-genesis",
|
||||||
@@ -203,10 +206,12 @@ function optionsForBooks(manifest) {
|
|||||||
return out
|
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 optionsForVoices(manifest) { return toOptions(manifest.voices) }
|
||||||
function optionsForTranslations(manifest) { return toOptions(manifest.translations) }
|
|
||||||
|
|
||||||
function optionsForChapters(manifest, sel) {
|
function optionsForChapters(manifest, sel) {
|
||||||
var chapters = chaptersFor(manifest, sel.translation, sel.voice, sel.age, sel.style, sel.book)
|
var chapters = chaptersFor(manifest, sel.translation, sel.voice, sel.age, sel.style, sel.book)
|
||||||
var out = []
|
var out = []
|
||||||
@@ -218,9 +223,11 @@ function parseStateFile(raw) {
|
|||||||
try {
|
try {
|
||||||
var data = JSON.parse(String(raw || ""))
|
var data = JSON.parse(String(raw || ""))
|
||||||
if (!data || typeof data !== "object") return null
|
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 {
|
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),
|
voice: String(data.voice),
|
||||||
style: String(data.style),
|
style: String(data.style),
|
||||||
book: String(data.book),
|
book: String(data.book),
|
||||||
|
|||||||
Reference in New Issue
Block a user