feat: reposition draft review annotations
This commit is contained in:
parent
94641795cf
commit
04fd25b60e
@ -8,6 +8,7 @@ export type WorkReviewDisplayAnnotation = {
|
||||
reviewerName?: string
|
||||
reviewerRole?: WorkReviewRole
|
||||
draft?: boolean
|
||||
positionEditable?: boolean
|
||||
}
|
||||
|
||||
function markerTone(item: WorkReviewDisplayAnnotation) {
|
||||
@ -33,7 +34,8 @@ export function WorkReviewAnnotationMarker({
|
||||
? '元泽'
|
||||
: '评审人'
|
||||
const summary = annotation.comment.trim() || '待填写修改意见'
|
||||
const label = `${roleLabel}标注 ${item.markerNumber}${item.reviewerName ? `,${item.reviewerName}` : ''}:${summary}`
|
||||
const positionState = item.positionEditable ? ',位置可调整' : ''
|
||||
const label = `${roleLabel}标注 ${item.markerNumber}${positionState}${item.reviewerName ? `,${item.reviewerName}` : ''}:${summary}`
|
||||
const visualTitle = showComment ? label : `${roleLabel}标注 ${item.markerNumber}`
|
||||
|
||||
return (
|
||||
@ -44,12 +46,13 @@ export function WorkReviewAnnotationMarker({
|
||||
onClick?.()
|
||||
}}
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
onPointerUp={(event) => event.stopPropagation()}
|
||||
aria-label={label}
|
||||
title={visualTitle}
|
||||
className="group absolute z-20 h-11 w-11 -translate-x-1/2 -translate-y-1/2 outline-none"
|
||||
className={`group absolute z-20 h-11 w-11 -translate-x-1/2 -translate-y-1/2 outline-none ${item.positionEditable ? 'cursor-move' : 'cursor-pointer'}`}
|
||||
style={{ left: `${annotation.x * 100}%`, top: `${annotation.y * 100}%` }}
|
||||
>
|
||||
<span className={`absolute left-1/2 top-1/2 flex h-7 w-7 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full text-[11px] font-black shadow-[0_3px_10px_rgba(15,23,42,0.35)] ring-4 ${markerTone(item)} group-focus-visible:ring-[#8ab9ff]`}>
|
||||
<span className={`absolute left-1/2 top-1/2 flex h-7 w-7 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full text-[11px] font-black shadow-[0_3px_10px_rgba(15,23,42,0.35)] ring-4 ${markerTone(item)} ${item.positionEditable ? 'outline outline-2 outline-offset-2 outline-[#8a5200]' : ''} group-focus-visible:ring-[#8ab9ff]`}>
|
||||
{item.markerNumber}
|
||||
</span>
|
||||
<span
|
||||
|
||||
@ -87,11 +87,14 @@ function LongImagePane({
|
||||
annotations = [],
|
||||
annotationEnabled = false,
|
||||
annotationMode = false,
|
||||
annotationRepositionMode = false,
|
||||
annotationPosition,
|
||||
annotationCount,
|
||||
showAnnotationComments = true,
|
||||
onToggleAnnotationMode,
|
||||
onToggleAnnotationComments,
|
||||
onAddAnnotation,
|
||||
onMoveAnnotation,
|
||||
onSelectDraftAnnotation,
|
||||
}: {
|
||||
label: string
|
||||
@ -103,11 +106,14 @@ function LongImagePane({
|
||||
annotations?: WorkReviewDisplayAnnotation[]
|
||||
annotationEnabled?: boolean
|
||||
annotationMode?: boolean
|
||||
annotationRepositionMode?: boolean
|
||||
annotationPosition?: Pick<WorkReviewImageAnnotation, 'x' | 'y'>
|
||||
annotationCount?: number
|
||||
showAnnotationComments?: boolean
|
||||
onToggleAnnotationMode?: () => void
|
||||
onToggleAnnotationComments?: () => void
|
||||
onAddAnnotation?: (x: number, y: number) => void
|
||||
onMoveAnnotation?: (x: number, y: number) => void
|
||||
onSelectDraftAnnotation?: (annotationId: string) => void
|
||||
}) {
|
||||
const displayGroups = useMemo(
|
||||
@ -121,6 +127,7 @@ function LongImagePane({
|
||||
Math.max(0, displayGroups.length - 1),
|
||||
)
|
||||
const activeGroup = displayGroups[activeIndex]
|
||||
const placementMode = annotationMode || annotationRepositionMode
|
||||
|
||||
useEffect(() => {
|
||||
if (controlledActiveIndex == null) setInternalActiveIndex(0)
|
||||
@ -136,7 +143,11 @@ function LongImagePane({
|
||||
if (bounds.width <= 0 || bounds.height <= 0) return
|
||||
const x = Math.min(1, Math.max(0, (clientX - bounds.left) / bounds.width))
|
||||
const y = Math.min(1, Math.max(0, (clientY - bounds.top) / bounds.height))
|
||||
onAddAnnotation?.(x, y)
|
||||
if (annotationRepositionMode) {
|
||||
onMoveAnnotation?.(x, y)
|
||||
} else {
|
||||
onAddAnnotation?.(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@ -148,8 +159,9 @@ function LongImagePane({
|
||||
<button
|
||||
type="button"
|
||||
onClick={onToggleAnnotationMode}
|
||||
disabled={annotationRepositionMode}
|
||||
aria-pressed={annotationMode}
|
||||
className={`min-h-8 shrink-0 rounded px-2.5 text-[11px] font-bold transition-colors ${
|
||||
className={`min-h-8 shrink-0 rounded px-2.5 text-[11px] font-bold transition-colors disabled:cursor-not-allowed disabled:opacity-45 ${
|
||||
annotationMode
|
||||
? 'bg-[#8a5200] text-white hover:bg-[#6d4100]'
|
||||
: 'border border-[#d3dae5] bg-white text-[#344054] hover:border-[#8ab9ff]'
|
||||
@ -201,25 +213,29 @@ function LongImagePane({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{annotationMode && (
|
||||
{placementMode && (
|
||||
<div className="shrink-0 bg-[#fff7e6] px-3 py-2 text-center text-[11px] font-semibold text-[#7a4a00]" role="status">
|
||||
点击作品中的修改位置添加批注;滚动不会产生标记
|
||||
{annotationRepositionMode
|
||||
? '完成前可点击作品图片重新调整当前标注位置;滚动不会移动标注'
|
||||
: '点击作品中的修改位置添加批注;滚动不会产生标记'}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="min-h-0 flex-1 overflow-x-hidden overflow-y-auto overscroll-y-contain bg-[#eef1f5] p-2 sm:p-3">
|
||||
{activeGroup ? (
|
||||
<div
|
||||
className={`relative mx-auto max-w-[1200px] overflow-hidden bg-white shadow-[0_8px_28px_rgba(15,23,42,0.08)] ${annotationMode ? 'cursor-crosshair touch-pan-y outline outline-2 outline-[#d49a31]' : ''}`}
|
||||
className={`relative mx-auto max-w-[1200px] overflow-hidden bg-white shadow-[0_8px_28px_rgba(15,23,42,0.08)] ${placementMode ? 'cursor-crosshair touch-pan-y outline outline-2 outline-[#d49a31]' : ''}`}
|
||||
role={annotationEnabled ? 'group' : undefined}
|
||||
aria-label={annotationEnabled ? `${activeGroup.sourceName},可添加位置批注` : undefined}
|
||||
tabIndex={annotationMode ? 0 : undefined}
|
||||
aria-label={annotationEnabled
|
||||
? `${activeGroup.sourceName},${annotationRepositionMode ? '可调整当前标注位置,使用方向键微调' : '可添加位置批注'}`
|
||||
: undefined}
|
||||
tabIndex={placementMode ? 0 : undefined}
|
||||
onPointerDown={(event) => {
|
||||
if (!annotationMode || (event.button ?? 0) !== 0) return
|
||||
if (!placementMode || event.isPrimary === false || (event.button ?? 0) !== 0) return
|
||||
pointerStartRef.current = { x: event.clientX, y: event.clientY }
|
||||
}}
|
||||
onPointerUp={(event) => {
|
||||
if (!annotationMode || !pointerStartRef.current) return
|
||||
if (!placementMode || event.isPrimary === false || !pointerStartRef.current) return
|
||||
const distance = Math.hypot(
|
||||
event.clientX - pointerStartRef.current.x,
|
||||
event.clientY - pointerStartRef.current.y,
|
||||
@ -231,7 +247,29 @@ function LongImagePane({
|
||||
pointerStartRef.current = null
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (annotationMode && (event.key === 'Enter' || event.key === ' ')) {
|
||||
if (annotationRepositionMode && annotationPosition && [
|
||||
'ArrowLeft',
|
||||
'ArrowRight',
|
||||
'ArrowUp',
|
||||
'ArrowDown',
|
||||
].includes(event.key)) {
|
||||
event.preventDefault()
|
||||
const step = event.shiftKey ? 0.05 : 0.01
|
||||
const nextX = event.key === 'ArrowLeft'
|
||||
? annotationPosition.x - step
|
||||
: event.key === 'ArrowRight'
|
||||
? annotationPosition.x + step
|
||||
: annotationPosition.x
|
||||
const nextY = event.key === 'ArrowUp'
|
||||
? annotationPosition.y - step
|
||||
: event.key === 'ArrowDown'
|
||||
? annotationPosition.y + step
|
||||
: annotationPosition.y
|
||||
onMoveAnnotation?.(
|
||||
Math.min(1, Math.max(0, nextX)),
|
||||
Math.min(1, Math.max(0, nextY)),
|
||||
)
|
||||
} else if (annotationMode && (event.key === 'Enter' || event.key === ' ')) {
|
||||
event.preventDefault()
|
||||
const bounds = event.currentTarget.getBoundingClientRect()
|
||||
placeAnnotation(event.currentTarget, bounds.left + bounds.width / 2, bounds.top + bounds.height / 2)
|
||||
@ -302,6 +340,7 @@ export function WorkReviewReviewerWorkspace({
|
||||
Array.from({ length: getImageCount(latestVersion) }, () => [])
|
||||
))
|
||||
const [selectedAnnotation, setSelectedAnnotation] = useState<{ imageIndex: number; id: string } | null>(null)
|
||||
const [positionEditableAnnotation, setPositionEditableAnnotation] = useState<{ imageIndex: number; id: string } | null>(null)
|
||||
const [annotationMode, setAnnotationMode] = useState(false)
|
||||
const [showAnnotationComments, setShowAnnotationComments] = useState(true)
|
||||
const [activeMobilePane, setActiveMobilePane] = useState<0 | 1>(0)
|
||||
@ -330,6 +369,7 @@ export function WorkReviewReviewerWorkspace({
|
||||
setActiveWorkImageIndex(0)
|
||||
setDraftAnnotations(Array.from({ length: workImageCount }, () => []))
|
||||
setSelectedAnnotation(null)
|
||||
setPositionEditableAnnotation(null)
|
||||
setAnnotationMode(false)
|
||||
setShowAnnotationComments(true)
|
||||
setActiveMobilePane(0)
|
||||
@ -370,6 +410,9 @@ export function WorkReviewReviewerWorkspace({
|
||||
const selectedDraft = selectedAnnotation
|
||||
? draftAnnotations[selectedAnnotation.imageIndex]?.find((item) => item.id === selectedAnnotation.id)
|
||||
: undefined
|
||||
const positionEditableDraft = positionEditableAnnotation
|
||||
? draftAnnotations[positionEditableAnnotation.imageIndex]?.find((item) => item.id === positionEditableAnnotation.id)
|
||||
: undefined
|
||||
const totalDraftCount = draftAnnotations.reduce((sum, annotations) => sum + annotations.length, 0)
|
||||
const totalSubmittedAnnotationCount = selectedVersion.reviews.reduce((reviewSum, review) => (
|
||||
reviewSum + (review.imageComments || []).reduce((imageSum, imageComment) => (
|
||||
@ -388,7 +431,13 @@ export function WorkReviewReviewerWorkspace({
|
||||
})
|
||||
const visibleAnnotations: WorkReviewDisplayAnnotation[] = [
|
||||
...submittedAnnotations,
|
||||
...currentDrafts.map((annotation, index) => ({ annotation, markerNumber: index + 1, draft: true })),
|
||||
...currentDrafts.map((annotation, index) => ({
|
||||
annotation,
|
||||
markerNumber: index + 1,
|
||||
draft: true,
|
||||
positionEditable: positionEditableAnnotation?.imageIndex === activeWorkImageIndex
|
||||
&& positionEditableAnnotation.id === annotation.id,
|
||||
})),
|
||||
]
|
||||
|
||||
const updateSplitFromPointer = (clientX: number) => {
|
||||
@ -404,7 +453,7 @@ export function WorkReviewReviewerWorkspace({
|
||||
setActiveMobilePane(index)
|
||||
if (index === 1) {
|
||||
setAnnotationMode(false)
|
||||
setSelectedAnnotation(null)
|
||||
if (!positionEditableAnnotation) setSelectedAnnotation(null)
|
||||
}
|
||||
scrollPaneTo(scroller, scroller.clientWidth * index, 'smooth')
|
||||
}
|
||||
@ -420,9 +469,24 @@ export function WorkReviewReviewerWorkspace({
|
||||
index === activeWorkImageIndex ? [...items, annotation] : items
|
||||
)))
|
||||
setSelectedAnnotation({ imageIndex: activeWorkImageIndex, id: annotation.id })
|
||||
setPositionEditableAnnotation({ imageIndex: activeWorkImageIndex, id: annotation.id })
|
||||
setAnnotationMode(false)
|
||||
setError(null)
|
||||
setLiveMessage(`已在图 ${activeWorkImageIndex + 1} 添加标注点`)
|
||||
setLiveMessage(`已在图 ${activeWorkImageIndex + 1} 添加标注点,完成前可点击图片调整位置`)
|
||||
}
|
||||
|
||||
const movePositionEditableAnnotation = (x: number, y: number) => {
|
||||
if (!positionEditableAnnotation || !positionEditableDraft) return
|
||||
setDraftAnnotations((current) => current.map((items, imageIndex) => (
|
||||
imageIndex === positionEditableAnnotation.imageIndex
|
||||
? items.map((item) => item.id === positionEditableAnnotation.id ? { ...item, x, y } : item)
|
||||
: items
|
||||
)))
|
||||
setSelectedAnnotation(positionEditableAnnotation)
|
||||
setError(null)
|
||||
const markerIndex = (draftAnnotations[positionEditableAnnotation.imageIndex] || [])
|
||||
.findIndex((item) => item.id === positionEditableAnnotation.id)
|
||||
setLiveMessage(`标注 ${markerIndex + 1} 已移动到新位置`)
|
||||
}
|
||||
|
||||
const updateSelectedAnnotation = (comment: string) => {
|
||||
@ -443,6 +507,10 @@ export function WorkReviewReviewerWorkspace({
|
||||
: items
|
||||
)))
|
||||
setSelectedAnnotation(null)
|
||||
if (positionEditableAnnotation?.imageIndex === selectedAnnotation.imageIndex
|
||||
&& positionEditableAnnotation.id === selectedAnnotation.id) {
|
||||
setPositionEditableAnnotation(null)
|
||||
}
|
||||
setError(null)
|
||||
setLiveMessage('批注已删除')
|
||||
}
|
||||
@ -452,9 +520,12 @@ export function WorkReviewReviewerWorkspace({
|
||||
setError('请填写这个标注点的修改意见')
|
||||
return
|
||||
}
|
||||
const lockedPosition = positionEditableAnnotation?.imageIndex === selectedAnnotation?.imageIndex
|
||||
&& positionEditableAnnotation?.id === selectedAnnotation?.id
|
||||
setSelectedAnnotation(null)
|
||||
if (lockedPosition) setPositionEditableAnnotation(null)
|
||||
setError(null)
|
||||
setLiveMessage('批注已保存到本次评审草稿')
|
||||
setLiveMessage(lockedPosition ? '标注位置已锁定' : '批注修改意见已保存')
|
||||
}
|
||||
|
||||
const findIncompleteAnnotation = () => {
|
||||
@ -465,8 +536,53 @@ export function WorkReviewReviewerWorkspace({
|
||||
return null
|
||||
}
|
||||
|
||||
const changeActiveWorkImage = (index: number) => {
|
||||
if (positionEditableAnnotation) {
|
||||
setSelectedAnnotation(positionEditableAnnotation)
|
||||
setAnnotationMode(false)
|
||||
if (index !== activeWorkImageIndex) {
|
||||
setError('请先点击“完成”锁定当前标注位置,再切换图片')
|
||||
}
|
||||
return
|
||||
}
|
||||
setActiveWorkImageIndex(index)
|
||||
setSelectedAnnotation(null)
|
||||
setAnnotationMode(false)
|
||||
}
|
||||
|
||||
const toggleAnnotationMode = () => {
|
||||
if (positionEditableAnnotation) {
|
||||
setSelectedAnnotation(positionEditableAnnotation)
|
||||
setAnnotationMode(false)
|
||||
setError('请先完成或删除当前标注')
|
||||
return
|
||||
}
|
||||
setAnnotationMode((current) => !current)
|
||||
setSelectedAnnotation(null)
|
||||
setError(null)
|
||||
}
|
||||
|
||||
const selectDraftAnnotation = (id: string) => {
|
||||
if (positionEditableAnnotation && positionEditableAnnotation.id !== id) {
|
||||
setSelectedAnnotation(positionEditableAnnotation)
|
||||
setAnnotationMode(false)
|
||||
setError('请先完成或删除当前标注')
|
||||
return
|
||||
}
|
||||
setSelectedAnnotation({ imageIndex: activeWorkImageIndex, id })
|
||||
setAnnotationMode(false)
|
||||
setError(null)
|
||||
}
|
||||
|
||||
const submitDecision = async (decision: 'approved' | 'revision_required') => {
|
||||
if (!pendingRole || workImageCount === 0) return
|
||||
if (positionEditableAnnotation) {
|
||||
setActiveWorkImageIndex(positionEditableAnnotation.imageIndex)
|
||||
setSelectedAnnotation(positionEditableAnnotation)
|
||||
setAnnotationMode(false)
|
||||
setError('请先点击“完成”锁定当前标注位置')
|
||||
return
|
||||
}
|
||||
const incomplete = findIncompleteAnnotation()
|
||||
if (incomplete) {
|
||||
setActiveWorkImageIndex(incomplete.imageIndex)
|
||||
@ -501,6 +617,7 @@ export function WorkReviewReviewerWorkspace({
|
||||
})
|
||||
setDraftAnnotations(Array.from({ length: workImageCount }, () => []))
|
||||
setSelectedAnnotation(null)
|
||||
setPositionEditableAnnotation(null)
|
||||
setAnnotationMode(false)
|
||||
} catch (submitError) {
|
||||
setError(submitError instanceof Error ? submitError.message : '评审提交失败')
|
||||
@ -597,28 +714,19 @@ export function WorkReviewReviewerWorkspace({
|
||||
imageUrls={selectedVersion.imageUrls || []}
|
||||
emptyMessage="当前版本没有作品图片"
|
||||
activeIndex={activeWorkImageIndex}
|
||||
onActiveIndexChange={(index) => {
|
||||
setActiveWorkImageIndex(index)
|
||||
setSelectedAnnotation(null)
|
||||
setAnnotationMode(false)
|
||||
}}
|
||||
onActiveIndexChange={changeActiveWorkImage}
|
||||
annotations={visibleAnnotations}
|
||||
annotationEnabled={Boolean(pendingRole)}
|
||||
annotationMode={annotationMode}
|
||||
annotationRepositionMode={Boolean(positionEditableDraft)}
|
||||
annotationPosition={positionEditableDraft}
|
||||
annotationCount={totalAnnotationCount}
|
||||
showAnnotationComments={showAnnotationComments}
|
||||
onToggleAnnotationMode={() => {
|
||||
setAnnotationMode((current) => !current)
|
||||
setSelectedAnnotation(null)
|
||||
setError(null)
|
||||
}}
|
||||
onToggleAnnotationMode={toggleAnnotationMode}
|
||||
onToggleAnnotationComments={() => setShowAnnotationComments((current) => !current)}
|
||||
onAddAnnotation={addAnnotation}
|
||||
onSelectDraftAnnotation={(id) => {
|
||||
setSelectedAnnotation({ imageIndex: activeWorkImageIndex, id })
|
||||
setAnnotationMode(false)
|
||||
setError(null)
|
||||
}}
|
||||
onMoveAnnotation={movePositionEditableAnnotation}
|
||||
onSelectDraftAnnotation={selectDraftAnnotation}
|
||||
/>
|
||||
</div>
|
||||
{hasReferenceImages && (
|
||||
@ -718,28 +826,19 @@ export function WorkReviewReviewerWorkspace({
|
||||
imageUrls={selectedVersion.imageUrls || []}
|
||||
emptyMessage="当前版本没有作品图片"
|
||||
activeIndex={activeWorkImageIndex}
|
||||
onActiveIndexChange={(index) => {
|
||||
setActiveWorkImageIndex(index)
|
||||
setSelectedAnnotation(null)
|
||||
setAnnotationMode(false)
|
||||
}}
|
||||
onActiveIndexChange={changeActiveWorkImage}
|
||||
annotations={visibleAnnotations}
|
||||
annotationEnabled={Boolean(pendingRole)}
|
||||
annotationMode={annotationMode}
|
||||
annotationRepositionMode={Boolean(positionEditableDraft)}
|
||||
annotationPosition={positionEditableDraft}
|
||||
annotationCount={totalAnnotationCount}
|
||||
showAnnotationComments={showAnnotationComments}
|
||||
onToggleAnnotationMode={() => {
|
||||
setAnnotationMode((current) => !current)
|
||||
setSelectedAnnotation(null)
|
||||
setError(null)
|
||||
}}
|
||||
onToggleAnnotationMode={toggleAnnotationMode}
|
||||
onToggleAnnotationComments={() => setShowAnnotationComments((current) => !current)}
|
||||
onAddAnnotation={addAnnotation}
|
||||
onSelectDraftAnnotation={(id) => {
|
||||
setSelectedAnnotation({ imageIndex: activeWorkImageIndex, id })
|
||||
setAnnotationMode(false)
|
||||
setError(null)
|
||||
}}
|
||||
onMoveAnnotation={movePositionEditableAnnotation}
|
||||
onSelectDraftAnnotation={selectDraftAnnotation}
|
||||
/>
|
||||
</div>
|
||||
{hasReferenceImages && (
|
||||
@ -765,6 +864,10 @@ export function WorkReviewReviewerWorkspace({
|
||||
<label className="grid min-w-0 gap-1.5">
|
||||
<span className="text-[12px] font-bold text-[#344054]">
|
||||
图 {selectedAnnotation!.imageIndex + 1} · 标注 {(draftAnnotations[selectedAnnotation!.imageIndex] || []).findIndex((item) => item.id === selectedAnnotation!.id) + 1} 修改意见
|
||||
{positionEditableAnnotation?.imageIndex === selectedAnnotation!.imageIndex
|
||||
&& positionEditableAnnotation.id === selectedAnnotation!.id
|
||||
? '(位置可调整)'
|
||||
: ''}
|
||||
</span>
|
||||
<textarea
|
||||
ref={annotationInputRef}
|
||||
@ -775,6 +878,12 @@ export function WorkReviewReviewerWorkspace({
|
||||
placeholder="说明这个位置需要如何调整"
|
||||
className="min-h-16 resize-y rounded border border-[#cfd6e1] px-3 py-2 text-[13px] leading-5 outline-none focus:border-[#0050cb] focus:ring-2 focus:ring-[#dcecff] disabled:bg-[#f2f4f7]"
|
||||
/>
|
||||
{positionEditableAnnotation?.imageIndex === selectedAnnotation!.imageIndex
|
||||
&& positionEditableAnnotation.id === selectedAnnotation!.id ? (
|
||||
<span className="text-[11px] font-semibold text-[#7a4a00]">
|
||||
完成前可点击作品图片重新调整标注位置
|
||||
</span>
|
||||
) : null}
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-2 sm:flex">
|
||||
<button
|
||||
@ -791,7 +900,10 @@ export function WorkReviewReviewerWorkspace({
|
||||
disabled={Boolean(busyAction)}
|
||||
className="min-h-10 rounded bg-[#101828] px-4 text-[12px] font-bold text-white hover:bg-[#26354a] disabled:opacity-45"
|
||||
>
|
||||
完成
|
||||
{positionEditableAnnotation?.imageIndex === selectedAnnotation!.imageIndex
|
||||
&& positionEditableAnnotation.id === selectedAnnotation!.id
|
||||
? '完成'
|
||||
: '保存修改'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -63,7 +63,7 @@ const projectWithReference: WorkReviewProject = {
|
||||
}
|
||||
|
||||
describe('WorkReviewReviewerWorkspace', () => {
|
||||
test('adds a normalized point annotation and submits the current review for revision', async () => {
|
||||
test('moves an unfinished annotation, locks it on completion, and submits the moved position', async () => {
|
||||
window.PointerEvent = MouseEvent as typeof PointerEvent
|
||||
const user = userEvent.setup()
|
||||
const onSubmitReview = jest.fn().mockResolvedValue(undefined)
|
||||
@ -92,8 +92,14 @@ describe('WorkReviewReviewerWorkspace', () => {
|
||||
|
||||
const comment = screen.getByRole('textbox', { name: /图 1 · 标注 1 修改意见/ })
|
||||
await user.type(comment, '人物位置向左移动')
|
||||
|
||||
fireEvent.pointerDown(canvas, { button: 0, clientX: 750, clientY: 1200 })
|
||||
fireEvent.pointerUp(canvas, { button: 0, clientX: 750, clientY: 1200 })
|
||||
await user.click(screen.getByRole('button', { name: '完成' }))
|
||||
|
||||
fireEvent.pointerDown(canvas, { button: 0, clientX: 100, clientY: 200 })
|
||||
fireEvent.pointerUp(canvas, { button: 0, clientX: 100, clientY: 200 })
|
||||
|
||||
expect(screen.getAllByText('已标注 1 个修改点')).toHaveLength(2)
|
||||
const commentLabels = screen.getAllByText('人物位置向左移动')
|
||||
expect(commentLabels).toHaveLength(2)
|
||||
@ -112,11 +118,40 @@ describe('WorkReviewReviewerWorkspace', () => {
|
||||
imageComments: [{
|
||||
imageIndex: 0,
|
||||
decision: 'revision_required',
|
||||
annotations: [{ x: 0.25, y: 0.25, comment: '人物位置向左移动' }],
|
||||
annotations: [{ x: 0.75, y: 0.6, comment: '人物位置向左移动' }],
|
||||
}],
|
||||
})
|
||||
})
|
||||
|
||||
test('requires the reviewer to complete the current annotation before submitting', async () => {
|
||||
window.PointerEvent = MouseEvent as typeof PointerEvent
|
||||
const user = userEvent.setup()
|
||||
const onSubmitReview = jest.fn().mockResolvedValue(undefined)
|
||||
render(<WorkReviewReviewerWorkspace project={project} onSubmitReview={onSubmitReview} />)
|
||||
|
||||
await user.click(screen.getAllByRole('button', { name: '+ 添加标注' })[0])
|
||||
const canvas = screen.getAllByRole('group', { name: /作品长图,可添加位置批注/ })[0]
|
||||
jest.spyOn(canvas, 'getBoundingClientRect').mockReturnValue({
|
||||
x: 0,
|
||||
y: 0,
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 1000,
|
||||
bottom: 2000,
|
||||
width: 1000,
|
||||
height: 2000,
|
||||
toJSON: () => ({}),
|
||||
})
|
||||
fireEvent.pointerDown(canvas, { button: 0, clientX: 250, clientY: 500 })
|
||||
fireEvent.pointerUp(canvas, { button: 0, clientX: 250, clientY: 500 })
|
||||
await user.type(screen.getByRole('textbox', { name: /图 1 · 标注 1 修改意见/ }), '需要调整')
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '提交给修改人' }))
|
||||
|
||||
expect(onSubmitReview).not.toHaveBeenCalled()
|
||||
expect(screen.getByRole('alert')).toHaveTextContent('请先点击“完成”锁定当前标注位置')
|
||||
})
|
||||
|
||||
test('approves directly without requiring a modification annotation', async () => {
|
||||
const user = userEvent.setup()
|
||||
const onSubmitReview = jest.fn().mockResolvedValue(undefined)
|
||||
@ -151,7 +186,7 @@ describe('WorkReviewReviewerWorkspace', () => {
|
||||
expect(referenceButton).toHaveAttribute('aria-pressed', 'false')
|
||||
|
||||
await user.click(referenceButton)
|
||||
expect(scrollTo).toHaveBeenLastCalledWith({ left: 360, behavior: 'smooth' })
|
||||
expect(scrollTo).toHaveBeenCalledWith({ left: 360, behavior: 'smooth' })
|
||||
expect(referenceButton).toHaveAttribute('aria-pressed', 'true')
|
||||
|
||||
Object.defineProperty(scroller, 'scrollLeft', { configurable: true, value: 0 })
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user