mirror of
https://github.com/azaion/ui.git
synced 2026-06-24 20:01:10 +00:00
Merge branch 'dev' into feat/dataset-explorer
This commit is contained in:
@@ -1,15 +1,14 @@
|
||||
import { useState, useCallback, useEffect, useRef } from 'react'
|
||||
import { useResizablePanel } from '../../hooks/useResizablePanel'
|
||||
import { api } from '../../api/client'
|
||||
import { useResizablePanel } from '../../hooks'
|
||||
import { api, endpoints } from '../../api'
|
||||
import MediaList from './MediaList'
|
||||
import VideoPlayer, { type VideoPlayerHandle } from './VideoPlayer'
|
||||
import CanvasEditor, { type CanvasEditorHandle } from './CanvasEditor'
|
||||
import AnnotationsSidebar from './AnnotationsSidebar'
|
||||
import DetectionClasses from '../../components/DetectionClasses'
|
||||
import { DetectionClasses, useFlight } from '../../components'
|
||||
import { useSavedAnnotations } from '../../components/SavedAnnotationsContext'
|
||||
import { useFlight } from '../../components/FlightContext'
|
||||
import { AnnotationSource, AnnotationStatus, MediaType } from '../../types'
|
||||
import { getClassColor, getClassNameFallback, getPhotoModeSuffix } from './classColors'
|
||||
import { getClassColor, getClassNameFallback, getPhotoModeSuffix } from '../../class-colors'
|
||||
import { captureThumbnails } from './thumbnail'
|
||||
import type { Media, AnnotationListItem, Detection } from '../../types'
|
||||
|
||||
@@ -65,9 +64,9 @@ export default function AnnotationsPage() {
|
||||
|
||||
if (!selectedMedia.path.startsWith('blob:')) {
|
||||
try {
|
||||
await api.post('/api/annotations/annotations', body)
|
||||
await api.post(endpoints.annotations.annotations(), body)
|
||||
const res = await api.get<{ items: AnnotationListItem[] }>(
|
||||
`/api/annotations/annotations?mediaId=${selectedMedia.id}&pageSize=1000`,
|
||||
endpoints.annotations.annotationsByMedia(selectedMedia.id),
|
||||
)
|
||||
setAnnotations(res.items)
|
||||
pushToStore(`saved-${crypto.randomUUID()}`)
|
||||
@@ -127,7 +126,7 @@ export default function AnnotationsPage() {
|
||||
img.crossOrigin = 'anonymous'
|
||||
img.src = selectedMedia.path.startsWith('blob:')
|
||||
? selectedMedia.path
|
||||
: `/api/annotations/media/${selectedMedia.id}/file`
|
||||
: endpoints.annotations.mediaFile(selectedMedia.id)
|
||||
await new Promise(res => { img.onload = res; img.onerror = res })
|
||||
w = img.naturalWidth
|
||||
h = img.naturalHeight
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FaDownload } from 'react-icons/fa'
|
||||
import { api } from '../../api/client'
|
||||
import { createSSE } from '../../api/sse'
|
||||
import { getClassColor } from './classColors'
|
||||
import { api, createSSE, endpoints } from '../../api'
|
||||
import { getClassColor } from '../../class-colors'
|
||||
import type { Media, AnnotationListItem, PaginatedResponse } from '../../types'
|
||||
|
||||
interface Props {
|
||||
@@ -22,10 +21,10 @@ export default function AnnotationsSidebar({ media, annotations, selectedAnnotat
|
||||
|
||||
useEffect(() => {
|
||||
if (!media) return
|
||||
return createSSE<{ annotationId: string; mediaId: string; status: number }>('/api/annotations/annotations/events', (event) => {
|
||||
return createSSE<{ annotationId: string; mediaId: string; status: number }>(endpoints.annotations.annotationEvents(), (event) => {
|
||||
if (event.mediaId === media.id) {
|
||||
api.get<PaginatedResponse<AnnotationListItem>>(
|
||||
`/api/annotations/annotations?mediaId=${media.id}&pageSize=1000`
|
||||
endpoints.annotations.annotationsByMedia(media.id),
|
||||
).then(res => onAnnotationsUpdate(res.items)).catch(() => {})
|
||||
}
|
||||
})
|
||||
@@ -36,7 +35,7 @@ export default function AnnotationsSidebar({ media, annotations, selectedAnnotat
|
||||
setDetecting(true)
|
||||
setDetectLog(['Starting AI detection...'])
|
||||
try {
|
||||
await api.post(`/api/detect/${media.id}`)
|
||||
await api.post(endpoints.detect.media(media.id))
|
||||
setDetectLog(prev => [...prev, 'Detection complete.'])
|
||||
} catch (e: any) {
|
||||
setDetectLog(prev => [...prev, `Error: ${e.message}`])
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useRef, useEffect, useState, useCallback, forwardRef, useImperativeHandle } from 'react'
|
||||
import { endpoints } from '../../api'
|
||||
import { MediaType } from '../../types'
|
||||
import type { Media, AnnotationListItem, Detection, Affiliation, CombatReadiness } from '../../types'
|
||||
import { getClassColor, getPhotoModeSuffix, getClassNameFallback } from './classColors'
|
||||
import { getClassColor, getPhotoModeSuffix, getClassNameFallback } from '../../class-colors'
|
||||
|
||||
interface Props {
|
||||
media: Media
|
||||
@@ -77,11 +78,11 @@ const CanvasEditor = forwardRef<CanvasEditorHandle, Props>(function CanvasEditor
|
||||
img.crossOrigin = 'anonymous'
|
||||
const isLocalPath = media.path.startsWith('blob:') || media.path.startsWith('data:')
|
||||
if (annotation && !isLocalPath) {
|
||||
img.src = `/api/annotations/annotations/${annotation.id}/image`
|
||||
img.src = endpoints.annotations.annotationImage(annotation.id)
|
||||
} else if (isLocalPath) {
|
||||
img.src = media.path
|
||||
} else {
|
||||
img.src = `/api/annotations/media/${media.id}/file`
|
||||
img.src = endpoints.annotations.mediaFile(media.id)
|
||||
}
|
||||
img.onload = () => {
|
||||
imgRef.current = img
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useDropzone } from 'react-dropzone'
|
||||
import { useFlight } from '../../components/FlightContext'
|
||||
import { api } from '../../api/client'
|
||||
import { useDebounce } from '../../hooks/useDebounce'
|
||||
import ConfirmDialog from '../../components/ConfirmDialog'
|
||||
import { useFlight, ConfirmDialog } from '../../components'
|
||||
import { api, endpoints } from '../../api'
|
||||
import { useDebounce } from '../../hooks'
|
||||
import { MediaType } from '../../types'
|
||||
import type { Media, PaginatedResponse, AnnotationListItem } from '../../types'
|
||||
|
||||
@@ -28,7 +27,7 @@ export default function MediaList({ selectedMedia, onSelect, onAnnotationsLoaded
|
||||
if (selectedFlight) params.set('flightId', selectedFlight.id)
|
||||
if (debouncedFilter) params.set('name', debouncedFilter)
|
||||
try {
|
||||
const res = await api.get<PaginatedResponse<Media>>(`/api/annotations/media?${params}`)
|
||||
const res = await api.get<PaginatedResponse<Media>>(endpoints.annotations.media(params.toString()))
|
||||
setMedia(prev => {
|
||||
// Keep local-only (blob URL) entries, merge with backend entries
|
||||
const local = prev.filter(m => m.path.startsWith('blob:'))
|
||||
@@ -56,7 +55,7 @@ export default function MediaList({ selectedMedia, onSelect, onAnnotationsLoaded
|
||||
}
|
||||
try {
|
||||
const res = await api.get<PaginatedResponse<AnnotationListItem>>(
|
||||
`/api/annotations/annotations?mediaId=${m.id}&pageSize=1000`
|
||||
endpoints.annotations.annotationsByMedia(m.id),
|
||||
)
|
||||
onAnnotationsLoaded(res.items)
|
||||
} catch {
|
||||
@@ -73,7 +72,7 @@ export default function MediaList({ selectedMedia, onSelect, onAnnotationsLoaded
|
||||
setDeleteId(null)
|
||||
return
|
||||
}
|
||||
try { await api.delete(`/api/annotations/media/${deleteId}`) } catch {}
|
||||
try { await api.delete(endpoints.annotations.mediaItem(deleteId)) } catch {}
|
||||
setDeleteId(null)
|
||||
fetchMedia()
|
||||
}
|
||||
@@ -88,7 +87,7 @@ export default function MediaList({ selectedMedia, onSelect, onAnnotationsLoaded
|
||||
const form = new FormData()
|
||||
form.append('waypointId', '')
|
||||
for (const file of arr) form.append('files', file)
|
||||
await api.upload('/api/annotations/media/batch', form)
|
||||
await api.upload(endpoints.annotations.mediaBatch(), form)
|
||||
fetchMedia()
|
||||
return
|
||||
} catch {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useRef, useState, useCallback, useEffect, forwardRef, useImperativeHandle } from 'react'
|
||||
import { FaPlay, FaPause, FaStop, FaStepBackward, FaStepForward, FaVolumeMute, FaVolumeUp } from 'react-icons/fa'
|
||||
import { endpoints } from '../../api'
|
||||
import type { Media } from '../../types'
|
||||
|
||||
interface Props {
|
||||
@@ -38,7 +39,7 @@ const VideoPlayer = forwardRef<VideoPlayerHandle, Props>(function VideoPlayer({
|
||||
|
||||
const videoUrl = media.path.startsWith('blob:')
|
||||
? media.path
|
||||
: `/api/annotations/media/${media.id}/file`
|
||||
: endpoints.annotations.mediaFile(media.id)
|
||||
|
||||
const stepFrames = useCallback((count: number) => {
|
||||
const video = videoRef.current
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
const CLASS_COLORS = [
|
||||
'#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF', '#00FFFF',
|
||||
'#800000', '#008000', '#000080', '#808000', '#800080', '#008080',
|
||||
]
|
||||
|
||||
export const FALLBACK_CLASS_NAMES = [
|
||||
'Car', 'Person', 'Truck', 'Bicycle', 'Motorcycle', 'Bus',
|
||||
'Animal', 'Tree', 'Building', 'Sign', 'Boat', 'Plane',
|
||||
]
|
||||
|
||||
export function getClassColor(classNum: number): string {
|
||||
const base = classNum % 20
|
||||
return CLASS_COLORS[base % CLASS_COLORS.length]
|
||||
}
|
||||
|
||||
export function getPhotoModeSuffix(classNum: number): string {
|
||||
const mode = Math.floor(classNum / 20)
|
||||
return mode === 1 ? ' (winter)' : mode === 2 ? ' (night)' : ''
|
||||
}
|
||||
|
||||
export function getClassNameFallback(classNum: number): string {
|
||||
const base = classNum % 20
|
||||
return FALLBACK_CLASS_NAMES[base % FALLBACK_CLASS_NAMES.length] ?? `#${classNum}`
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export { default as AnnotationsPage } from './AnnotationsPage'
|
||||
// CanvasEditor remains in the Public API while F2 (cross-feature edge to
|
||||
// 07_dataset) is open. Closing F2 will remove this re-export.
|
||||
export { default as CanvasEditor } from './CanvasEditor'
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MediaType } from '../../types'
|
||||
import type { Detection, Media } from '../../types'
|
||||
import { getClassColor } from './classColors'
|
||||
import { getClassColor } from '../../class-colors'
|
||||
|
||||
const THUMB_MAX = 240
|
||||
const CROP_PAD = 0.15
|
||||
|
||||
Reference in New Issue
Block a user