// * Packages import { useEffect, useState } from "react"; import { VideoIcon } from "lucide-react"; // * Components shadcn import { Dialog, DialogDescription, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogFooter, } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; // * Other import { videoDetails, extractYoutubeId, extractVimeoId, videoTextSchema, isBoolsSchema, buildVideoSrc, } from "@/back/Components/Inputs/Video/VideoUtils"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import str from "@/hooks/use-string"; import VideoInputs from "./VideoInputs"; import SimpleVimeoId from "../SimpleVimeoId"; import SimpleYoutubeId from "../SimpleYoutubeId"; import { cn, GetTranslation } from "@/lib/utils"; import z from "zod"; import { LaravelEnum } from "@/back/types/global"; import { usePage } from "@inertiajs/react"; export interface VideoAttributes { videoId: string | null; width: number; style?: string | null; float?: string | null; height: number; caption: string | null; isMuted: boolean; hasControls: boolean; canFullScreen: boolean; alt: string; format: string; provider: number; hasCaption: boolean; } export type InputVideoValues = { alt: string; caption: string | null; hasCaption: boolean; isMuted: boolean; hasControls: boolean; canFullScreen: boolean; format: string; provider: number; videoId: string; }; type VideoFormatOption = { name: "landscape" | "portrait"; width: number; height: number; }; type VideoProps = { disabled: boolean; onValidateVideo?: (value: VideoAttributes | null) => void; value?: VideoAttributes | null; isSmallButton?: boolean; hasError?: boolean; forcedProvider?: number; forcedFormat?: string; }; const VIDEO_FORMATS: VideoFormatOption[] = [ { name: "landscape", width: Number(videoDetails.width), height: Number(videoDetails.height), }, { name: "portrait", width: Number(videoDetails.height), height: Number(videoDetails.width), }, ]; const animationTime = 200; type VideoProviderEnum = { [K in keyof LaravelEnum]: LaravelEnum[K] & { color: string }; }; export default function VideoButton({ disabled, onValidateVideo, value, isSmallButton = false, hasError = false, forcedProvider, forcedFormat, }: VideoProps) { const videoProviderEnum = usePage().props.videoProviderEnum as VideoProviderEnum; const defaultValueFormat = VIDEO_FORMATS.find((f) => f.name === value?.format) ?? VIDEO_FORMATS[0]; const DEFAULT_OPTIONS: InputVideoValues = { alt: value && value.alt ? value.alt : "", caption: value && value.caption ? value.caption : null, hasCaption: value && value.caption ? true : false, isMuted: value && value.isMuted ? Boolean(value.isMuted) : false, hasControls: value && value.hasControls ? Boolean(value.hasControls) : true, canFullScreen: value && value.canFullScreen ? Boolean(value.canFullScreen) : true, format: forcedFormat ? forcedFormat : value && value.format ? value.format : VIDEO_FORMATS[0].name, provider: forcedProvider ? forcedProvider : value && value.provider ? Number(value.provider) : videoProviderEnum.youtube.value, videoId: value && value.videoId ? value.videoId : "", }; const defaultProviderEnum = Object.values(videoProviderEnum).find( (provider) => provider.value == DEFAULT_OPTIONS.provider ); const [open, setOpen] = useState(false); const [loading, setLoading] = useState(false); const [errors, setErrors] = useState>({}); const [videoProvider, setVideoProvider] = useState( DEFAULT_OPTIONS.provider, ); const [videoUrl, setVideoUrl] = useState( DEFAULT_OPTIONS.videoId, ); const [videoUrlPreview, setVideoUrlPreview] = useState( DEFAULT_OPTIONS.videoId, ); const [inputFormat, setInputFormat] = useState(defaultValueFormat); const [options, setOptions] = useState(DEFAULT_OPTIONS); const src = buildVideoSrc({ videoId: videoUrl, isMuted: value?.isMuted, hasControls: value?.hasControls, provider: value?.provider ?? videoProviderEnum.youtube.value }); // * List of global functions for wysiwyg or basic input const resetComponent = () => { setVideoUrl(DEFAULT_OPTIONS.videoId); setVideoUrlPreview(DEFAULT_OPTIONS.videoId); setOptions(DEFAULT_OPTIONS); setVideoProvider(DEFAULT_OPTIONS.provider); setInputFormat(defaultValueFormat); setErrors({}); setLoading(false); }; //* List of functions used for wysiwyg usage //* TRANSLATIONS const transCaption = GetTranslation("back.input_video.caption"); const transIsMuted = GetTranslation("back.input_video.is_muted"); const transHasControls = GetTranslation("back.input_video.has_controls"); const transCanFullScreen = GetTranslation( "back.input_video.can_fullScreen", ); const youtubeError = str( GetTranslation("back.input_video.invalid_youtube_id"), ) .ucFirst() .value(); const vimeoError = str( GetTranslation("back.input_video.invalid_vimeo_id"), ) .ucFirst() .value(); const transAlt = GetTranslation("back.input_video.alt"); const transValidation = (attribute: string) => { return { min: GetTranslation("validation.min.string", { params: { attribute, min: "3" }, }), max: GetTranslation("validation.max.string", { params: { attribute, max: "255" }, }), string: GetTranslation("validation.string", { params: { attribute }, }), boolean: GetTranslation("validation.boolean", { params: { attribute }, }), }; }; const transValidationCaption = transValidation(transCaption); const transValidationIsMuted = transValidation(transIsMuted); const transValidationHasControls = transValidation(transHasControls); const transValidationCanFullScreen = transValidation(transCanFullScreen); const transValidationAlt = transValidation(transAlt); /** * Set new errors from object errors * * @param err unknown * @return void */ const setNewErrors = (err: unknown): void => { const fieldErrors: typeof errors = {}; const fieldMapping: Record = { caption: "caption", isMuted: "isMuted", hasControls: "hasControls", canFullScreen: "canFullScreen", alt: "alt", }; // Manage zod errors. if (err instanceof z.ZodError) { err.issues.forEach((error) => { const key = error.path[0] as string | undefined; const mappedKey = (key && fieldMapping[key]) ?? (error.path.length === 0 ? "form" : undefined); if (mappedKey) fieldErrors[mappedKey] = error.message; }); setErrors(fieldErrors); return; } }; const handleAddVideo = (): void => { setLoading(true); setErrors({}); const videoMetadataSchema = videoTextSchema({ hasCaption: options.hasCaption, transValidationCaption: transValidationCaption, transValidationAlt, }); const isBoolsSchemaValidation = isBoolsSchema({ canFullScreen: transValidationCanFullScreen.boolean, isMuted: transValidationIsMuted.boolean, hasControls: transValidationHasControls.boolean, }); const videoData = { caption: options.caption, alt: options.alt, isMuted: options.isMuted, hasControls: options.hasControls, canFullScreen: options.canFullScreen, url: videoUrl, }; let videoId: string | null; try { videoMetadataSchema.parse(videoData); isBoolsSchemaValidation.parse(videoData); if (videoProvider === videoProviderEnum.youtube.value) { videoId = extractYoutubeId(videoUrl); if (!videoId) { setErrors({ url: youtubeError }); setLoading(false); return; } } else { videoId = extractVimeoId(videoUrl); if (!videoId) { setErrors({ url: vimeoError }); setLoading(false); return; } } onValidateVideo?.({ canFullScreen: options.canFullScreen, caption: options.caption, hasControls: options.hasControls, isMuted: options.isMuted, videoId: videoId ?? "", provider: videoProvider, alt: options.alt, format: inputFormat.name, width: inputFormat.width, height: inputFormat.height, hasCaption: options.hasCaption }); setVideoUrlPreview(videoId ?? ""); setOpen(false); setTimeout(() => { if (!value) { resetComponent(); } }, animationTime); } catch (err) { console.log(err); setNewErrors(err); } setLoading(false); }; const resetOnQuit = () => { if (!value) { onValidateVideo?.(null); setOptions(DEFAULT_OPTIONS); setVideoUrl(""); setVideoProvider(DEFAULT_OPTIONS.provider); setInputFormat(defaultValueFormat); } else { onValidateVideo?.({ ...DEFAULT_OPTIONS, videoId: DEFAULT_OPTIONS.videoId, provider: DEFAULT_OPTIONS.provider, width: VIDEO_FORMATS[0].width, height: VIDEO_FORMATS[0].height }); setVideoUrl(DEFAULT_OPTIONS.videoId); setOptions(DEFAULT_OPTIONS); setVideoProvider(DEFAULT_OPTIONS.provider); setInputFormat(defaultValueFormat); } setVideoUrlPreview(DEFAULT_OPTIONS.videoId ?? ""); setErrors({}); setLoading(false); } const handleCancel = (): void => { setOpen(false); setTimeout(() => { resetOnQuit(); }, animationTime); }; return ( { setOpen(status); if (!status) { setTimeout(() => { resetOnQuit(); }, animationTime); } }} > {isSmallButton ? ( } tooltipContent={str( GetTranslation("back.input_video.tooltip.title"), ) .ucFirst() .value()} disabled={disabled} type="button" variant="ghost" size="icon" /> ) : (