UI Unification

This commit is contained in:
Danny
2026-11-02 19:55:13 -08:00
parent 7d7bf43fac
commit 755d82a7e6
4 changed files with 29 additions and 9 deletions
@@ -55,15 +55,15 @@ class DeveloperSettingsOwner:
new_setup_on = new_setup.enabled()
held = "Park the vehicle to change this setting."
rows = [FeatureRow(STARPORT_HEADING_KEY, "StarPort", "", available=False),
FeatureRow(STARPORT_NEW_SETUP_KEY, "StarPort: New pairing experience", "On" if new_setup_on else "Off",
FeatureRow(STARPORT_NEW_SETUP_KEY, "New pairing experience", "On" if new_setup_on else "Off",
b"1" if new_setup_on else b"0", ("Off", "On"), available=parked,
reason=STARPORT_NEW_SETUP_DESCRIPTION if parked else held, default_value="On")]
status = self._starport_status()
if status is not None:
enabled, hevc = bool(status.get("dedicated_wifi_interface")), bool(status.get("allow_hevc"))
rows.append(FeatureRow(STARPORT_WIFI_KEY, "StarPort: Stay on Wi-Fi", "On" if enabled else "Off", b"1" if enabled else b"0",
rows.append(FeatureRow(STARPORT_WIFI_KEY, "Stay on Wi-Fi", "On" if enabled else "Off", b"1" if enabled else b"0",
("Off", "On"), available=parked, reason=STARPORT_WIFI_DESCRIPTION if parked else held, default_value="On"))
rows.append(FeatureRow(STARPORT_HEVC_KEY, "StarPort: Allow H.265 video", "On" if hevc else "Off", b"1" if hevc else b"0",
rows.append(FeatureRow(STARPORT_HEVC_KEY, "Allow H.265 video", "On" if hevc else "Off", b"1" if hevc else b"0",
("Off", "On"), available=parked, reason=STARPORT_HEVC_DESCRIPTION if parked else held, default_value="Off"))
return tuple(rows)
@@ -58,7 +58,7 @@ class TestOffroadConfigurationPages(unittest.TestCase):
expected = saved[name]
if name == 'developer':
# gated by Park, not by the vehicle
expected, current = ([row for row in rows if row[0] != 'StarPort: New pairing experience'] for rows in (expected, current))
expected, current = ([row for row in rows if row[0] != 'New pairing experience'] for rows in (expected, current))
self.assertEqual([row[0] for row in expected], ['Use VOACC'])
self.assertEqual([row[1] for row in expected], [False])
self.assertEqual([row[1] for row in current], [True])
@@ -160,6 +160,17 @@ const filtered = SettingsPage.computed.visibleRows.call({ state: { query: "lane"
] } } })
assert.deepEqual(filtered.map(({ index }) => index), [1]) // Filter keeps the original owner row index.
const developerRows = [{ label: "Use VOACC", value: "Off", reason: "" }, { label: "StarPort", value: "", reason: "", heading: true },
{ label: "Stay on Wi-Fi", value: "On", reason: "" }, { label: "Allow H.265 video", value: "Off", reason: "" }]
const cardsFor = (query) => {
const view = { state: { query, data: { rows: developerRows } }, initialPage: "developer", activeSection: {} }
view.visibleRows = SettingsPage.computed.visibleRows.call(view)
return SettingsPage.computed.rowCards.call(view).map(({ title, entries }) => [title, entries.map(({ index }) => index)])
}
assert.deepEqual(cardsFor(""), [["", [0]], ["StarPort", [2, 3]]], "a heading row starts its own card")
assert.deepEqual(cardsFor("h.265"), [["", []], ["StarPort", [3]]], "search keeps a row in its card when the heading is filtered out")
assert.deepEqual(cardsFor("voacc"), [["", [0]]], "an emptied card is not shown")
const conditional = fixture()
conditional.feed.start()
await conditional.reply(0, { ...lane, page: "hub", rows: [{ label: "Conditional driving modes", page: "conditional",
+14 -5
View File
@@ -304,6 +304,14 @@ export const SettingsPage = {
}).filter(({ row }) =>
!needle || `${row.label} ${row.value} ${row.reason}`.toLocaleLowerCase().includes(needle))
},
// A heading row starts its own card; the rows after it belong to that card even when search hides the heading.
rowCards() {
const titles = []
const owner = (this.state.data?.rows || []).map((row) => (row.heading ? titles.push(row.label) : titles.length))
const cards = [{ title: "", entries: [] }, ...titles.map((title) => ({ title, entries: [] }))]
for (const entry of this.visibleRows) if (!entry.row.heading) cards[owner[entry.index] || 0].entries.push(entry)
return cards.filter((card, index) => !index || card.entries.length)
},
},
watch: {
"state.pending"() { this.flushSoundChoices() },
@@ -449,15 +457,16 @@ export const SettingsPage = {
<div v-if="state.data.page === 'pip'" class="gx-settings__actions gx-actions">
<button type="button" class="gx-btn gx-btn--tonal" @click="openCropEditor"><i class="bi bi-crop" aria-hidden="true"></i> Crop editor</button>
</div>
<section class="gx-card gx-settings__section">
<div class="gx-section__header"><i class="bi" :class="activeSection.icon" aria-hidden="true"></i><span class="gx-section__title">{{ state.data.page === 'hub' ? activeSection.label : state.data.title }}</span>
<section v-for="(card, cardIndex) in rowCards" :key="card.title || 'main'" class="gx-card gx-settings__section">
<div v-if="cardIndex" class="gx-section__header"><i class="bi bi-car-front" aria-hidden="true"></i><span class="gx-section__title">{{ card.title }}</span></div>
<div v-else class="gx-section__header"><i class="bi" :class="activeSection.icon" aria-hidden="true"></i><span class="gx-section__title">{{ state.data.page === 'hub' ? activeSection.label : state.data.title }}</span>
<button v-if="state.data.page !== 'hub' && state.data.rows.length" type="button" class="gx-icon-btn" :aria-label="state.searchOpen ? 'Close page search' : 'Search within this page'" :aria-pressed="state.searchOpen" @click="togglePageSearch"><i class="bi" :class="state.searchOpen ? 'bi-x-lg' : 'bi-search'" aria-hidden="true"></i></button>
</div>
<div v-if="state.searchOpen" class="gx-settings__search"><input ref="pageSearch" v-model="state.query" class="gx-field" type="search" aria-label="Search within this page" placeholder="Search within…"></div>
<GalaxySettingRow v-for="{ row, index } in visibleRows" :key="rowKey(row, index)" :row="row" :index="index"
<div v-if="state.searchOpen && !cardIndex" class="gx-settings__search"><input ref="pageSearch" v-model="state.query" class="gx-field" type="search" aria-label="Search within this page" placeholder="Search within…"></div>
<GalaxySettingRow v-for="{ row, index } in card.entries" :key="rowKey(row, index)" :row="row" :index="index"
:busy="busy" :disabled="!!state.error || !row.available" :save-value="(index, value) => feed.previewValue(index, value)"
@open="open" @review="(index, direction) => feed.preview(index, direction)" @reset-default="index => feed.resetDefault(index)" />
<div v-if="!visibleRows.length" class="gx-empty">No matching settings.</div>
<div v-if="!cardIndex && !visibleRows.length" class="gx-empty">No matching settings.</div>
</section>
<SoundPacks v-if="state.data.page === 'sounds'" :unauthorized="unauthorized" :busy="busy" @installed="refreshSoundChoices" />
</div>