import { useState } from "react"; import { Check, ClipboardList, Film, Music } from "../../icons/SystemIcons"; import type { DomEditSelection } from "./domEditing"; import { formatNumericValue, formatTimingValue, LABEL, parseNumericValue, RESPONSIVE_GRID, } from "./propertyPanelHelpers"; import { Section, SegmentedControl, SelectField, SliderControl } from "./propertyPanelPrimitives"; const MEDIA_TAGS = new Set(["video", "audio"]); export function isMediaElement(element: DomEditSelection): boolean { return MEDIA_TAGS.has(element.tagName); } export function MediaSection({ projectDir, element, styles, onSetStyle, onSetAttribute, onSetHtmlAttribute, }: { projectDir: string | null; element: DomEditSelection; styles: Record; onSetStyle: (prop: string, value: string) => void | Promise; onSetAttribute: (attr: string, value: string) => void | Promise; onSetHtmlAttribute: (attr: string, value: string | null) => void | Promise; }) { const isVideo = element.tagName === "video"; const el = element.element; const volume = parseNumericValue(element.dataAttributes.volume ?? "") ?? 1; const volumePercent = Math.round(volume * 100); const mediaStart = Number.parseFloat( element.dataAttributes["media-start"] ?? element.dataAttributes["playback-start"] ?? "0", ) || 0; const hasLoop = el.hasAttribute("loop"); const hasMuted = el.hasAttribute("muted"); const hasAudio = element.dataAttributes["has-audio"] === "true"; const playbackRate = Number.parseFloat(element.dataAttributes["playback-rate"] ?? "1") || 1; const objectFit = styles["object-fit"] || "contain"; const objectPosition = styles["object-position"] || "center"; const sourceDuration = Number.parseFloat(element.dataAttributes["source-duration"] ?? "") || (el as HTMLMediaElement).duration || 0; const mediaStartMax = Math.max(30, Math.ceil(sourceDuration || mediaStart + 10)); const srcAttr = el.getAttribute("src") ?? ""; const [copied, setCopied] = useState(false); const absoluteSrc = projectDir && srcAttr && !srcAttr.startsWith("http") ? `${projectDir}/${srcAttr}` : srcAttr; return (
: } >
{srcAttr && (
Source
{absoluteSrc}
)}
Volume `${Math.round(next)}%`} onCommit={(next) => { void onSetAttribute("volume", formatNumericValue(next / 100)); }} />
Playback rate `${formatNumericValue(next / 100)}x`} onCommit={(next) => { void onSetAttribute("playback-rate", formatNumericValue(next / 100)); }} />
Media start formatTimingValue(next / 100)} onCommit={(next) => { void onSetAttribute("media-start", (next / 100).toFixed(2)); }} />
Loop { void onSetHtmlAttribute("loop", next === "on" ? "true" : null); }} options={[ { label: "On", value: "on" }, { label: "Off", value: "off" }, ]} />
Muted { void onSetHtmlAttribute("muted", next === "on" ? "true" : null); }} options={[ { label: "On", value: "on" }, { label: "Off", value: "off" }, ]} />
{isVideo && (
Has audio track { if (next === "yes") { void onSetAttribute("has-audio", "true"); void onSetHtmlAttribute("muted", null); } else { void onSetAttribute("has-audio", ""); void onSetHtmlAttribute("muted", "true"); } }} options={[ { label: "Yes", value: "yes" }, { label: "No", value: "no" }, ]} />
)} {isVideo && ( <>
{ void onSetStyle("object-fit", next); }} options={["contain", "cover", "fill", "none", "scale-down"]} /> { void onSetStyle("object-position", next); }} options={[ "center", "top", "bottom", "left", "right", "left top", "right top", "left bottom", "right bottom", ]} />
)}
); }