293 lines
8.0 KiB
TypeScript
Executable File
293 lines
8.0 KiB
TypeScript
Executable File
import React, {useState} from 'react';
|
|
import {useFormContext, UseFormReturn} from 'react-hook-form';
|
|
import {FileUploadProvider} from '@common/uploads/uploader/file-upload-provider';
|
|
import {FormTextField} from '@common/ui/forms/input-field/text-field/text-field';
|
|
import {Trans} from '@common/i18n/trans';
|
|
import {TitleSelect} from '@app/titles/title-select';
|
|
import {FormImageSelector} from '@common/ui/images/image-selector';
|
|
import {FormSelect, Option} from '@common/ui/forms/select/select';
|
|
import {useSettings} from '@common/core/settings/use-settings';
|
|
import {useTrans} from '@common/i18n/use-trans';
|
|
import {useValueLists} from '@common/http/value-lists';
|
|
import {message} from '@common/i18n/message';
|
|
import {CreateVideoPayload} from '@app/admin/videos/requests/use-create-video';
|
|
import {InfoDialogTriggerIcon} from '@common/ui/overlays/dialog/info-dialog-trigger/info-dialog-trigger-icon';
|
|
import {Video} from '@app/titles/models/video';
|
|
import {VideoPlayerSkeleton} from '@app/videos/video-player-skeleton';
|
|
import {SiteVideoPlayer} from '@app/videos/site-video-player';
|
|
import {CaptionsPanel} from '@app/admin/videos/captions/captions-panel';
|
|
import {FormFileEntryField} from '@common/ui/forms/input-field/file-entry-field';
|
|
import {RadioGroup} from '@common/ui/forms/radio-group/radio-group';
|
|
import {Radio} from '@common/ui/forms/radio-group/radio';
|
|
import {Disk} from '@common/uploads/types/backend-metadata';
|
|
|
|
interface Props {
|
|
form: UseFormReturn<CreateVideoPayload>;
|
|
video?: Video;
|
|
}
|
|
export function CrupdateVideoForm({form, video}: Props) {
|
|
return (
|
|
<div className="flex items-start gap-54">
|
|
<div className="flex-auto">
|
|
<VideoPreview video={video} />
|
|
<ReloadMessage form={form} />
|
|
<CaptionsPanel />
|
|
</div>
|
|
<div className="w-440 flex-shrink-0">
|
|
<VideoForm />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function ReloadMessage({form}: Props) {
|
|
const dirty = form.formState.dirtyFields;
|
|
if (!dirty.src && !dirty.thumbnail) return null;
|
|
|
|
return (
|
|
<div className="mt-12 flex items-center gap-6 text-sm text-muted">
|
|
<InfoDialogTriggerIcon
|
|
size="xs"
|
|
className="text-muted"
|
|
viewBox="0 0 16 16"
|
|
/>
|
|
<Trans message="Save your changes to reload video preview." />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
interface VideoPreviewProps {
|
|
video?: Video;
|
|
}
|
|
function VideoPreview({video}: VideoPreviewProps) {
|
|
if (!video || !video.src) {
|
|
return <VideoPlayerSkeleton animate={false} />;
|
|
}
|
|
// timestamp will force reload of player when video is updated
|
|
return (
|
|
<SiteVideoPlayer
|
|
video={video}
|
|
mediaItemId={`${video.id}-${video.updated_at}`}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function VideoForm() {
|
|
return (
|
|
<FileUploadProvider>
|
|
<FormTextField
|
|
name="name"
|
|
label={<Trans message="Name" />}
|
|
className="mb-24"
|
|
required
|
|
/>
|
|
<TitleSelect
|
|
name="title_id"
|
|
seasonName="season_num"
|
|
episodeName="episode_num"
|
|
className="mb-24"
|
|
/>
|
|
<FormImageSelector
|
|
name="thumbnail"
|
|
label={<Trans message="Thumbnail" />}
|
|
diskPrefix="video-thumbnails"
|
|
className="mb-24"
|
|
/>
|
|
<SourceTypeSelect />
|
|
<SourceField />
|
|
<QualitySelect />
|
|
<LanguageSelect />
|
|
<ContentTypeSelect />
|
|
</FileUploadProvider>
|
|
);
|
|
}
|
|
|
|
function SourceTypeSelect() {
|
|
const {setValue} = useFormContext<CreateVideoPayload>();
|
|
return (
|
|
<FormSelect
|
|
name="type"
|
|
selectionMode="single"
|
|
label={<Trans message="Source type" />}
|
|
className="mb-24"
|
|
onSelectionChange={() => setValue('src', '')}
|
|
>
|
|
<Option
|
|
value="embed"
|
|
description={
|
|
<Trans message="Embed video hosted on another site. Youtube, vimeo etc." />
|
|
}
|
|
>
|
|
<Trans message="Embed" />
|
|
</Option>
|
|
<Option
|
|
value="video"
|
|
description={
|
|
<Trans message="Upload a video file or enter a url to direct video (.mp4, .webm, .avi, .mov etc.) hosted online." />
|
|
}
|
|
>
|
|
<Trans message="Direct" />
|
|
</Option>
|
|
<Option
|
|
value="stream"
|
|
description={<Trans message="Enter a url to HLS or DASH stream." />}
|
|
>
|
|
<Trans message="Adaptive stream" />
|
|
</Option>
|
|
<Option
|
|
value="external"
|
|
description={
|
|
<Trans message="Enter any url. User will be redirected to this url after clicking the video." />
|
|
}
|
|
>
|
|
<Trans message="Basic url" />
|
|
</Option>
|
|
</FormSelect>
|
|
);
|
|
}
|
|
|
|
function SourceField() {
|
|
const {watch} = useFormContext<CreateVideoPayload>();
|
|
const isEmbed = watch('type') === 'embed';
|
|
const isUrl = watch('type') === 'external';
|
|
const canUpload = watch('type') === 'video';
|
|
const {trans} = useTrans();
|
|
|
|
if (canUpload) {
|
|
return <DirectSourceField />;
|
|
}
|
|
|
|
return (
|
|
<FormTextField
|
|
required
|
|
name="src"
|
|
label={<Trans message="Source" />}
|
|
className="mb-24"
|
|
type={isUrl ? 'url' : undefined}
|
|
placeholder={
|
|
isEmbed
|
|
? trans(message('Full embed code snippet or just src url'))
|
|
: undefined
|
|
}
|
|
inputElementType={isEmbed ? 'textarea' : 'input'}
|
|
rows={4}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function DirectSourceField() {
|
|
const form = useFormContext<CreateVideoPayload>();
|
|
const [type, setType] = useState<'url' | 'file'>(() => {
|
|
const src = form.getValues('src');
|
|
return src.includes('api/v1/file-entries') ||
|
|
src.includes('storage/title-videos')
|
|
? 'file'
|
|
: 'url';
|
|
});
|
|
return (
|
|
<div className="mb-24">
|
|
<RadioGroup size="sm" className="mb-8" name="direct-type">
|
|
<Radio
|
|
value="url"
|
|
checked={type === 'url'}
|
|
onChange={e => setType(e.target.value as any)}
|
|
>
|
|
<Trans message="Url" />
|
|
</Radio>
|
|
<Radio
|
|
value="file"
|
|
checked={type === 'file'}
|
|
onChange={e => setType(e.target.value as any)}
|
|
>
|
|
<Trans message="File" />
|
|
</Radio>
|
|
</RadioGroup>
|
|
{type === 'file' ? (
|
|
<FormFileEntryField
|
|
required
|
|
name="src"
|
|
disk={Disk.public}
|
|
diskPrefix="title-videos"
|
|
label={<Trans message="Source" />}
|
|
/>
|
|
) : (
|
|
<FormTextField
|
|
name="src"
|
|
label={<Trans message="source" />}
|
|
inputElementType="textarea"
|
|
rows={2}
|
|
required
|
|
type="url"
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function QualitySelect() {
|
|
const {streaming} = useSettings();
|
|
const qualities = streaming?.qualities || [];
|
|
return (
|
|
<FormSelect
|
|
name="quality"
|
|
selectionMode="single"
|
|
label={<Trans message="Quality" />}
|
|
className="mb-24"
|
|
>
|
|
{qualities.map((quality: string) => (
|
|
<Option value={quality.toLowerCase()} key={quality} capitalizeFirst>
|
|
<Trans message={quality} />
|
|
</Option>
|
|
))}
|
|
</FormSelect>
|
|
);
|
|
}
|
|
|
|
function LanguageSelect() {
|
|
const {trans} = useTrans();
|
|
const query = useValueLists(['languages']);
|
|
return (
|
|
<FormSelect
|
|
name="language"
|
|
selectionMode="single"
|
|
showSearchField
|
|
searchPlaceholder={trans(message('Search languages'))}
|
|
label={<Trans message="Language" />}
|
|
className="mb-24"
|
|
>
|
|
{query.data?.languages?.map(language => (
|
|
<Option value={language.code} key={language.code} capitalizeFirst>
|
|
<Trans message={language.name} />
|
|
</Option>
|
|
))}
|
|
</FormSelect>
|
|
);
|
|
}
|
|
|
|
function ContentTypeSelect() {
|
|
return (
|
|
<FormSelect
|
|
name="category"
|
|
selectionMode="single"
|
|
label={<Trans message="Content type" />}
|
|
className="mb-24"
|
|
>
|
|
<Option value="trailer">
|
|
<Trans message="Trailer" />
|
|
</Option>
|
|
<Option value="clip">
|
|
<Trans message="Clip" />
|
|
</Option>
|
|
<Option value="featurette">
|
|
<Trans message="Featurette" />
|
|
</Option>
|
|
<Option value="teaser">
|
|
<Trans message="Teaser" />
|
|
</Option>
|
|
<Option value="full">
|
|
<Trans message="Full Movie or Episode" />
|
|
</Option>
|
|
</FormSelect>
|
|
);
|
|
}
|