bugfixes
This commit is contained in:
parent
d073473d2f
commit
8b5e0b6101
BIN
backend/dist/nsfwapp-linux-amd64
vendored
BIN
backend/dist/nsfwapp-linux-amd64
vendored
Binary file not shown.
BIN
backend/dist/nsfwapp.exe
vendored
BIN
backend/dist/nsfwapp.exe
vendored
Binary file not shown.
BIN
backend/dist/nsfwapp_amd64.deb
vendored
BIN
backend/dist/nsfwapp_amd64.deb
vendored
Binary file not shown.
@ -1,7 +1,7 @@
|
|||||||
// frontend\src\components\ui\TrainingTab.tsx
|
// frontend\src\components\ui\TrainingTab.tsx
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties } from 'react'
|
import { Fragment, useCallback, useEffect, useMemo, useRef, useState, type CSSProperties } from 'react'
|
||||||
import Button from './Button'
|
import Button from './Button'
|
||||||
import LoadingSpinner from './LoadingSpinner'
|
import LoadingSpinner from './LoadingSpinner'
|
||||||
import { formatDuration } from './formatters'
|
import { formatDuration } from './formatters'
|
||||||
@ -637,11 +637,15 @@ function poseKeypointLabel(name?: string | null) {
|
|||||||
return POSE_KEYPOINT_LABELS[key] || key.replace(/_/g, ' ') || 'Punkt'
|
return POSE_KEYPOINT_LABELS[key] || key.replace(/_/g, ' ') || 'Punkt'
|
||||||
}
|
}
|
||||||
|
|
||||||
function poseKeypointMarkerClassName(present: boolean, pending: boolean) {
|
function poseKeypointMarkerClassName(present: boolean, pending: boolean, highlighted = false) {
|
||||||
if (pending) {
|
if (pending) {
|
||||||
return 'fill-amber-500 stroke-white/90 dark:stroke-gray-950/90'
|
return 'fill-amber-500 stroke-white/90 dark:stroke-gray-950/90'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (highlighted) {
|
||||||
|
return 'fill-cyan-500 stroke-white/95 dark:stroke-gray-950/90'
|
||||||
|
}
|
||||||
|
|
||||||
if (present) {
|
if (present) {
|
||||||
return 'fill-blue-500 stroke-white/90 dark:stroke-gray-950/90'
|
return 'fill-blue-500 stroke-white/90 dark:stroke-gray-950/90'
|
||||||
}
|
}
|
||||||
@ -913,20 +917,6 @@ function poseCoordPx(value: number, size: number) {
|
|||||||
return clamp01(Number(value)) * Math.max(0, Number(size) || 0)
|
return clamp01(Number(value)) * Math.max(0, Number(size) || 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
function poseBoxPixelStyle(box: TrainingBox, layerWidth: number, layerHeight: number): CSSProperties {
|
|
||||||
const x = clamp01(Number(box.x))
|
|
||||||
const y = clamp01(Number(box.y))
|
|
||||||
const w = Math.max(0, Math.min(1 - x, clamp01(Number(box.w))))
|
|
||||||
const h = Math.max(0, Math.min(1 - y, clamp01(Number(box.h))))
|
|
||||||
|
|
||||||
return {
|
|
||||||
left: poseCoordPx(x, layerWidth),
|
|
||||||
top: poseCoordPx(y, layerHeight),
|
|
||||||
width: w * Math.max(0, Number(layerWidth) || 0),
|
|
||||||
height: h * Math.max(0, Number(layerHeight) || 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function backendText(data: any, fallback: string) {
|
function backendText(data: any, fallback: string) {
|
||||||
return String(
|
return String(
|
||||||
data?.message ||
|
data?.message ||
|
||||||
@ -4250,6 +4240,7 @@ export default function TrainingTab(props: {
|
|||||||
setShowPoseSkeleton(false)
|
setShowPoseSkeleton(false)
|
||||||
setActivePosePersonIndex(null)
|
setActivePosePersonIndex(null)
|
||||||
setPendingPoseKeypoint(null)
|
setPendingPoseKeypoint(null)
|
||||||
|
setHoveredPoseKeypoint(null)
|
||||||
if (poseKeypointMapAnimationFrameRef.current !== null) {
|
if (poseKeypointMapAnimationFrameRef.current !== null) {
|
||||||
cancelAnimationFrame(poseKeypointMapAnimationFrameRef.current)
|
cancelAnimationFrame(poseKeypointMapAnimationFrameRef.current)
|
||||||
poseKeypointMapAnimationFrameRef.current = null
|
poseKeypointMapAnimationFrameRef.current = null
|
||||||
@ -4409,6 +4400,7 @@ export default function TrainingTab(props: {
|
|||||||
const [boxInteraction, setBoxInteraction] = useState<BoxInteraction | null>(null)
|
const [boxInteraction, setBoxInteraction] = useState<BoxInteraction | null>(null)
|
||||||
const [poseInteraction, setPoseInteraction] = useState<PoseInteraction | null>(null)
|
const [poseInteraction, setPoseInteraction] = useState<PoseInteraction | null>(null)
|
||||||
const [pendingPoseKeypoint, setPendingPoseKeypoint] = useState<PoseKeypointPlacement | null>(null)
|
const [pendingPoseKeypoint, setPendingPoseKeypoint] = useState<PoseKeypointPlacement | null>(null)
|
||||||
|
const [hoveredPoseKeypoint, setHoveredPoseKeypoint] = useState<PoseKeypointPlacement | null>(null)
|
||||||
const [poseKeypointMapView, setPoseKeypointMapView] = useState<PoseKeypointMapView>('body')
|
const [poseKeypointMapView, setPoseKeypointMapView] = useState<PoseKeypointMapView>('body')
|
||||||
const [poseKeypointMapViewBoxValue, setPoseKeypointMapViewBoxValue] =
|
const [poseKeypointMapViewBoxValue, setPoseKeypointMapViewBoxValue] =
|
||||||
useState(POSE_BODY_MAP_VIEW_BOX)
|
useState(POSE_BODY_MAP_VIEW_BOX)
|
||||||
@ -4567,6 +4559,7 @@ export default function TrainingTab(props: {
|
|||||||
setShowPoseSkeleton(false)
|
setShowPoseSkeleton(false)
|
||||||
setActivePosePersonIndex(null)
|
setActivePosePersonIndex(null)
|
||||||
setPendingPoseKeypoint(null)
|
setPendingPoseKeypoint(null)
|
||||||
|
setHoveredPoseKeypoint(null)
|
||||||
|
|
||||||
poseFaceImagePanGestureRef.current = null
|
poseFaceImagePanGestureRef.current = null
|
||||||
poseFaceImagePanCenterRef.current = null
|
poseFaceImagePanCenterRef.current = null
|
||||||
@ -9782,7 +9775,39 @@ export default function TrainingTab(props: {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`pose-list-point-${personIndex}-${poseKeypointId(point.name)}-${pointIndex}`}
|
key={`pose-list-point-${personIndex}-${poseKeypointId(point.name)}-${pointIndex}`}
|
||||||
className="flex items-center justify-between gap-2 rounded-lg border border-gray-200 bg-white px-2 py-1.5 text-[11px] shadow-sm dark:border-white/10 dark:bg-gray-900/70"
|
className={[
|
||||||
|
'flex items-center justify-between gap-2 rounded-lg border border-gray-200 bg-white px-2 py-1.5 text-[11px] shadow-sm transition',
|
||||||
|
uiLocked
|
||||||
|
? 'cursor-not-allowed opacity-70'
|
||||||
|
: 'cursor-pointer hover:border-cyan-200 hover:bg-cyan-50/50 dark:hover:border-cyan-300/30 dark:hover:bg-cyan-500/10',
|
||||||
|
'dark:border-white/10 dark:bg-gray-900/70',
|
||||||
|
].join(' ')}
|
||||||
|
onPointerEnter={() => {
|
||||||
|
if (uiLocked) return
|
||||||
|
setHoveredPoseKeypoint({
|
||||||
|
personIndex,
|
||||||
|
keypointName: poseKeypointId(point.name),
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
onPointerLeave={() => {
|
||||||
|
const keypointName = poseKeypointId(point.name)
|
||||||
|
setHoveredPoseKeypoint((current) =>
|
||||||
|
current?.personIndex === personIndex &&
|
||||||
|
current.keypointName === keypointName
|
||||||
|
? null
|
||||||
|
: current
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation()
|
||||||
|
if (uiLocked) return
|
||||||
|
setShowPoseSkeleton(true)
|
||||||
|
setActivePosePersonIndex(personIndex)
|
||||||
|
setPendingPoseKeypoint({
|
||||||
|
personIndex,
|
||||||
|
keypointName: poseKeypointId(point.name),
|
||||||
|
})
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<div className="truncate font-bold text-gray-800 dark:text-gray-100">
|
<div className="truncate font-bold text-gray-800 dark:text-gray-100">
|
||||||
@ -10244,6 +10269,9 @@ export default function TrainingTab(props: {
|
|||||||
const pending =
|
const pending =
|
||||||
pendingPoseKeypoint?.personIndex === targetIndex &&
|
pendingPoseKeypoint?.personIndex === targetIndex &&
|
||||||
pendingPoseKeypoint.keypointName === shape.keypointName
|
pendingPoseKeypoint.keypointName === shape.keypointName
|
||||||
|
const highlighted =
|
||||||
|
hoveredPoseKeypoint?.personIndex === targetIndex &&
|
||||||
|
hoveredPoseKeypoint.keypointName === shape.keypointName
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<circle
|
<circle
|
||||||
@ -10251,7 +10279,7 @@ export default function TrainingTab(props: {
|
|||||||
cx={shape.center.x}
|
cx={shape.center.x}
|
||||||
cy={shape.center.y}
|
cy={shape.center.y}
|
||||||
r={mapMarkerRadius}
|
r={mapMarkerRadius}
|
||||||
className={poseKeypointMarkerClassName(present, pending)}
|
className={poseKeypointMarkerClassName(present, pending, highlighted)}
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
pointerEvents="none"
|
pointerEvents="none"
|
||||||
vectorEffect="non-scaling-stroke"
|
vectorEffect="non-scaling-stroke"
|
||||||
@ -10266,6 +10294,9 @@ export default function TrainingTab(props: {
|
|||||||
const pending =
|
const pending =
|
||||||
pendingPoseKeypoint?.personIndex === targetIndex &&
|
pendingPoseKeypoint?.personIndex === targetIndex &&
|
||||||
pendingPoseKeypoint.keypointName === keypointName
|
pendingPoseKeypoint.keypointName === keypointName
|
||||||
|
const highlighted =
|
||||||
|
hoveredPoseKeypoint?.personIndex === targetIndex &&
|
||||||
|
hoveredPoseKeypoint.keypointName === keypointName
|
||||||
const active = present || pending
|
const active = present || pending
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -10282,7 +10313,9 @@ export default function TrainingTab(props: {
|
|||||||
'fill-transparent stroke-transparent',
|
'fill-transparent stroke-transparent',
|
||||||
pending
|
pending
|
||||||
? 'hover:stroke-amber-700 focus:stroke-amber-700 dark:hover:stroke-amber-200 dark:focus:stroke-amber-200'
|
? 'hover:stroke-amber-700 focus:stroke-amber-700 dark:hover:stroke-amber-200 dark:focus:stroke-amber-200'
|
||||||
: present
|
: highlighted
|
||||||
|
? 'stroke-cyan-600 dark:stroke-cyan-200'
|
||||||
|
: present
|
||||||
? 'hover:stroke-indigo-800 focus:stroke-indigo-800 dark:hover:stroke-indigo-100 dark:focus:stroke-indigo-100'
|
? 'hover:stroke-indigo-800 focus:stroke-indigo-800 dark:hover:stroke-indigo-100 dark:focus:stroke-indigo-100'
|
||||||
: 'hover:stroke-indigo-600 focus:stroke-indigo-600 dark:hover:stroke-indigo-200 dark:focus:stroke-indigo-200',
|
: 'hover:stroke-indigo-600 focus:stroke-indigo-600 dark:hover:stroke-indigo-200 dark:focus:stroke-indigo-200',
|
||||||
disabled ? 'pointer-events-none opacity-45' : '',
|
disabled ? 'pointer-events-none opacity-45' : '',
|
||||||
@ -10290,6 +10323,36 @@ export default function TrainingTab(props: {
|
|||||||
strokeWidth={pending || present ? 2.5 : 2}
|
strokeWidth={pending || present ? 2.5 : 2}
|
||||||
pointerEvents="all"
|
pointerEvents="all"
|
||||||
vectorEffect="non-scaling-stroke"
|
vectorEffect="non-scaling-stroke"
|
||||||
|
onPointerEnter={() => {
|
||||||
|
if (disabled || targetIndex === null) return
|
||||||
|
setHoveredPoseKeypoint({
|
||||||
|
personIndex: targetIndex,
|
||||||
|
keypointName,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
onPointerLeave={() => {
|
||||||
|
setHoveredPoseKeypoint((current) =>
|
||||||
|
current?.personIndex === targetIndex &&
|
||||||
|
current.keypointName === keypointName
|
||||||
|
? null
|
||||||
|
: current
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
onFocus={() => {
|
||||||
|
if (disabled || targetIndex === null) return
|
||||||
|
setHoveredPoseKeypoint({
|
||||||
|
personIndex: targetIndex,
|
||||||
|
keypointName,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
setHoveredPoseKeypoint((current) =>
|
||||||
|
current?.personIndex === targetIndex &&
|
||||||
|
current.keypointName === keypointName
|
||||||
|
? null
|
||||||
|
: current
|
||||||
|
)
|
||||||
|
}}
|
||||||
onClick={() => selectPoseKeypoint(keypointName)}
|
onClick={() => selectPoseKeypoint(keypointName)}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
if (event.key !== 'Enter' && event.key !== ' ') return
|
if (event.key !== 'Enter' && event.key !== ' ') return
|
||||||
@ -10725,118 +10788,6 @@ export default function TrainingTab(props: {
|
|||||||
className="pointer-events-none absolute z-[305] overflow-visible"
|
className="pointer-events-none absolute z-[305] overflow-visible"
|
||||||
style={imageLayerStyle}
|
style={imageLayerStyle}
|
||||||
>
|
>
|
||||||
{posePersons.map((person, personIndex) => {
|
|
||||||
if (!shouldRenderPoseOverlayPerson(personIndex)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (poseFaceImageZoomActive) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasVisiblePoseBox(person) && !person.keypoints?.some(isPoseKeypointVisible)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const reliable = isPosePersonReliable(person)
|
|
||||||
const activePose = activePosePersonIndex === personIndex
|
|
||||||
const color = reliable
|
|
||||||
? POSE_PERSON_COLORS[personIndex % POSE_PERSON_COLORS.length]
|
|
||||||
: POSE_UNRELIABLE_COLOR
|
|
||||||
const score = Math.round(clamp01(Number(person.score)) * 100)
|
|
||||||
const quality = Math.round(posePersonQuality(person) * 100)
|
|
||||||
const visibleKeypoints = posePersonVisibleKeypoints(person)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={`pose-box-${personIndex}`}
|
|
||||||
className={[
|
|
||||||
pendingPoseKeypoint ? 'pointer-events-none' : 'pointer-events-auto',
|
|
||||||
'absolute rounded border border-dashed',
|
|
||||||
activePose ? 'bg-blue-500/10' : reliable ? '' : 'bg-slate-500/5',
|
|
||||||
].join(' ')}
|
|
||||||
style={{
|
|
||||||
...poseBoxPixelStyle(person.box, layerWidth, layerHeight),
|
|
||||||
borderColor: color,
|
|
||||||
boxShadow: activePose
|
|
||||||
? `0 0 0 2px ${color}aa`
|
|
||||||
: reliable ? `0 0 0 1px ${color}55` : `0 0 0 1px ${color}33`,
|
|
||||||
opacity: reliable || activePose ? 1 : 0.78,
|
|
||||||
}}
|
|
||||||
onPointerDown={(e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
setActivePosePersonIndex(personIndex)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-pose-control="true"
|
|
||||||
className={[
|
|
||||||
pendingPoseKeypoint ? 'pointer-events-none' : 'pointer-events-auto',
|
|
||||||
'absolute left-0 top-0 z-[360] flex -translate-y-[calc(100%+4px)] touch-none select-none items-center',
|
|
||||||
'[-webkit-user-select:none] [-webkit-touch-callout:none]',
|
|
||||||
].join(' ')}
|
|
||||||
title={`Score ${score}% | Qualitaet ${quality}% | Keypoints ${visibleKeypoints}`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={[
|
|
||||||
'group/label flex h-5 max-w-full min-w-0 touch-none select-none items-center overflow-hidden rounded-full text-left',
|
|
||||||
'text-[10px] font-bold leading-none shadow-md ring-1 transition',
|
|
||||||
'bg-white/95 text-gray-950 ring-black/10 hover:bg-gray-50',
|
|
||||||
'dark:bg-white/95 dark:text-gray-950 dark:ring-black/10 dark:hover:bg-gray-50',
|
|
||||||
].join(' ')}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={[
|
|
||||||
'flex h-5 min-w-0 touch-none items-center gap-1 pl-1.5 pr-1',
|
|
||||||
'focus:outline-none focus-visible:ring-2 focus-visible:ring-white/80',
|
|
||||||
uiLocked ? 'cursor-default' : 'cursor-pointer',
|
|
||||||
].join(' ')}
|
|
||||||
disabled={uiLocked}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
setActivePosePersonIndex(personIndex)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="shrink-0 rounded-full px-1 py-0.5 text-[9px] font-black leading-none text-white"
|
|
||||||
style={{ backgroundColor: color }}
|
|
||||||
>
|
|
||||||
{visiblePosePersonCount > 1 ? `#${personIndex + 1}` : 'Pose'}
|
|
||||||
</span>
|
|
||||||
<UserGroupIcon className="h-3 w-3 shrink-0 text-current" aria-hidden="true" />
|
|
||||||
<span className="min-w-0 max-w-[104px] truncate text-current sm:max-w-[132px]">
|
|
||||||
{score}%
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={[
|
|
||||||
'flex h-5 w-5 shrink-0 cursor-pointer touch-none items-center justify-center',
|
|
||||||
'!bg-red-600 !text-white ring-1 ring-red-800/40 transition',
|
|
||||||
'hover:!bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500/60',
|
|
||||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
||||||
'dark:!bg-red-500 dark:!text-white dark:ring-white/20 dark:hover:!bg-red-600',
|
|
||||||
].join(' ')}
|
|
||||||
disabled={uiLocked}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
deletePosePerson(personIndex)
|
|
||||||
}}
|
|
||||||
title="Diese Pose aus dem Training entfernen"
|
|
||||||
aria-label="Pose entfernen"
|
|
||||||
>
|
|
||||||
<TrashIcon className="h-3 w-3 shrink-0 !text-white" aria-hidden="true" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
className="absolute inset-0 h-full w-full overflow-visible"
|
className="absolute inset-0 h-full w-full overflow-visible"
|
||||||
viewBox={`0 0 ${layerWidth} ${layerHeight}`}
|
viewBox={`0 0 ${layerWidth} ${layerHeight}`}
|
||||||
@ -10921,140 +10872,94 @@ export default function TrainingTab(props: {
|
|||||||
|
|
||||||
const left = poseCoordPx(point.x, layerWidth)
|
const left = poseCoordPx(point.x, layerWidth)
|
||||||
const top = poseCoordPx(point.y, layerHeight)
|
const top = poseCoordPx(point.y, layerHeight)
|
||||||
|
const label = poseKeypointLabel(point.name)
|
||||||
|
const confidence = Math.round(clamp01(Number(point.conf)) * 100)
|
||||||
|
const selected =
|
||||||
|
pendingPoseKeypoint?.personIndex === personIndex &&
|
||||||
|
pendingPoseKeypoint.keypointName === keypointId
|
||||||
|
const hovered =
|
||||||
|
hoveredPoseKeypoint?.personIndex === personIndex &&
|
||||||
|
hoveredPoseKeypoint.keypointName === keypointId
|
||||||
|
const dragging =
|
||||||
|
poseInteraction?.personIndex === personIndex &&
|
||||||
|
poseInteraction.keypointIndex === pointIndex
|
||||||
|
const highlighted = selected || hovered || dragging
|
||||||
|
const showLabel = selected || dragging
|
||||||
const facePlacement = poseFaceImageZoomActive
|
const facePlacement = poseFaceImageZoomActive
|
||||||
? poseFaceKeypointLabelPlacement(keypointId)
|
? poseFaceKeypointLabelPlacement(keypointId)
|
||||||
: null
|
: null
|
||||||
const labelToLeft = facePlacement?.labelToLeft ?? left > layerWidth * 0.72
|
const labelToLeft = facePlacement?.labelToLeft ?? left > layerWidth * 0.72
|
||||||
const label = poseKeypointLabel(point.name)
|
|
||||||
const confidence = Math.round(clamp01(Number(point.conf)) * 100)
|
|
||||||
|
|
||||||
const dot = (
|
|
||||||
<span
|
|
||||||
className="h-2 w-2 shrink-0 rounded-full border border-white shadow-[0_0_0_1px_rgba(0,0,0,0.45)]"
|
|
||||||
style={{ backgroundColor: color }}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
const labelToneClass = reliable
|
|
||||||
? 'bg-white/95 text-gray-950 ring-black/10 hover:bg-gray-50 dark:bg-white/95 dark:text-gray-950 dark:ring-black/10 dark:hover:bg-gray-50'
|
|
||||||
: 'bg-white/90 text-slate-700 ring-slate-400/30 hover:bg-gray-50 dark:bg-white/90 dark:text-slate-700 dark:ring-black/10'
|
|
||||||
|
|
||||||
const text = (
|
|
||||||
<span className="min-w-0 max-w-[92px] truncate px-1 text-[10px] font-bold leading-none text-current sm:max-w-[120px]">
|
|
||||||
{label}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Fragment
|
||||||
data-pose-control="true"
|
|
||||||
key={`pose-point-${personIndex}-${poseKeypointId(point.name)}-${pointIndex}`}
|
key={`pose-point-${personIndex}-${poseKeypointId(point.name)}-${pointIndex}`}
|
||||||
className={[
|
|
||||||
pendingPoseKeypoint ? 'pointer-events-none' : 'pointer-events-auto',
|
|
||||||
'absolute top-0 flex touch-none items-center whitespace-nowrap rounded-sm',
|
|
||||||
].join(' ')}
|
|
||||||
style={{
|
|
||||||
left,
|
|
||||||
top,
|
|
||||||
transform: facePlacement?.transform ?? (
|
|
||||||
labelToLeft
|
|
||||||
? 'translate(calc(-100% + 4px), -50%)'
|
|
||||||
: 'translate(-4px, -50%)'
|
|
||||||
),
|
|
||||||
opacity: reliable ? 1 : 0.72,
|
|
||||||
zIndex: facePlacement?.zIndex,
|
|
||||||
}}
|
|
||||||
title={`${label} | ${confidence}%`}
|
|
||||||
>
|
>
|
||||||
<div
|
<button
|
||||||
|
data-pose-control="true"
|
||||||
|
type="button"
|
||||||
|
disabled={uiLocked}
|
||||||
className={[
|
className={[
|
||||||
'flex h-5 touch-none select-none items-center overflow-hidden rounded-full shadow-md ring-1 transition',
|
pendingPoseKeypoint ? 'pointer-events-none' : 'pointer-events-auto',
|
||||||
labelToneClass,
|
'absolute z-[350] flex h-8 w-8 -translate-x-1/2 -translate-y-1/2 touch-none items-center justify-center rounded-full',
|
||||||
|
'focus:outline-none focus:ring-2 focus:ring-white/80',
|
||||||
|
uiLocked ? 'cursor-default opacity-60' : 'cursor-grab active:cursor-grabbing',
|
||||||
].join(' ')}
|
].join(' ')}
|
||||||
|
style={{
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
opacity: reliable ? 1 : 0.72,
|
||||||
|
}}
|
||||||
|
title={`${label} | ${confidence}%`}
|
||||||
|
aria-label={`${label} verschieben`}
|
||||||
|
onPointerEnter={() => {
|
||||||
|
if (uiLocked) return
|
||||||
|
setHoveredPoseKeypoint({
|
||||||
|
personIndex,
|
||||||
|
keypointName: keypointId,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
onPointerLeave={() => {
|
||||||
|
setHoveredPoseKeypoint((current) =>
|
||||||
|
current?.personIndex === personIndex &&
|
||||||
|
current.keypointName === keypointId
|
||||||
|
? null
|
||||||
|
: current
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
onPointerDown={(e) => startPoseKeypointDrag(e, personIndex, pointIndex)}
|
||||||
>
|
>
|
||||||
{labelToLeft ? (
|
<span
|
||||||
<>
|
className={[
|
||||||
<button
|
'block rounded-full border-2 border-white shadow-[0_0_0_1px_rgba(0,0,0,0.55),0_0_10px_rgba(0,0,0,0.35)] transition-all duration-150',
|
||||||
type="button"
|
highlighted ? 'h-4 w-4 ring-4 ring-cyan-300/45' : 'h-2.5 w-2.5 ring-2 ring-black/25',
|
||||||
disabled={uiLocked}
|
].join(' ')}
|
||||||
className={[
|
style={{ backgroundColor: highlighted ? '#22d3ee' : color }}
|
||||||
'flex h-5 w-5 shrink-0 cursor-pointer touch-none items-center justify-center',
|
/>
|
||||||
'!bg-red-600 !text-white ring-1 ring-red-800/40 transition',
|
</button>
|
||||||
'hover:!bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500/60',
|
|
||||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
{showLabel ? (
|
||||||
'dark:!bg-red-500 dark:!text-white dark:ring-white/20 dark:hover:!bg-red-600',
|
<div
|
||||||
].join(' ')}
|
className="pointer-events-none absolute z-[370] flex max-w-[9rem] items-center gap-1 rounded-full bg-white/95 px-2 py-1 text-[10px] font-black leading-none text-gray-950 shadow-lg ring-1 ring-black/10 dark:bg-white/95 dark:text-gray-950"
|
||||||
title={`${label} ausblenden`}
|
style={{
|
||||||
aria-label={`${label} ausblenden`}
|
left,
|
||||||
onPointerDown={(e) => {
|
top,
|
||||||
e.stopPropagation()
|
transform: facePlacement?.transform ?? (
|
||||||
}}
|
labelToLeft
|
||||||
onClick={(e) => {
|
? 'translate(calc(-100% - 12px), -50%)'
|
||||||
e.preventDefault()
|
: 'translate(12px, -50%)'
|
||||||
e.stopPropagation()
|
),
|
||||||
removePoseKeypoint(personIndex, pointIndex)
|
opacity: reliable ? 1 : 0.82,
|
||||||
}}
|
zIndex: facePlacement?.zIndex,
|
||||||
>
|
}}
|
||||||
<TrashIcon className="h-3 w-3 shrink-0 !text-white" aria-hidden="true" />
|
>
|
||||||
</button>
|
<span
|
||||||
<button
|
className="h-2 w-2 shrink-0 rounded-full"
|
||||||
type="button"
|
style={{ backgroundColor: color }}
|
||||||
disabled={uiLocked}
|
/>
|
||||||
className={[
|
<span className="min-w-0 truncate">{label}</span>
|
||||||
'flex h-5 min-w-0 touch-none items-center gap-1 pl-1 pr-1',
|
</div>
|
||||||
'focus:outline-none focus-visible:ring-2 focus-visible:ring-white/80',
|
) : null}
|
||||||
uiLocked ? 'cursor-default' : 'cursor-grab active:cursor-grabbing',
|
</Fragment>
|
||||||
].join(' ')}
|
|
||||||
onPointerDown={(e) => startPoseKeypointDrag(e, personIndex, pointIndex)}
|
|
||||||
aria-label={`${label} verschieben`}
|
|
||||||
>
|
|
||||||
{text}
|
|
||||||
{dot}
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
disabled={uiLocked}
|
|
||||||
className={[
|
|
||||||
'flex h-5 min-w-0 touch-none items-center gap-1 pl-1 pr-1',
|
|
||||||
'focus:outline-none focus-visible:ring-2 focus-visible:ring-white/80',
|
|
||||||
uiLocked ? 'cursor-default' : 'cursor-grab active:cursor-grabbing',
|
|
||||||
].join(' ')}
|
|
||||||
onPointerDown={(e) => startPoseKeypointDrag(e, personIndex, pointIndex)}
|
|
||||||
aria-label={`${label} verschieben`}
|
|
||||||
>
|
|
||||||
{dot}
|
|
||||||
{text}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
disabled={uiLocked}
|
|
||||||
className={[
|
|
||||||
'flex h-5 w-5 shrink-0 cursor-pointer touch-none items-center justify-center',
|
|
||||||
'!bg-red-600 !text-white ring-1 ring-red-800/40 transition',
|
|
||||||
'hover:!bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500/60',
|
|
||||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
||||||
'dark:!bg-red-500 dark:!text-white dark:ring-white/20 dark:hover:!bg-red-600',
|
|
||||||
].join(' ')}
|
|
||||||
title={`${label} ausblenden`}
|
|
||||||
aria-label={`${label} ausblenden`}
|
|
||||||
onPointerDown={(e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
}}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
removePoseKeypoint(personIndex, pointIndex)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TrashIcon className="h-3 w-3 shrink-0 !text-white" aria-hidden="true" />
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})}
|
})}
|
||||||
@ -11683,13 +11588,6 @@ export default function TrainingTab(props: {
|
|||||||
})() : null}
|
})() : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{poseFaceImageZoomApplied ? (
|
|
||||||
<div
|
|
||||||
className="pointer-events-none absolute inset-0 z-[420] rounded-lg border-[8px] border-black shadow-[inset_0_0_0_1px_rgba(255,255,255,0.08)] sm:border-[12px]"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{poseFaceImageZoomActive && poseCorrectionPersonIndex !== null ? (
|
{poseFaceImageZoomActive && poseCorrectionPersonIndex !== null ? (
|
||||||
<div
|
<div
|
||||||
className="pointer-events-auto absolute left-3 top-3 z-[430] flex max-w-[calc(100%-1.5rem)] flex-wrap items-center gap-1.5 rounded-full bg-black/65 p-1 text-white shadow-lg ring-1 ring-white/20 backdrop-blur"
|
className="pointer-events-auto absolute left-3 top-3 z-[430] flex max-w-[calc(100%-1.5rem)] flex-wrap items-center gap-1.5 rounded-full bg-black/65 p-1 text-white shadow-lg ring-1 ring-white/20 backdrop-blur"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user