Files
k-tv/k-tv-frontend/hooks/use-channel-form.ts

173 lines
5.6 KiB
TypeScript

"use client";
import { useState, useEffect, useRef } from "react";
import { minsToTime } from "@/app/(main)/dashboard/components/block-timeline";
import type {
AccessMode,
ChannelResponse,
LogoPosition,
ProgrammingBlock,
MediaFilter,
RecyclePolicy,
Weekday,
} from "@/lib/types";
import { WEEKDAYS } from "@/lib/types";
export const WEBHOOK_PRESETS = {
discord: `{
"embeds": [{
"title": "📺 {{event}}",
"description": "{{#if data.item.title}}Now playing: **{{data.item.title}}**{{else}}No signal{{/if}}",
"color": 3447003,
"timestamp": "{{timestamp}}"
}]
}`,
slack: `{
"text": "📺 *{{event}}*{{#if data.item.title}} — {{data.item.title}}{{/if}}"
}`,
} as const;
export type WebhookFormat = "default" | "discord" | "slack" | "custom";
export function defaultFilter(): MediaFilter {
return {
content_type: null,
genres: [],
decade: null,
tags: [],
min_duration_secs: null,
max_duration_secs: null,
collections: [],
series_names: [],
search_term: null,
};
}
export function defaultBlock(startMins = 20 * 60, durationMins = 60): ProgrammingBlock {
return {
id: crypto.randomUUID(),
name: "",
start_time: minsToTime(startMins),
duration_mins: durationMins,
content: { type: "algorithmic", filter: defaultFilter(), strategy: "random" },
loop_on_finish: true,
ignore_recycle_policy: false,
access_mode: "public",
};
}
function emptyDayBlocks(): Record<Weekday, ProgrammingBlock[]> {
const result = {} as Record<Weekday, ProgrammingBlock[]>;
for (const d of WEEKDAYS) result[d] = [];
return result;
}
export function useChannelForm(channel: ChannelResponse | null) {
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [timezone, setTimezone] = useState("UTC");
const [dayBlocks, setDayBlocks] = useState<Record<Weekday, ProgrammingBlock[]>>(emptyDayBlocks);
const [recyclePolicy, setRecyclePolicy] = useState<RecyclePolicy>({
cooldown_days: null,
cooldown_generations: null,
min_available_ratio: 0.1,
});
const [autoSchedule, setAutoSchedule] = useState(false);
const [accessMode, setAccessMode] = useState<AccessMode>("public");
const [accessPassword, setAccessPassword] = useState("");
const [logo, setLogo] = useState<string | null>(null);
const [logoPosition, setLogoPosition] = useState<LogoPosition>("top_right");
const [logoOpacity, setLogoOpacity] = useState(100);
const [webhookUrl, setWebhookUrl] = useState("");
const [webhookPollInterval, setWebhookPollInterval] = useState<number | "">(5);
const [webhookFormat, setWebhookFormat] = useState<WebhookFormat>("default");
const [webhookBodyTemplate, setWebhookBodyTemplate] = useState("");
const [webhookHeaders, setWebhookHeaders] = useState("");
const [selectedBlockId, setSelectedBlockId] = useState<string | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
// Reset form when channel changes
useEffect(() => {
if (channel) {
setName(channel.name);
setDescription(channel.description ?? "");
setTimezone(channel.timezone);
setDayBlocks({
...emptyDayBlocks(),
...channel.schedule_config.day_blocks,
});
setRecyclePolicy(channel.recycle_policy);
setAutoSchedule(channel.auto_schedule);
setAccessMode(channel.access_mode ?? "public");
setAccessPassword("");
setLogo(channel.logo ?? null);
setLogoPosition(channel.logo_position ?? "top_right");
setLogoOpacity(Math.round((channel.logo_opacity ?? 1) * 100));
setWebhookUrl(channel.webhook_url ?? "");
setWebhookPollInterval(channel.webhook_poll_interval_secs ?? 5);
const tmpl = channel.webhook_body_template ?? "";
setWebhookBodyTemplate(tmpl);
setWebhookHeaders(channel.webhook_headers ?? "");
if (!tmpl) {
setWebhookFormat("default");
} else if (tmpl === WEBHOOK_PRESETS.discord) {
setWebhookFormat("discord");
} else if (tmpl === WEBHOOK_PRESETS.slack) {
setWebhookFormat("slack");
} else {
setWebhookFormat("custom");
}
setSelectedBlockId(null);
}
}, [channel]);
const addBlock = (day: Weekday, startMins = 20 * 60, durationMins = 60) => {
const block = defaultBlock(startMins, durationMins);
setDayBlocks((prev) => ({ ...prev, [day]: [...(prev[day] ?? []), block] }));
setSelectedBlockId(block.id);
};
const updateBlock = (day: Weekday, idx: number, block: ProgrammingBlock) =>
setDayBlocks((prev) => ({
...prev,
[day]: (prev[day] ?? []).map((b, i) => (i === idx ? block : b)),
}));
const removeBlock = (day: Weekday, idx: number) => {
setDayBlocks((prev) => {
const dayArr = prev[day] ?? [];
if (selectedBlockId === dayArr[idx]?.id) setSelectedBlockId(null);
return { ...prev, [day]: dayArr.filter((_, i) => i !== idx) };
});
};
return {
// Basic info
name, setName,
description, setDescription,
timezone, setTimezone,
autoSchedule, setAutoSchedule,
// Access
accessMode, setAccessMode,
accessPassword, setAccessPassword,
// Logo
logo, setLogo,
logoPosition, setLogoPosition,
logoOpacity, setLogoOpacity,
fileInputRef,
// Webhook
webhookUrl, setWebhookUrl,
webhookPollInterval, setWebhookPollInterval,
webhookFormat, setWebhookFormat,
webhookBodyTemplate, setWebhookBodyTemplate,
webhookHeaders, setWebhookHeaders,
// Blocks (day-keyed)
dayBlocks, setDayBlocks,
selectedBlockId, setSelectedBlockId,
recyclePolicy, setRecyclePolicy,
addBlock,
updateBlock,
removeBlock,
};
}