fix: keep active page on config save, add Cyrillic preview support, fix grid sizing

- daemon.go: save/restore active page on config reload instead of
  always activating default_page (fixes page switch on save)
- render.go: replace basicfont.Face7x13 with parseDisplayFace() for
  unicode/Cyrillic support in edit key preview
- web.go: add SSE endpoint (GET /api/events) for real-time page_changed
  events and POST /api/activate-page API
- app.js: persist active page via localStorage, listen for SSE
  page_changed events
- styles.css, index.html: fix grid sizing (width 100%, minmax(0, 1fr),
  aspect-ratio on key buttons, max-width 525px)
This commit is contained in:
totmin
2026-06-17 10:48:31 +07:00
parent f6f3950d28
commit 5bf8f5d3b0
6 changed files with 195 additions and 20 deletions
+34 -1
View File
@@ -51,6 +51,28 @@ document.addEventListener('alpine:init', () => {
await this.loadConfig()
await this.loadDecks()
setInterval(() => this.pollDisplayOutputs(), 3000)
this.connectSSE()
},
connectSSE() {
if (this._sse) this._sse.close()
const es = new EventSource('/api/events')
this._sse = es
es.addEventListener('page_changed', (e) => {
try {
const data = JSON.parse(e.data)
if (data.page && this.pages.find(p => p.name === data.page)) {
this.activePage = data.page
localStorage.setItem('sd_active_page', data.page)
this.displayOutputs = {}
}
} catch (_) {}
})
es.onerror = () => {
es.close()
this._sse = null
setTimeout(() => this.connectSSE(), 3000)
}
},
async loadDecks() {
@@ -79,7 +101,12 @@ document.addEventListener('alpine:init', () => {
this.config = await res.json()
this.pages = this.config.pages
if (this.pages.length > 0) {
this.activePage = this.config.default_page || this.pages[0].name
const saved = localStorage.getItem('sd_active_page')
if (saved && this.pages.find(p => p.name === saved)) {
this.activePage = saved
} else {
this.activePage = this.config.default_page || this.pages[0].name
}
}
this.syncSettings()
} catch (e) {
@@ -460,7 +487,13 @@ document.addEventListener('alpine:init', () => {
// ── Page management ──
switchPage(name) {
this.activePage = name
localStorage.setItem('sd_active_page', name)
this.displayOutputs = {}
fetch('/api/activate-page', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ page: name })
}).catch(() => {})
},
async addPage() {
+3 -3
View File
@@ -14,7 +14,7 @@
<!-- ═══ Header ═══ -->
<header>
<button class="icon-btn" @click="subView = 'switcher'" title="Switch page">
<button class="icon-btn" @click="view = 'grid'; subView = 'switcher'" title="Switch page">
<i class="fas fa-layer-group"></i>
</button>
@@ -53,7 +53,7 @@
x-text="d.model + ' (' + d.keys_x + '\u00d7' + d.keys_y + ')'"></button>
</template>
</div>
<div class="deck-grid" :style="'grid-template-columns: repeat(' + activeDeck.keys_x + ', 1fr)'">
<div class="deck-grid" :style="'grid-template-columns: repeat(' + activeDeck.keys_x + ', minmax(0, 1fr))'">
<template x-for="key in gridKeys" :key="key.index">
<button class="key-btn"
:style="key.displayBg ? { backgroundColor: key.displayBg } : {}"
@@ -68,7 +68,7 @@
<span x-show="key.configured && !key.background && !key.icon && key.label"
style="font-size:12px;color:#fff;opacity:0.75;text-align:center;line-height:1.2;padding:4px;"
x-text="key.label"></span>
<span x-show="!key.configured" style="color:var(--text-muted);font-size:24px;opacity:0.5;">+</span>
<span x-show="!key.configured" style="color:var(--text-muted);font-size:24px;opacity:0.5;line-height:1;">+</span>
<div class="action-icons" x-show="key.hasAction">
<template x-for="(t, ti) in key.actionTypes.slice(0,3)" :key="ti">
<i :class="'fas fa-' + actionIconMap[t] + ' action-icon type-' + t"
+6 -2
View File
@@ -112,6 +112,9 @@ main {
background: var(--surface);
border-radius: var(--radius);
border: 1px solid var(--border);
width: 100%;
max-width: 525px;
contain: layout style;
}
.deck-selector {
@@ -155,6 +158,8 @@ main {
align-items: center;
justify-content: center;
width: 100%;
min-width: 0;
min-height: 0;
}
.key-btn:hover {
@@ -169,11 +174,10 @@ main {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: inherit;
}
.key-btn.empty {
opacity: 0.3;
border-color: var(--border-light);
}
.key-btn.empty:hover {
opacity: 0.5;