Merge branch 'zh' into 'main'
Zh See merge request prism/lable/labelimage!5
This commit is contained in:
@@ -33,6 +33,8 @@ import {
|
||||
IconRefresh,
|
||||
IconSearch,
|
||||
} from "@tabler/icons-react"
|
||||
import dayjs from "dayjs"
|
||||
import duration from "dayjs/plugin/duration"
|
||||
import { DataTable, DataTableColumn } from "mantine-datatable"
|
||||
import { useRouter, useSearchParams } from "next/navigation"
|
||||
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||
@@ -41,6 +43,7 @@ import DispatchModal from "./components/DispatchModal"
|
||||
import ReleaseModal from "./components/ReleaseModal"
|
||||
import TaskStatusTag from "./components/TaskStatusTag"
|
||||
import WorkflowModal from "./components/WorkflowModal"
|
||||
dayjs.extend(duration)
|
||||
|
||||
interface FilterFormRecord {
|
||||
search_id: string
|
||||
@@ -441,6 +444,10 @@ export default function TaskTableContainer(props: {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const getTime = (time: number) => {
|
||||
return dayjs.duration(time, "seconds").format("HH:mm:ss")
|
||||
}
|
||||
|
||||
const columns = useMemo(() => {
|
||||
const cols: DataTableColumn<Task.DataProps>[] = [
|
||||
{
|
||||
@@ -673,37 +680,55 @@ export default function TaskTableContainer(props: {
|
||||
accessor: "work_time.label_work_time",
|
||||
title: "标注总耗时",
|
||||
width: 120,
|
||||
render: (record) => record.work_time?.label_work_time ?? "-",
|
||||
render: (record) =>
|
||||
record.work_time?.label_work_time
|
||||
? getTime(record.work_time?.label_work_time)
|
||||
: "-",
|
||||
},
|
||||
{
|
||||
accessor: "work_time.first_review_work_time",
|
||||
title: "审核总耗时",
|
||||
width: 120,
|
||||
render: (record) => record.work_time?.first_review_work_time ?? "-",
|
||||
render: (record) =>
|
||||
record.work_time?.first_review_work_time
|
||||
? getTime(record.work_time?.first_review_work_time)
|
||||
: "-",
|
||||
},
|
||||
{
|
||||
accessor: "work_time.second_review_work_time",
|
||||
title: "复审总耗时",
|
||||
width: 120,
|
||||
render: (record) => record.work_time?.second_review_work_time ?? "-",
|
||||
render: (record) =>
|
||||
record.work_time?.second_review_work_time
|
||||
? getTime(record.work_time?.second_review_work_time)
|
||||
: "-",
|
||||
},
|
||||
{
|
||||
accessor: "label_first_work_time",
|
||||
title: "标注首次耗时",
|
||||
width: 120,
|
||||
render: (record) => record.label_first_work_time ?? "-",
|
||||
render: (record) =>
|
||||
record.label_first_work_time
|
||||
? getTime(record.label_first_work_time)
|
||||
: "-",
|
||||
},
|
||||
{
|
||||
accessor: "review1_first_work_time",
|
||||
title: "审核首次耗时",
|
||||
width: 120,
|
||||
render: (record) => record.review1_first_work_time ?? "-",
|
||||
render: (record) =>
|
||||
record.review1_first_work_time
|
||||
? getTime(record.review1_first_work_time)
|
||||
: "-",
|
||||
},
|
||||
{
|
||||
accessor: "review2_first_work_time",
|
||||
title: "复审首次耗时",
|
||||
width: 120,
|
||||
render: (record) => record.review2_first_work_time ?? "-",
|
||||
render: (record) =>
|
||||
record.review2_first_work_time
|
||||
? getTime(record.review2_first_work_time)
|
||||
: "-",
|
||||
},
|
||||
{
|
||||
accessor: "first_reject_label_time",
|
||||
|
||||
@@ -19,6 +19,7 @@ const DISPLAY_COLUMNS: Array<{ accessor: string; title: string }> = [
|
||||
{ accessor: "task_status_dst", title: "目标状态" },
|
||||
{ accessor: "old_username", title: "原处理人" },
|
||||
{ accessor: "new_username", title: "目标处理人" },
|
||||
{ accessor: "create_at", title: "创建时间" },
|
||||
{ accessor: "action", title: "操作" },
|
||||
]
|
||||
|
||||
|
||||
@@ -1642,7 +1642,6 @@ const LabelPage = ({
|
||||
showGroupList,
|
||||
showDescList && projectDetail && projectDetail.label_type === 5,
|
||||
showDescList && projectDetail && projectDetail.label_type === 6,
|
||||
showDescList && projectDetail && projectDetail.label_type === 7,
|
||||
showObjectList,
|
||||
]
|
||||
let count = 0
|
||||
|
||||
@@ -17,6 +17,10 @@ import {
|
||||
IconChevronsLeft,
|
||||
IconChevronsRight,
|
||||
IconFlag,
|
||||
IconPlayerTrackNext,
|
||||
IconPlayerTrackNextFilled,
|
||||
IconPlayerTrackPrev,
|
||||
IconPlayerTrackPrevFilled,
|
||||
} from "@tabler/icons-react"
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import { Project } from "../api/project/typing"
|
||||
@@ -46,6 +50,8 @@ interface BottomToolsComponentProps {
|
||||
) => void | null
|
||||
}
|
||||
|
||||
type PlaybackDirection = "backward" | "forward"
|
||||
|
||||
const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
|
||||
const { labelState, projectDetail, taskDetail, renderPolygons } = props
|
||||
|
||||
@@ -72,35 +78,34 @@ const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
const [conflictOpen, setConflictOpen] = useState(false)
|
||||
const [playDirection, setPlayDirection] = useState<PlaybackDirection | null>(
|
||||
null
|
||||
)
|
||||
const allKeys = useMemo(() => Array.from(labelState.keys()), [labelState])
|
||||
const isShowKeyFrame =
|
||||
projectDetail &&
|
||||
[5, 6].includes(projectDetail.label_type) &&
|
||||
Array.from(labelState.keys()).some(
|
||||
allKeys.some(
|
||||
(k) => keyFrameData.has(k) && keyFrameData.get(k)?.key_frame
|
||||
) &&
|
||||
onlyKeyFrame
|
||||
const currentKeys = Array.from(labelState.keys()).filter((k) =>
|
||||
isShowKeyFrame
|
||||
? keyFrameData.has(k) && keyFrameData.get(k)?.key_frame
|
||||
: true
|
||||
const currentKeys = useMemo(
|
||||
() =>
|
||||
allKeys.filter((k) =>
|
||||
isShowKeyFrame
|
||||
? keyFrameData.has(k) && keyFrameData.get(k)?.key_frame
|
||||
: true
|
||||
),
|
||||
[allKeys, isShowKeyFrame, keyFrameData]
|
||||
)
|
||||
// 上一帧图片id
|
||||
const previousFrameKey = useMemo(() => {
|
||||
let key = ""
|
||||
const keys = Array.from(labelState.keys()).filter((k) =>
|
||||
isShowKeyFrame
|
||||
? keyFrameData.has(k) && keyFrameData.get(k)?.key_frame
|
||||
: true
|
||||
)
|
||||
keys.forEach((k, index) => {
|
||||
if (k === activeImage && index > 0) key = keys[index - 1]
|
||||
})
|
||||
return key
|
||||
}, [activeImage, isShowKeyFrame, keyFrameData, labelState])
|
||||
const currentIndex = currentKeys.findIndex((k) => k === activeImage)
|
||||
return currentIndex > 0 ? currentKeys[currentIndex - 1] : ""
|
||||
}, [activeImage, currentKeys])
|
||||
|
||||
useEffect(() => {
|
||||
const keys = Array.from(labelState.keys())
|
||||
if (!keys.length) {
|
||||
if (!currentKeys.length) {
|
||||
setActiveImage("")
|
||||
setInputNumber("")
|
||||
setActiveImageId(0)
|
||||
@@ -108,36 +113,42 @@ const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
|
||||
}
|
||||
|
||||
const currentIndex = activeImage
|
||||
? keys.findIndex((k) => k === activeImage)
|
||||
? currentKeys.findIndex((k) => k === activeImage)
|
||||
: -1
|
||||
if (currentIndex >= 0) {
|
||||
setInputNumber(String(currentIndex + 1))
|
||||
setActiveImageId(currentIndex + 1)
|
||||
const absoluteIndex = allKeys.findIndex((k) => k === activeImage)
|
||||
setActiveImageId(absoluteIndex >= 0 ? absoluteIndex + 1 : 0)
|
||||
return
|
||||
}
|
||||
|
||||
const firstKey = keys[0]
|
||||
const firstKey = currentKeys[0]
|
||||
const absoluteIndex = allKeys.findIndex((k) => k === firstKey)
|
||||
setActiveImage(firstKey)
|
||||
setInputNumber("1")
|
||||
setActiveImageId(1)
|
||||
setActiveImageId(absoluteIndex >= 0 ? absoluteIndex + 1 : 1)
|
||||
}, [
|
||||
activeImage,
|
||||
labelState,
|
||||
allKeys,
|
||||
currentKeys,
|
||||
setActiveImage,
|
||||
setActiveImageId,
|
||||
setInputNumber,
|
||||
])
|
||||
|
||||
const scrollToIndex = (index: number) => {
|
||||
const key = currentKeys[index]
|
||||
if (key && itemRefs.current[key]) {
|
||||
itemRefs.current[key]?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
inline: "center",
|
||||
})
|
||||
}
|
||||
}
|
||||
const scrollToIndex = useCallback(
|
||||
(index: number) => {
|
||||
const key = currentKeys[index]
|
||||
if (key && itemRefs.current[key]) {
|
||||
itemRefs.current[key]?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
inline: "center",
|
||||
})
|
||||
}
|
||||
},
|
||||
[currentKeys]
|
||||
)
|
||||
|
||||
const closeRightContext = useCallback(() => {
|
||||
useOtherToolsStore.getState().setShowContextMenu({
|
||||
@@ -152,6 +163,72 @@ const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
|
||||
})
|
||||
}, [])
|
||||
|
||||
const goToVisibleIndex = useCallback(
|
||||
(index: number) => {
|
||||
if (index < 0 || index >= currentKeys.length) return false
|
||||
|
||||
const nextImage = currentKeys[index]
|
||||
const absoluteIndex = allKeys.findIndex((k) => k === nextImage)
|
||||
|
||||
scrollToIndex(index)
|
||||
setInputNumber(String(index + 1))
|
||||
setActiveImageId(absoluteIndex >= 0 ? absoluteIndex + 1 : 0)
|
||||
setActiveImage(nextImage)
|
||||
closeRightContext()
|
||||
|
||||
return true
|
||||
},
|
||||
[
|
||||
allKeys,
|
||||
closeRightContext,
|
||||
currentKeys,
|
||||
scrollToIndex,
|
||||
setActiveImage,
|
||||
setActiveImageId,
|
||||
setInputNumber,
|
||||
]
|
||||
)
|
||||
|
||||
const stepVisibleImage = useCallback(
|
||||
(direction: PlaybackDirection) => {
|
||||
const currentIndex = currentKeys.findIndex((k) => k === activeImage)
|
||||
if (currentIndex < 0) return false
|
||||
|
||||
const nextIndex =
|
||||
direction === "backward" ? currentIndex - 1 : currentIndex + 1
|
||||
|
||||
return goToVisibleIndex(nextIndex)
|
||||
},
|
||||
[activeImage, currentKeys, goToVisibleIndex]
|
||||
)
|
||||
|
||||
const togglePlayback = useCallback(
|
||||
(direction: PlaybackDirection) => {
|
||||
if (currentKeys.length < 2) return
|
||||
setPlayDirection((current) => (current === direction ? null : direction))
|
||||
},
|
||||
[currentKeys.length]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!playDirection) return
|
||||
if (currentKeys.length < 2) {
|
||||
setPlayDirection(null)
|
||||
return
|
||||
}
|
||||
|
||||
const timer = window.setInterval(() => {
|
||||
const moved = stepVisibleImage(playDirection)
|
||||
if (!moved) {
|
||||
setPlayDirection(null)
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
return () => {
|
||||
window.clearInterval(timer)
|
||||
}
|
||||
}, [currentKeys.length, playDirection, stepVisibleImage])
|
||||
|
||||
const getOperationSchema = useCallback(
|
||||
(operationId: string) => {
|
||||
return (
|
||||
@@ -368,28 +445,32 @@ const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
|
||||
color="gray"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
scrollToIndex(0)
|
||||
currentKeys.length && setActiveImage(currentKeys[0])
|
||||
setInputNumber("1")
|
||||
setActiveImageId(1)
|
||||
closeRightContext()
|
||||
goToVisibleIndex(0)
|
||||
}}>
|
||||
<IconChevronsLeft size={16} />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant={playDirection === "backward" ? "filled" : "subtle"}
|
||||
color={playDirection === "backward" ? "blue" : "gray"}
|
||||
size="sm"
|
||||
disabled={currentKeys.length < 2}
|
||||
aria-label="向前播放"
|
||||
title="向前播放"
|
||||
onClick={() => {
|
||||
togglePlayback("backward")
|
||||
}}>
|
||||
{playDirection === "backward" ? (
|
||||
<IconPlayerTrackPrevFilled size={16} />
|
||||
) : (
|
||||
<IconPlayerTrackPrev size={16} />
|
||||
)}
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
if (inputNumber !== "1") {
|
||||
let newActiveIndex = +inputNumber - 1
|
||||
setInputNumber(newActiveIndex.toString())
|
||||
setActiveImageId(newActiveIndex)
|
||||
scrollToIndex(newActiveIndex - 1)
|
||||
|
||||
setActiveImage(currentKeys?.[newActiveIndex - 1])
|
||||
closeRightContext()
|
||||
}
|
||||
stepVisibleImage("backward")
|
||||
}}>
|
||||
<IconChevronLeft size={16} />
|
||||
</ActionIcon>
|
||||
@@ -410,10 +491,21 @@ const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
scrollToIndex(+inputNumber - 1)
|
||||
setActiveImage(currentKeys?.[+inputNumber - 1])
|
||||
setActiveImageId(+inputNumber)
|
||||
closeRightContext()
|
||||
const targetIndex = Number(inputNumber)
|
||||
if (
|
||||
Number.isInteger(targetIndex) &&
|
||||
targetIndex >= 1 &&
|
||||
targetIndex <= currentKeys.length
|
||||
) {
|
||||
goToVisibleIndex(targetIndex - 1)
|
||||
} else {
|
||||
const currentIndex = currentKeys.findIndex(
|
||||
(key) => key === activeImage
|
||||
)
|
||||
setInputNumber(
|
||||
currentIndex >= 0 ? String(currentIndex + 1) : ""
|
||||
)
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -423,27 +515,32 @@ const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
|
||||
color="gray"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
if (+inputNumber < currentKeys.length) {
|
||||
let newActiveIndex = +inputNumber + 1
|
||||
setInputNumber(newActiveIndex.toString())
|
||||
setActiveImageId(newActiveIndex)
|
||||
scrollToIndex(newActiveIndex - 1)
|
||||
setActiveImage(currentKeys?.[newActiveIndex - 1])
|
||||
closeRightContext()
|
||||
}
|
||||
stepVisibleImage("forward")
|
||||
}}>
|
||||
<IconChevronRight size={16} />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant={playDirection === "forward" ? "filled" : "subtle"}
|
||||
color={playDirection === "forward" ? "blue" : "gray"}
|
||||
size="sm"
|
||||
disabled={currentKeys.length < 2}
|
||||
aria-label="向后播放"
|
||||
title="向后播放"
|
||||
onClick={() => {
|
||||
togglePlayback("forward")
|
||||
}}>
|
||||
{playDirection === "forward" ? (
|
||||
<IconPlayerTrackNextFilled size={16} />
|
||||
) : (
|
||||
<IconPlayerTrackNext size={16} />
|
||||
)}
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
scrollToIndex(currentKeys.length - 1)
|
||||
setInputNumber(currentKeys.length.toString())
|
||||
setActiveImageId(currentKeys.length)
|
||||
setActiveImage(currentKeys?.[currentKeys.length - 1])
|
||||
closeRightContext()
|
||||
goToVisibleIndex(currentKeys.length - 1)
|
||||
}}>
|
||||
<IconChevronsRight size={16} />
|
||||
</ActionIcon>
|
||||
@@ -657,9 +754,7 @@ const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
|
||||
if (ctrlKey || shiftKey) {
|
||||
setSelectedItems(key, ctrlKey, shiftKey)
|
||||
} else {
|
||||
setInputNumber(index + 1 + "")
|
||||
setActiveImage(key)
|
||||
setActiveImageId(index + 1)
|
||||
goToVisibleIndex(index)
|
||||
setSelectedItems("", false, false)
|
||||
}
|
||||
closeRightContext()
|
||||
|
||||
@@ -26,6 +26,7 @@ import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
@@ -94,6 +95,13 @@ const renderRaster = (paperGroup: paper.Group, option: any) => {
|
||||
}
|
||||
|
||||
type NormalizedPoint = [number, number]
|
||||
type SupportBox = [number, number, number, number]
|
||||
type ContextMenuLayout = {
|
||||
top: number
|
||||
left: number
|
||||
maxHeight: number
|
||||
maxWidth: number
|
||||
}
|
||||
|
||||
const normalizePoint = (value: any): NormalizedPoint | null => {
|
||||
if (value instanceof paper.Point) return [value.x, value.y]
|
||||
@@ -133,10 +141,19 @@ const PaperContainer = (
|
||||
) => {
|
||||
const { forceUpdate, projectDetail, labelState, updateLoadingFlag } = props
|
||||
const containerRef = useRef<any>(null)
|
||||
const contextMenuRef = useRef<HTMLDivElement>(null)
|
||||
const [imgSize, setImageSize] = useState<{
|
||||
clientWidth: number
|
||||
clientHeight: number
|
||||
}>()
|
||||
const [contextMenuLayout, setContextMenuLayout] = useState<ContextMenuLayout>(
|
||||
{
|
||||
top: 0,
|
||||
left: 0,
|
||||
maxHeight: 0,
|
||||
maxWidth: 0,
|
||||
}
|
||||
)
|
||||
|
||||
const [firstRender, setFirstRender] = useState(false)
|
||||
const [confirmModalOpened, setConfirmModalOpened] = useState(false)
|
||||
@@ -431,7 +448,8 @@ const PaperContainer = (
|
||||
async (
|
||||
points: Array<[number, number]>,
|
||||
tags: number[],
|
||||
clear_previous_state = false
|
||||
clear_previous_state = false,
|
||||
boxes: SupportBox[] = []
|
||||
) => {
|
||||
void clear_previous_state
|
||||
try {
|
||||
@@ -459,6 +477,9 @@ const PaperContainer = (
|
||||
usePaperStore.getState().rasterScale[activeImage] ?? 1
|
||||
|
||||
const draw = () => {
|
||||
const paperGroup = usePaperStore.getState().group!
|
||||
const raster = paperGroup.getItems({ data: { name: "pic" } })[0]
|
||||
|
||||
points.forEach((point, index) => {
|
||||
const drawPoint = new paper.Path.Circle({
|
||||
center: point,
|
||||
@@ -470,16 +491,30 @@ const PaperContainer = (
|
||||
type: "point",
|
||||
},
|
||||
})
|
||||
drawPoint.parent = usePaperStore.getState().group!
|
||||
const raster = usePaperStore
|
||||
.getState()
|
||||
.group!.getItems({ data: { name: "pic" } })[0]
|
||||
drawPoint.parent = paperGroup
|
||||
if (!raster.contains(drawPoint.position)) {
|
||||
throw new Error("点位超出图片范围")
|
||||
}
|
||||
})
|
||||
|
||||
boxes.forEach(([left, top, right, bottom]) => {
|
||||
new paper.Path.Rectangle({
|
||||
parent: paperGroup,
|
||||
from: [left, top],
|
||||
to: [right, bottom],
|
||||
strokeColor: "#FFF",
|
||||
strokeWidth: 2,
|
||||
strokeScaling: false,
|
||||
dashArray: [6, 4],
|
||||
fillColor: new paper.Color(1, 1, 1, 0.05),
|
||||
data: {
|
||||
id: "support",
|
||||
type: "box",
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
console.log(points, tags)
|
||||
console.log(points, tags, boxes)
|
||||
clearSupportAnnotationItem()
|
||||
draw()
|
||||
|
||||
@@ -496,8 +531,12 @@ const PaperContainer = (
|
||||
const promptPoints = foregroundPoints.length
|
||||
? foregroundPoints
|
||||
: normalizedPoints
|
||||
// const promptBox =
|
||||
// promptPoints.length > 1 ? getContourBox(promptPoints) : null
|
||||
const normalizedBoxes = boxes.map(([left, top, right, bottom]) => [
|
||||
Math.round(Math.min(left, right) / currentScale),
|
||||
Math.round(Math.min(top, bottom) / currentScale),
|
||||
Math.round(Math.max(left, right) / currentScale),
|
||||
Math.round(Math.max(top, bottom) / currentScale),
|
||||
]) as SupportBox[]
|
||||
const projectId =
|
||||
projectDetail?.name ||
|
||||
projectDetail?.rpath ||
|
||||
@@ -506,8 +545,8 @@ const PaperContainer = (
|
||||
if (!projectId) {
|
||||
throw new Error("缺少 project_id,无法调用模型接口")
|
||||
}
|
||||
if (!promptPoints.length) {
|
||||
throw new Error("缺少有效点位,无法调用模型接口")
|
||||
if (!promptPoints.length && !normalizedBoxes.length) {
|
||||
throw new Error("缺少有效提示,无法调用模型接口")
|
||||
}
|
||||
// const picSize = usePaperStore.getState().rasterSize[activeImage]
|
||||
|
||||
@@ -517,9 +556,13 @@ const PaperContainer = (
|
||||
image: activeImage,
|
||||
// project_id: "image_test_bk",
|
||||
prompt: {
|
||||
foreground_points: promptPoints,
|
||||
background_points: background_points,
|
||||
// ...(promptBox ? { boxes: [promptBox] } : {}),
|
||||
...(promptPoints.length
|
||||
? { foreground_points: promptPoints }
|
||||
: {}),
|
||||
...(background_points.length
|
||||
? { background_points: background_points }
|
||||
: {}),
|
||||
...(normalizedBoxes.length ? { boxes: normalizedBoxes } : {}),
|
||||
},
|
||||
})
|
||||
const { contours = [], hollows = [] } = data || {}
|
||||
@@ -1918,6 +1961,61 @@ const PaperContainer = (
|
||||
?.find((item) => item[0] === id)
|
||||
}, [activeImage, id, operationId, storeLabel])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!showContextMenu) return
|
||||
const container = containerRef.current
|
||||
const menu = contextMenuRef.current
|
||||
if (!container || !menu) return
|
||||
|
||||
const safeMargin = 8
|
||||
const updateMenuLayout = () => {
|
||||
const availableWidth = Math.max(container.clientWidth - safeMargin * 2, 0)
|
||||
const availableHeight = Math.max(
|
||||
container.clientHeight - safeMargin * 2,
|
||||
0
|
||||
)
|
||||
const menuWidth = Math.min(menu.offsetWidth, availableWidth)
|
||||
const menuHeight = Math.min(menu.offsetHeight, availableHeight)
|
||||
const nextLeft = Math.min(
|
||||
Math.max(position.left, safeMargin),
|
||||
Math.max(container.clientWidth - menuWidth - safeMargin, safeMargin)
|
||||
)
|
||||
const nextTop = Math.min(
|
||||
Math.max(position.top, safeMargin),
|
||||
Math.max(container.clientHeight - menuHeight - safeMargin, safeMargin)
|
||||
)
|
||||
|
||||
setContextMenuLayout((prev) => {
|
||||
if (
|
||||
prev.left === nextLeft &&
|
||||
prev.top === nextTop &&
|
||||
prev.maxHeight === availableHeight &&
|
||||
prev.maxWidth === availableWidth
|
||||
) {
|
||||
return prev
|
||||
}
|
||||
return {
|
||||
left: nextLeft,
|
||||
top: nextTop,
|
||||
maxHeight: availableHeight,
|
||||
maxWidth: availableWidth,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
updateMenuLayout()
|
||||
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
updateMenuLayout()
|
||||
})
|
||||
resizeObserver.observe(container)
|
||||
resizeObserver.observe(menu)
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect()
|
||||
}
|
||||
}, [position.left, position.top, showContextMenu])
|
||||
|
||||
// 打开弹窗时,初始化form数据
|
||||
useEffect(() => {
|
||||
if (showContextMenu) {
|
||||
@@ -2264,6 +2362,7 @@ const PaperContainer = (
|
||||
|
||||
return (
|
||||
<Card
|
||||
ref={contextMenuRef}
|
||||
shadow="sm"
|
||||
padding="xs"
|
||||
radius="md"
|
||||
@@ -2272,10 +2371,20 @@ const PaperContainer = (
|
||||
position: "absolute",
|
||||
zIndex: 1000,
|
||||
width: 224,
|
||||
maxWidth: contextMenuLayout.maxWidth
|
||||
? `${contextMenuLayout.maxWidth}px`
|
||||
: undefined,
|
||||
maxHeight: contextMenuLayout.maxHeight
|
||||
? `${contextMenuLayout.maxHeight}px`
|
||||
: undefined,
|
||||
height: "fit-content",
|
||||
display: !showContextMenu ? "none" : "block",
|
||||
top: `${position.top}px`,
|
||||
left: `${position.left}px`,
|
||||
top: `${contextMenuLayout.top}px`,
|
||||
left: `${contextMenuLayout.left}px`,
|
||||
overflowY: "auto",
|
||||
overflowX: "hidden",
|
||||
overscrollBehavior: "contain",
|
||||
wordBreak: "break-word",
|
||||
}}>
|
||||
<Box p="xs">
|
||||
<Stack gap="xs">
|
||||
|
||||
@@ -628,16 +628,48 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
|
||||
<Stack gap="xs" pb="md">
|
||||
{labelState.get(activeImage) &&
|
||||
labelState.get(activeImage)?.map((operationId) => {
|
||||
const operationSchema = getOperationSchema(operationId)
|
||||
const isCollapsed =
|
||||
operationStatus[activeImage + operationId]?.["COLLAPSE"]
|
||||
const isHide =
|
||||
operationStatus[activeImage + operationId]?.HIDE
|
||||
const hasSubAttrError = getOperationSubAttrStatus(operationId)
|
||||
const operationLabel = operationSchema?.label_class
|
||||
const operationChildren =
|
||||
storeLabel.get(activeImage)?.get(operationId) || []
|
||||
const objectCount = operationChildren.length
|
||||
const visibleChildren = operationChildren.filter((child) => {
|
||||
const { comment } = child[2]
|
||||
let visibleFlag = true
|
||||
if (comment && comment.length) {
|
||||
const latest_comment =
|
||||
(taskDetail && taskDetail.label_status === 2) ||
|
||||
comment.length === 1
|
||||
? comment[comment.length - 1]
|
||||
: comment[comment.length - 2]
|
||||
let type = latest_comment.comment_type
|
||||
if (type === 2) {
|
||||
const { last_modified_timestamp } = child[2]
|
||||
if (
|
||||
last_modified_timestamp &&
|
||||
last_modified_timestamp > latest_comment.date
|
||||
)
|
||||
type = 3
|
||||
}
|
||||
visibleFlag = checkBoxsChecked[3 - type]
|
||||
}
|
||||
if (checkBoxsChecked.every((value) => !value))
|
||||
visibleFlag = true
|
||||
return visibleFlag
|
||||
})
|
||||
return (
|
||||
<Box key={operationId} w="100%">
|
||||
<Paper withBorder p="xs" radius="md" shadow="sm">
|
||||
<Flex justify="space-between" align="center">
|
||||
<Group gap="xs">
|
||||
<Flex align="center" gap="xs" style={{ minWidth: 0 }}>
|
||||
<Flex
|
||||
align="center"
|
||||
gap="xs"
|
||||
style={{ flex: 1, minWidth: 0 }}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
@@ -650,23 +682,62 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
|
||||
<EyeOff size={16} />
|
||||
)}
|
||||
</ActionIcon>
|
||||
{renderOperationIcon(
|
||||
getOperationSchema(operationId)
|
||||
)}
|
||||
<Text
|
||||
size="sm"
|
||||
c={hasSubAttrError ? "red" : undefined}>
|
||||
{renderShortcutKey(operationId)}
|
||||
</Text>
|
||||
</Group>
|
||||
<Text
|
||||
size="sm"
|
||||
c={hasSubAttrError ? "red" : undefined}>
|
||||
{getOperationSchema(operationId)?.label_class}
|
||||
</Text>
|
||||
<Group gap="xs">
|
||||
<Group
|
||||
gap={8}
|
||||
wrap="nowrap"
|
||||
style={{ flex: 1, minWidth: 0 }}>
|
||||
<Group
|
||||
gap={6}
|
||||
wrap="nowrap"
|
||||
px="xs"
|
||||
py={4}
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
borderRadius: 999,
|
||||
border: `1px solid ${
|
||||
hasSubAttrError
|
||||
? "var(--mantine-color-red-2)"
|
||||
: "var(--mantine-color-gray-2)"
|
||||
}`,
|
||||
background: hasSubAttrError
|
||||
? "var(--mantine-color-red-0)"
|
||||
: "var(--mantine-color-gray-0)",
|
||||
}}>
|
||||
{renderOperationIcon(operationSchema)}
|
||||
<Text
|
||||
size="xs"
|
||||
fw={700}
|
||||
c={hasSubAttrError ? "red" : "dimmed"}
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
lineHeight: 1,
|
||||
}}>
|
||||
{renderShortcutKey(operationId)}
|
||||
</Text>
|
||||
</Group>
|
||||
<Box style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text
|
||||
size="sm"
|
||||
fw={500}
|
||||
c={hasSubAttrError ? "red" : undefined}
|
||||
title={operationLabel || undefined}
|
||||
style={{
|
||||
minWidth: 0,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
}}>
|
||||
{operationLabel}
|
||||
</Text>
|
||||
</Box>
|
||||
</Group>
|
||||
</Flex>
|
||||
<Group
|
||||
gap={6}
|
||||
wrap="nowrap"
|
||||
style={{ flexShrink: 0, marginLeft: "auto" }}>
|
||||
<TextInput
|
||||
w={96}
|
||||
w={88}
|
||||
size="xs"
|
||||
placeholder="搜索子属性"
|
||||
value={searchSubAttrs[operationId] || ""}
|
||||
@@ -680,10 +751,13 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
|
||||
handleSelectedBySearchSubAttr(e, operationId)
|
||||
}
|
||||
/>
|
||||
<Text size="sm">
|
||||
{storeLabel.get(activeImage)?.get(operationId)
|
||||
?.length || 0}
|
||||
</Text>
|
||||
<Badge
|
||||
size="sm"
|
||||
variant="light"
|
||||
radius="xl"
|
||||
color={hasSubAttrError ? "red" : "gray"}>
|
||||
{objectCount}
|
||||
</Badge>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
@@ -704,58 +778,93 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
|
||||
</Flex>
|
||||
</Paper>
|
||||
<Collapse in={!isCollapsed}>
|
||||
<Stack gap="xs" mt="xs" pl="xs">
|
||||
{storeLabel
|
||||
.get(activeImage)
|
||||
?.get(operationId)
|
||||
?.map((child) => {
|
||||
const { comment } = child[2]
|
||||
let visibleFlag = true
|
||||
if (comment && comment.length) {
|
||||
const latest_comment =
|
||||
(taskDetail &&
|
||||
taskDetail.label_status === 2) ||
|
||||
comment.length === 1
|
||||
? comment[comment.length - 1]
|
||||
: comment[comment.length - 2]
|
||||
let type = latest_comment.comment_type
|
||||
if (type === 2) {
|
||||
const { last_modified_timestamp } = child[2]
|
||||
if (
|
||||
last_modified_timestamp &&
|
||||
last_modified_timestamp >
|
||||
latest_comment.date
|
||||
)
|
||||
type = 3
|
||||
}
|
||||
visibleFlag = checkBoxsChecked[3 - type]
|
||||
}
|
||||
if (checkBoxsChecked.every((value) => !value))
|
||||
visibleFlag = true
|
||||
<Stack gap="xs" mt="xs" ml="xs">
|
||||
{visibleChildren.map((child, childIndex) => {
|
||||
const { comment } = child[2]
|
||||
const isSelected = selectedPath[
|
||||
activeImage
|
||||
]?.includes(child[0])
|
||||
const isChildHide =
|
||||
pathStatus[activeImage + child[0]]?.["HIDE"]
|
||||
const hasChildError = getSubAttrStatus(
|
||||
getOperationSchema(operationId),
|
||||
child[2]?.sub_attributes
|
||||
)
|
||||
const isLastVisibleChild =
|
||||
childIndex === visibleChildren.length - 1
|
||||
|
||||
const isSelected = selectedPath[
|
||||
activeImage
|
||||
]?.includes(child[0])
|
||||
const isChildHide =
|
||||
pathStatus[activeImage + child[0]]?.["HIDE"]
|
||||
const hasChildError = getSubAttrStatus(
|
||||
getOperationSchema(operationId),
|
||||
child[2]?.sub_attributes
|
||||
)
|
||||
let textColor = undefined
|
||||
let connectorColor = "var(--mantine-color-gray-4)"
|
||||
|
||||
let textColor = undefined
|
||||
if (hasChildError) {
|
||||
textColor = "red"
|
||||
connectorColor = "var(--mantine-color-red-3)"
|
||||
} else if (isSelected) {
|
||||
textColor = "white"
|
||||
connectorColor =
|
||||
"var(--mantine-primary-color-filled)"
|
||||
}
|
||||
|
||||
if (hasChildError) {
|
||||
textColor = "red"
|
||||
} else if (isSelected) {
|
||||
textColor = "white"
|
||||
}
|
||||
|
||||
if (!visibleFlag) return null
|
||||
|
||||
return (
|
||||
return (
|
||||
<Box
|
||||
key={child[0]}
|
||||
pl="lg"
|
||||
style={{ position: "relative" }}>
|
||||
<Box
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: 7,
|
||||
top: 0,
|
||||
bottom: isLastVisibleChild ? "75%" : 0,
|
||||
width: 1.5,
|
||||
borderRadius: 999,
|
||||
background: connectorColor,
|
||||
opacity: isSelected ? 0.9 : 0.35,
|
||||
}}
|
||||
/>
|
||||
{isLastVisibleChild ? (
|
||||
<Box
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: 7,
|
||||
top: "calc(50% - 10px)",
|
||||
width: 14,
|
||||
height: 10,
|
||||
borderLeft: `1.5px solid ${connectorColor}`,
|
||||
borderBottom: `1.5px solid ${connectorColor}`,
|
||||
borderBottomLeftRadius: 10,
|
||||
opacity: isSelected ? 0.9 : 0.55,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Box
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: 7,
|
||||
top: "50%",
|
||||
width: 14,
|
||||
borderTop: `1.5px solid ${connectorColor}`,
|
||||
opacity: isSelected ? 0.9 : 0.45,
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: 4,
|
||||
top: "calc(50% - 3px)",
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: 999,
|
||||
background: connectorColor,
|
||||
boxShadow:
|
||||
"0 0 0 2px var(--mantine-color-body)",
|
||||
opacity: isSelected ? 1 : 0.85,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Paper
|
||||
key={child[0]}
|
||||
withBorder
|
||||
p="xs"
|
||||
radius="md"
|
||||
@@ -763,7 +872,7 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
|
||||
bg={
|
||||
isSelected
|
||||
? "var(--mantine-primary-color-filled)"
|
||||
: undefined
|
||||
: "var(--mantine-color-gray-0)"
|
||||
}
|
||||
c={isSelected ? "white" : undefined}
|
||||
onClick={() =>
|
||||
@@ -944,8 +1053,9 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
|
||||
) : null}
|
||||
</Flex>
|
||||
</Paper>
|
||||
)
|
||||
})}
|
||||
</Box>
|
||||
)
|
||||
})}
|
||||
</Stack>
|
||||
</Collapse>
|
||||
</Box>
|
||||
|
||||
@@ -139,7 +139,8 @@ interface PaperState {
|
||||
renderPath: (
|
||||
points: Array<[number, number]>,
|
||||
tags: number[],
|
||||
flag?: boolean
|
||||
flag?: boolean,
|
||||
boxes?: Array<[number, number, number, number]>
|
||||
) => void
|
||||
) => void
|
||||
getClickedCircle: (
|
||||
@@ -290,6 +291,49 @@ const handleMouseMove = (e: paper.MouseEvent) => {
|
||||
usePositionStore.getState().setCurrentMousePosition([e.point.x, e.point.y])
|
||||
}
|
||||
|
||||
const hidePaperContextMenu = () => {
|
||||
useOtherToolsStore.getState().setShowContextMenu({
|
||||
showContextMenu: false,
|
||||
position: {
|
||||
top: 0,
|
||||
left: 0,
|
||||
},
|
||||
imageId: useOtherToolsStore.getState().imageId,
|
||||
operationId: useOtherToolsStore.getState().operationId,
|
||||
id: useOtherToolsStore.getState().id,
|
||||
})
|
||||
}
|
||||
|
||||
let isMiddleMousePanning = false
|
||||
|
||||
const startMiddleMousePan = (event?: MouseEvent) => {
|
||||
if (!event || event.button !== 1) return false
|
||||
|
||||
event.preventDefault()
|
||||
isMiddleMousePanning = true
|
||||
hidePaperContextMenu()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const dragMiddleMousePan = (e: { event: MouseEvent; delta: paper.Point }) => {
|
||||
if (!isMiddleMousePanning) return false
|
||||
|
||||
e.event.preventDefault()
|
||||
usePaperStore.getState().group!.position = usePaperStore
|
||||
.getState()
|
||||
.group!.position.add(e.delta)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const stopMiddleMousePan = () => {
|
||||
if (!isMiddleMousePanning) return false
|
||||
|
||||
isMiddleMousePanning = false
|
||||
return true
|
||||
}
|
||||
|
||||
const { user_id } = usePermissionStore.getState()
|
||||
|
||||
type OrderPoint = [number, number]
|
||||
@@ -427,45 +471,23 @@ export const clearSupportAnnotationItem = () => {
|
||||
}
|
||||
|
||||
// 根据点击位置判断弹窗弹出的top、left
|
||||
const containerWidth = 244
|
||||
const containerHeight = 240
|
||||
export const getShowContextMenuPosition = (
|
||||
clickPoint: paper.Point,
|
||||
category: Project.LabelSchemaList,
|
||||
_category: Project.LabelSchemaList,
|
||||
offsetWidth = 0,
|
||||
offsetHeight = 0
|
||||
) => {
|
||||
let subAttrHeight = 0
|
||||
if (category) {
|
||||
const { sub_attributes_describe } = category
|
||||
console.log(sub_attributes_describe)
|
||||
|
||||
if (sub_attributes_describe.length) subAttrHeight += 30
|
||||
sub_attributes_describe.forEach((item) => {
|
||||
if (item.chinese_name) subAttrHeight += 32
|
||||
if (item.optional_item && item.optional_item.length)
|
||||
item.optional_item.forEach(() => {
|
||||
subAttrHeight += 22
|
||||
})
|
||||
if (item.select_mode === 0) subAttrHeight += 32
|
||||
})
|
||||
}
|
||||
|
||||
const finalHeight = containerHeight + subAttrHeight
|
||||
const element = document.getElementById("paper-container")
|
||||
const rasterPos = [element!.clientWidth, element!.clientHeight]
|
||||
console.log(rasterPos, finalHeight)
|
||||
let left =
|
||||
clickPoint.x + containerWidth > rasterPos[0]
|
||||
? clickPoint.x + offsetWidth - containerWidth
|
||||
: clickPoint.x + offsetWidth
|
||||
let top =
|
||||
clickPoint.y + finalHeight > rasterPos[1]
|
||||
? clickPoint.y + offsetHeight - finalHeight < 0
|
||||
? 0
|
||||
: clickPoint.y + offsetHeight - finalHeight
|
||||
: clickPoint.y + offsetHeight
|
||||
console.log("转换之后的x、y", left, top)
|
||||
const containerWidth = element?.clientWidth ?? 0
|
||||
const containerHeight = element?.clientHeight ?? 0
|
||||
const left = Math.min(
|
||||
Math.max(clickPoint.x + offsetWidth, 0),
|
||||
containerWidth || clickPoint.x + offsetWidth
|
||||
)
|
||||
const top = Math.min(
|
||||
Math.max(clickPoint.y + offsetHeight, 0),
|
||||
containerHeight || clickPoint.y + offsetHeight
|
||||
)
|
||||
|
||||
return {
|
||||
top,
|
||||
@@ -722,25 +744,13 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
}
|
||||
|
||||
panTool.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
|
||||
if (startMiddleMousePan(e.event)) return
|
||||
if (pressCtrl) return
|
||||
const currentTime = Date.now()
|
||||
const timeDelta = currentTime - lastTime
|
||||
|
||||
if (e.event && e.event.button === 0) {
|
||||
useOtherToolsStore.getState().setShowContextMenu({
|
||||
showContextMenu: false,
|
||||
position: {
|
||||
top: 0,
|
||||
left: 0,
|
||||
},
|
||||
imageId: useOtherToolsStore.getState().imageId,
|
||||
operationId: useOtherToolsStore.getState().operationId,
|
||||
id: useOtherToolsStore.getState().id,
|
||||
})
|
||||
} else if (e.event && e.event.button === 1) {
|
||||
// 鼠标中键操作图片移动
|
||||
panMode = "item"
|
||||
return
|
||||
hidePaperContextMenu()
|
||||
}
|
||||
if (useTopToolsStore.getState().isView) {
|
||||
panMode = ""
|
||||
@@ -1433,12 +1443,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
downPoint: paper.Point
|
||||
delta: paper.Point
|
||||
}) => {
|
||||
// 优先判断移动图片
|
||||
if (panMode === "item") {
|
||||
usePaperStore.getState().group!.position = usePaperStore
|
||||
.getState()
|
||||
.group?.position.add(e.delta)!
|
||||
}
|
||||
if (dragMiddleMousePan(e)) return
|
||||
|
||||
if (useTopToolsStore.getState().isView) return
|
||||
|
||||
@@ -1677,6 +1682,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
|
||||
// 改变对象不能用 activeImage activeOperation (因为图形只在对应图片渲染,所以activeImage是一样的)
|
||||
panTool.onMouseUp = (_e: paper.ToolEvent) => {
|
||||
if (stopMiddleMousePan()) return
|
||||
if (useTopToolsStore.getState().isView) return
|
||||
|
||||
let newDrawPath: paper.Path | paper.CompoundPath | null = null
|
||||
@@ -2569,6 +2575,12 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
}
|
||||
polygon.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
|
||||
if (e.event && e.event.button === 2) {
|
||||
if (
|
||||
["polygon", "brush", "point"].includes(
|
||||
usePaperStore.getState().mode || ""
|
||||
)
|
||||
)
|
||||
return
|
||||
if (polygon.data.id) {
|
||||
let category = useTopToolsStore
|
||||
.getState()
|
||||
@@ -2615,6 +2627,12 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
point: paper.Point
|
||||
}) => {
|
||||
if (e.event && e.event.button === 2) {
|
||||
if (
|
||||
["polygon", "brush", "point"].includes(
|
||||
usePaperStore.getState().mode || ""
|
||||
)
|
||||
)
|
||||
return
|
||||
if (compoundPath.data.id) {
|
||||
let category = useTopToolsStore
|
||||
.getState()
|
||||
@@ -2654,11 +2672,12 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
let polygon: paper.Path | paper.CompoundPath | null
|
||||
let compoundPolygon: paper.Path | paper.CompoundPath | null
|
||||
let points: paper.Point[] = []
|
||||
let lastPoint: paper.Point
|
||||
let lastTime: number
|
||||
let lastPoint: paper.Point | null = null
|
||||
let lastTime = 0
|
||||
let isMouseDown: boolean = false
|
||||
let polygonTool = paperPolygonTool
|
||||
let currentMagnetPoint: paper.Point | null = null
|
||||
let draftId: number | null = null
|
||||
|
||||
const handleCircles = (path: paper.Path | paper.CompoundPath | null) => {
|
||||
// 更新选中的点
|
||||
@@ -2683,6 +2702,71 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
})
|
||||
}
|
||||
|
||||
const getDraftPolygonData = () => ({
|
||||
imageId: useBottomToolsStore.getState().activeImage,
|
||||
operationId: useTopToolsStore.getState().activeOperation,
|
||||
id:
|
||||
draftId ??
|
||||
(draftId =
|
||||
useRightToolsStore
|
||||
.getState()
|
||||
.pathIds.reduce((a, b) => Math.max(a, b), 0) + 1),
|
||||
})
|
||||
|
||||
const clearPolygonMagnet = () => {
|
||||
const magnetPaths = usePaperStore
|
||||
.getState()
|
||||
.group!.getItems({ data: { type: "magnet" } })
|
||||
magnetPaths.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
currentMagnetPoint = null
|
||||
}
|
||||
|
||||
const rebuildPolygonDraft = () => {
|
||||
polygon?.remove()
|
||||
polygon = null
|
||||
|
||||
if (points.length) {
|
||||
polygon = usePaperStore.getState().addPolygon(
|
||||
renderPath,
|
||||
points,
|
||||
{
|
||||
...usePaperStore.getState().toolOption,
|
||||
},
|
||||
getDraftPolygonData()
|
||||
)
|
||||
} else {
|
||||
draftId = null
|
||||
}
|
||||
|
||||
handleCircles(polygon)
|
||||
lastPoint = points.length
|
||||
? usePaperStore
|
||||
.getState()
|
||||
.group!.localToGlobal(points[points.length - 1])
|
||||
: null
|
||||
clearPolygonMagnet()
|
||||
}
|
||||
|
||||
const resetPolygonDraft = () => {
|
||||
polygon?.remove()
|
||||
polygon = null
|
||||
points = []
|
||||
handleCircles(null)
|
||||
lastPoint = null
|
||||
lastTime = 0
|
||||
draftId = null
|
||||
clearPolygonMagnet()
|
||||
}
|
||||
|
||||
const undoPolygonPoint = () => {
|
||||
if (!points.length) return false
|
||||
points.pop()
|
||||
rebuildPolygonDraft()
|
||||
return true
|
||||
}
|
||||
|
||||
const handlePolygonIntersectRaster = (savePath: paper.Path) => {
|
||||
let oldPolygon: any = savePath
|
||||
polygon = oldPolygon.intersect(
|
||||
@@ -2775,6 +2859,13 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
event: MouseEvent
|
||||
point: paper.Point
|
||||
}) => {
|
||||
if (startMiddleMousePan(e.event)) return
|
||||
if (e.event.button === 2) {
|
||||
e.event.preventDefault()
|
||||
hidePaperContextMenu()
|
||||
undoPolygonPoint()
|
||||
return
|
||||
}
|
||||
if (e.event.button !== 0) return
|
||||
isMouseDown = true
|
||||
|
||||
@@ -2797,6 +2888,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
selectedCircles.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
selectedCircles = []
|
||||
}
|
||||
|
||||
// 调整polygon点位初始顺序
|
||||
@@ -2994,15 +3086,12 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
selectedCircles.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
const magnetPaths = usePaperStore
|
||||
.getState()
|
||||
.group!.getItems({ data: { type: "magnet" } })
|
||||
magnetPaths.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
currentMagnetPoint = null
|
||||
|
||||
selectedCircles = []
|
||||
clearPolygonMagnet()
|
||||
useTopToolsStore.getState().setPressA(false)
|
||||
draftId = null
|
||||
lastPoint = null
|
||||
lastTime = 0
|
||||
return
|
||||
}
|
||||
} else {
|
||||
@@ -3030,14 +3119,15 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
let point = currentMagnetPoint
|
||||
? currentMagnetPoint
|
||||
: temp.globalToLocal(e.point)
|
||||
const draftData = getDraftPolygonData()
|
||||
|
||||
let circle = usePaperStore
|
||||
.getState()
|
||||
.getClickedCircle(
|
||||
point,
|
||||
selectedCircles.length,
|
||||
polygon?.data.id,
|
||||
polygon?.data.blankColor
|
||||
draftData.id,
|
||||
usePaperStore.getState().toolOption.blankColor
|
||||
)
|
||||
// add circle
|
||||
selectedCircles.push(circle)
|
||||
@@ -3054,20 +3144,20 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
// option
|
||||
...usePaperStore.getState().toolOption,
|
||||
},
|
||||
{
|
||||
imageId: useBottomToolsStore.getState().activeImage,
|
||||
operationId: useTopToolsStore.getState().activeOperation,
|
||||
id:
|
||||
useRightToolsStore
|
||||
.getState()
|
||||
.pathIds.reduce((a, b) => Math.max(a, b), 0) + 1,
|
||||
}
|
||||
draftData
|
||||
)
|
||||
|
||||
lastPoint = checkPoint
|
||||
lastTime = Date.now()
|
||||
}
|
||||
polygonTool.onMouseDrag = (e: {
|
||||
event: MouseEvent
|
||||
delta: paper.Point
|
||||
}) => {
|
||||
if (dragMiddleMousePan(e)) return
|
||||
}
|
||||
polygonTool.onMouseUp = (_e: paper.ToolEvent) => {
|
||||
if (stopMiddleMousePan()) return
|
||||
isMouseDown = false
|
||||
}
|
||||
polygonTool.onMouseMove = (e: paper.MouseEvent) => {
|
||||
@@ -3126,7 +3216,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
}
|
||||
}
|
||||
})
|
||||
if (isMouseDown) {
|
||||
if (isMouseDown && lastPoint) {
|
||||
const delta = currentMagnetPoint
|
||||
? (currentMagnetPoint as paper.Point).subtract(lastPoint)
|
||||
: e.point.subtract(lastPoint)
|
||||
@@ -3174,27 +3264,17 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
// option
|
||||
...usePaperStore.getState().toolOption,
|
||||
},
|
||||
{}
|
||||
getDraftPolygonData()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
polygonTool.onKeyDown = (e: paper.KeyEvent) => {
|
||||
if (e.key === "escape" || e.key === "alt") {
|
||||
if (polygon) {
|
||||
polygon.remove()
|
||||
polygon = null
|
||||
points = []
|
||||
usePaperSupportStore.getState().handleBufferPaths(polygon)
|
||||
handleCircles(polygon)
|
||||
if (polygon || points.length) {
|
||||
resetPolygonDraft()
|
||||
usePaperSupportStore.getState().handleBufferPaths(null)
|
||||
}
|
||||
const magnetPaths = usePaperStore
|
||||
.getState()
|
||||
.group!.getItems({ data: { type: "magnet" } })
|
||||
magnetPaths.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
currentMagnetPoint = null
|
||||
} else if (e.key === "delete") {
|
||||
handleDelete()
|
||||
forceUpdate()
|
||||
@@ -3242,6 +3322,12 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
}
|
||||
rect.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
|
||||
if (e.event && e.event.button === 2) {
|
||||
if (
|
||||
["polygon", "brush", "point"].includes(
|
||||
usePaperStore.getState().mode || ""
|
||||
)
|
||||
)
|
||||
return
|
||||
if (rect.data.id) {
|
||||
let category = useTopToolsStore
|
||||
.getState()
|
||||
@@ -3278,6 +3364,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
let rectangleTool = paperRectangleTool
|
||||
|
||||
rectangleTool.onMouseDown = (e: { event: MouseEvent }) => {
|
||||
if (startMiddleMousePan(e.event)) return
|
||||
if (e.event && e.event.button !== 2)
|
||||
useOtherToolsStore.getState().setShowContextMenu({
|
||||
showContextMenu: false,
|
||||
@@ -3300,7 +3387,9 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
event: MouseEvent
|
||||
point: paper.Point
|
||||
downPoint: paper.Point
|
||||
delta: paper.Point
|
||||
}) => {
|
||||
if (dragMiddleMousePan(e)) return
|
||||
const delta = e.point.subtract(e.downPoint)
|
||||
// 阻止右键移动矩形(e.event.button === 0)
|
||||
if (delta.length > 10 && e.event.button === 0) {
|
||||
@@ -3337,6 +3426,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
event: MouseEvent
|
||||
point: paper.Point
|
||||
}) => {
|
||||
if (stopMiddleMousePan()) return
|
||||
// add segment judgement
|
||||
if (rect && rect.segments?.length && rect?.data?.id) {
|
||||
// 去除超出图片边界的部分
|
||||
@@ -3452,6 +3542,12 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
// });
|
||||
brush.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
|
||||
if (e.event && e.event.button === 2) {
|
||||
if (
|
||||
["polygon", "brush", "point"].includes(
|
||||
usePaperStore.getState().mode || ""
|
||||
)
|
||||
)
|
||||
return
|
||||
// console.log(e.event.clientX, e.event.clientY);
|
||||
if (brush.data.id) {
|
||||
let category = useTopToolsStore
|
||||
@@ -3489,10 +3585,66 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
let brushCircles: paper.Path.Circle[] = []
|
||||
let myPath: paper.Path | null
|
||||
let compoundPolygon: paper.Path | paper.CompoundPath | null
|
||||
let lastPoint: paper.Point
|
||||
let lastPoint: paper.Point | null = null
|
||||
let points: paper.Point[] = []
|
||||
|
||||
let brushTool = paperBrushTool
|
||||
let draftId: number | null = null
|
||||
|
||||
const getDraftBrushData = () => ({
|
||||
imageId: useBottomToolsStore.getState().activeImage,
|
||||
operationId: useTopToolsStore.getState().activeOperation,
|
||||
id:
|
||||
draftId ??
|
||||
(draftId =
|
||||
useRightToolsStore
|
||||
.getState()
|
||||
.pathIds.reduce((a, b) => Math.max(a, b), 0) + 1),
|
||||
})
|
||||
|
||||
const rebuildBrushDraft = () => {
|
||||
myPath?.remove()
|
||||
myPath = null
|
||||
|
||||
if (points.length) {
|
||||
myPath = usePaperStore.getState().addBrush(
|
||||
renderPath,
|
||||
points,
|
||||
{
|
||||
...usePaperStore.getState().toolOption,
|
||||
},
|
||||
getDraftBrushData()
|
||||
)
|
||||
} else {
|
||||
draftId = null
|
||||
}
|
||||
|
||||
lastPoint = points.length
|
||||
? usePaperStore
|
||||
.getState()
|
||||
.group!.localToGlobal(points[points.length - 1])
|
||||
: null
|
||||
}
|
||||
|
||||
const resetBrushDraft = () => {
|
||||
brushCircles.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
brushCircles = []
|
||||
myPath?.remove()
|
||||
myPath = null
|
||||
points = []
|
||||
lastPoint = null
|
||||
draftId = null
|
||||
}
|
||||
|
||||
const undoBrushPoint = () => {
|
||||
if (!points.length) return false
|
||||
points.pop()
|
||||
brushCircles.pop()?.remove()
|
||||
rebuildBrushDraft()
|
||||
return true
|
||||
}
|
||||
|
||||
const handleBrushIntersectRaster = (savePath: paper.Path) => {
|
||||
let intersections = savePath.getIntersections(
|
||||
@@ -3558,6 +3710,13 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
}
|
||||
|
||||
brushTool.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
|
||||
if (startMiddleMousePan(e.event)) return
|
||||
if (e.event.button === 2) {
|
||||
e.event.preventDefault()
|
||||
hidePaperContextMenu()
|
||||
undoBrushPoint()
|
||||
return
|
||||
}
|
||||
if (e.event.button !== 0) return
|
||||
|
||||
// 绘制时去除选中辅助
|
||||
@@ -3753,8 +3912,11 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
brushCircles.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
brushCircles = []
|
||||
|
||||
useTopToolsStore.getState().setPressA(false)
|
||||
draftId = null
|
||||
lastPoint = null
|
||||
return
|
||||
}
|
||||
} else {
|
||||
@@ -3772,6 +3934,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
|
||||
let temp = renderPath(usePaperStore.getState().group!, {})
|
||||
let point = temp.globalToLocal(e.point)
|
||||
const draftData = getDraftBrushData()
|
||||
|
||||
// add point
|
||||
points.push(point)
|
||||
@@ -3785,22 +3948,15 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
// option
|
||||
...usePaperStore.getState().toolOption,
|
||||
},
|
||||
{
|
||||
imageId: useBottomToolsStore.getState().activeImage,
|
||||
operationId: useTopToolsStore.getState().activeOperation,
|
||||
id:
|
||||
useRightToolsStore
|
||||
.getState()
|
||||
.pathIds.reduce((a, b) => Math.max(a, b), 0) + 1,
|
||||
}
|
||||
draftData
|
||||
)
|
||||
let circle = usePaperStore
|
||||
.getState()
|
||||
.getClickedCircle(
|
||||
point,
|
||||
brushCircles.length,
|
||||
myPath?.data.id,
|
||||
myPath?.data.blankColor
|
||||
draftData.id,
|
||||
usePaperStore.getState().toolOption.blankColor
|
||||
)
|
||||
brushCircles.push(circle)
|
||||
|
||||
@@ -3808,6 +3964,10 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
temp.remove()
|
||||
}
|
||||
|
||||
brushTool.onMouseDrag = (e: { event: MouseEvent; delta: paper.Point }) => {
|
||||
if (dragMiddleMousePan(e)) return
|
||||
}
|
||||
|
||||
brushTool.onMouseMove = (e: paper.MouseEvent) => {
|
||||
handleMouseMove(e)
|
||||
|
||||
@@ -3824,20 +3984,19 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
fillColor: undefined,
|
||||
closed: false,
|
||||
},
|
||||
{}
|
||||
getDraftBrushData()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
brushTool.onMouseUp = (_e: paper.ToolEvent) => {
|
||||
stopMiddleMousePan()
|
||||
}
|
||||
|
||||
brushTool.onKeyDown = (e: paper.KeyEvent) => {
|
||||
if (e.key === "escape" || e.key === "alt") {
|
||||
if (myPath) {
|
||||
brushCircles.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
myPath.remove()
|
||||
myPath = null
|
||||
points = []
|
||||
if (myPath || points.length) {
|
||||
resetBrushDraft()
|
||||
}
|
||||
} else if (e.key === "delete") {
|
||||
handleDelete()
|
||||
@@ -3874,6 +4033,12 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
myCircle.data = Object.assign({ type: "circle", ...option }, data)
|
||||
myCircle.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
|
||||
if (e.event && e.event.button === 2) {
|
||||
if (
|
||||
["polygon", "brush", "point"].includes(
|
||||
usePaperStore.getState().mode || ""
|
||||
)
|
||||
)
|
||||
return
|
||||
// console.log(e.event.clientX, e.event.clientY);
|
||||
if (myCircle.data.id) {
|
||||
let category = useTopToolsStore
|
||||
@@ -3947,12 +4112,82 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
let myCircleText: paper.PointText[] = []
|
||||
let textNumbers: number[] = []
|
||||
let myPath: paper.Path | null
|
||||
let lastPoint: paper.Point
|
||||
let lastPoint: paper.Point | null = null
|
||||
let points: paper.Point[] = []
|
||||
|
||||
let pointTool = paperPointTool
|
||||
let draftId: number | null = null
|
||||
|
||||
const getDraftPointData = () => ({
|
||||
imageId: useBottomToolsStore.getState().activeImage,
|
||||
operationId: useTopToolsStore.getState().activeOperation,
|
||||
id:
|
||||
draftId ??
|
||||
(draftId =
|
||||
useRightToolsStore
|
||||
.getState()
|
||||
.pathIds.reduce((a, b) => Math.max(a, b), 0) + 1),
|
||||
})
|
||||
|
||||
const rebuildPointDraft = () => {
|
||||
myPath?.remove()
|
||||
myPath = null
|
||||
|
||||
if (points.length) {
|
||||
myPath = usePaperStore.getState().addPointLine(
|
||||
renderPath,
|
||||
points,
|
||||
{
|
||||
...usePaperStore.getState().toolOption,
|
||||
},
|
||||
getDraftPointData()
|
||||
)
|
||||
} else {
|
||||
draftId = null
|
||||
}
|
||||
|
||||
lastPoint = points.length
|
||||
? usePaperStore
|
||||
.getState()
|
||||
.group!.localToGlobal(points[points.length - 1])
|
||||
: null
|
||||
}
|
||||
|
||||
const resetPointDraft = () => {
|
||||
myPath?.remove()
|
||||
myPath = null
|
||||
points = []
|
||||
textNumbers = []
|
||||
myCircleText.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
myCircleText = []
|
||||
myCircle.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
myCircle = []
|
||||
lastPoint = null
|
||||
draftId = null
|
||||
}
|
||||
|
||||
const undoPoint = () => {
|
||||
if (!points.length) return false
|
||||
points.pop()
|
||||
textNumbers.pop()
|
||||
myCircle.pop()?.remove()
|
||||
myCircleText.pop()?.remove()
|
||||
rebuildPointDraft()
|
||||
return true
|
||||
}
|
||||
|
||||
pointTool.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
|
||||
if (startMiddleMousePan(e.event)) return
|
||||
if (e.event.button === 2) {
|
||||
e.event.preventDefault()
|
||||
hidePaperContextMenu()
|
||||
undoPoint()
|
||||
return
|
||||
}
|
||||
if (e.event.button !== 0) return
|
||||
|
||||
// 绘制时去除选中辅助
|
||||
@@ -4079,6 +4314,8 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
myCircleText = []
|
||||
// 避免后续按键删除已绘制的circle
|
||||
myCircle = []
|
||||
draftId = null
|
||||
lastPoint = null
|
||||
|
||||
return
|
||||
}
|
||||
@@ -4098,6 +4335,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
let temp = renderCircle(usePaperStore.getState().group!, {})
|
||||
let point = temp.globalToLocal(e.point)
|
||||
let text = textNumbers.reduce((a, b) => Math.max(a, b), 0) + 1
|
||||
const draftData = getDraftPointData()
|
||||
|
||||
let circle = usePaperStore.getState().addPoint(
|
||||
renderCircle,
|
||||
@@ -4110,13 +4348,8 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
strokeScaling: false,
|
||||
},
|
||||
{
|
||||
imageId: useBottomToolsStore.getState().activeImage,
|
||||
operationId: useTopToolsStore.getState().activeOperation,
|
||||
...draftData,
|
||||
text: text,
|
||||
id:
|
||||
useRightToolsStore
|
||||
.getState()
|
||||
.pathIds.reduce((a, b) => Math.max(a, b), 0) + 1,
|
||||
}
|
||||
)
|
||||
// add circle
|
||||
@@ -4147,14 +4380,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
// option
|
||||
...usePaperStore.getState().toolOption,
|
||||
},
|
||||
{
|
||||
imageId: useBottomToolsStore.getState().activeImage,
|
||||
operationId: useTopToolsStore.getState().activeOperation,
|
||||
id:
|
||||
useRightToolsStore
|
||||
.getState()
|
||||
.pathIds.reduce((a, b) => Math.max(a, b), 0) + 1,
|
||||
}
|
||||
draftData
|
||||
)
|
||||
|
||||
lastPoint = e.point
|
||||
@@ -4162,6 +4388,9 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
// group.current?.addChild(myCircle);
|
||||
temp.remove()
|
||||
}
|
||||
pointTool.onMouseDrag = (e: { event: MouseEvent; delta: paper.Point }) => {
|
||||
if (dragMiddleMousePan(e)) return
|
||||
}
|
||||
pointTool.onMouseMove = (e: paper.MouseEvent) => {
|
||||
handleMouseMove(e)
|
||||
|
||||
@@ -4178,25 +4407,17 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
fillColor: undefined,
|
||||
closed: false,
|
||||
},
|
||||
{}
|
||||
getDraftPointData()
|
||||
)
|
||||
}
|
||||
}
|
||||
pointTool.onMouseUp = (_e: paper.ToolEvent) => {
|
||||
stopMiddleMousePan()
|
||||
}
|
||||
pointTool.onKeyDown = (e: paper.KeyEvent) => {
|
||||
if (e.key === "escape" || e.key === "alt") {
|
||||
if (myPath) {
|
||||
myPath.remove()
|
||||
myPath = null
|
||||
points = []
|
||||
textNumbers = []
|
||||
myCircleText.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
myCircleText = []
|
||||
myCircle.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
myCircle = []
|
||||
if (myPath || points.length) {
|
||||
resetPointDraft()
|
||||
}
|
||||
} else if (e.key === "delete") {
|
||||
handleDelete()
|
||||
@@ -4250,6 +4471,12 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
circle.scale(newScale / currentScaling)
|
||||
circle.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
|
||||
if (e.event && e.event.button === 2) {
|
||||
if (
|
||||
["polygon", "brush", "point"].includes(
|
||||
usePaperStore.getState().mode || ""
|
||||
)
|
||||
)
|
||||
return
|
||||
// console.log(e.event.clientX, e.event.clientY);
|
||||
if (circle.data.id) {
|
||||
let category = useTopToolsStore
|
||||
@@ -4292,28 +4519,125 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
initSupportTool: (tool, renderPath) => {
|
||||
let points: Array<[number, number]> = []
|
||||
let tags: number[] = []
|
||||
let boxes: Array<[number, number, number, number]> = []
|
||||
let dragStartPoint: paper.Point | null = null
|
||||
let dragButton: number | null = null
|
||||
let supportBox: paper.Path.Rectangle | null = null
|
||||
let isBoxDragging = false
|
||||
|
||||
const BOX_DRAG_THRESHOLD = 6
|
||||
|
||||
const clearSupportBoxPreview = () => {
|
||||
supportBox?.remove()
|
||||
supportBox = null
|
||||
isBoxDragging = false
|
||||
}
|
||||
|
||||
const getSupportBox = (
|
||||
from: paper.Point,
|
||||
to: paper.Point
|
||||
): [number, number, number, number] => [
|
||||
Math.min(from.x, to.x),
|
||||
Math.min(from.y, to.y),
|
||||
Math.max(from.x, to.x),
|
||||
Math.max(from.y, to.y),
|
||||
]
|
||||
|
||||
const renderSupportBoxPreview = (from: paper.Point, to: paper.Point) => {
|
||||
const [left, top, right, bottom] = getSupportBox(from, to)
|
||||
supportBox?.remove()
|
||||
supportBox = new paper.Path.Rectangle({
|
||||
parent: usePaperStore.getState().group!,
|
||||
from: [left, top],
|
||||
to: [right, bottom],
|
||||
strokeColor: "#FFF",
|
||||
strokeWidth: 2,
|
||||
strokeScaling: false,
|
||||
dashArray: [6, 4],
|
||||
fillColor: new paper.Color(1, 1, 1, 0.05),
|
||||
data: {
|
||||
id: "support",
|
||||
type: "box",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const renderSupportPrompt = () => {
|
||||
void renderPath(points, tags, points.length + boxes.length === 1, boxes)
|
||||
}
|
||||
|
||||
tool.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
|
||||
let point = usePaperStore.getState().group!.globalToLocal(e.point)
|
||||
if (e.event.button === 1) return
|
||||
points.push([point.x, point.y])
|
||||
tags.push(e.event.button === 0 ? 1 : 0)
|
||||
renderPath(points, tags, points.length === 1)
|
||||
if (startMiddleMousePan(e.event)) return
|
||||
dragStartPoint = usePaperStore.getState().group!.globalToLocal(e.point)
|
||||
dragButton = e.event.button
|
||||
clearSupportBoxPreview()
|
||||
}
|
||||
|
||||
tool.onMouseDrag = (e: {
|
||||
event: MouseEvent
|
||||
point: paper.Point
|
||||
delta: paper.Point
|
||||
}) => {
|
||||
if (dragMiddleMousePan(e)) return
|
||||
|
||||
if (!dragStartPoint || dragButton !== 0) return
|
||||
|
||||
const currentPoint = usePaperStore
|
||||
.getState()
|
||||
.group!.globalToLocal(e.point)
|
||||
if (currentPoint.subtract(dragStartPoint).length <= BOX_DRAG_THRESHOLD)
|
||||
return
|
||||
|
||||
isBoxDragging = true
|
||||
renderSupportBoxPreview(dragStartPoint, currentPoint)
|
||||
}
|
||||
|
||||
tool.onMouseUp = (e: paper.ToolEvent) => {
|
||||
if (stopMiddleMousePan()) return
|
||||
if (!dragStartPoint || dragButton === null) return
|
||||
|
||||
const currentPoint = usePaperStore
|
||||
.getState()
|
||||
.group!.globalToLocal(e.point)
|
||||
|
||||
if (dragButton === 0 && isBoxDragging) {
|
||||
boxes.push(getSupportBox(dragStartPoint, currentPoint))
|
||||
} else {
|
||||
points.push([dragStartPoint.x, dragStartPoint.y])
|
||||
tags.push(dragButton === 0 ? 1 : 0)
|
||||
}
|
||||
|
||||
renderSupportPrompt()
|
||||
clearSupportBoxPreview()
|
||||
dragStartPoint = null
|
||||
dragButton = null
|
||||
}
|
||||
|
||||
tool.onKeyDown = (e: paper.KeyEvent) => {
|
||||
if (e.key === "escape") {
|
||||
points = []
|
||||
tags = []
|
||||
boxes = []
|
||||
dragStartPoint = null
|
||||
dragButton = null
|
||||
clearSupportBoxPreview()
|
||||
clearSupportAnnotationItem()
|
||||
usePaperStore.getState().setPaperMode("pan")
|
||||
}
|
||||
if (e.key === "f1") {
|
||||
points = []
|
||||
tags = []
|
||||
boxes = []
|
||||
dragStartPoint = null
|
||||
dragButton = null
|
||||
clearSupportBoxPreview()
|
||||
}
|
||||
}
|
||||
|
||||
tool.onMouseMove = (e: paper.MouseEvent) => {
|
||||
handleMouseMove(e)
|
||||
}
|
||||
|
||||
set((state: PaperState) => ({
|
||||
...state,
|
||||
supportTool: tool,
|
||||
@@ -4416,6 +4740,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
return bufferPath
|
||||
},
|
||||
setPaperMode: (modeParam) => {
|
||||
stopMiddleMousePan()
|
||||
set((state: PaperState) => ({
|
||||
...state,
|
||||
mode: modeParam,
|
||||
|
||||
Reference in New Issue
Block a user