1
0

fix: load FA locally instead of CDN; Alpine color input error via x-init+

This commit is contained in:
Maksim Totmin 2026-06-17 13:53:48 +07:00
parent 44ad064488
commit fe8ca312c4
15 changed files with 71 additions and 16 deletions

View File

@ -101,7 +101,7 @@ func Run(ctx context.Context, cfg *config.Config, opts RunOptions) error {
ssCtrl := NewScreensaver(&cfg.Screensaver) ssCtrl := NewScreensaver(&cfg.Screensaver)
ge := NewGestureEngine(cfg.Timing.LongPressMs, cfg.Timing.DoubleTapMs, func(a *config.Action) { ge := NewGestureEngine(cfg.Timing.LongPressMs, cfg.Timing.DoubleTapMs, func(idx int, a *config.Action) {
if a == nil { if a == nil {
return return
} }
@ -119,6 +119,8 @@ func Run(ctx context.Context, cfg *config.Config, opts RunOptions) error {
pm.startPeriodicKeys() pm.startPeriodicKeys()
} }
web.BroadcastPageChange(newPage) web.BroadcastPageChange(newPage)
} else {
primaryPM.RefreshDisplayKey(idx)
} }
}) })

View File

@ -7,7 +7,7 @@ import (
"streamdeck-lets-go/internal/config" "streamdeck-lets-go/internal/config"
) )
type ActionCallback func(a *config.Action) type ActionCallback func(idx int, a *config.Action)
type gestureKeyState struct { type gestureKeyState struct {
mu sync.Mutex mu sync.Mutex
@ -122,9 +122,9 @@ func (ge *GestureEngine) handleKeyPress(state *gestureKeyState, evt Event, actio
state.mu.Unlock() state.mu.Unlock()
if a := findAction(actions, "hold_start"); a != nil { if a := findAction(actions, "hold_start"); a != nil {
ge.onAction(a) ge.onAction(evt.Index, a)
} else if a := findAction(actions, "long_press"); a != nil { } else if a := findAction(actions, "long_press"); a != nil {
ge.onAction(a) ge.onAction(evt.Index, a)
} }
}) })
state.mu.Unlock() state.mu.Unlock()
@ -138,7 +138,7 @@ func (ge *GestureEngine) handleKeyRelease(state *gestureKeyState, evt Event, act
state.holdActive = false state.holdActive = false
state.mu.Unlock() state.mu.Unlock()
if a := findAction(actions, "hold_end"); a != nil { if a := findAction(actions, "hold_end"); a != nil {
ge.onAction(a) ge.onAction(evt.Index, a)
} }
return return
} }
@ -147,7 +147,7 @@ func (ge *GestureEngine) handleKeyRelease(state *gestureKeyState, evt Event, act
state.lastTapAt = time.Time{} state.lastTapAt = time.Time{}
state.mu.Unlock() state.mu.Unlock()
if a := findAction(actions, "double_tap"); a != nil { if a := findAction(actions, "double_tap"); a != nil {
ge.onAction(a) ge.onAction(evt.Index, a)
} }
return return
} }
@ -162,7 +162,7 @@ func (ge *GestureEngine) handleKeyRelease(state *gestureKeyState, evt Event, act
state.pendingTap = nil state.pendingTap = nil
state.mu.Unlock() state.mu.Unlock()
if a := findAction(actions, "tap"); a != nil { if a := findAction(actions, "tap"); a != nil {
ge.onAction(a) ge.onAction(evt.Index, a)
} }
return return
} }

22
page.go
View File

@ -490,6 +490,28 @@ func (pm *PageManager) renderKeyOutput(idx int, d *config.DisplayCfg, output str
} }
} }
func (pm *PageManager) RefreshDisplayKey(idx int) {
pm.mu.RLock()
page := pm.pages[pm.active]
pm.mu.RUnlock()
if page == nil {
return
}
for _, k := range page.Keys {
if k.Index == idx && k.Display != nil {
timeout := 30 * time.Second
if k.Display.Timeout != "" {
if t, err := time.ParseDuration(k.Display.Timeout); err == nil && t > 0 {
timeout = t
}
}
output, err := execDisplayCapture(k.Display, timeout)
pm.renderKeyOutput(idx, k.Display, output, err)
return
}
}
}
var ( var (
unicodeFontOnce sync.Once unicodeFontOnce sync.Once
unicodeFontData []byte unicodeFontData []byte

View File

@ -15,6 +15,7 @@ document.addEventListener('alpine:init', () => {
// ── Edit state ── // ── Edit state ──
editing: null, editing: null,
editForm: { editForm: {
bg_color: '',
actions: { actions: {
tap: {}, tap: {},
long_press: {}, long_press: {},

9
static/fontawesome/css/all.min.css vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -6,7 +6,7 @@
<title>StreamDeck</title> <title>StreamDeck</title>
<script src="/static/app.js"></script> <script src="/static/app.js"></script>
<script defer src="/static/alpine.min.js"></script> <script defer src="/static/alpine.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css"> <link rel="stylesheet" href="/static/fontawesome/css/all.min.css">
<link rel="stylesheet" href="/static/styles.css"> <link rel="stylesheet" href="/static/styles.css">
</head> </head>
<body> <body>
@ -505,8 +505,8 @@
<div class="form-group"> <div class="form-group">
<label>Background</label> <label>Background</label>
<div style="display:flex;gap:8px;align-items:center;"> <div style="display:flex;gap:8px;align-items:center;">
<input type="color" :value="editForm.bg_color || '#000000'" <input type="color"
@input="editForm.bg_color = $event.target.value" x-init="$el.value = editForm.bg_color || '#000000'; $el.addEventListener('input', () => editForm.bg_color = $el.value); $watch('editForm.bg_color', v => $el.value = v || '#000000')"
style="width:40px;height:40px;padding:2px;border-radius:var(--radius-xs);cursor:pointer;"> style="width:40px;height:40px;padding:2px;border-radius:var(--radius-xs);cursor:pointer;">
<input type="text" x-model="editForm.bg_color" <input type="text" x-model="editForm.bg_color"
placeholder="#rrggbb or #rrggbbaa" maxlength="9" placeholder="#rrggbb or #rrggbbaa" maxlength="9"

33
web.go
View File

@ -459,6 +459,16 @@ func backupOldConfig(path string) {
} }
} }
func collectIcons(cfg *config.Config, used map[string]bool) {
for _, p := range cfg.Pages {
for _, k := range p.Keys {
if k.Icon != "" {
used[k.Icon] = true
}
}
}
}
func gcImages(cfg *config.Config) { func gcImages(cfg *config.Config) {
imgDir := filepath.Join(configDir(), "images") imgDir := filepath.Join(configDir(), "images")
entries, err := os.ReadDir(imgDir) entries, err := os.ReadDir(imgDir)
@ -466,13 +476,24 @@ func gcImages(cfg *config.Config) {
return return
} }
used := make(map[string]bool, len(entries)) used := make(map[string]bool)
for _, p := range cfg.Pages { collectIcons(cfg, used)
for _, k := range p.Keys {
if k.Icon != "" { backupDir := filepath.Join(configDir(), "backups")
used[k.Icon] = true backupEntries, _ := os.ReadDir(backupDir)
} for _, be := range backupEntries {
if be.IsDir() {
continue
} }
data, err := os.ReadFile(filepath.Join(backupDir, be.Name()))
if err != nil {
continue
}
var backupCfg config.Config
if err := json.Unmarshal(data, &backupCfg); err != nil {
continue
}
collectIcons(&backupCfg, used)
} }
for _, e := range entries { for _, e := range entries {