diff --git a/app/Enums/VideoProviderEnum.php b/app/Enums/VideoProviderEnum.php new file mode 100644 index 00000000..2d2f8cc9 --- /dev/null +++ b/app/Enums/VideoProviderEnum.php @@ -0,0 +1,23 @@ +name => 'youtube', + self::vimeo->name => 'viméo', + ]; +} diff --git a/lang/en/back.php b/lang/en/back.php index f7572ab5..1cc37ff1 100644 --- a/lang/en/back.php +++ b/lang/en/back.php @@ -295,6 +295,37 @@ return [ "cancel" => "Cancel", "tooltip_cancel" => "Cancel the replacement", ], + "video" => [ + "tooltip" => "Video", + "title" => "Insert an video", + "subtitle" => "Add a new video to your content.", + "insert" => "Insert", + "tooltip_insert" => "Add this video", + "cancel" => "Cancel", + "tooltip_cancel" => "Cancel adding this video", + "edit_title" => "Update a video", + "edit_subtitle" => "Edit the attributes associated with the video.", + "edit_apply" => "Apply", + "edit_tooltip_apply" => "Update the video attributes", + "edit_cancel" => "Cancel", + "edit_tooltip_cancel" => "Cancel updating the video attributes", + "position" => "Image position", + "format" => "Image format", + "video" => "Image", + "alt" => "ALT attribute of the video", + "caption" => "Caption of the video", + "has_caption" => "Presence of a legend", + "landscape" => "Landscape", + "portrait" => "Portrait", + "square" => "Square", + "align-left" => "Align left", + "align-center" => "Align center", + "align-right" => "Align right", + "wrap-left" => "Wrap left", + "wrap-right" => "Wrap right", + "youtube" => "Youtube", + "vimeo" => "Vimeo", + ], ], "json_editor" => [ "item_single" => "1 item", diff --git a/lang/fr/back.php b/lang/fr/back.php index ffedbaf9..92ae3a1d 100644 --- a/lang/fr/back.php +++ b/lang/fr/back.php @@ -296,6 +296,42 @@ return [ "cancel" => "Annuler", "tooltip_cancel" => "Annuler le remplacement", ], + "video" => [ + "tooltip" => "Vidéo", + "title" => "Insérer une vidéo", + "subtitle" => "Ajouter une nouvelle vidéo à votre contenu.", + "insert" => "Insérer", + "tooltip_insert" => "Ajouter cette vidéo", + "cancel" => "Annuler", + "tooltip_cancel" => "Annuler l'ajout de cette vidéo", + "edit_title" => "Mise à jour d'une vidéo", + "edit_subtitle" => "Modifier les attributs associés à la vidéo.", + "edit_apply" => "Appliquer", + "edit_tooltip_apply" => "Mettre à jour les attributs de la vidéo", + "edit_cancel" => "Annuler", + "edit_tooltip_cancel" => "Annuler la mise à jour des attributs de la vidéo", + "position" => "Position de la vidéo", + "format" => "Format de la vidéo", + "image" => "Vidéo", + "alt" => "Texte alternatif de la vidéo", + "caption" => "Légende de la vidéo", + "has_caption" => "Présence d'une légende", + "landscape" => "Paysage", + "portrait" => "Portrait", + "align-left" => "Aligner à gauche", + "align-center" => "Aligner au centre", + "align-right" => "Aligner à droite", + "wrap-left" => "Flottant à gauche", + "wrap-right" => "Flottant à droite", + "youtube" => "Youtube", + "vimeo" => "Viméo", + "is_muted" => "muter la vidéo ?", + "has_controls" => "permettre de controler la vidéo ?", + "can_fullScreen" => "autoriser le plein écran ?", + "provider" => "hébergeur de la vidéo", + 'invalid_youtube_id' => "vidéo YOUTUBE invalide", + "invalid_vimeo_id" => "vidéo VIMEO invalide" + ], ], "json_editor" => [ "item_single" => "1 élément", diff --git a/resources/ts/back/Components/Inputs/SimpleVideoUpload.tsx b/resources/ts/back/Components/Inputs/SimpleVideoUpload.tsx new file mode 100644 index 00000000..5573da6f --- /dev/null +++ b/resources/ts/back/Components/Inputs/SimpleVideoUpload.tsx @@ -0,0 +1,155 @@ +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { cn } from "@/lib/utils"; +import { usePage } from "@inertiajs/react"; +import { UploadIcon } from "lucide-react"; +import { + ChangeEvent, + MouseEvent, + useEffect, + useRef, + useState, +} from "react"; + +type Props = { + width: number | string; + height: number; + + className?: string; + name: string; + label: string; + value?: string; + id?: string; + required?: boolean; + disabled?: boolean; + parentErrors?: Record | null; + setData: (name: string, value: string) => void; +}; + +export default function SimpleVideoUpload(props: Props) { + const { + width, + height, + className, + name, + label, + id, + required, + disabled, + parentErrors, + setData, + } = props; + + const inputRef = useRef(null); + const { errors } = usePage<{ errors: Record }>().props; + const localErrors = parentErrors ?? errors ?? {}; + + const [source, setSource] = useState(null); + const [fileName, setFileName] = useState(null); + + const handleFileChange = (event: ChangeEvent) => { + const file = event.target.files?.[0]; + if (!file) return; + + const url = URL.createObjectURL(file); + setSource(url); + setFileName(file.name); + + // Important pour Inertia + setData(name, file as unknown as string); + }; + + const handleChoose = (event: React.MouseEvent) => { + event.preventDefault(); + inputRef.current?.click(); + }; + + // Nettoyage mémoire + useEffect(() => { + return () => { + if (source) { + URL.revokeObjectURL(source); + } + }; + }, [source]); + + return ( +
+ + + {/* Input caché */} + + + {/* Zone preview */} +
+ {!source ? ( +
+ +

+ Cliquer pour sélectionner une vidéo +

+ + Format accepté : .mp4, .mov + +
+ ) : ( +
+
+ )} +
+ + {/* Erreur */} + {localErrors[name] && ( +

+ {localErrors[name]} +

+ )} +
+ ); +} diff --git a/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Video/Video.tsx b/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Video/Video.tsx new file mode 100644 index 00000000..34df7041 --- /dev/null +++ b/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Video/Video.tsx @@ -0,0 +1,120 @@ +import { Node } from '@tiptap/core'; +import { ReactNodeViewRenderer } from "@tiptap/react"; +import { buildVideoSrc } from "./VideoUtils"; +import VideoNodeView, { FLOAT_STYLES, FloatOption } from "./VideoNodeView"; +import { CommandProps } from '@tiptap/core'; +import { VideoAttributes } from './video'; + +export const Video = Node.create({ + name: "video", + group: "block", + atom: true, + + addAttributes() { + return { + youtubeId: { default: null }, + vimeoId: { default: null }, + width: { default: 1008 }, + height: { default: 567 }, + caption: { default: null }, + style: { default: null }, + float: { default: null }, + isMuted: { default: false }, + hasControls: { default: true }, + canFullScreen: { default: true }, + }; + }, + + // Parse