"use client"
import {
ActionIcon,
Badge,
Box,
Checkbox,
Collapse,
Flex,
Group,
Paper,
Popover,
ScrollArea,
Stack,
Text,
TextInput,
Title,
Tooltip,
} from "@mantine/core"
import dayjs from "dayjs"
import {
ChevronDown,
ChevronUp,
CircleDot,
Eye,
EyeOff,
Info,
RectangleHorizontal,
Settings,
Spline,
} from "lucide-react"
import paper from "paper"
import { useCallback, useState } from "react"
import { Project } from "../api/project/typing"
import { Task } from "../api/task/typing"
import { LabelState } from "../LabelNossr"
import { useKeyEventStore, useLabelStore, useObjectStore } from "../store"
import { useAllUserStore } from "../store/auth"
import { useBottomToolsStore } from "../useBottomToolsStore"
import { useOtherToolsStore } from "../useOtherToolsStore"
import { getShowContextMenuPosition, usePaperStore } from "../usePaperStore"
import { usePaperSupportStore } from "../usePaperSupportStore"
import { useTopToolsStore } from "../useTopToolsStore"
import { getSubAttrStatus } from "../util"
import { labelTypeMap } from "../utils/constants"
interface RightObjectToolsComponentProps {
labelState: LabelState
projectDetail?: Project.DetailResponse
taskDetail?: Task.DataProps
}
export const renderOperationIcon = (
operationSchema: Project.LabelSchemaList | null
) => {
if (operationSchema) {
let color = `rgb(${operationSchema.color[0]},${operationSchema.color[1]},${operationSchema.color[2]})`
const style = { color }
switch (operationSchema && labelTypeMap.get(operationSchema.label_type)) {
case "多边形": {
return
}
case "关键点": {
return
}
case "多线段": {
return
}
case "2D框": {
return
}
default:
return
}
} else return
}
const RightObjectTools: React.FC = (props) => {
const { labelState, projectDetail, taskDetail } = props
const { label: storeLabel } = useLabelStore()
const { activeImage } = useBottomToolsStore()
const {
operationStatus,
pathStatus,
selectedPath,
imageStatus,
updateImageStatus,
updateOperationStatus,
updatePathStatus,
updateSelectedPath,
} = useObjectStore()
const { userOpts } = useAllUserStore()
const [searchId, setSearchId] = useState("")
const [searchSubAttrs, setSearchSubAttrs] = useState<{
[x: string]: string
}>({})
const [checkBoxsChecked, setCheckBoxsChecked] = useState([
true,
true,
true,
true,
])
const getOperationSchema = useCallback(
(operationId: string) => {
return (
projectDetail?.label_schema_list?.find(
(item) => item.category_id === +operationId
) || null
)
},
[projectDetail?.label_schema_list]
)
const renderShortcutKey = (operationId: string) => {
if (+operationId > 10) {
return "ctrl+" + (+operationId % 10).toString()
} else {
return (+operationId % 10).toString()
}
}
const getCheckBoxColor = useCallback((type: number) => {
switch (type) {
case 3:
return "yellow"
case 2:
return "red"
case 1:
return "green"
case 0:
return "gray"
default:
return "blue"
}
}, [])
const { shift: pressShift } = useKeyEventStore()
const { getItemById, getItemsById } = usePaperStore()
// 切换选中对象时,清除所有其他对象的当前选中状态
const clearSelectedItems = () => {
usePaperStore
.getState()
.paperScope?.project.selectedItems.forEach((item) => {
item.data.selected = false
})
// const group = usePaperStore.getState().group!;
// const needClear = [
// ...group.getItems({ data: { type: "mask" } }),
// ...group.getItems({ data: { type: "text" } }),
// ];
// needClear.forEach((item) => {
// item.remove();
// });
usePaperSupportStore.getState().clearAll()
}
const handleSelectedPath = (path: paper.Path) => {
console.log("handleSelectedPath", path)
if (path.data.type === "rectangle") {
path.data.selected = true
usePaperSupportStore.getState().handleBufferPaths(path)
usePaperSupportStore.getState().handleCircles(path)
path.fillColor = path.data.fillColor
} else if (path.data.type === "brush") {
path.data.selected = true
usePaperSupportStore.getState().handleMask(path)
usePaperSupportStore.getState().handleBufferPaths(path)
usePaperSupportStore.getState().handleCircles(path)
} else if (path.data.type === "point") {
path.data.selected = true
usePaperSupportStore.getState().handleMask(path)
usePaperSupportStore.getState().handleText(path)
usePaperSupportStore.getState().handleBufferPaths(path)
usePaperSupportStore.getState().handleCircles(path)
} else if (path.data.type === "polygon") {
path.fillColor = path.data.fillColor
}
path.bringToFront()
}
const cancelSelectedPathColor = (path: paper.Path | paper.CompoundPath) => {
if (path.data.type === "rectangle" || path.data.type === "polygon")
path.fillColor = path.data.blankColor
}
const handleSelect = useCallback(
(id: number, operationId: any) => {
if (selectedPath[activeImage]?.length) {
if (!selectedPath[activeImage]?.includes(id)) {
if (pressShift) {
if (getItemsById(id)?.length) {
getItemsById(id).forEach((item) => {
if (item instanceof paper.Path) {
handleSelectedPath(item)
} else if (item instanceof paper.CompoundPath) {
if (item.data.type === "polygon")
item.fillColor = item.data.fillColor
else item.data.selected = true
}
})
let category = useTopToolsStore
.getState()
.objectOperations.find(
(item) => item.category_id.toString() === operationId
)
const globalPosition = getItemById(id)!.globalMatrix.transform(
getItemById(id)!.position
)
const { top, left } = getShowContextMenuPosition(
globalPosition,
category!
)
useOtherToolsStore.getState().setShowContextMenu({
showContextMenu: true,
position: { top, left },
imageId: activeImage,
operationId: operationId,
id,
})
}
} else {
clearSelectedItems()
selectedPath[activeImage].forEach((selectedId) => {
if (getItemsById(selectedId)?.length) {
getItemsById(selectedId).forEach((item) => {
cancelSelectedPathColor(item)
})
useOtherToolsStore.getState().setShowContextMenu({
showContextMenu: false,
position: {
top: 0,
left: 0,
},
imageId: activeImage,
operationId: operationId,
id,
})
}
})
if (getItemsById(id)?.length) {
getItemsById(id).forEach((item) => {
if (item instanceof paper.Path) {
handleSelectedPath(item)
} else if (item instanceof paper.CompoundPath) {
if (item.data.type === "polygon")
item.fillColor = item.data.fillColor
else item.data.selected = true
}
})
let category = useTopToolsStore
.getState()
.objectOperations.find(
(item) => item.category_id.toString() === operationId
)
const globalPosition = getItemById(id)!.globalMatrix.transform(
getItemById(id)!.position
)
const { top, left } = getShowContextMenuPosition(
globalPosition,
category!
)
useOtherToolsStore.getState().setShowContextMenu({
showContextMenu: true,
position: { top, left },
imageId: activeImage,
operationId: operationId,
id,
})
}
}
updateSelectedPath(activeImage, "ADD", id)
} else {
clearSelectedItems()
updateSelectedPath(activeImage, "DELETE", id)
if (getItemsById(id)?.length) {
getItemsById(id).forEach((item) => {
cancelSelectedPathColor(item)
})
useOtherToolsStore.getState().setShowContextMenu({
showContextMenu: false,
position: {
top: 0,
left: 0,
},
imageId: activeImage,
operationId: operationId,
id,
})
}
}
} else {
clearSelectedItems()
updateSelectedPath(activeImage, "ADD", id)
if (getItemsById(id)?.length) {
getItemsById(id).forEach((item) => {
if (item instanceof paper.Path) {
handleSelectedPath(item)
} else if (item instanceof paper.CompoundPath) {
if (item.data.type === "polygon")
item.fillColor = item.data.fillColor
else item.data.selected = true
}
// if (item.data.type === "rectangle") {
// item.data.selected = true;
// usePaperSupportStore.getState().handleCircles(item);
// }
// item.fillColor = item.data?.fillColor;
// item.bringToFront();
})
let category = useTopToolsStore
.getState()
.objectOperations.find(
(item) => item.category_id.toString() === operationId
)
const globalPosition = getItemById(id)!.globalMatrix.transform(
getItemById(id)!.position
)
const { top, left } = getShowContextMenuPosition(
globalPosition,
category!
)
useOtherToolsStore.getState().setShowContextMenu({
showContextMenu: true,
position: { top, left },
imageId: activeImage,
operationId: operationId,
id,
})
}
}
},
[
activeImage,
getItemById,
getItemsById,
pressShift,
selectedPath,
updateSelectedPath,
]
)
// 根据img operationId 更新操作栏operation状态
const checkIfOperationNeedsUpdate = (imgId: string, operationId: any) => {
const operationMap = storeLabel.get(imgId)
if (operationMap) {
const objList = operationMap.get(operationId)
if (objList && objList.length) {
let hideFlag = true
objList.forEach(([id]) => {
const objStatus =
useObjectStore.getState().pathStatus[`${imgId}${id}`]
if (objStatus && !objStatus["HIDE"]) {
hideFlag = false
}
})
updateOperationStatus(imgId + operationId, "HIDE", hideFlag)
}
}
}
// 隐藏时清空当前选中的item选中状态
const setSelectedItemFalse = (id: number) => {
if (!id) return
const sameIdItems = usePaperStore.getState().getItemsById(id)
sameIdItems.forEach((i) => {
if (i.data.type === "mask" || i.data.type === "text") {
i.remove()
}
if (i.data.selected) i.data.selected = false
})
}
const operationHide = useCallback(
(operationId: string, hide: boolean) => {
updateOperationStatus(activeImage + operationId, "HIDE", hide)
storeLabel
.get(activeImage)
?.get(operationId)
?.map((child) => {
updatePathStatus(activeImage + child[0], "HIDE", hide)
if (getItemsById(child[0]) && getItemsById(child[0]).length) {
getItemsById(child[0]).forEach((item) => {
item.visible = !hide
})
// 隐藏时清空选中状态
if (hide) setSelectedItemFalse(child[0])
}
})
},
[
activeImage,
getItemsById,
storeLabel,
updateOperationStatus,
updatePathStatus,
]
)
const objectHide = useCallback(
(operationId: string, objectId: number, hide: boolean) => {
if (hide) {
updatePathStatus(activeImage + objectId, "HIDE", hide)
if (getItemsById(objectId) && getItemsById(objectId).length) {
getItemsById(objectId).forEach((item) => (item.visible = !hide))
// 隐藏时清空选中状态
setSelectedItemFalse(objectId)
}
let childrenHideStatus = storeLabel
.get(activeImage)
?.get(operationId)
?.map((child) => {
return getItemById(child[0])!.visible
})
if (childrenHideStatus?.findIndex((bool) => bool === true) === -1) {
updateOperationStatus(activeImage + operationId, "HIDE", hide)
}
} else {
updateOperationStatus(activeImage + operationId, "HIDE", hide)
updatePathStatus(activeImage + objectId, "HIDE", hide)
const configs = useTopToolsStore.getState().showTagsConfigs
if (getItemsById(objectId) && getItemsById(objectId).length)
getItemsById(objectId).forEach((item) => {
let visible = !hide
if (item.data.type === "tag") {
if (item.data.tag_category === "mask") {
visible = configs.includes("外框")
} else if (item.data.tag_category === "point_text") {
visible = configs.includes("点ID")
} else {
visible = true
}
}
item.visible = visible
})
}
},
[
activeImage,
getItemById,
getItemsById,
storeLabel,
updateOperationStatus,
updatePathStatus,
]
)
// const renderObjectName = useCallback((name: string, input: string) => {
// const index = name?.indexOf(input);
// const beforeStr = name?.substring(0, index);
// const afterStr = name?.slice(index + input.length);
// return (
//
// {beforeStr}
// {input}
// {afterStr}
//
// );
// }, []);
// child 是否存在不符合要求的子属性
const getOperationSubAttrStatus = useCallback(
(operationId: string) => {
let bool = storeLabel
.get(activeImage)
?.get(operationId)
?.some((child) => {
return getSubAttrStatus(
getOperationSchema(operationId),
child[2]?.sub_attributes
)
})
return bool
},
[activeImage, getOperationSchema, storeLabel]
)
// 搜索标注对象id
const handleSelectedBySearchId = (e: any) => {
if (e.key === "Enter") {
if (searchId && !isNaN(Number(searchId))) {
const id = Number(searchId)
const labelObjMap = storeLabel.get(activeImage)
Array.from(labelObjMap!.entries()).forEach(([operationId, objList]) => {
objList.forEach((obj) => {
if (id === obj[0]) {
handleSelect(id, operationId)
}
})
})
}
}
}
// 搜索标注对象子属性
const handleSelectedBySearchSubAttr = (e: any, currentOperationId: any) => {
if (e.key === "Enter") {
Array.from(storeLabel.entries()).forEach(([imgId, objectMap]) => {
if (objectMap.get(currentOperationId)) {
const list = objectMap.get(currentOperationId)
list!.forEach((item) => {
const { sub_attributes } = item[2]
// 如果没有子属性搜索值,则把对象HIDE设为false
if (!searchSubAttrs[currentOperationId]) {
updatePathStatus(imgId + item[0], "HIDE", false)
updateOperationStatus(imgId + currentOperationId, "HIDE", false)
if (getItemsById(item[0]))
getItemsById(item[0]).forEach((i) => {
i.visible = true
})
return
}
if (sub_attributes && Object.entries(sub_attributes).length) {
let flag = true
Object.entries(sub_attributes).forEach(([_k, value]) => {
if (searchSubAttrs[currentOperationId] === value) {
flag = false
}
})
if (flag) {
updatePathStatus(imgId + item[0], "HIDE", true)
if (getItemsById(item[0])) {
getItemsById(item[0]).forEach((i) => {
i.visible = false
})
setSelectedItemFalse(item[0])
}
} else {
updatePathStatus(imgId + item[0], "HIDE", false)
if (getItemsById(item[0]))
getItemsById(item[0]).forEach((i) => {
i.visible = true
})
}
} else {
// 如果有子属性搜索值,但对象无子属性,则把对象HIDE设为true
updatePathStatus(imgId + item[0], "HIDE", true)
if (getItemsById(item[0])) {
getItemsById(item[0]).forEach((i) => {
i.visible = false
})
setSelectedItemFalse(item[0])
}
}
})
checkIfOperationNeedsUpdate(imgId, currentOperationId)
}
})
}
}
return (
对象列表
setSearchId(e.target.value)}
onKeyDown={handleSelectedBySearchId}
/>
{[3, 2, 1, 0].map((type, index) => (
{
setCheckBoxsChecked((pre) => {
const arr = [...pre]
arr[index] = e.target.checked
return arr
})
}}
size="xs"
/>
))}
{
updateImageStatus(
activeImage,
"COLLAPSE",
!imageStatus[activeImage]?.["COLLAPSE"]
)
}}>
{!imageStatus[activeImage]?.["COLLAPSE"] ? (
) : (
)}
{labelState.get(activeImage) &&
labelState.get(activeImage)?.map((operationId) => {
const isCollapsed =
operationStatus[activeImage + operationId]?.["COLLAPSE"]
const isHide =
operationStatus[activeImage + operationId]?.HIDE
const hasSubAttrError = getOperationSubAttrStatus(operationId)
return (
operationHide(operationId, !isHide)
}>
{!isHide ? (
) : (
)}
{renderOperationIcon(
getOperationSchema(operationId)
)}
{renderShortcutKey(operationId)}
{getOperationSchema(operationId)?.label_class}
{
setSearchSubAttrs((searchValues) => ({
...searchValues,
[operationId]: e.target.value,
}))
}}
onKeyDown={(e) =>
handleSelectedBySearchSubAttr(e, operationId)
}
/>
{storeLabel.get(activeImage)?.get(operationId)
?.length || 0}
{
updateOperationStatus(
activeImage + operationId,
"COLLAPSE",
!isCollapsed
)
}}>
{!isCollapsed ? (
) : (
)}
{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
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
if (hasChildError) {
textColor = "red"
} else if (isSelected) {
textColor = "white"
}
if (!visibleFlag) return null
return (
handleSelect(child[0], operationId)
}
style={{ cursor: "pointer" }}>
{
e.stopPropagation()
objectHide(
operationId,
child[0],
!isChildHide
)
}}>
{!isChildHide ? (
) : (
)}
{child[0]?.toString()}
创建:
{
userOpts.find(
(item) =>
item.value ===
child[2]?.uid
)?.label
}
{dayjs(
(child[2]?.create_timestamp ||
0) * 1000
).format("YYYY-MM-DD HH:mm:ss")}
{child[2]?.first_modified_uid && (
首次修改:
{
userOpts.find(
(item) =>
item.value ===
child[2]
?.first_modified_uid
)?.label
}
{dayjs(
(child[2]
?.first_modified_timestamp ||
0) * 1000
).format(
"YYYY-MM-DD HH:mm:ss"
)}
)}
{child[2]?.last_modified_uid && (
最新修改:
{
userOpts.find(
(item) =>
item.value ===
child[2]
?.last_modified_uid
)?.label
}
{dayjs(
(child[2]
?.last_modified_timestamp ||
0) * 1000
).format(
"YYYY-MM-DD HH:mm:ss"
)}
)}
}
multiline
bg="white"
c="black"
withArrow>
{comment ? (
{comment.map((c: any) => {
const count =
c.check_box_comments.length +
c.text_comments.length
const { last_modified_timestamp } =
child[2]
let flag = false
if (
count > 0 &&
last_modified_timestamp &&
last_modified_timestamp > c.date
)
flag = true
let badgeColor = "gray"
if (c.comment_type === 1)
badgeColor = "green"
if (c.comment_type === 2)
badgeColor = "red"
if (flag) badgeColor = "yellow"
return (
{count}
{`P${child[2].image_id}:${
child[2].uid
}:${c.check_box_comments.join(
","
)},${c.text_comments.join(
","
)} ${
c.date
? dayjs(
c.date * 1000
).format(
"YYYY-MM-DD HH:mm:ss"
)
: ""
}`}
)
})}
) : null}
)
})}
)
})}
)
}
export default RightObjectTools