import {
DownOutlined,
ExpandOutlined,
MutedOutlined,
PauseCircleOutlined,
PlayCircleOutlined,
SoundOutlined,
} from "@ant-design/icons";
import { useEffect, useRef, useState, type ChangeEvent, type MouseEvent } from "react";
import type { CanvasOption } from "./canvasTypes";
import { getOptionLabel } from "./canvasUtils";
import { formatCanvasVideoTime } from "./canvasWorkflowDeserialize";
export function CanvasSelectChip({
value,
options,
open,
onToggle,
onChange,
ariaLabel,
className,
compact = false,
}: {
value: string;
options: CanvasOption[];
open: boolean;
onToggle: () => void;
onChange: (value: string) => void;
ariaLabel: string;
className?: string;
compact?: boolean;
}) {
return (
{open ? (
event.stopPropagation()}
onClick={(event) => event.stopPropagation()}
>
{options.map((option, index) => (
))}
) : null}
);
}
export function CanvasNodeVideoPlayer({ src, title, onVideoMeta }: { src: string; title: string; onVideoMeta?: (width: number, height: number) => void }) {
const videoRef = useRef(null);
const [isPlaying, setIsPlaying] = useState(false);
const [isMuted, setIsMuted] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(0);
useEffect(() => {
setIsPlaying(false);
setCurrentTime(0);
setDuration(0);
}, [src]);
const stopPlayerEvent = (event: MouseEvent) => {
event.preventDefault();
event.stopPropagation();
};
const syncVideoState = () => {
const video = videoRef.current;
if (!video) return;
setCurrentTime(video.currentTime || 0);
setDuration(Number.isFinite(video.duration) ? video.duration : 0);
setIsMuted(video.muted);
setIsPlaying(!video.paused && !video.ended);
};
const togglePlayback = async (event: MouseEvent) => {
stopPlayerEvent(event);
const video = videoRef.current;
if (!video) return;
if (video.paused || video.ended) {
try {
await video.play();
} catch {
setIsPlaying(false);
}
} else {
video.pause();
}
syncVideoState();
};
const toggleMuted = (event: MouseEvent) => {
stopPlayerEvent(event);
const video = videoRef.current;
if (!video) return;
video.muted = !video.muted;
setIsMuted(video.muted);
};
const handleSeek = (event: ChangeEvent) => {
event.stopPropagation();
const video = videoRef.current;
const nextTime = Number(event.target.value);
if (!video || !Number.isFinite(nextTime)) return;
video.currentTime = nextTime;
setCurrentTime(nextTime);
};
return (
);
}
export interface CanvasNodeToolbarAction {
key: string;
label: string;
icon: React.ReactNode;
disabled?: boolean;
loading?: boolean;
}
export function CanvasNodeToolbar({
actions,
onAction,
moreActions,
onMoreAction,
}: {
actions: CanvasNodeToolbarAction[];
onAction: (key: string) => void;
moreActions?: CanvasNodeToolbarAction[];
onMoreAction?: (key: string) => void;
}) {
const [moreOpen, setMoreOpen] = useState(false);
const moreRef = useRef(null);
useEffect(() => {
if (!moreOpen) return;
const handler = (e: globalThis.MouseEvent) => {
if (moreRef.current && !moreRef.current.contains(e.target as Node)) {
setMoreOpen(false);
}
};
document.addEventListener("mousedown", handler);
return () => document.removeEventListener("mousedown", handler);
}, [moreOpen]);
return (
e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()}
>
{actions.map((action) => (
))}
{moreActions && moreActions.length > 0 && (
{moreOpen && (
{moreActions.map((action) => (
))}
)}
)}
);
}