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
|
||||
'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 LoadingSpinner from './LoadingSpinner'
|
||||
import { formatDuration } from './formatters'
|
||||
@ -637,11 +637,15 @@ function poseKeypointLabel(name?: string | null) {
|
||||
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) {
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
return String(
|
||||
data?.message ||
|
||||
@ -4250,6 +4240,7 @@ export default function TrainingTab(props: {
|
||||
setShowPoseSkeleton(false)
|
||||
setActivePosePersonIndex(null)
|
||||
setPendingPoseKeypoint(null)
|
||||
setHoveredPoseKeypoint(null)
|
||||
if (poseKeypointMapAnimationFrameRef.current !== null) {
|
||||
cancelAnimationFrame(poseKeypointMapAnimationFrameRef.current)
|
||||
poseKeypointMapAnimationFrameRef.current = null
|
||||
@ -4409,6 +4400,7 @@ export default function TrainingTab(props: {
|
||||
const [boxInteraction, setBoxInteraction] = useState<BoxInteraction | null>(null)
|
||||
const [poseInteraction, setPoseInteraction] = useState<PoseInteraction | null>(null)
|
||||
const [pendingPoseKeypoint, setPendingPoseKeypoint] = useState<PoseKeypointPlacement | null>(null)
|
||||
const [hoveredPoseKeypoint, setHoveredPoseKeypoint] = useState<PoseKeypointPlacement | null>(null)
|
||||
const [poseKeypointMapView, setPoseKeypointMapView] = useState<PoseKeypointMapView>('body')
|
||||
const [poseKeypointMapViewBoxValue, setPoseKeypointMapViewBoxValue] =
|
||||
useState(POSE_BODY_MAP_VIEW_BOX)
|
||||
@ -4567,6 +4559,7 @@ export default function TrainingTab(props: {
|
||||
setShowPoseSkeleton(false)
|
||||
setActivePosePersonIndex(null)
|
||||
setPendingPoseKeypoint(null)
|
||||
setHoveredPoseKeypoint(null)
|
||||
|
||||
poseFaceImagePanGestureRef.current = null
|
||||
poseFaceImagePanCenterRef.current = null
|
||||
@ -9782,7 +9775,39 @@ export default function TrainingTab(props: {
|
||||
return (
|
||||
<div
|
||||
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="truncate font-bold text-gray-800 dark:text-gray-100">
|
||||
@ -10244,6 +10269,9 @@ export default function TrainingTab(props: {
|
||||
const pending =
|
||||
pendingPoseKeypoint?.personIndex === targetIndex &&
|
||||
pendingPoseKeypoint.keypointName === shape.keypointName
|
||||
const highlighted =
|
||||
hoveredPoseKeypoint?.personIndex === targetIndex &&
|
||||
hoveredPoseKeypoint.keypointName === shape.keypointName
|
||||
|
||||
return (
|
||||
<circle
|
||||
@ -10251,7 +10279,7 @@ export default function TrainingTab(props: {
|
||||
cx={shape.center.x}
|
||||
cy={shape.center.y}
|
||||
r={mapMarkerRadius}
|
||||
className={poseKeypointMarkerClassName(present, pending)}
|
||||
className={poseKeypointMarkerClassName(present, pending, highlighted)}
|
||||
strokeWidth={2}
|
||||
pointerEvents="none"
|
||||
vectorEffect="non-scaling-stroke"
|
||||
@ -10266,6 +10294,9 @@ export default function TrainingTab(props: {
|
||||
const pending =
|
||||
pendingPoseKeypoint?.personIndex === targetIndex &&
|
||||
pendingPoseKeypoint.keypointName === keypointName
|
||||
const highlighted =
|
||||
hoveredPoseKeypoint?.personIndex === targetIndex &&
|
||||
hoveredPoseKeypoint.keypointName === keypointName
|
||||
const active = present || pending
|
||||
|
||||
return (
|
||||
@ -10282,7 +10313,9 @@ export default function TrainingTab(props: {
|
||||
'fill-transparent stroke-transparent',
|
||||
pending
|
||||
? '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-600 focus:stroke-indigo-600 dark:hover:stroke-indigo-200 dark:focus:stroke-indigo-200',
|
||||
disabled ? 'pointer-events-none opacity-45' : '',
|
||||
@ -10290,6 +10323,36 @@ export default function TrainingTab(props: {
|
||||
strokeWidth={pending || present ? 2.5 : 2}
|
||||
pointerEvents="all"
|
||||
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)}
|
||||
onKeyDown={(event) => {
|
||||
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"
|
||||
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
|
||||
className="absolute inset-0 h-full w-full overflow-visible"
|
||||
viewBox={`0 0 ${layerWidth} ${layerHeight}`}
|
||||
@ -10921,140 +10872,94 @@ export default function TrainingTab(props: {
|
||||
|
||||
const left = poseCoordPx(point.x, layerWidth)
|
||||
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
|
||||
? poseFaceKeypointLabelPlacement(keypointId)
|
||||
: null
|
||||
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 (
|
||||
<div
|
||||
data-pose-control="true"
|
||||
<Fragment
|
||||
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={[
|
||||
'flex h-5 touch-none select-none items-center overflow-hidden rounded-full shadow-md ring-1 transition',
|
||||
labelToneClass,
|
||||
pendingPoseKeypoint ? 'pointer-events-none' : 'pointer-events-auto',
|
||||
'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(' ')}
|
||||
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 ? (
|
||||
<>
|
||||
<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>
|
||||
<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`}
|
||||
>
|
||||
{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>
|
||||
<span
|
||||
className={[
|
||||
'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',
|
||||
highlighted ? 'h-4 w-4 ring-4 ring-cyan-300/45' : 'h-2.5 w-2.5 ring-2 ring-black/25',
|
||||
].join(' ')}
|
||||
style={{ backgroundColor: highlighted ? '#22d3ee' : color }}
|
||||
/>
|
||||
</button>
|
||||
|
||||
{showLabel ? (
|
||||
<div
|
||||
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"
|
||||
style={{
|
||||
left,
|
||||
top,
|
||||
transform: facePlacement?.transform ?? (
|
||||
labelToLeft
|
||||
? 'translate(calc(-100% - 12px), -50%)'
|
||||
: 'translate(12px, -50%)'
|
||||
),
|
||||
opacity: reliable ? 1 : 0.82,
|
||||
zIndex: facePlacement?.zIndex,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className="h-2 w-2 shrink-0 rounded-full"
|
||||
style={{ backgroundColor: color }}
|
||||
/>
|
||||
<span className="min-w-0 truncate">{label}</span>
|
||||
</div>
|
||||
) : null}
|
||||
</Fragment>
|
||||
)
|
||||
})
|
||||
})}
|
||||
@ -11683,13 +11588,6 @@ export default function TrainingTab(props: {
|
||||
})() : null}
|
||||
</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 ? (
|
||||
<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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user