add css files for components

This commit is contained in:
Armen Rohalov
2025-03-26 20:30:27 +02:00
parent e18157648c
commit 9e6596f1e0
15 changed files with 301 additions and 253 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import AnnotationMain from './components/AnnotationMain'; import AnnotationMain from './components/AnnotationMain/AnnotationMain';
function App() { function App() {
-91
View File
@@ -1,91 +0,0 @@
import React from 'react';
function AnnotationControls({ onFrameBackward, onPlayPause, isPlaying, onFrameForward, onSaveAnnotation, onDelete }) {
return (
<div style={{
display: 'flex',
justifyContent: 'center',
position: 'relative',
zIndex: 1,
padding: '10px',
background: '#f5f5f5',
borderRadius: '4px',
marginTop: '10px'
}}>
<button
style={{
padding: '10px 20px',
fontSize: '16px',
margin: '0 5px',
background: '#e0e0e0',
border: '1px solid #ccc',
borderRadius: '4px',
cursor: 'pointer'
}}
onClick={onFrameBackward}
title="Previous Frame"
>
</button>
<button
style={{
padding: '10px 20px',
fontSize: '16px',
margin: '0 5px',
background: isPlaying ? '#ff8a8a' : '#8aff8a',
border: '1px solid #ccc',
borderRadius: '4px',
cursor: 'pointer'
}}
onClick={onPlayPause}
>
{isPlaying ? '⏸️ Pause' : '▶️ Play'}
</button>
<button
style={{
padding: '10px 20px',
fontSize: '16px',
margin: '0 5px',
background: '#e0e0e0',
border: '1px solid #ccc',
borderRadius: '4px',
cursor: 'pointer'
}}
onClick={onFrameForward}
title="Next Frame"
>
</button>
<button
style={{
padding: '10px 20px',
fontSize: '16px',
margin: '0 5px',
background: '#8ad4ff',
border: '1px solid #ccc',
borderRadius: '4px',
cursor: 'pointer'
}}
onClick={onSaveAnnotation}
>
💾 Save
</button>
<button
style={{
padding: '10px 20px',
fontSize: '16px',
margin: '0 5px',
background: '#ffb38a',
border: '1px solid #ccc',
borderRadius: '4px',
cursor: 'pointer'
}}
onClick={onDelete}
>
🗑 Delete
</button>
</div>
);
}
export default AnnotationControls;
@@ -0,0 +1,39 @@
.buttons-group {
display: flex;
justify-content: center;
position: relative;
z-index: 1;
padding: 10px;
background: #f5f5f5;
border-radius: 4px;
margin-top: 10px;
}
.control-btn {
padding: 10px 20px;
font-size: 16px;
margin: 0 5px;
border: 1px solid #ccc;
border-radius: 4px;
cursor: pointer;
}
.play-btn{
background: #8aff8a;
}
.pause-btn{
background: #ff8a8a;
}
.arrow-btn{
background: #e0e0e0;
}
.save-btn{
background: #8ad4ff;
}
.delete-btn{
background: #ffb38a;
}
@@ -0,0 +1,43 @@
import React from 'react';
import './AnnotationControls.css';
function AnnotationControls({ onFrameBackward, onPlayPause, isPlaying, onFrameForward, onSaveAnnotation, onDelete }) {
return (
<div className='buttons-group' >
<button
className='control-btn arrow-btn'
onClick={onFrameBackward}
title="Previous Frame"
>
</button>
<button
className={isPlaying ? 'control-btn pause-btn': 'control-btn play-btn'}
onClick={onPlayPause}
>
{isPlaying ? '⏸️ Pause' : '▶️ Play'}
</button>
<button
className='control-btn arrow-btn'
onClick={onFrameForward}
title="Next Frame"
>
</button>
<button
className='control-btn save-btn'
onClick={onSaveAnnotation}
>
💾 Save
</button>
<button
className='control-btn delete-btn'
onClick={onDelete}
>
🗑 Delete
</button>
</div>
);
}
export default AnnotationControls;
+1 -1
View File
@@ -5,7 +5,7 @@ import React from 'react';
function AnnotationList({ annotations, onAnnotationClick }) { function AnnotationList({ annotations, onAnnotationClick }) {
return ( return (
<div> <div>
<h3>Annotations</h3> <h3 className='menu-title'>Annotations</h3>
<ul> <ul>
{annotations.map((annotation, index) => ( {annotations.map((annotation, index) => (
<li key={index} onClick={() => onAnnotationClick(annotation.time)}> <li key={index} onClick={() => onAnnotationClick(annotation.time)}>
@@ -0,0 +1,54 @@
.content-wrapper {
display: flex;
height: 100vh;
overflow: hidden;
}
.side-menu {
display: flex;
flex-direction: column;
width: 15%;
height: 100%;
}
.left-menu{
border-right: 1px solid #ccc;
}
.right-menu{
overflow-y: auto;
border-left: 1px solid #ccc;
}
.player-wrapper {
width: 70%;
height: 100%;
display: flex;
flex-direction: column;
}
.error-message {
background: #ffdddd;
color: #d8000c;
padding: 6px;
margin: 6px;
border-radius: 4px;
}
.player-container {
display: flex;
flex: 1;
flex-direction: column;
position: relative;
min-height: 0;
}
.player-block {
display: flex;
flex: 1;
position: relative;
background: #000;
justify-content: center;
align-items: center;
overflow: hidden;
}
@@ -1,12 +1,13 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import VideoPlayer from './VideoPlayer'; import VideoPlayer from '../VideoPlayer/VideoPlayer';
import AnnotationList from './AnnotationList'; import AnnotationList from '../AnnotationList';
import MediaList from './MediaList'; import MediaList from '../MediaList/MediaList';
import DetectionClassList from './DetectionClassList'; import DetectionClassList from '../DetectionClassList/DetectionClassList';
import CanvasEditor from './CanvasEditor'; import CanvasEditor from '../CanvasEditor/CanvasEditor';
import * as AnnotationService from '../services/AnnotationService'; import * as AnnotationService from '../../services/AnnotationService';
import AnnotationControls from './AnnotationControls'; import AnnotationControls from '../AnnotationControls/AnnotationControls';
import saveAnnotation from '../services/DataHandler'; import saveAnnotation from '../../services/DataHandler';
import './AnnotationMain.css';
function AnnotationMain() { function AnnotationMain() {
const [files, setFiles] = useState([]); const [files, setFiles] = useState([]);
@@ -169,60 +170,27 @@ function AnnotationMain() {
}, []); }, []);
return ( return (
<div style={{ display: 'flex', height: '100vh', overflow: 'hidden' }}> <div className='content-wrapper' >
<div style={{ <div className='side-menu left-menu' >
width: '15%',
height: '100%',
overflowY: 'auto',
borderRight: '1px solid #ccc',
display: 'flex',
flexDirection: 'column'
}}>
<MediaList <MediaList
files={files} files={files}
selectedFile={selectedFile} selectedFile={selectedFile}
onFileSelect={handleFileSelect} onFileSelect={handleFileSelect}
onDropNewFiles={handleDropNewFiles} onDropNewFiles={handleDropNewFiles}
/> />
<div style={{ flexGrow: 1 }}>
<DetectionClassList onClassSelect={handleClassSelect} /> <DetectionClassList onClassSelect={handleClassSelect} />
</div> </div>
</div>
<div style={{ <div className='player-wrapper' >
width: '70%',
height: '100%',
display: 'flex',
flexDirection: 'column'
}}>
{errorMessage && ( {errorMessage && (
<div style={{ <div className='error-message' >
backgroundColor: '#ffdddd',
color: '#d8000c',
padding: '6px',
margin: '6px',
borderRadius: '4px'
}}>
{errorMessage} {errorMessage}
</div> </div>
)} )}
<div style={{ <div className='player-container' ref={containerRef}>
flex: 1, <div className='player-block' >
display: 'flex',
flexDirection: 'column',
position: 'relative',
minHeight: 0
}} ref={containerRef}>
<div style={{
flex: 1,
position: 'relative',
backgroundColor: '#000',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
overflow: 'hidden'
}}>
<VideoPlayer <VideoPlayer
videoFile={selectedFile} videoFile={selectedFile}
currentTime={currentTime} currentTime={currentTime}
@@ -254,12 +222,7 @@ function AnnotationMain() {
</div> </div>
</div> </div>
<div style={{ <div className='side-menu right-menu'>
width: '15%',
height: '100%',
overflowY: 'auto',
borderLeft: '1px solid #ccc'
}}>
<AnnotationList <AnnotationList
annotations={Object.values(annotations)} annotations={Object.values(annotations)}
onAnnotationClick={handleAnnotationClick} onAnnotationClick={handleAnnotationClick}
@@ -0,0 +1,15 @@
.editor-container {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
pointer-events: auto;
}
.canvas-editor {
position: absolute;
width: 100%;
height: 100%;
pointer-events: auto;
}
@@ -1,6 +1,7 @@
import React, { useRef, useState, useEffect } from 'react'; import React, { useRef, useState, useEffect } from 'react';
import * as AnnotationService from '../services/AnnotationService'; import * as AnnotationService from '../../services/AnnotationService';
import DetectionContainer from './DetectionContainer'; import DetectionContainer from '../DetectionContainer';
import './CanvasEditor.css';
function CanvasEditor({ function CanvasEditor({
width, width,
@@ -12,7 +13,7 @@ function CanvasEditor({
onSelectionChange, onSelectionChange,
children, children,
detectionClass detectionClass
}) { }) {
const containerRef = useRef(null); const containerRef = useRef(null);
const [currentDetection, setCurrentDetection] = useState(initialCurrentDetection); const [currentDetection, setCurrentDetection] = useState(initialCurrentDetection);
const [mouseDownPos, setMouseDownPos] = useState(null); const [mouseDownPos, setMouseDownPos] = useState(null);
@@ -197,7 +198,7 @@ function CanvasEditor({
x: mouseX - localDetections[index].x1, x: mouseX - localDetections[index].x1,
y: mouseY - localDetections[index].y1, y: mouseY - localDetections[index].y1,
}); });
setMouseDownPos({x: mouseX, y: mouseY}); setMouseDownPos({ x: mouseX, y: mouseY });
setIsDragging(true); setIsDragging(true);
}; };
@@ -209,7 +210,7 @@ function CanvasEditor({
setLocalSelectedIndices([index]); setLocalSelectedIndices([index]);
onSelectionChange && onSelectionChange([index]); onSelectionChange && onSelectionChange([index]);
} }
else{ else {
const newSelectedIndices = [...localSelectedIndices, index]; const newSelectedIndices = [...localSelectedIndices, index];
setLocalSelectedIndices(newSelectedIndices); setLocalSelectedIndices(newSelectedIndices);
onSelectionChange && onSelectionChange(newSelectedIndices); onSelectionChange && onSelectionChange(newSelectedIndices);
@@ -240,23 +241,9 @@ function CanvasEditor({
}, [isDragging, resizeData, mouseDownPos]); }, [isDragging, resizeData, mouseDownPos]);
return ( return (
<div <div className='editor-container' >
style={{ <div className='canvas-editor'
position: 'absolute', ref={containerRef}
width: '100%',
height: '100%',
top: 0,
left: 0,
pointerEvents: 'auto',
}}
>
<div ref={containerRef}
style={{
position: 'absolute',
width: '100%',
height: '100%',
pointerEvents: 'auto',
}}
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove} onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp} onMouseUp={handleMouseUp}
@@ -0,0 +1,19 @@
.class-list {
flex-grow: 1;
}
.class-list-group {
list-style-type: none;
padding: 0;
margin: 0;
}
.class-list-item {
display: flex;
align-items: center;
cursor: pointer;
padding: 8px;
font-size: 14px;
margin-bottom: 2px;
border-radius: 4px;
}
@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import DetectionClass from '../models/DetectionClass'; import DetectionClass from '../../models/DetectionClass';
import './DetectionClassList.css';
function DetectionClassList({ onClassSelect }) { function DetectionClassList({ onClassSelect }) {
const [detectionClasses, setDetectionClasses] = useState([]); const [detectionClasses, setDetectionClasses] = useState([]);
@@ -83,9 +84,9 @@ function DetectionClassList({ onClassSelect }) {
}; };
return ( return (
<div> <div className='class-list'>
<h3 style={{ marginTop: '15px', fontSize: '14px' }}>Classes</h3> <h3 className='menu-title'>Classes</h3>
<ul style={{ listStyleType: 'none', padding: 0, margin: 0 }}> <ul className='class-list-group' >
{detectionClasses.map((cls) => { {detectionClasses.map((cls) => {
const backgroundColor = calculateColor(cls.Id); const backgroundColor = calculateColor(cls.Id);
const darkBg = calculateColor(cls.Id, '0.8'); const darkBg = calculateColor(cls.Id, '0.8');
@@ -94,16 +95,10 @@ function DetectionClassList({ onClassSelect }) {
return ( return (
<li <li
key={cls.Id} key={cls.Id}
className='class-list-item'
style={{ style={{
cursor: 'pointer',
padding: '8px',
border: `1px solid ${isSelected ? '#000' : '#eee'}`, border: `1px solid ${isSelected ? '#000' : '#eee'}`,
backgroundColor: isSelected ? darkBg : backgroundColor, backgroundColor: isSelected ? darkBg : backgroundColor,
fontSize: '14px',
marginBottom: '2px',
display: 'flex',
alignItems: 'center',
borderRadius: '4px',
}} }}
onClick={() => handleClassClick(cls)} onClick={() => handleClassClick(cls)}
> >
+28
View File
@@ -0,0 +1,28 @@
.menu-title {
font-size: 14px;
margin: 15px 0;
}
.file-list-group {
padding: 0;
list-style-type: none;
}
.file-list-item {
cursor: pointer;
padding: 6px;
border-bottom: 1px solid #eee;
font-size: 12px;
}
.label {
font-size: 12px;
}
.file-input-block {
border: 2px dashed #ccc;
padding: 8px;
text-align: center;
margin-top: 10px;
cursor: pointer;
}
@@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { useDropzone } from 'react-dropzone'; import { useDropzone } from 'react-dropzone';
import './MediaList.css'
function MediaList({ files, selectedFile, onFileSelect, onDropNewFiles }) { function MediaList({ files, selectedFile, onFileSelect, onDropNewFiles }) {
const { getRootProps, getInputProps, isDragActive } = useDropzone({ const { getRootProps, getInputProps, isDragActive } = useDropzone({
@@ -7,18 +8,15 @@ function MediaList({ files, selectedFile, onFileSelect, onDropNewFiles }) {
}); });
return ( return (
<div> <div className='explorer'>
<h3 style={{ fontSize: '14px' }}>Files</h3> <h3 className='menu-title' >Files</h3>
<ul style={{ listStyleType: 'none', padding: 0 }}> <ul className='file-list-group' >
{files.map((file) => ( {files.map((file) => (
<li <li
className='file-list-item'
key={file.name} key={file.name}
style={{ style={{
cursor: 'pointer', backgroundColor: selectedFile === file ? '#f0f0f0' : 'transparent'
padding: '6px',
borderBottom: '1px solid #eee',
backgroundColor: selectedFile === file ? '#f0f0f0' : 'transparent',
fontSize: '12px',
}} }}
onClick={() => onFileSelect(file)} onClick={() => onFileSelect(file)}
> >
@@ -26,12 +24,12 @@ function MediaList({ files, selectedFile, onFileSelect, onDropNewFiles }) {
</li> </li>
))} ))}
</ul> </ul>
<div {...getRootProps()} style={{ border: '2px dashed #ccc', padding: '8px', textAlign: 'center', marginTop: '10px', cursor: 'pointer' }}> <div className='file-input-block' {...getRootProps()} >
<input {...getInputProps()} /> <input {...getInputProps()} />
{isDragActive ? ( {isDragActive ? (
<p style={{ fontSize: '12px' }}>Drop here</p> <p className='label' >Drop here</p>
) : ( ) : (
<p style={{ fontSize: '12px' }}>Drag new files</p> <p className='label' >Drag new files</p>
)} )}
</div> </div>
</div> </div>
@@ -0,0 +1,40 @@
.player {
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: #000;
overflow: hidden;
}
.video {
width: 100%;
height: auto;
max-height: 100%;
display: block;
object-fit: contain;
pointer-events: none;
}
.player-error {
position: absolute;
top: 10px;
left: 10px;
background: rgba(255, 0, 0, 0.7);
color: white;
padding: 5px;
border-radius: 3px;
font-size: 12px;
z-index: 10;
}
.player-item{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: auto;
}
@@ -1,4 +1,5 @@
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import './VideoPlayer.css';
function VideoPlayer({ children, videoFile, currentTime, videoRef, isPlaying, onSizeChanged, onSetCurrentTime }) { function VideoPlayer({ children, videoFile, currentTime, videoRef, isPlaying, onSizeChanged, onSetCurrentTime }) {
const containerRef = useRef(null); const containerRef = useRef(null);
@@ -131,58 +132,15 @@ function VideoPlayer({ children, videoFile, currentTime, videoRef, isPlaying, on
}, [onSetCurrentTime, isPlaying]); }, [onSetCurrentTime, isPlaying]);
return ( return (
<div <div className='player' ref={containerRef} >
ref={containerRef} <video className='video' ref={videoRef} preload="auto" playsInline muted />
style={{
position: 'relative',
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#000',
overflow: 'hidden'
}}
>
<video
ref={videoRef}
style={{
width: '100%',
height: 'auto',
maxHeight: '100%',
display: 'block',
objectFit: 'contain',
pointerEvents: 'none'
}}
preload="auto"
playsInline
muted
/>
{playbackError && ( {playbackError && (
<div style={{ <div className='player-error' >
position: 'absolute',
top: '10px',
left: '10px',
background: 'rgba(255,0,0,0.7)',
color: 'white',
padding: '5px',
borderRadius: '3px',
fontSize: '12px',
zIndex: 10
}}>
{playbackError} {playbackError}
</div> </div>
)} )}
<div <div className='player-item'>
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
pointerEvents: 'auto'
}}
>
{children} {children}
</div> </div>
</div> </div>