feat(kiosk): display-size presets in Settings (Auto/Large/Balanced/Native)

system.kiosk-display.get/set RPCs write /etc/archipelago/kiosk-display.conf
(sourced by the kiosk launcher) and try-restart the kiosk so the choice
applies immediately. The Settings section only appears on nodes that have
the kiosk unit installed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-07-21 13:27:04 -04:00
co-authored by Claude Fable 5
parent 1b78da1d7a
commit 97358d2314
4 changed files with 176 additions and 0 deletions
@@ -0,0 +1,82 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { rpcClient } from '@/api/rpc-client'
// Kiosk display resolution/scaling presets. Only shown on nodes that actually
// have a kiosk display (has_kiosk from the backend).
const hasKiosk = ref(false)
const preset = ref('auto')
const applying = ref(false)
const error = ref('')
const presets = [
{
id: 'auto',
label: 'Auto (recommended)',
description: 'Pick a comfortable size for the detected screen — 4K TVs get a Full-HD-sized layout at double sharpness.',
},
{
id: 'large',
label: 'Large UI',
description: 'Biggest text and buttons — easiest to read from across the room, fits less on screen.',
},
{
id: 'balanced',
label: 'Balanced',
description: 'Desktop-sized layout on any screen that can carry it — more on screen, still sharp.',
},
{
id: 'native',
label: 'Native (no scaling)',
description: 'Use the screens full native resolution 1:1 — the most content, the smallest UI.',
},
]
onMounted(async () => {
try {
const res = await rpcClient.call<{ has_kiosk: boolean; preset: string }>({ method: 'system.kiosk-display.get' })
hasKiosk.value = res.has_kiosk
preset.value = res.preset
} catch { /* backend without the RPC — leave the section hidden */ }
})
async function apply(id: string) {
if (applying.value || id === preset.value) return
applying.value = true
error.value = ''
const prev = preset.value
preset.value = id
try {
await rpcClient.call({ method: 'system.kiosk-display.set', params: { preset: id }, timeout: 20000 })
} catch (e: unknown) {
preset.value = prev
error.value = e instanceof Error ? e.message : 'Failed to apply display setting'
} finally {
applying.value = false
}
}
</script>
<template>
<!-- Kiosk Display Section only on nodes with an attached kiosk screen -->
<div v-if="hasKiosk" class="glass-card px-6 py-6 mb-6">
<h2 class="text-xl font-semibold text-white/96 mb-2">Display</h2>
<p class="text-sm text-white/60 mb-6">
How big the interface renders on the screen attached to this node. Changing this restarts the on-screen display.
</p>
<div data-controller-container tabindex="0" class="grid grid-cols-1 md:grid-cols-2 gap-4">
<button
v-for="p in presets"
:key="p.id"
:disabled="applying"
@click="apply(p.id)"
class="path-option-card text-left p-5 disabled:opacity-60"
:class="{ 'path-option-card--selected': preset === p.id }"
>
<div class="font-medium text-white/90 mb-1">{{ p.label }}</div>
<p class="text-sm text-white/60">{{ p.description }}</p>
</button>
</div>
<div v-if="error" class="mt-4 alert-error text-sm">{{ error }}</div>
</div>
</template>
@@ -1,5 +1,6 @@
<script setup lang="ts">
import InterfaceModeSection from '@/views/settings/InterfaceModeSection.vue'
import KioskDisplaySection from '@/views/settings/KioskDisplaySection.vue'
import ClaudeAuthSection from '@/views/settings/ClaudeAuthSection.vue'
import AIDataAccessSection from '@/views/settings/AIDataAccessSection.vue'
import WebhookSection from '@/views/settings/WebhookSection.vue'
@@ -10,6 +11,7 @@ import SystemDangerZone from '@/views/settings/SystemDangerZone.vue'
<template>
<InterfaceModeSection />
<KioskDisplaySection />
<ClaudeAuthSection />
<AIDataAccessSection />
<WebhookSection />