Adiciona features de ordenação, drag-and-drop e melhorias de UX

---

- Adicionado campo order em Stream e migração automática em readStreams() para ordenação persistida no streams.json;
- Implementado drag-and-drop de cards com @dnd-kit/core + @dnd-kit/sortable, com faixa de drag dedicada no topo de cada card;
- Adicionado endpoint PUT /api/streams/reorder para persistir a nova ordem no servidor;
- Atualizada playlist M3U para respeitar a ordem dos cards e incluir tvg-chno com número de canal;
- Corrigida geração de thumbnail para capturar via ffmpeg -f x11grab direto do Xvfb, usando arquivo temporário thumb.tmp.jpg;
- Adicionada política gerenciada do Chromium no Dockerfile para suprimir diálogo de salvar senha;
- Adicionadas flags --password-store=basic e --disable-features=PasswordManagerRedesign no template do Chromium;
- Substituído confirm() nativo por modal de confirmação customizado no delete de stream;
- Adicionado tamanho mini e redefinidos os tamanhos de card; padrão alterado para md (300px);
- Adicionado logo do projeto no header e ícone GripVertical na faixa de drag;
- Erros de validação do formulário agora exibidos em vermelho negrito;

---
This commit is contained in:
2026-04-25 03:24:20 -03:00
parent 1f8385e450
commit f6879781c1
20 changed files with 348 additions and 97 deletions
+8 -1
View File
@@ -12,7 +12,14 @@ function ensureFile() {
export function readStreams(): Stream[] {
ensureFile()
return JSON.parse(fs.readFileSync(STREAMS_FILE, "utf-8")) as Stream[]
const streams = JSON.parse(fs.readFileSync(STREAMS_FILE, "utf-8")) as Stream[]
// migrate: assign order to streams that don't have it yet
let dirty = false
streams.forEach((s, i) => {
if (s.order === undefined) { s.order = i; dirty = true }
})
if (dirty) fs.writeFileSync(STREAMS_FILE, JSON.stringify(streams, null, 2), "utf-8")
return streams.sort((a, b) => a.order - b.order)
}
export function writeStreams(streams: Stream[]): void {