173 lines
5.6 KiB
TypeScript
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,
|
|
};
|
|
}
|