470 lines
17 KiB
TypeScript
470 lines
17 KiB
TypeScript
"use client"
|
|
|
|
import {
|
|
ActionIcon,
|
|
Box,
|
|
Collapse,
|
|
Flex,
|
|
Group,
|
|
Paper,
|
|
ScrollArea,
|
|
Stack,
|
|
Switch,
|
|
Text,
|
|
Title,
|
|
Tooltip,
|
|
} from "@mantine/core"
|
|
import { ChevronDown, ChevronUp, Link2Off, Pencil, Trash2 } from "lucide-react"
|
|
import { useCallback, useState } from "react"
|
|
import { Project } from "../api/project/typing"
|
|
import { LabelState } from "../LabelNossr"
|
|
import { useKeyEventStore, useLabelStore, useObjectStore } from "../store"
|
|
import { useBottomToolsStore } from "../useBottomToolsStore"
|
|
import { useOtherToolsStore } from "../useOtherToolsStore"
|
|
import { getShowContextMenuPosition, usePaperStore } from "../usePaperStore"
|
|
import { renderGroupPath } from "../useRenderGroupPath"
|
|
import { useRightToolsStore } from "../useRightToolsStore"
|
|
import { useTopToolsStore } from "../useTopToolsStore"
|
|
import { labelTypeMap } from "../utils/constants"
|
|
import RightGroupEditModal from "./RightGroupEditModal"
|
|
|
|
interface RightGroupToolsComponentProps {
|
|
labelState: LabelState
|
|
projectDetail?: Project.DetailResponse
|
|
}
|
|
|
|
const RightGroupTools: React.FC<RightGroupToolsComponentProps> = (props) => {
|
|
const { projectDetail } = props
|
|
|
|
const [open, setOpen] = useState(false)
|
|
const [editId, setEditId] = useState<number | null>(null)
|
|
const getOperationSchema = useCallback(
|
|
(operationId: string) => {
|
|
return (
|
|
projectDetail?.label_schema_list?.find(
|
|
(item) => item.category_id === +operationId
|
|
) || null
|
|
)
|
|
},
|
|
[projectDetail?.label_schema_list]
|
|
)
|
|
const { setLabel, pushStateStack } = useLabelStore()
|
|
const { pathGroupMap, groupPathVisible, setGroupPathVisible } =
|
|
useRightToolsStore()
|
|
const { activeImage } = useBottomToolsStore()
|
|
const { groupStatus, updateGroupStatus, selectedPath, updateSelectedPath } =
|
|
useObjectStore()
|
|
const { getItemById, getItemsById } = usePaperStore()
|
|
const { shift: pressShift } = useKeyEventStore()
|
|
const [isGroupCollapsed, setIsGroupCollapsed] = useState<boolean>(true)
|
|
|
|
const handleSelect = useCallback(
|
|
(id: number) => {
|
|
let operationId = getItemById(id)?.data.operationId
|
|
if (selectedPath[activeImage]?.length) {
|
|
if (!selectedPath[activeImage]?.includes(id)) {
|
|
if (pressShift) {
|
|
if (getItemsById(id)?.length) {
|
|
getItemsById(id).forEach((item) => {
|
|
item.fillColor = item.data?.fillColor
|
|
})
|
|
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 {
|
|
selectedPath[activeImage].forEach((selectedId) => {
|
|
if (getItemsById(selectedId)?.length) {
|
|
getItemsById(selectedId).forEach((item) => {
|
|
item.fillColor = item.data?.blankColor
|
|
})
|
|
useOtherToolsStore.getState().setShowContextMenu({
|
|
showContextMenu: false,
|
|
position: {
|
|
top: 0,
|
|
left: 0,
|
|
},
|
|
imageId: activeImage,
|
|
operationId: operationId,
|
|
id,
|
|
})
|
|
}
|
|
})
|
|
if (getItemsById(id)?.length) {
|
|
getItemsById(id).forEach((item) => {
|
|
item.fillColor = item.data?.fillColor
|
|
})
|
|
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 {
|
|
updateSelectedPath(activeImage, "DELETE", id)
|
|
if (getItemsById(id)?.length) {
|
|
getItemsById(id).forEach((item) => {
|
|
item.fillColor = item.data?.blankColor
|
|
})
|
|
useOtherToolsStore.getState().setShowContextMenu({
|
|
showContextMenu: false,
|
|
position: {
|
|
top: 0,
|
|
left: 0,
|
|
},
|
|
imageId: activeImage,
|
|
operationId: operationId,
|
|
id,
|
|
})
|
|
}
|
|
}
|
|
} else {
|
|
updateSelectedPath(activeImage, "ADD", id)
|
|
if (getItemsById(id)?.length) {
|
|
getItemsById(id).forEach((item) => {
|
|
item.fillColor = item.data?.fillColor
|
|
})
|
|
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,
|
|
]
|
|
)
|
|
|
|
const getOperationIdByPathId = (pathId: number) => {
|
|
let pathItem = getItemById(pathId)
|
|
return pathItem?.data?.operationId || ""
|
|
}
|
|
|
|
const unlinkGroup = (id: number) => {
|
|
const group = usePaperStore.getState().group
|
|
const imageId = useBottomToolsStore.getState().activeImage
|
|
const groupMap =
|
|
useRightToolsStore.getState().pathGroupMap.get(imageId) ??
|
|
new Map<number, number[]>()
|
|
|
|
if (!group) return
|
|
if (!Array.from(groupMap.values()).length) return
|
|
// update children path data
|
|
const currentGroupChildrenIds = groupMap.get(id) ?? []
|
|
const needUpdateOperationIds: any[] = []
|
|
currentGroupChildrenIds?.forEach((childId, index) => {
|
|
const childPath = usePaperStore.getState().getItemById(childId)!
|
|
childPath.data.parentGroupId = null
|
|
if (childPath.data.operationId && !needUpdateOperationIds[index])
|
|
needUpdateOperationIds[index] = childPath.data.operationId
|
|
})
|
|
// handle labeldata update
|
|
const currentData = structuredClone(useLabelStore.getState().label)
|
|
needUpdateOperationIds.forEach((operationId, index) => {
|
|
if (currentData.get(imageId)?.get(operationId)?.length) {
|
|
let result =
|
|
currentData
|
|
.get(imageId)
|
|
?.get(operationId)
|
|
?.map((item) =>
|
|
item[0] !== currentGroupChildrenIds[index]
|
|
? item
|
|
: [
|
|
item[0],
|
|
item[1],
|
|
{ ...item[2], parentGroupId: null },
|
|
item[3],
|
|
]
|
|
) || []
|
|
currentData.get(imageId)?.set(operationId, result as any)
|
|
}
|
|
})
|
|
// update group tool
|
|
useRightToolsStore.getState().removePathGroupInMap(imageId, id)
|
|
// setlabeldata
|
|
setLabel(currentData)
|
|
pushStateStack(currentData)
|
|
}
|
|
|
|
const deleteGroup = (id: number) => {
|
|
const group = usePaperStore.getState().group
|
|
const imageId = useBottomToolsStore.getState().activeImage
|
|
const groupMap =
|
|
useRightToolsStore.getState().pathGroupMap.get(imageId) ??
|
|
new Map<number, number[]>()
|
|
|
|
if (!group) return
|
|
if (!Array.from(groupMap.values()).length) return
|
|
|
|
// remove group children
|
|
const currentGroupChildrenIds = groupMap.get(id) ?? []
|
|
const needRemoveOperationIds: any[] = []
|
|
currentGroupChildrenIds?.forEach((childId, index) => {
|
|
const childPaths = group.getItems({ data: { id: childId } })
|
|
childPaths.forEach((p) => {
|
|
p.remove()
|
|
if (p.data.operationId && !needRemoveOperationIds[index])
|
|
needRemoveOperationIds[index] = p.data.operationId
|
|
})
|
|
})
|
|
const currentData = structuredClone(useLabelStore.getState().label)
|
|
needRemoveOperationIds.forEach((operationId, index) => {
|
|
if (currentData.get(imageId)?.get(operationId)?.length) {
|
|
let result =
|
|
currentData
|
|
.get(imageId)
|
|
?.get(operationId)
|
|
?.filter((item) => item[0] !== currentGroupChildrenIds[index]) || []
|
|
currentData.get(imageId)?.set(operationId, result)
|
|
}
|
|
})
|
|
// update group tool
|
|
useRightToolsStore.getState().removePathGroupInMap(imageId, id)
|
|
setLabel(currentData)
|
|
pushStateStack(currentData)
|
|
}
|
|
|
|
return (
|
|
<Box h="100%" w="100%">
|
|
<Flex direction="column" h="100%" gap="xs">
|
|
<Flex justify="space-between" align="center" px="md" py="xs">
|
|
<Title order={4} size="h4">
|
|
群组列表
|
|
</Title>
|
|
<ActionIcon
|
|
variant="subtle"
|
|
onClick={() => setIsGroupCollapsed(!isGroupCollapsed)}>
|
|
{isGroupCollapsed ? (
|
|
<ChevronUp size={16} />
|
|
) : (
|
|
<ChevronDown size={16} />
|
|
)}
|
|
</ActionIcon>
|
|
</Flex>
|
|
<Collapse
|
|
in={isGroupCollapsed}
|
|
style={{
|
|
flex: 1,
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
overflow: "hidden",
|
|
}}>
|
|
<Flex direction="column" h="100%">
|
|
<Box px="md" py="xs">
|
|
<Group gap="xs">
|
|
<Text size="sm">展示群组框</Text>
|
|
<Switch
|
|
onLabel="是"
|
|
offLabel="否"
|
|
checked={groupPathVisible}
|
|
onChange={(event) => {
|
|
setGroupPathVisible(event.currentTarget.checked)
|
|
renderGroupPath()
|
|
}}
|
|
/>
|
|
</Group>
|
|
</Box>
|
|
<ScrollArea style={{ flex: 1 }} px="md">
|
|
<Stack gap="xs" pb="md">
|
|
{pathGroupMap.get(activeImage) &&
|
|
Array.from(pathGroupMap.get(activeImage)!.keys())?.map(
|
|
(groupId) => {
|
|
const isOpen =
|
|
!groupStatus[activeImage + groupId]?.["COLLAPSE"]
|
|
return (
|
|
<Box key={groupId} w="100%">
|
|
<Paper withBorder p="xs" radius="md" shadow="sm">
|
|
<Flex justify="space-between" align="center">
|
|
<Group>
|
|
<Text size="sm" ff="mono">
|
|
@{groupId}
|
|
</Text>
|
|
<Group
|
|
gap={4}
|
|
display={
|
|
useTopToolsStore.getState().isView
|
|
? "none"
|
|
: "flex"
|
|
}>
|
|
<Tooltip label="编辑群组">
|
|
<ActionIcon
|
|
variant="subtle"
|
|
size="sm"
|
|
onClick={() => {
|
|
setOpen(true)
|
|
setEditId(groupId)
|
|
}}>
|
|
<Pencil size={14} />
|
|
</ActionIcon>
|
|
</Tooltip>
|
|
<Tooltip label="解除群组">
|
|
<ActionIcon
|
|
variant="subtle"
|
|
size="sm"
|
|
onClick={() => {
|
|
unlinkGroup(groupId)
|
|
}}>
|
|
<Link2Off size={14} />
|
|
</ActionIcon>
|
|
</Tooltip>
|
|
<Tooltip label="删除群组">
|
|
<ActionIcon
|
|
variant="subtle"
|
|
color="red"
|
|
size="sm"
|
|
onClick={() => {
|
|
deleteGroup(groupId)
|
|
}}>
|
|
<Trash2 size={14} />
|
|
</ActionIcon>
|
|
</Tooltip>
|
|
</Group>
|
|
</Group>
|
|
<Group gap="xs">
|
|
<Text size="sm">
|
|
{pathGroupMap.get(activeImage)?.get(groupId)
|
|
?.length || 0}
|
|
</Text>
|
|
<ActionIcon
|
|
variant="subtle"
|
|
size="sm"
|
|
onClick={() => {
|
|
updateGroupStatus(
|
|
activeImage + groupId,
|
|
"COLLAPSE",
|
|
isOpen
|
|
)
|
|
}}>
|
|
{isOpen ? (
|
|
<ChevronUp size={14} />
|
|
) : (
|
|
<ChevronDown size={14} />
|
|
)}
|
|
</ActionIcon>
|
|
</Group>
|
|
</Flex>
|
|
</Paper>
|
|
<Collapse in={isOpen}>
|
|
<Stack gap="xs" mt="xs" pl="xs">
|
|
{pathGroupMap
|
|
.get(activeImage)
|
|
?.get(groupId)
|
|
?.map((child) => {
|
|
const isSelected =
|
|
selectedPath[activeImage]?.includes(child)
|
|
return (
|
|
<Paper
|
|
withBorder
|
|
p="xs"
|
|
radius="md"
|
|
shadow="sm"
|
|
key={child}
|
|
bg={
|
|
isSelected
|
|
? "var(--mantine-primary-color-filled)"
|
|
: undefined
|
|
}
|
|
c={isSelected ? "white" : undefined}
|
|
onClick={() => {
|
|
handleSelect(child)
|
|
}}
|
|
style={{ cursor: "pointer" }}>
|
|
<Flex
|
|
justify="flex-end"
|
|
align="center"
|
|
gap="md">
|
|
<Text size="sm">
|
|
{child}-
|
|
{labelTypeMap.get(
|
|
getOperationSchema(
|
|
getOperationIdByPathId(child)
|
|
)?.label_type || 0
|
|
)}
|
|
</Text>
|
|
</Flex>
|
|
</Paper>
|
|
)
|
|
})}
|
|
</Stack>
|
|
</Collapse>
|
|
</Box>
|
|
)
|
|
}
|
|
)}
|
|
</Stack>
|
|
</ScrollArea>
|
|
</Flex>
|
|
</Collapse>
|
|
</Flex>
|
|
{open && (
|
|
<RightGroupEditModal
|
|
open={open}
|
|
editId={editId}
|
|
afterChange={(val: number) => {
|
|
setEditId(val)
|
|
}}
|
|
closeModal={() => {
|
|
setOpen(false)
|
|
setEditId(null)
|
|
}}
|
|
/>
|
|
)}
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
export default RightGroupTools
|