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:
@@ -8,10 +8,11 @@ export async function GET(req: Request) {
|
||||
const streams = readStreams()
|
||||
|
||||
const lines = ["#EXTM3U"]
|
||||
for (const s of streams) {
|
||||
lines.push(`#EXTINF:-1 tvg-id="${s.id}" tvg-name="${s.name}" group-title="DecapStream",${s.name} [${s.id}] ${s.resolution} ${s.fps}fps`)
|
||||
streams.forEach((s, i) => {
|
||||
const chno = i + 1
|
||||
lines.push(`#EXTINF:-1 tvg-id="${s.id}" tvg-name="${s.name}" tvg-chno="${chno}" group-title="DecapStream",${chno}. ${s.name} [${s.id}] ${s.resolution} ${s.fps}fps`)
|
||||
lines.push(`http://${host}:${port}/live/${s.id}`)
|
||||
}
|
||||
})
|
||||
|
||||
return new Response(lines.join("\n"), {
|
||||
headers: {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { NextResponse } from "next/server"
|
||||
import { readStreams, writeStreams } from "@/lib/db"
|
||||
|
||||
export async function PUT(req: Request) {
|
||||
const { ids } = (await req.json()) as { ids: string[] }
|
||||
|
||||
if (!Array.isArray(ids))
|
||||
return NextResponse.json({ error: "ids must be an array" }, { status: 400 })
|
||||
|
||||
const streams = readStreams()
|
||||
const streamMap = new Map(streams.map((s) => [s.id, s]))
|
||||
|
||||
ids.forEach((id, i) => {
|
||||
const s = streamMap.get(id)
|
||||
if (s) s.order = i
|
||||
})
|
||||
|
||||
writeStreams(streams.sort((a, b) => a.order - b.order))
|
||||
return NextResponse.json({ ok: true })
|
||||
}
|
||||
@@ -23,6 +23,8 @@ export async function POST(req: Request) {
|
||||
|
||||
const ports = allocatePorts()
|
||||
const now = new Date().toISOString()
|
||||
const existing = readStreams()
|
||||
const nextOrder = existing.length > 0 ? Math.max(...existing.map((s) => s.order)) + 1 : 0
|
||||
|
||||
const stream = {
|
||||
...STREAM_DEFAULTS,
|
||||
@@ -30,6 +32,7 @@ export async function POST(req: Request) {
|
||||
scale: normalizeScale(body.scale ?? STREAM_DEFAULTS.scale), // #13
|
||||
...ports,
|
||||
desiredState: "running" as const, // #19
|
||||
order: nextOrder,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user