bugfixes
This commit is contained in:
parent
a1000e9085
commit
9cf9dfeb21
@ -1,14 +1,25 @@
|
|||||||
// 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 { useCallback, useEffect, useMemo, useRef, useState, type ComponentType, type CSSProperties } from 'react'
|
||||||
import Button from './Button'
|
import Button from './Button'
|
||||||
import LoadingSpinner from './LoadingSpinner'
|
import LoadingSpinner from './LoadingSpinner'
|
||||||
import { formatBytes, formatDuration } from './formatters'
|
import { formatBytes, formatDuration } from './formatters'
|
||||||
import { TrashIcon } from '@heroicons/react/20/solid'
|
import {
|
||||||
|
ArrowPathIcon,
|
||||||
|
BoltIcon,
|
||||||
|
ClockIcon,
|
||||||
|
ForwardIcon,
|
||||||
|
TrashIcon,
|
||||||
|
} from '@heroicons/react/20/solid'
|
||||||
import { getSegmentLabelItem } from './Icons'
|
import { getSegmentLabelItem } from './Icons'
|
||||||
import Modal from './Modal'
|
import Modal from './Modal'
|
||||||
|
|
||||||
|
type DrawingTrainingBox = TrainingBox & {
|
||||||
|
startX: number
|
||||||
|
startY: number
|
||||||
|
}
|
||||||
|
|
||||||
type ScoredLabel = {
|
type ScoredLabel = {
|
||||||
label: string
|
label: string
|
||||||
score: number
|
score: number
|
||||||
@ -163,11 +174,20 @@ type TrainingStats = {
|
|||||||
|
|
||||||
type TrainingNoticeKind = 'success' | 'error' | 'info' | 'warning'
|
type TrainingNoticeKind = 'success' | 'error' | 'info' | 'warning'
|
||||||
|
|
||||||
|
type TrainingNoticeItem = {
|
||||||
|
icon: ComponentType<{
|
||||||
|
className?: string
|
||||||
|
'aria-hidden'?: boolean | 'true' | 'false'
|
||||||
|
}>
|
||||||
|
label: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
type TrainingNotice = {
|
type TrainingNotice = {
|
||||||
kind: TrainingNoticeKind
|
kind: TrainingNoticeKind
|
||||||
title: string
|
title: string
|
||||||
message: string
|
message: string
|
||||||
detail?: string
|
items?: TrainingNoticeItem[]
|
||||||
progress?: number
|
progress?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,26 +266,37 @@ function TrainingNoticeCard(props: {
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-0.5 break-words text-xs leading-snug">
|
{props.notice.items?.length ? (
|
||||||
{props.notice.message}
|
<div className="mt-2 grid grid-cols-1 gap-1.5 text-xs sm:grid-cols-2">
|
||||||
</div>
|
{props.notice.items.map((item) => {
|
||||||
|
const Icon = item.icon
|
||||||
|
|
||||||
{props.notice.detail ? (
|
return (
|
||||||
<details className="mt-2">
|
<div
|
||||||
<summary className="cursor-pointer select-none text-xs font-medium opacity-80 hover:opacity-100">
|
key={`${item.label}-${item.value}`}
|
||||||
Details anzeigen
|
className="flex min-w-0 items-center gap-2 rounded-lg bg-black/5 px-2 py-1.5 ring-1 ring-black/5 dark:bg-white/10 dark:ring-white/10"
|
||||||
</summary>
|
>
|
||||||
|
<Icon
|
||||||
|
className="h-4 w-4 shrink-0 opacity-80"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
|
||||||
<pre
|
<span className="min-w-0 flex-1 truncate opacity-75">
|
||||||
className={[
|
{item.label}
|
||||||
'mt-2 max-h-40 overflow-auto whitespace-pre-wrap rounded-lg bg-black/5 p-2 text-[11px] leading-relaxed dark:bg-black/20',
|
</span>
|
||||||
cls.detail,
|
|
||||||
].join(' ')}
|
<span className="shrink-0 font-bold">
|
||||||
>
|
{item.value}
|
||||||
{props.notice.detail}
|
</span>
|
||||||
</pre>
|
</div>
|
||||||
</details>
|
)
|
||||||
) : null}
|
})}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="mt-0.5 break-words text-xs leading-snug">
|
||||||
|
{props.notice.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{props.onClose ? (
|
{props.onClose ? (
|
||||||
@ -1966,9 +1997,11 @@ export default function TrainingTab(props: {
|
|||||||
const detectorBoxItemRefs = useRef<Array<HTMLDivElement | null>>([])
|
const detectorBoxItemRefs = useRef<Array<HTMLDivElement | null>>([])
|
||||||
|
|
||||||
const epochTimingRef = useRef<{
|
const epochTimingRef = useRef<{
|
||||||
|
firstEpochAt: number
|
||||||
lastEpoch: number
|
lastEpoch: number
|
||||||
lastAt: number
|
lastAt: number
|
||||||
}>({
|
}>({
|
||||||
|
firstEpochAt: 0,
|
||||||
lastEpoch: 0,
|
lastEpoch: 0,
|
||||||
lastAt: 0,
|
lastAt: 0,
|
||||||
})
|
})
|
||||||
@ -1984,7 +2017,7 @@ export default function TrainingTab(props: {
|
|||||||
|
|
||||||
const [estimatedEpochMs, setEstimatedEpochMs] = useState(0)
|
const [estimatedEpochMs, setEstimatedEpochMs] = useState(0)
|
||||||
|
|
||||||
const [drawingBox, setDrawingBox] = useState<TrainingBox | null>(null)
|
const [drawingBox, setDrawingBox] = useState<DrawingTrainingBox | null>(null)
|
||||||
const [boxInteraction, setBoxInteraction] = useState<BoxInteraction | null>(null)
|
const [boxInteraction, setBoxInteraction] = useState<BoxInteraction | null>(null)
|
||||||
const [touchMagnifier, setTouchMagnifier] = useState<MagnifierState | null>(null)
|
const [touchMagnifier, setTouchMagnifier] = useState<MagnifierState | null>(null)
|
||||||
const [boxLabel, setBoxLabel] = useState('')
|
const [boxLabel, setBoxLabel] = useState('')
|
||||||
@ -2370,6 +2403,41 @@ export default function TrainingTab(props: {
|
|||||||
}
|
}
|
||||||
}, [applyTrainingStatus])
|
}, [applyTrainingStatus])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const draggingBox = Boolean(drawingBox || boxInteraction)
|
||||||
|
|
||||||
|
if (!draggingBox) return
|
||||||
|
|
||||||
|
const previousUserSelect = document.body.style.userSelect
|
||||||
|
const previousWebkitUserSelect = document.body.style.webkitUserSelect
|
||||||
|
|
||||||
|
document.body.style.userSelect = 'none'
|
||||||
|
document.body.style.webkitUserSelect = 'none'
|
||||||
|
|
||||||
|
const clearSelection = () => {
|
||||||
|
window.getSelection()?.removeAllRanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
const preventSelection = (event: Event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSelection()
|
||||||
|
|
||||||
|
document.addEventListener('selectionchange', clearSelection)
|
||||||
|
document.addEventListener('selectstart', preventSelection)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.body.style.userSelect = previousUserSelect
|
||||||
|
document.body.style.webkitUserSelect = previousWebkitUserSelect
|
||||||
|
|
||||||
|
document.removeEventListener('selectionchange', clearSelection)
|
||||||
|
document.removeEventListener('selectstart', preventSelection)
|
||||||
|
|
||||||
|
clearSelection()
|
||||||
|
}
|
||||||
|
}, [drawingBox, boxInteraction])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!statsModalOpen) return
|
if (!statsModalOpen) return
|
||||||
|
|
||||||
@ -2516,9 +2584,11 @@ export default function TrainingTab(props: {
|
|||||||
|
|
||||||
if (!trainingRunning || epoch <= 0 || epochs <= 0) {
|
if (!trainingRunning || epoch <= 0 || epochs <= 0) {
|
||||||
epochTimingRef.current = {
|
epochTimingRef.current = {
|
||||||
|
firstEpochAt: 0,
|
||||||
lastEpoch: 0,
|
lastEpoch: 0,
|
||||||
lastAt: 0,
|
lastAt: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
setEstimatedEpochMs(0)
|
setEstimatedEpochMs(0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -2526,33 +2596,48 @@ export default function TrainingTab(props: {
|
|||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const previous = epochTimingRef.current
|
const previous = epochTimingRef.current
|
||||||
|
|
||||||
|
const firstEpochAt =
|
||||||
|
previous.firstEpochAt > 0
|
||||||
|
? previous.firstEpochAt
|
||||||
|
: now
|
||||||
|
|
||||||
if (
|
if (
|
||||||
previous.lastEpoch > 0 &&
|
previous.lastEpoch > 0 &&
|
||||||
previous.lastAt > 0 &&
|
previous.lastAt > 0 &&
|
||||||
epoch > previous.lastEpoch
|
epoch > previous.lastEpoch
|
||||||
) {
|
) {
|
||||||
const measuredMsPerEpoch = (now - previous.lastAt) / (epoch - previous.lastEpoch)
|
const measuredMsPerEpoch =
|
||||||
|
(now - previous.lastAt) / (epoch - previous.lastEpoch)
|
||||||
|
|
||||||
if (Number.isFinite(measuredMsPerEpoch) && measuredMsPerEpoch > 0) {
|
if (Number.isFinite(measuredMsPerEpoch) && measuredMsPerEpoch > 0) {
|
||||||
setEstimatedEpochMs((old) => {
|
setEstimatedEpochMs((old) => {
|
||||||
if (!Number.isFinite(old) || old <= 0) return measuredMsPerEpoch
|
if (!Number.isFinite(old) || old <= 0) {
|
||||||
|
return measuredMsPerEpoch
|
||||||
|
}
|
||||||
|
|
||||||
const clampedMeasured = Math.max(
|
const clampedMeasured = Math.max(
|
||||||
old * 0.5,
|
old * 0.60,
|
||||||
Math.min(old * 2, measuredMsPerEpoch)
|
Math.min(old * 1.60, measuredMsPerEpoch)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Stärker glätten, damit Restzeit/Ø-Zeit nicht springt.
|
// Glättung:
|
||||||
return old * 0.85 + clampedMeasured * 0.15
|
// Genug stabil, aber reagiert schneller als vorher auf echte Änderungen.
|
||||||
|
return old * 0.75 + clampedMeasured * 0.25
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (epoch !== previous.lastEpoch) {
|
if (epoch !== previous.lastEpoch) {
|
||||||
epochTimingRef.current = {
|
epochTimingRef.current = {
|
||||||
|
firstEpochAt,
|
||||||
lastEpoch: epoch,
|
lastEpoch: epoch,
|
||||||
lastAt: now,
|
lastAt: now,
|
||||||
}
|
}
|
||||||
|
} else if (previous.firstEpochAt <= 0) {
|
||||||
|
epochTimingRef.current = {
|
||||||
|
...previous,
|
||||||
|
firstEpochAt,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
trainingRunning,
|
trainingRunning,
|
||||||
@ -2746,6 +2831,8 @@ export default function TrainingTab(props: {
|
|||||||
|
|
||||||
setDrawingBox({
|
setDrawingBox({
|
||||||
label: boxLabel,
|
label: boxLabel,
|
||||||
|
startX: pos.x,
|
||||||
|
startY: pos.y,
|
||||||
x: pos.x,
|
x: pos.x,
|
||||||
y: pos.y,
|
y: pos.y,
|
||||||
w: 0,
|
w: 0,
|
||||||
@ -2846,13 +2933,13 @@ export default function TrainingTab(props: {
|
|||||||
|
|
||||||
if (!drawingBox) return
|
if (!drawingBox) return
|
||||||
|
|
||||||
const x1 = drawingBox.x
|
const x1 = drawingBox.startX
|
||||||
const y1 = drawingBox.y
|
const y1 = drawingBox.startY
|
||||||
const x2 = pos.x
|
const x2 = pos.x
|
||||||
const y2 = pos.y
|
const y2 = pos.y
|
||||||
|
|
||||||
setDrawingBox({
|
setDrawingBox({
|
||||||
label: drawingBox.label,
|
...drawingBox,
|
||||||
x: Math.min(x1, x2),
|
x: Math.min(x1, x2),
|
||||||
y: Math.min(y1, y2),
|
y: Math.min(y1, y2),
|
||||||
w: Math.abs(x2 - x1),
|
w: Math.abs(x2 - x1),
|
||||||
@ -2875,7 +2962,8 @@ export default function TrainingTab(props: {
|
|||||||
|
|
||||||
if (!drawingBox) return
|
if (!drawingBox) return
|
||||||
|
|
||||||
const box = normalizeBox(drawingBox)
|
const { startX, startY, ...drawingTrainingBox } = drawingBox
|
||||||
|
const box = normalizeBox(drawingTrainingBox)
|
||||||
|
|
||||||
setDrawingBox(null)
|
setDrawingBox(null)
|
||||||
|
|
||||||
@ -2983,25 +3071,60 @@ export default function TrainingTab(props: {
|
|||||||
return trainingDurationMs(job)
|
return trainingDurationMs(job)
|
||||||
}, [trainingStatus?.training, trainingNowMs])
|
}, [trainingStatus?.training, trainingNowMs])
|
||||||
|
|
||||||
const rawTrainingEtaMs = useMemo(() => {
|
const estimatedTrainingTotalMs = useMemo(() => {
|
||||||
if (!trainingRunning) return 0
|
if (!trainingRunning) return 0
|
||||||
|
|
||||||
const job = trainingStatus?.training
|
const job = trainingStatus?.training
|
||||||
|
const started = job?.startedAt ? Date.parse(job.startedAt) : NaN
|
||||||
const epoch = Number(job?.epoch ?? 0)
|
const epoch = Number(job?.epoch ?? 0)
|
||||||
const epochs = Number(job?.epochs ?? 0)
|
const epochs = Number(job?.epochs ?? 0)
|
||||||
|
const firstEpochAt = epochTimingRef.current.firstEpochAt
|
||||||
|
|
||||||
if (
|
if (
|
||||||
Number.isFinite(epoch) &&
|
!Number.isFinite(started) ||
|
||||||
Number.isFinite(epochs) &&
|
started <= 0 ||
|
||||||
Number.isFinite(estimatedEpochMs) &&
|
firstEpochAt <= 0 ||
|
||||||
epoch > 0 &&
|
!Number.isFinite(epoch) ||
|
||||||
epochs > 0 &&
|
!Number.isFinite(epochs) ||
|
||||||
epoch < epochs &&
|
epoch <= 0 ||
|
||||||
estimatedEpochMs > 0
|
epochs <= 0 ||
|
||||||
|
!Number.isFinite(estimatedEpochMs) ||
|
||||||
|
estimatedEpochMs <= 0
|
||||||
) {
|
) {
|
||||||
return Math.max(0, (epochs - epoch) * estimatedEpochMs)
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Zeit vor dem eigentlichen YOLO-Epochen-Training:
|
||||||
|
// z.B. Scene-Modell, Dataset-Check, Modell-Laden.
|
||||||
|
const warmupMs = Math.max(0, firstEpochAt - started)
|
||||||
|
|
||||||
|
// Kleine Reserve für Validierung, best.pt kopieren, Status schreiben usw.
|
||||||
|
const finalizeOverheadMs = 8_000
|
||||||
|
|
||||||
|
const total =
|
||||||
|
warmupMs +
|
||||||
|
estimatedEpochMs * epochs +
|
||||||
|
finalizeOverheadMs
|
||||||
|
|
||||||
|
// Nie kleiner anzeigen als die bereits vergangene Laufzeit.
|
||||||
|
return Math.max(total, shownTrainingDurationMs)
|
||||||
|
}, [
|
||||||
|
trainingRunning,
|
||||||
|
trainingStatus?.training?.startedAt,
|
||||||
|
trainingStatus?.training?.epoch,
|
||||||
|
trainingStatus?.training?.epochs,
|
||||||
|
estimatedEpochMs,
|
||||||
|
shownTrainingDurationMs,
|
||||||
|
])
|
||||||
|
|
||||||
|
const rawTrainingEtaMs = useMemo(() => {
|
||||||
|
if (!trainingRunning) return 0
|
||||||
|
|
||||||
|
if (estimatedTrainingTotalMs > 0) {
|
||||||
|
return Math.max(0, estimatedTrainingTotalMs - shownTrainingDurationMs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback, solange noch keine brauchbare Epochenzeit bekannt ist.
|
||||||
const progress = clampPercent(Number(shownTrainingProgress))
|
const progress = clampPercent(Number(shownTrainingProgress))
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -3009,14 +3132,16 @@ export default function TrainingTab(props: {
|
|||||||
progress >= 5 &&
|
progress >= 5 &&
|
||||||
progress < 99
|
progress < 99
|
||||||
) {
|
) {
|
||||||
return Math.max(0, shownTrainingDurationMs * ((100 - progress) / progress))
|
return Math.max(
|
||||||
|
0,
|
||||||
|
shownTrainingDurationMs * ((100 - progress) / progress)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}, [
|
}, [
|
||||||
trainingRunning,
|
trainingRunning,
|
||||||
trainingStatus?.training,
|
estimatedTrainingTotalMs,
|
||||||
estimatedEpochMs,
|
|
||||||
shownTrainingDurationMs,
|
shownTrainingDurationMs,
|
||||||
shownTrainingProgress,
|
shownTrainingProgress,
|
||||||
])
|
])
|
||||||
@ -3066,6 +3191,28 @@ export default function TrainingTab(props: {
|
|||||||
|
|
||||||
const shownTrainingEtaMs = smoothedTrainingEtaMs
|
const shownTrainingEtaMs = smoothedTrainingEtaMs
|
||||||
|
|
||||||
|
const shownTrainingTotalMs = useMemo(() => {
|
||||||
|
if (!trainingRunning) return 0
|
||||||
|
|
||||||
|
if (estimatedTrainingTotalMs > 0) {
|
||||||
|
return Math.max(
|
||||||
|
estimatedTrainingTotalMs,
|
||||||
|
shownTrainingDurationMs + shownTrainingEtaMs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shownTrainingEtaMs > 0) {
|
||||||
|
return shownTrainingDurationMs + shownTrainingEtaMs
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}, [
|
||||||
|
trainingRunning,
|
||||||
|
estimatedTrainingTotalMs,
|
||||||
|
shownTrainingDurationMs,
|
||||||
|
shownTrainingEtaMs,
|
||||||
|
])
|
||||||
|
|
||||||
const shownTrainingEpochText = useMemo(() => {
|
const shownTrainingEpochText = useMemo(() => {
|
||||||
const epoch = Number(trainingStatus?.training?.epoch ?? 0)
|
const epoch = Number(trainingStatus?.training?.epoch ?? 0)
|
||||||
const epochs = Number(trainingStatus?.training?.epochs ?? 0)
|
const epochs = Number(trainingStatus?.training?.epochs ?? 0)
|
||||||
@ -3101,32 +3248,52 @@ export default function TrainingTab(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (trainingRunning) {
|
if (trainingRunning) {
|
||||||
const parts: string[] = []
|
const items: TrainingNoticeItem[] = []
|
||||||
|
|
||||||
if (shownTrainingDurationMs > 0) {
|
items.push({
|
||||||
parts.push(`Laufzeit ${formatDuration(shownTrainingDurationMs)}`)
|
icon: ClockIcon,
|
||||||
}
|
label: 'Bisherige Laufzeit',
|
||||||
|
value: formatDuration(shownTrainingDurationMs),
|
||||||
|
})
|
||||||
|
|
||||||
if (shownTrainingEtaMs > 0) {
|
if (shownTrainingEtaMs > 0) {
|
||||||
parts.push(`Restzeit ca. ${formatDuration(shownTrainingEtaMs)}`)
|
items.push({
|
||||||
|
icon: ForwardIcon,
|
||||||
|
label: 'Geschätzte Restzeit',
|
||||||
|
value: `ca. ${formatDuration(shownTrainingEtaMs)}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shownTrainingTotalMs > 0) {
|
||||||
|
items.push({
|
||||||
|
icon: ClockIcon,
|
||||||
|
label: 'Geschätzte Gesamtdauer',
|
||||||
|
value: `ca. ${formatDuration(shownTrainingTotalMs)}`,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shownTrainingEpochText) {
|
if (shownTrainingEpochText) {
|
||||||
parts.push(shownTrainingEpochText)
|
items.push({
|
||||||
|
icon: ArrowPathIcon,
|
||||||
|
label: 'Epoche',
|
||||||
|
value: shownTrainingEpochText.replace(/^Epoche\s+/i, ''),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (estimatedEpochMs > 0) {
|
if (estimatedEpochMs > 0) {
|
||||||
parts.push(`Ø ${formatDuration(estimatedEpochMs)} pro Epoche`)
|
items.push({
|
||||||
|
icon: BoltIcon,
|
||||||
|
label: 'Ø Zeit pro Epoche',
|
||||||
|
value: formatDuration(estimatedEpochMs),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
kind: 'info',
|
kind: 'info',
|
||||||
title: 'Trainingsprognose',
|
title: 'Training läuft',
|
||||||
progress: shownTrainingProgress,
|
progress: shownTrainingProgress,
|
||||||
message: parts.length > 0
|
message: shownTrainingStep || 'Training läuft…',
|
||||||
? parts.join(' · ')
|
items,
|
||||||
: shownTrainingStep || 'Training läuft…',
|
|
||||||
detail: shownTrainingStep || undefined,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3137,6 +3304,7 @@ export default function TrainingTab(props: {
|
|||||||
trainingRunning,
|
trainingRunning,
|
||||||
shownTrainingStep,
|
shownTrainingStep,
|
||||||
shownTrainingDurationMs,
|
shownTrainingDurationMs,
|
||||||
|
shownTrainingTotalMs,
|
||||||
shownTrainingEtaMs,
|
shownTrainingEtaMs,
|
||||||
shownTrainingEpochText,
|
shownTrainingEpochText,
|
||||||
estimatedEpochMs,
|
estimatedEpochMs,
|
||||||
@ -3481,13 +3649,15 @@ export default function TrainingTab(props: {
|
|||||||
width: `${width}%`,
|
width: `${width}%`,
|
||||||
height: `${height}%`,
|
height: `${height}%`,
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
|
zIndex: isActiveBox ? 80 : isDraft ? 70 : 10,
|
||||||
}}
|
}}
|
||||||
title={box.label}
|
title={box.label}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
data-box-control="true"
|
data-box-control="true"
|
||||||
className={[
|
className={[
|
||||||
'pointer-events-auto absolute left-0 top-0 z-40 flex h-5 -translate-y-[calc(100%+1px)] touch-none items-stretch overflow-hidden rounded-md text-[10px] font-semibold leading-none shadow',
|
'pointer-events-auto absolute left-0 top-0 z-[95] flex h-5 -translate-y-[calc(100%+1px)] touch-none select-none items-stretch overflow-hidden rounded-md text-[10px] font-semibold leading-none shadow',
|
||||||
|
'[-webkit-user-select:none] [-webkit-touch-callout:none]',
|
||||||
isSmallBox
|
isSmallBox
|
||||||
? 'max-w-none'
|
? 'max-w-none'
|
||||||
: 'max-w-[min(180px,calc(100vw-24px))]',
|
: 'max-w-[min(180px,calc(100vw-24px))]',
|
||||||
@ -3501,8 +3671,9 @@ export default function TrainingTab(props: {
|
|||||||
type="button"
|
type="button"
|
||||||
className={[
|
className={[
|
||||||
isSmallBox
|
isSmallBox
|
||||||
? 'flex min-w-0 touch-none items-center gap-1 px-2 text-left'
|
? 'flex min-w-0 touch-none select-none items-center gap-1 px-2 text-left'
|
||||||
: 'flex min-w-0 touch-none items-center gap-1 px-1.5 text-left',
|
: 'flex min-w-0 touch-none select-none items-center gap-1 px-1.5 text-left',
|
||||||
|
'[-webkit-user-select:none] [-webkit-touch-callout:none]',
|
||||||
isActiveBox
|
isActiveBox
|
||||||
? '!bg-blue-600 !text-white hover:!bg-blue-700'
|
? '!bg-blue-600 !text-white hover:!bg-blue-700'
|
||||||
: '',
|
: '',
|
||||||
@ -3519,6 +3690,7 @@ export default function TrainingTab(props: {
|
|||||||
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
window.getSelection()?.removeAllRanges()
|
||||||
|
|
||||||
const requiresActivationFirst = !isActiveBox
|
const requiresActivationFirst = !isActiveBox
|
||||||
|
|
||||||
@ -3615,7 +3787,7 @@ export default function TrainingTab(props: {
|
|||||||
className={[
|
className={[
|
||||||
isActiveBox
|
isActiveBox
|
||||||
? [
|
? [
|
||||||
'pointer-events-auto absolute z-20 flex h-7 w-7 touch-none items-center justify-center',
|
'pointer-events-auto absolute z-[90] flex h-7 w-7 touch-none items-center justify-center',
|
||||||
'appearance-none rounded-none border-0 p-0 opacity-100 shadow-none ring-0',
|
'appearance-none rounded-none border-0 p-0 opacity-100 shadow-none ring-0',
|
||||||
'!bg-transparent hover:!bg-transparent active:!bg-transparent focus:!bg-transparent',
|
'!bg-transparent hover:!bg-transparent active:!bg-transparent focus:!bg-transparent',
|
||||||
'focus:outline-none disabled:opacity-50 sm:h-5 sm:w-5',
|
'focus:outline-none disabled:opacity-50 sm:h-5 sm:w-5',
|
||||||
@ -3637,6 +3809,7 @@ export default function TrainingTab(props: {
|
|||||||
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
window.getSelection()?.removeAllRanges()
|
||||||
|
|
||||||
const requiresActivationFirst = !isActiveBox
|
const requiresActivationFirst = !isActiveBox
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user