mirror of
https://github.com/azaion/ui.git
synced 2026-06-21 11:21:10 +00:00
Azaion Suite to the web. First commit. Only rough sketches of future components is done.
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
|
||||
function AnnotationControls({ onFrameBackward, onPlayPause, isPlaying, onFrameForward, onSaveAnnotation, onDelete }) {
|
||||
return (
|
||||
<div style={{ display: 'flex', justifyContent: 'center', position: 'relative', zIndex: 1 }}>
|
||||
<button style={{ padding: '10px 20px', fontSize: '16px', margin: '0 5px' }} onClick={onFrameBackward}></button>
|
||||
<button style={{ padding: '10px 20px', fontSize: '16px', margin: '0 5px' }} onClick={onPlayPause}>{isPlaying ? 'Pause' : 'Play'}</button>
|
||||
<button style={{ padding: '10px 20px', fontSize: '16px', margin: '0 5px' }} onClick={onFrameForward}></button>
|
||||
<button style={{ padding: '10px 20px', fontSize: '16px', margin: '0 5px' }} onClick={onSaveAnnotation}>Save Annotation</button>
|
||||
<button style={{ padding: '10px 20px', fontSize: '16px', margin: '0 5px' }} onClick={onDelete}>Delete</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AnnotationControls;
|
||||
@@ -0,0 +1,20 @@
|
||||
// src/components/AnnotationList.js
|
||||
// No changes
|
||||
import React from 'react';
|
||||
|
||||
function AnnotationList({ annotations, onAnnotationClick }) {
|
||||
return (
|
||||
<div>
|
||||
<h3>Annotations</h3>
|
||||
<ul>
|
||||
{annotations.map((annotation, index) => (
|
||||
<li key={index} onClick={() => onAnnotationClick(annotation.time)}>
|
||||
Frame {index + 1} - {annotation.annotations.length} objects
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AnnotationList;
|
||||
@@ -0,0 +1,156 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import VideoPlayer from './VideoPlayer';
|
||||
import AnnotationList from './AnnotationList';
|
||||
import MediaList from './MediaList';
|
||||
import DetectionClassList from './DetectionClassList';
|
||||
import CanvasEditor from './CanvasEditor';
|
||||
import * as AnnotationService from '../services/AnnotationService';
|
||||
import AnnotationControls from './AnnotationControls'; // Import the new component
|
||||
|
||||
function AnnotationMain() {
|
||||
const [files, setFiles] = useState([]);
|
||||
const [selectedFile, setSelectedFile] = useState(null);
|
||||
const [annotations, setAnnotations] = useState({});
|
||||
const [currentTime, setCurrentTime] = useState(0);
|
||||
const [selectedClass, setSelectedClass] = useState(null);
|
||||
const [detections, setDetections] = useState([]);
|
||||
const [selectedDetectionIndices, setSelectedDetectionIndices] = useState([]);
|
||||
const [isPlaying, setIsPlaying] = useState(false); // Add isPlaying state here
|
||||
const videoRef = React.createRef();
|
||||
|
||||
useEffect(() => {
|
||||
const initialFiles = [];
|
||||
setFiles(initialFiles);
|
||||
}, []);
|
||||
|
||||
const handleFileSelect = (file) => {
|
||||
console.log("handleFileSelect called with:", file);
|
||||
setSelectedFile(file);
|
||||
setAnnotations({});
|
||||
setDetections([]);
|
||||
setSelectedDetectionIndices([]);
|
||||
setCurrentTime(0);
|
||||
setIsPlaying(false); // Reset playing state
|
||||
};
|
||||
|
||||
const handleDropNewFiles = (newFiles) => {
|
||||
setFiles(prevFiles => [...prevFiles, ...newFiles]);
|
||||
if (!selectedFile) {
|
||||
setSelectedFile(newFiles[0]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAnnotationSave = () => {
|
||||
const containerRef = { current: { offsetWidth: videoRef.current.videoWidth, offsetHeight: videoRef.current.videoHeight } };
|
||||
const annotationSelectedClass = selectedClass;
|
||||
const imageData = AnnotationService.createAnnotationImage(videoRef, detections, null, annotationSelectedClass, containerRef);
|
||||
if (imageData) {
|
||||
setAnnotations(prevAnnotations => ({
|
||||
...prevAnnotations,
|
||||
[currentTime]: { time: currentTime, detections: detections, imageData },
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
const newDetections = detections.filter((_, index) => !selectedDetectionIndices.includes(index));
|
||||
setDetections(newDetections);
|
||||
};
|
||||
|
||||
const handleAnnotationClick = (time) => {
|
||||
setCurrentTime(time);
|
||||
const annotation = annotations[time];
|
||||
if (annotation) {
|
||||
setDetections(annotation.detections);
|
||||
setSelectedDetectionIndices([]);
|
||||
}
|
||||
if (videoRef.current) {
|
||||
videoRef.current.currentTime = time;
|
||||
}
|
||||
setIsPlaying(false); // Pause when clicking an annotation
|
||||
};
|
||||
|
||||
const handleClassSelect = (cls) => {
|
||||
setSelectedClass(cls);
|
||||
};
|
||||
|
||||
const handleDetectionsChange = (newDetections) => {
|
||||
setDetections(newDetections);
|
||||
};
|
||||
|
||||
const handleSelectionChange = (newSelection) => {
|
||||
setSelectedDetectionIndices(newSelection);
|
||||
};
|
||||
|
||||
const handlePlayPause = () => { // Moved from VideoPlayer
|
||||
setIsPlaying(prev => !prev);
|
||||
if (videoRef.current) {
|
||||
if (isPlaying) {
|
||||
videoRef.current.pause();
|
||||
} else {
|
||||
videoRef.current.play();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleFrameForward = () => { // Moved from VideoPlayer
|
||||
if (videoRef.current) {
|
||||
videoRef.current.currentTime += 1 / 30;
|
||||
setCurrentTime(videoRef.current.currentTime)
|
||||
}
|
||||
};
|
||||
|
||||
const handleFrameBackward = () => { // Moved from VideoPlayer
|
||||
if (videoRef.current) {
|
||||
videoRef.current.currentTime -= 1 / 30;
|
||||
setCurrentTime(videoRef.current.currentTime)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex' }}>
|
||||
<div style={{ width: '15%', paddingRight: '10px', display: 'flex', flexDirection: 'column' }}>
|
||||
<MediaList
|
||||
files={files}
|
||||
selectedFile={selectedFile}
|
||||
onFileSelect={handleFileSelect}
|
||||
onDropNewFiles={handleDropNewFiles}
|
||||
/>
|
||||
<div style={{ marginTop: 'auto' }}>
|
||||
<DetectionClassList onClassSelect={handleClassSelect} />
|
||||
</div>
|
||||
<AnnotationList annotations={Object.values(annotations)} onAnnotationClick={handleAnnotationClick} />
|
||||
|
||||
</div>
|
||||
<div style={{ width: '85%' }}>
|
||||
<VideoPlayer
|
||||
videoFile={selectedFile}
|
||||
currentTime={currentTime}
|
||||
videoRef={videoRef}
|
||||
isPlaying = {isPlaying}
|
||||
>
|
||||
<CanvasEditor
|
||||
width={videoRef.current ? videoRef.current.videoWidth : 0}
|
||||
height={videoRef.current ? videoRef.current.videoHeight : 0}
|
||||
detections={detections}
|
||||
selectedDetectionIndices={selectedDetectionIndices}
|
||||
onDetectionsChange={handleDetectionsChange}
|
||||
onSelectionChange={handleSelectionChange}
|
||||
detectionClass={selectedClass}
|
||||
/>
|
||||
</VideoPlayer>
|
||||
<AnnotationControls
|
||||
onFrameBackward={handleFrameBackward}
|
||||
onPlayPause={handlePlayPause}
|
||||
isPlaying={isPlaying}
|
||||
onFrameForward={handleFrameForward}
|
||||
onSaveAnnotation={handleAnnotationSave}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AnnotationMain;
|
||||
@@ -0,0 +1,229 @@
|
||||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import * as AnnotationService from '../services/AnnotationService';
|
||||
import DetectionContainer from './DetectionContainer';
|
||||
|
||||
function CanvasEditor({
|
||||
width,
|
||||
height,
|
||||
detections,
|
||||
initialCurrentDetection = null,
|
||||
selectedDetectionIndices,
|
||||
onDetectionsChange,
|
||||
onSelectionChange,
|
||||
children,
|
||||
detectionClass
|
||||
}) {
|
||||
const containerRef = useRef(null);
|
||||
const [currentDetection, setCurrentDetection] = useState(initialCurrentDetection);
|
||||
const [mouseDownPos, setMouseDownPos] = useState(null);
|
||||
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
|
||||
const [resizeData, setResizeData] = useState(null);
|
||||
const [localDetections, setLocalDetections] = useState(detections || []);
|
||||
const [localSelectedIndices, setLocalSelectedIndices] = useState(selectedDetectionIndices || []);
|
||||
|
||||
useEffect(() => {
|
||||
setLocalDetections(detections || []);
|
||||
}, [detections]);
|
||||
|
||||
useEffect(() => {
|
||||
setLocalSelectedIndices(selectedDetectionIndices || []);
|
||||
}, [selectedDetectionIndices]);
|
||||
|
||||
const handleMouseDown = (e) => {
|
||||
e.preventDefault();
|
||||
if (!containerRef.current) return;
|
||||
|
||||
const { x: mouseX, y: mouseY } = AnnotationService.calculateRelativeCoordinates(e, containerRef);
|
||||
setMouseDownPos({ mouseX, mouseY });
|
||||
|
||||
let detectionFound = false;
|
||||
for (let i = localDetections.length - 1; i >= 0; i--) {
|
||||
if (AnnotationService.isMouseOverDetection(e.clientX, e.clientY, localDetections[i], containerRef)) {
|
||||
if (e.ctrlKey) {
|
||||
const newSelectedIndices = localSelectedIndices.includes(i)
|
||||
? localSelectedIndices.filter(index => index !== i)
|
||||
: [...localSelectedIndices, i];
|
||||
setLocalSelectedIndices(newSelectedIndices);
|
||||
if (onSelectionChange) {
|
||||
onSelectionChange(newSelectedIndices);
|
||||
}
|
||||
} else {
|
||||
const newSelectedIndices = [i];
|
||||
setLocalSelectedIndices(newSelectedIndices);
|
||||
if (onSelectionChange) {
|
||||
onSelectionChange(newSelectedIndices);
|
||||
}
|
||||
}
|
||||
setDragOffset({
|
||||
x: mouseX - localDetections[i].x1,
|
||||
y: mouseY - localDetections[i].y1,
|
||||
});
|
||||
detectionFound = true;
|
||||
break; // Stop the loop once a detection is found
|
||||
}
|
||||
}
|
||||
|
||||
if (!detectionFound) {
|
||||
if (!e.ctrlKey) {
|
||||
setLocalSelectedIndices([]);
|
||||
if (onSelectionChange) {
|
||||
onSelectionChange([]);
|
||||
}
|
||||
}
|
||||
if (detectionClass) {
|
||||
setCurrentDetection({ x1: mouseX, y1: mouseY, x2: mouseX, y2: mouseY, class: detectionClass });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseMove = (e) => {
|
||||
if (!containerRef.current) return;
|
||||
const { x: mouseX, y: mouseY } = AnnotationService.calculateRelativeCoordinates(e, containerRef);
|
||||
|
||||
if (localSelectedIndices.length > 0 && mouseDownPos && !resizeData) {
|
||||
// Dragging logic
|
||||
const newDetections = [...localDetections];
|
||||
const firstSelectedIndex = localSelectedIndices[0];
|
||||
|
||||
// Check for valid index before accessing.
|
||||
if (firstSelectedIndex === undefined || !newDetections[firstSelectedIndex]) return;
|
||||
|
||||
const firstSelectedDetection = newDetections[firstSelectedIndex];
|
||||
const { newX1, newY1, newX2, newY2 } = AnnotationService.calculateNewPosition(mouseX, mouseY, dragOffset, firstSelectedDetection, containerRef);
|
||||
const deltaX = newX1 - firstSelectedDetection.x1;
|
||||
const deltaY = newY1 - firstSelectedDetection.y1;
|
||||
|
||||
localSelectedIndices.forEach(index => {
|
||||
// Check for valid index before accessing.
|
||||
if (newDetections[index] === undefined) return;
|
||||
|
||||
const detection = newDetections[index];
|
||||
let updatedX1 = detection.x1 + deltaX;
|
||||
let updatedY1 = detection.y1 + deltaY;
|
||||
let updatedX2 = detection.x2 + deltaX;
|
||||
let updatedY2 = detection.y2 + deltaY;
|
||||
|
||||
const bounds = AnnotationService.calculateNewPosition(updatedX1 + dragOffset.x, updatedY1 + dragOffset.y, dragOffset, { ...detection, x1: updatedX1, y1: updatedY1, x2: updatedX2, y2: updatedY2 }, containerRef);
|
||||
detection.x1 = bounds.newX1;
|
||||
detection.y1 = bounds.newY1;
|
||||
detection.x2 = bounds.newX2;
|
||||
detection.y2 = bounds.newY2;
|
||||
});
|
||||
|
||||
setLocalDetections(newDetections);
|
||||
if (onDetectionsChange) {
|
||||
onDetectionsChange(newDetections); // Notify about changes
|
||||
}
|
||||
} else if (currentDetection && !resizeData) {
|
||||
// Drawing a new detection.
|
||||
setCurrentDetection(prev => ({ ...prev, x2: mouseX, y2: mouseY }));
|
||||
} else if (resizeData) {
|
||||
const { index, position } = resizeData;
|
||||
if (localDetections[index] === undefined) return;
|
||||
|
||||
const newDetections = [...localDetections];
|
||||
const detection = newDetections[index];
|
||||
const updatedDetection = AnnotationService.calculateResizedPosition(mouseX, mouseY, position, detection, containerRef);
|
||||
newDetections[index] = updatedDetection;
|
||||
setLocalDetections(newDetections);
|
||||
if (onDetectionsChange) {
|
||||
onDetectionsChange(newDetections);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
if (currentDetection && mouseDownPos) {
|
||||
const dx = Math.abs(currentDetection.x2 - currentDetection.x1);
|
||||
const dy = Math.abs(currentDetection.y2 - currentDetection.y1);
|
||||
|
||||
if (dx > 5 && dy > 5) {
|
||||
const newDetections = [...localDetections, currentDetection];
|
||||
setLocalDetections(newDetections);
|
||||
if (onDetectionsChange) {
|
||||
onDetectionsChange(newDetections);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setCurrentDetection(null);
|
||||
setMouseDownPos(null);
|
||||
setDragOffset({ x: 0, y: 0 });
|
||||
setResizeData(null);
|
||||
};
|
||||
|
||||
const handleDetectionMouseDown = (e, index) => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (!localSelectedIndices.includes(index)) {
|
||||
if (!e.ctrlKey) {
|
||||
const newSelectedIndices = [index];
|
||||
setLocalSelectedIndices(newSelectedIndices);
|
||||
onSelectionChange && onSelectionChange(newSelectedIndices);
|
||||
} else {
|
||||
const newSelectedIndices = [...localSelectedIndices, index];
|
||||
setLocalSelectedIndices(newSelectedIndices);
|
||||
onSelectionChange && onSelectionChange(newSelectedIndices);
|
||||
}
|
||||
}
|
||||
|
||||
const { x: mouseX, y: mouseY } = AnnotationService.calculateRelativeCoordinates(e, containerRef);
|
||||
setDragOffset({
|
||||
x: mouseX - localDetections[index].x1,
|
||||
y: mouseY - localDetections[index].y1,
|
||||
});
|
||||
setMouseDownPos({x: mouseX, y: mouseY})
|
||||
|
||||
};
|
||||
|
||||
const handleResize = (e, index, position) => {
|
||||
e.stopPropagation();
|
||||
setResizeData({ index, position });
|
||||
if (!localSelectedIndices.includes(index)) {
|
||||
if (!e.ctrlKey) {
|
||||
setLocalSelectedIndices([index]);
|
||||
onSelectionChange && onSelectionChange([index]);
|
||||
}
|
||||
else{
|
||||
const newSelectedIndices = [...localSelectedIndices, index];
|
||||
setLocalSelectedIndices(newSelectedIndices);
|
||||
onSelectionChange && onSelectionChange(newSelectedIndices);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
width: `${width}px`,
|
||||
height: `${height}px`,
|
||||
pointerEvents: 'auto',
|
||||
}}
|
||||
>
|
||||
<div ref={containerRef}
|
||||
style={{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
pointerEvents: 'auto',
|
||||
}}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseUp={handleMouseUp}
|
||||
onMouseLeave={handleMouseUp}
|
||||
>
|
||||
{children}
|
||||
<DetectionContainer
|
||||
detections={localDetections}
|
||||
selectedDetectionIndices={localSelectedIndices}
|
||||
onDetectionMouseDown={handleDetectionMouseDown}
|
||||
currentDetection={currentDetection}
|
||||
onResize={handleResize}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CanvasEditor;
|
||||
@@ -0,0 +1,85 @@
|
||||
// src/components/Detection.js
|
||||
import React from 'react';
|
||||
|
||||
function Detection({ detection, isSelected, onDetectionMouseDown, onResize }) { // Corrected prop name
|
||||
if (!detection || !detection.class) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { Color: color } = detection.class;
|
||||
|
||||
if (!color) {
|
||||
console.error("Color is undefined for detection class:", detection.class);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Use startsWith to correctly handle RGBA and hex colors
|
||||
const backgroundColor = color.startsWith('rgba') ? color : color.replace('1', '0.4'); // Ensure opacity for background
|
||||
const borderColor = color.startsWith('rgba') ? color.replace('0.4', '1') : color; // Ensure full opacity for border
|
||||
|
||||
const resizeHandleSize = 8;
|
||||
|
||||
const resizeHandles = [
|
||||
{ position: 'top-left', cursor: 'nwse-resize', x: -resizeHandleSize, y: -resizeHandleSize, },
|
||||
{ position: 'top-right', cursor: 'nesw-resize', x: detection.x2 - detection.x1 , y: -resizeHandleSize,},
|
||||
{ position: 'bottom-left', cursor: 'nesw-resize', x: -resizeHandleSize, y: detection.y2 - detection.y1, },
|
||||
{ position: 'bottom-right', cursor: 'nwse-resize', x: detection.x2 - detection.x1, y: detection.y2 - detection.y1 , },
|
||||
{ position: 'top-middle', cursor: 'ns-resize', x: (detection.x2 - detection.x1) / 2 - resizeHandleSize / 2, y: -resizeHandleSize },
|
||||
{ position: 'bottom-middle', cursor: 'ns-resize', x: (detection.x2 - detection.x1) / 2 - resizeHandleSize / 2, y: detection.y2 - detection.y1 },
|
||||
{ position: 'left-middle', cursor: 'ew-resize', x: -resizeHandleSize, y: (detection.y2 - detection.y1) / 2 - resizeHandleSize / 2 },
|
||||
{ position: 'right-middle', cursor: 'ew-resize', x: detection.x2 - detection.x1, y: (detection.y2 - detection.y1) / 2 - resizeHandleSize / 2 },
|
||||
];
|
||||
|
||||
const style = {
|
||||
position: 'absolute',
|
||||
left: `${detection.x1}px`,
|
||||
top: `${detection.y1}px`,
|
||||
width: `${detection.x2 - detection.x1}px`,
|
||||
height: `${detection.y2 - detection.y1}px`,
|
||||
backgroundColor: backgroundColor, // Use the calculated backgroundColor
|
||||
border: `2px solid ${borderColor}`, // Use the calculated borderColor
|
||||
boxSizing: 'border-box',
|
||||
cursor: isSelected ? 'move' : 'default',
|
||||
pointerEvents: 'auto',
|
||||
};
|
||||
|
||||
if (isSelected) {
|
||||
style.border = `3px solid black`;
|
||||
style.boxShadow = `0 0 4px 2px ${borderColor}`; // Use calculated border color
|
||||
}
|
||||
|
||||
const handleMouseDown = (e) => {
|
||||
e.stopPropagation();
|
||||
onDetectionMouseDown(e); // Corrected prop name
|
||||
};
|
||||
|
||||
const handleResizeMouseDown = (e, position) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
onResize(e, position);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={style} onMouseDown={handleMouseDown}>
|
||||
{isSelected && resizeHandles.map((handle) => (
|
||||
<div
|
||||
key={handle.position}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: `${handle.x}px`,
|
||||
top: `${handle.y}px`,
|
||||
width: `${resizeHandleSize}px`,
|
||||
height: `${resizeHandleSize}px`,
|
||||
backgroundColor: 'black',
|
||||
cursor: handle.cursor,
|
||||
pointerEvents: 'auto',
|
||||
}}
|
||||
onMouseDown={(e) => handleResizeMouseDown(e, handle.position)}
|
||||
/>
|
||||
))}
|
||||
<span style={{ color: 'white', fontSize: '12px', position: "absolute", top: "-18px", left: "0px" }}>{detection.class.Name}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Detection;
|
||||
@@ -0,0 +1,85 @@
|
||||
// --- START OF FILE DetectionClassList.js --- (No changes needed)
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import DetectionClass from '../models/DetectionClass';
|
||||
|
||||
function DetectionClassList({ onClassSelect }) {
|
||||
const [detectionClasses, setDetectionClasses] = useState([]);
|
||||
const [selectedClassId, setSelectedClassId] = useState(null); // Use an ID for selection
|
||||
|
||||
const colors = [ // Define colors *inside* the component
|
||||
"#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF", "#000000",
|
||||
"#800000", "#008000", "#000080", "#808000", "#800080", "#008080", "#808080",
|
||||
"#C00000", "#00C000", "#0000C0", "#C0C000", "#C000C0", "#00C0C0", "#C0C0C0",
|
||||
"#400000", "#004000", "#000040", "#404000", "#400040", "#004040", "#404040",
|
||||
"#200000", "#002000", "#000020", "#202000", "#200020", "#002020", "#202020",
|
||||
"#600000", "#006000", "#000060", "#606000", "#600060", "#006060", "#606060",
|
||||
"#A00000", "#00A000", "#0000A0", "#A0A000", "#A000A0", "#00A0A0", "#A0A0A0",
|
||||
"#E00000", "#00E000", "#0000E0", "#E0E000", "#E000E0", "#00E0E0", "#E0E0E0"
|
||||
];
|
||||
|
||||
// Calculate color with opacity
|
||||
const calculateColor = (id, opacity = '0.2') => {
|
||||
const hexColor = colors[id % colors.length];
|
||||
const r = parseInt(hexColor.slice(1, 3), 16);
|
||||
const g = parseInt(hexColor.slice(3, 5), 16);
|
||||
const b = parseInt(hexColor.slice(5, 7), 16);
|
||||
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetch('config.json') // Make sure this path is correct
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const classObjects = data.classes.map(cls => {
|
||||
const color = calculateColor(cls.Id, '1'); // Full opacity for border
|
||||
return new DetectionClass(cls.Id, cls.Name, color);
|
||||
});
|
||||
setDetectionClasses(classObjects);
|
||||
|
||||
if (classObjects.length > 0 && onClassSelect) {
|
||||
onClassSelect(classObjects[0]); // Select the first class by default
|
||||
}
|
||||
})
|
||||
.catch(error => console.error("Error loading detection classes:", error));
|
||||
}, [onClassSelect]);
|
||||
|
||||
const handleClassClick = (cls) => {
|
||||
setSelectedClassId(cls.Id); // Update the selected ID
|
||||
onClassSelect && onClassSelect(cls);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 style={{ marginTop: '15px', fontSize: '14px' }}>Classes</h3>
|
||||
<ul style={{ listStyleType: 'none', padding: 0, margin: 0 }}>
|
||||
{detectionClasses.map((cls) => {
|
||||
const backgroundColor = calculateColor(cls.Id); // Calculate background color (0.2 opacity)
|
||||
const darkBg = calculateColor(cls.Id, '0.8'); // Calculate selected background color (0.4 opacity)
|
||||
const isSelected = selectedClassId === cls.Id;
|
||||
|
||||
return (
|
||||
<li
|
||||
key={cls.Id}
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
padding: '8px',
|
||||
border: `1px solid ${isSelected ? '#000' : '#eee'}`, // Use cls.Color for the selected border
|
||||
backgroundColor: isSelected ? darkBg : backgroundColor, // Conditional background
|
||||
fontSize: '14px',
|
||||
marginBottom: '2px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
onClick={() => handleClassClick(cls)}
|
||||
>
|
||||
{cls.Name}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetectionClassList;
|
||||
@@ -0,0 +1,30 @@
|
||||
// src/components/DetectionContainer.js
|
||||
import React from 'react';
|
||||
import Detection from './Detection';
|
||||
|
||||
function DetectionContainer({ detections, selectedDetectionIndices, calculateColor, onDetectionMouseDown, currentDetection, onResize }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{detections.map((detection, index) => (
|
||||
<Detection
|
||||
key={index}
|
||||
detection={detection}
|
||||
isSelected={selectedDetectionIndices.includes(index)}
|
||||
onDetectionMouseDown={(e) => onDetectionMouseDown(e, index)}
|
||||
onResize={(e, position) => onResize(e, index, position)}
|
||||
/>
|
||||
))}
|
||||
{currentDetection && (
|
||||
<Detection
|
||||
detection={currentDetection}
|
||||
isSelected={false}
|
||||
onDetectionMouseDown={() => {}} // No-op handler for the current detection
|
||||
onResize={() => {}} // No-op handler for the current detection
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetectionContainer;
|
||||
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
|
||||
function MediaList({ files, selectedFile, onFileSelect, onDropNewFiles }) {
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||
onDrop: onDropNewFiles,
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 style={{ fontSize: '14px' }}>Files</h3>
|
||||
<ul style={{ listStyleType: 'none', padding: 0 }}>
|
||||
{files.map((file) => (
|
||||
<li
|
||||
key={file.name}
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
padding: '6px',
|
||||
borderBottom: '1px solid #eee',
|
||||
backgroundColor: selectedFile === file ? '#f0f0f0' : 'transparent',
|
||||
fontSize: '12px',
|
||||
}}
|
||||
onClick={() => onFileSelect(file)}
|
||||
>
|
||||
{file.name}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div {...getRootProps()} style={{ border: '2px dashed #ccc', padding: '8px', textAlign: 'center', marginTop: '10px', cursor: 'pointer' }}>
|
||||
<input {...getInputProps()} />
|
||||
{isDragActive ? (
|
||||
<p style={{ fontSize: '12px' }}>Drop here</p>
|
||||
) : (
|
||||
<p style={{ fontSize: '12px' }}>Drag new files</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MediaList;
|
||||
@@ -0,0 +1,48 @@
|
||||
import React, {useEffect } from 'react';
|
||||
|
||||
function VideoPlayer({ children, videoFile, currentTime, videoRef, isPlaying }) {
|
||||
|
||||
useEffect(() => {
|
||||
console.log("useEffect Videoplayer");
|
||||
if (!(videoFile && videoRef.current))
|
||||
return;
|
||||
|
||||
console.log("Setting video source:", videoFile);
|
||||
videoRef.current.src = URL.createObjectURL(videoFile);
|
||||
videoRef.current.onloadedmetadata = () => {
|
||||
|
||||
};
|
||||
}, [videoFile]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if(videoRef.current){
|
||||
// videoRef.current.currentTime = currentTime;
|
||||
// }
|
||||
// }, [currentTime, videoRef])
|
||||
//
|
||||
// useEffect(() => {
|
||||
// if (videoRef.current) {
|
||||
// if(isPlaying){
|
||||
// videoRef.current.play()
|
||||
// }
|
||||
// else{
|
||||
// videoRef.current.pause()
|
||||
// }
|
||||
// }
|
||||
// }, [isPlaying, videoRef])
|
||||
|
||||
return (
|
||||
<div style={{ position: 'relative'}}>
|
||||
{/* Video Element */}
|
||||
<div style={{ width: '100%', height: '100%'}}>
|
||||
{children}
|
||||
<video
|
||||
ref={videoRef}
|
||||
style={{ width: '100%', height: '100%', pointerEvents: isPlaying ? 'none' : 'auto' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default VideoPlayer;
|
||||
Reference in New Issue
Block a user