import React, {useState} from "react"; import {Grid, StatisticLabel, StatisticValue, TableCell, TableRow,} from "semantic-ui-react"; import {createChannel, deleteChannel, refreshChannel, tagChannel, tagChannelInfo} from "../api"; import {CollectionTagModal} from "./collections/CollectionTagModal"; import { APIButton, BackButton, ErrorMessage, humanFileSize, humanNumber, InfoPopup, SearchInput, secondsToFrequency, secondsToFullDuration, SimpleAccordion, useTitle, WROLModeMessage } from "./Common"; import {Link, useNavigate, useParams} from "react-router-dom"; import Message from "semantic-ui-react/dist/commonjs/collections/Message"; import {useChannel, useChannels, useOneQuery} from "../hooks/customHooks"; import _ from "lodash"; import { Button, Form, Header, Loader, Modal, ModalContent, Segment, Statistic } from "./Theme"; import {Media, ThemeContext} from "../contexts/contexts"; import {toast} from "react-semantic-toasts-2"; import {RecurringDownloadsTable} from "./admin/Downloads"; import {InputForm, ToggleForm} from "../hooks/useForm"; import {ChannelDownloadForm, DestinationForm, DownloadTagsSelector} from "./Download"; import {CollectionTable} from "./collections/CollectionTable"; import {CollectionEditForm} from "./collections/CollectionEditForm"; // Channel table column configuration const CHANNEL_COLUMNS = [ {key: 'name', label: 'Name', sortable: true, width: 7}, {key: 'tag_name', label: 'Tag', sortable: true, width: 2}, {key: 'video_count', label: 'Videos', sortable: true, align: 'right', width: 2}, {key: 'min_download_frequency', label: 'Download Frequency', sortable: true, format: 'frequency', width: 2, hideOnMobile: true}, {key: 'total_size', label: 'Size', sortable: true, align: 'right', format: 'bytes', width: 2, hideOnMobile: true}, {key: 'actions', label: 'Manage', sortable: false, type: 'actions', width: 1} ]; const CHANNEL_ROUTES = { list: '/videos/channel', edit: '/videos/channel/:id/edit', search: '/videos/channel/:id/video', id_field: 'channel_id' }; function ChannelStatistics({statistics}) { if (!statistics) { return <> } return
Statistics
{statistics.video_count} Videos {humanFileSize(statistics.size, true)} Total Size {humanFileSize(statistics.largest_video, true)} Largest Video {secondsToFullDuration(statistics.length)} Total Duration {humanNumber(statistics.video_tags)} Video Tags
} export function ChannelEditPage() { const navigate = useNavigate(); const {channelId} = useParams(); const [tagEditModalOpen, setTagEditModalOpen] = useState(false); const [downloadModalOpen, setDownloadModalOpen] = useState(false); const {channel, form, fetchChannel} = useChannel(channelId); useTitle(_.isEmpty(channel) ? null : `${channel.name} Channel`); if (!form.ready) { return ; } const handleRefreshChannel = async (e) => { if (e) { e.preventDefault(); } await refreshChannel(channelId); } const handleDelete = async () => { try { let response = await deleteChannel(channelId); if (response.status === 204) { navigate('/videos/channel'); } } catch (e) { console.error('Failed to delete channel', e); } } // Handler for tag modal save const handleTagSave = async (tagName, directory) => { try { await tagChannel(channelId, tagName, directory); toast({ type: 'success', title: 'Channel Tagged', description: `Channel has been tagged with "${tagName}"`, time: 3000, }); } catch (e) { console.error('Failed to tag channel', e); } finally { setTimeout(async () => { await fetchChannel(); }, 500); } }; // Handler for fetching tag info const handleGetTagInfo = async (tagName) => { return await tagChannelInfo(channelId, tagName); }; const afterNewDownloadSave = async () => { setDownloadModalOpen(false); await form.fetcher(); } const onDelete = async () => { await form.fetcher(); setDownloadModalOpen(false); } const handleSubmit = async (e) => { if (e) e.preventDefault(); try { await form.onSubmit(); toast({ type: 'success', title: 'Channel Updated', description: 'Channel was successfully updated', time: 3000, }); await fetchChannel(); } catch (e) { console.error('Failed to update channel:', e); } }; const deleteButton = Delete; const refreshButton = Refresh; const tagButton = ; const actionButtons = <> {deleteButton} {refreshButton} {tagButton} ; const downloadMissingDataInfo = 'Automatically download missing comments, etc, in the background.'; const downloadMissingDataLabel = <>Download Missing Data; return <> {(channel.url || channel.rss_url) && {channel.url &&
URL
{channel.url}
} {channel.rss_url &&
RSS URL
{channel.rss_url}
}
}
{/* Tag Modal */} setTagEditModalOpen(false)} currentTagName={channel.tag_name} originalDirectory={channel.directory} getTagInfo={handleGetTagInfo} onSave={handleTagSave} collectionName="Channel" hasDirectory={!!channel.directory} /> {/* Downloads Segment */}
Downloads
setDownloadModalOpen(false)}>
New Channel Download
setDownloadModalOpen(false)} onDelete={onDelete} />
{channel && channel.statistics && } ; } export function ChannelNewPage() { useTitle('New Channel'); // Used to display messages to maintainer. const [error, setError] = useState(false); const [success, setSuccess] = useState(false); const [messageHeader, setMessageHeader] = useState(); const [messageContent, setMessageContent] = useState(); const {channel, form} = useChannel(null); const setErrorMessage = (header, message) => { setError(true); setSuccess(false); setMessageHeader(header); setMessageContent(message); } const setSuccessMessage = (header, message) => { setError(false); setSuccess(true); setMessageHeader(header); setMessageContent(message); } const handleSubmit = async () => { const body = { name: channel.name, directory: channel.directory, url: channel.url, download_missing_data: channel.download_missing_data, tag_name: _.isEmpty(channel.tag_name) ? null : channel.tag_name[0], }; let response = null; try { response = await createChannel(body); } catch (e) { console.error(e); toast({ type: 'error', title: 'Unexpected server response', description: 'Could not save channel', time: 5000, }); return; } if (response && response.ok) { let location = response.headers.get('Location'); let channelResponse = await fetch(location); let data = await channelResponse.json(); let newChannel = data['channel']; setSuccessMessage( 'Channel created', Your channel was created. View it here ); } else if (response) { let error = await response.json(); let cause = error.cause; if (cause && cause.code === 'CHANNEL_DIRECTORY_CONFLICT') { setErrorMessage( 'Invalid directory', 'This directory is already used by another channel', ); } else if (cause && cause.code === 'CHANNEL_NAME_CONFLICT') { setErrorMessage( 'Invalid name', 'This channel name is already taken', ); } else { setErrorMessage('Invalid channel', error.message || error.error || 'Unable to save channel. See logs.'); } } else { console.error('Did not get a response for channel!'); } } const downloadMissingDataInfo = 'Automatically download missing comments, etc, in the background.'; const downloadMissingDataLabel = <>Download Missing Data; let messageRow; if (error || success) { messageRow = {error && } {success && } } return <>
New Channel
{messageRow} Save
; } export function ChannelsPage() { useTitle('Channels'); const [channels] = useChannels(); const [searchStr, setSearchStr] = useOneQuery('name'); // Header section matching DomainsPage pattern const header =
setSearchStr('')} onChange={setSearchStr} onSubmit={null} />
; // Empty state if (channels && channels.length === 0) { return <> {header} No channels exist yet! Create one. ; } // Error state if (channels === undefined) { return <> {header} Could not fetch Channels ; } return <> {header} ; }