import React, {useState} from "react"; import {Grid, StatisticLabel, StatisticValue, TableCell, TableRow,} from "semantic-ui-react"; import {createChannel, deleteChannel, refreshChannel, tagChannel, tagChannelInfo, updateChannel} 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 {SortableTable} from "./SortableTable"; import {toast} from "react-semantic-toasts-2"; import {RecurringDownloadsTable} from "./admin/Downloads"; import {TagsContext} from "../Tags"; import {InputForm, ToggleForm} from "../hooks/useForm"; import {ChannelDownloadForm, DestinationForm, DownloadTagsSelector} from "./Download"; import {CollectionTable} from "./collections/CollectionTable"; 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 ChannelPage({create, header}) { // 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 [downloadModalOpen, setDownloadModalOpen] = useState(false); const navigate = useNavigate(); const {channelId} = useParams(); const {SingleTag} = React.useContext(TagsContext); const [tagEditModalOpen, setTagEditModalOpen] = useState(false); const {channel, form, fetchChannel} = useChannel(channelId); useTitle(_.isEmpty(channel) ? null : `${channel.name} Channel`); if (!create && !form.ready) { // Waiting for editing Channel to be fetched. return ; } 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, }; let response = null; try { if (create !== undefined) { // Can create a Channel with a Tag. body.tag_name = _.isEmpty(channel.tag_name) ? null : channel.tag_name[0]; response = await createChannel(body); } else { response = await updateChannel(channelId, 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 channel = data['channel']; if (response.status === 201) { setSuccessMessage( 'Channel created', Your channel was created. View it here ); } else { await fetchChannel(); toast({ type: 'success', title: 'Channel updated', description: 'The channel was updated', }) } } else if (response) { // Some error occurred. 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', 'directory', ); } else if (cause && cause.code === 'CHANNEL_NAME_CONFLICT') { setErrorMessage( 'Invalid name', 'This channel name is already taken', 'name', ); } 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 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) { setErrorMessage('Failed to delete', 'Failed to delete this channel, check logs.'); } } let downloads = channel && channel.downloads ? channel.downloads : null; const afterNewDownloadSave = async () => { setDownloadModalOpen(false); await form.fetcher(); } const onDelete = async () => { await form.fetcher(); setDownloadModalOpen(false); } // Handler for tag modal save const handleTagSave = async (tagName, directory) => { try { await tagChannel(channelId, tagName, directory); } catch (e) { console.error('Failed to tag channel', e); } finally { setTimeout(async () => { // Delay fetch because config needs to be written. await fetchChannel(); }, 500); } }; // Handler for fetching tag info const handleGetTagInfo = async (tagName) => { return await tagChannelInfo(channelId, tagName); }; let tagEditModal; if (!create) { // User is editing the Tag of the Channel. tagEditModal = setTagEditModalOpen(false)} currentTagName={channel.tag_name} originalDirectory={channel.directory} getTagInfo={handleGetTagInfo} onSave={handleTagSave} collectionName="Channel" />; } let channelUrlRow; if (channel && channel.url) { channelUrlRow =
URL
{channel.url}
; } let channelRssUrlRow; if (channel && channel.rss_url) { channelRssUrlRow =
RSS URL
{channel.rss_url}
; } let channelTagRow; if (channel && channel.tag_name) { channelTagRow = {channel.tag_name && !create && } ; } const downloadMissingDataInfo = 'Automatically download missing comments, etc, in the background.'; const downloadMissingDataLabel = <>Download Missing Data; const downloadMissingDataRow = ; let messageRow; if (error || success) { messageRow = {error && } {success && } } return <> {!create && }
{header}
{channelTagRow} {downloadMissingDataRow} {!create && (channel.url || channel.rss_url) && {channelUrlRow} {channelRssUrlRow} } {messageRow} {!create ? <> Delete Refresh {tagEditModal} : } Save
{!create &&
Downloads
setDownloadModalOpen(false)}>
New Channel Download
setDownloadModalOpen(false)} onDelete={onDelete} />
} {channel && channel.statistics && } } export function ChannelEditPage(props) { return ; } export function ChannelNewPage(props) { useTitle('New Channel'); return } export function ChannelsPage() { useTitle('Channels'); const [channels, total, metadata] = 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} ; }