feat(tool): hide all object

This commit is contained in:
zhangheng
2026-04-10 11:14:06 +08:00
parent bd32d5318d
commit 6aff25a38f

View File

@@ -355,13 +355,10 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
if (operationMap) { if (operationMap) {
const objList = operationMap.get(operationId) const objList = operationMap.get(operationId)
if (objList && objList.length) { if (objList && objList.length) {
let hideFlag = true const hideFlag = objList.every(([id]) => {
objList.forEach(([id]) => { return useObjectStore.getState().pathStatus[`${imgId}${id}`]?.["HIDE"]
const objStatus = ? true
useObjectStore.getState().pathStatus[`${imgId}${id}`] : false
if (objStatus && !objStatus["HIDE"]) {
hideFlag = false
}
}) })
updateOperationStatus(imgId + operationId, "HIDE", hideFlag) updateOperationStatus(imgId + operationId, "HIDE", hideFlag)
} }
@@ -401,6 +398,39 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
[activeImage] [activeImage]
) )
const activeImageOperations = labelState.get(activeImage) || []
const isObjectHidden = (imageId: string, objectId: number) => {
return pathStatus[imageId + objectId]?.["HIDE"] ?? false
}
const isOperationHidden = (imageId: string, operationId: string) => {
const operationChildren = storeLabel.get(imageId)?.get(operationId) || []
return (
operationChildren.length > 0 &&
operationChildren.every(([objectId]) => isObjectHidden(imageId, objectId))
)
}
const totalObjectCount = activeImageOperations.reduce(
(count, operationId) => {
return (
count + (storeLabel.get(activeImage)?.get(operationId)?.length || 0)
)
},
0
)
const areAllObjectsHidden =
totalObjectCount > 0 &&
activeImageOperations.every((operationId) => {
const operationChildren =
storeLabel.get(activeImage)?.get(operationId) || []
return operationChildren.every(([objectId]) =>
isObjectHidden(activeImage, objectId)
)
})
// const renderObjectName = useCallback((name: string, input: string) => { // const renderObjectName = useCallback((name: string, input: string) => {
// const index = name?.indexOf(input); // const index = name?.indexOf(input);
// const beforeStr = name?.substring(0, index); // const beforeStr = name?.substring(0, index);
@@ -509,9 +539,22 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
<Box h="100%" w="100%"> <Box h="100%" w="100%">
<Flex direction="column" h="100%" gap="xs"> <Flex direction="column" h="100%" gap="xs">
<Flex justify="space-between" align="center" px="md" py="xs"> <Flex justify="space-between" align="center" px="md" py="xs">
<Title order={4} size="h4"> <Group gap="xs">
<ActionIcon
</Title> variant="subtle"
aria-label={areAllObjectsHidden ? "显示全部对象" : "隐藏全部对象"}
onClick={() => {
if (!totalObjectCount) return
activeImageOperations.forEach((operationId) => {
operationHide(operationId, !areAllObjectsHidden)
})
}}>
{areAllObjectsHidden ? <EyeOff size={16} /> : <Eye size={16} />}
</ActionIcon>
<Title order={4} size="h4">
</Title>
</Group>
<Group gap="xs"> <Group gap="xs">
<TextInput <TextInput
w={96} w={96}
@@ -566,441 +609,424 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
}}> }}>
<ScrollArea style={{ flex: 1 }} px="md"> <ScrollArea style={{ flex: 1 }} px="md">
<Stack gap="xs" pb="md"> <Stack gap="xs" pb="md">
{labelState.get(activeImage) && {activeImageOperations.map((operationId) => {
labelState.get(activeImage)?.map((operationId) => { const operationSchema = getOperationSchema(operationId)
const operationSchema = getOperationSchema(operationId) const isCollapsed =
const isCollapsed = operationStatus[activeImage + operationId]?.["COLLAPSE"]
operationStatus[activeImage + operationId]?.["COLLAPSE"] const isHide = isOperationHidden(activeImage, operationId)
const isHide = const hasSubAttrError = getOperationSubAttrStatus(operationId)
operationStatus[activeImage + operationId]?.HIDE const operationLabel = operationSchema?.label_class
const hasSubAttrError = getOperationSubAttrStatus(operationId) const operationChildren =
const operationLabel = operationSchema?.label_class storeLabel.get(activeImage)?.get(operationId) || []
const operationChildren = const objectCount = operationChildren.length
storeLabel.get(activeImage)?.get(operationId) || [] const visibleChildren = operationChildren.filter((child) => {
const objectCount = operationChildren.length const { comment } = child[2]
const visibleChildren = operationChildren.filter((child) => { let visibleFlag = true
const { comment } = child[2] if (comment && comment.length) {
let visibleFlag = true const latest_comment =
if (comment && comment.length) { (taskDetail && taskDetail.label_status === 2) ||
const latest_comment = comment.length === 1
(taskDetail && taskDetail.label_status === 2) || ? comment[comment.length - 1]
comment.length === 1 : comment[comment.length - 2]
? comment[comment.length - 1] let type = latest_comment.comment_type
: comment[comment.length - 2] if (type === 2) {
let type = latest_comment.comment_type const { last_modified_timestamp } = child[2]
if (type === 2) { if (
const { last_modified_timestamp } = child[2] last_modified_timestamp &&
if ( last_modified_timestamp > latest_comment.date
last_modified_timestamp && )
last_modified_timestamp > latest_comment.date type = 3
)
type = 3
}
visibleFlag = checkBoxsChecked[3 - type]
} }
if (checkBoxsChecked.every((value) => !value)) visibleFlag = checkBoxsChecked[3 - type]
visibleFlag = true }
return visibleFlag if (checkBoxsChecked.every((value) => !value))
}) visibleFlag = true
return ( return visibleFlag
<Box key={operationId} w="100%"> })
<Paper withBorder p="xs" radius="md" shadow="sm"> return (
<Flex align="center" gap="xs" style={{ minWidth: 0 }}> <Box key={operationId} w="100%">
<Flex <Paper withBorder p="xs" radius="md" shadow="sm">
align="center" <Flex align="center" gap="xs" style={{ minWidth: 0 }}>
gap="xs" <Flex
style={{ flex: 1, minWidth: 0 }}> align="center"
<ActionIcon gap="xs"
variant="subtle" style={{ flex: 1, minWidth: 0 }}>
size="sm" <ActionIcon
onClick={() => variant="subtle"
operationHide(operationId, !isHide) size="sm"
}> onClick={() => operationHide(operationId, !isHide)}>
{!isHide ? ( {!isHide ? <Eye size={16} /> : <EyeOff size={16} />}
<Eye size={16} /> </ActionIcon>
) : (
<EyeOff size={16} />
)}
</ActionIcon>
<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 <Group
gap={6} gap={8}
wrap="nowrap" wrap="nowrap"
style={{ flexShrink: 0, marginLeft: "auto" }}> style={{ flex: 1, minWidth: 0 }}>
<TextInput <Group
w={88} gap={6}
size="xs" wrap="nowrap"
placeholder="搜索子属性" px="xs"
value={searchSubAttrs[operationId] || ""} py={4}
onChange={(e) => { style={{
setSearchSubAttrs((searchValues) => ({ flexShrink: 0,
...searchValues, borderRadius: 999,
[operationId]: e.target.value, border: `1px solid ${
})) hasSubAttrError
}} ? "var(--mantine-color-red-2)"
onKeyDown={(e) => : "var(--mantine-color-gray-2)"
handleSelectedBySearchSubAttr(e, operationId) }`,
} background: hasSubAttrError
/> ? "var(--mantine-color-red-0)"
<Badge : "var(--mantine-color-gray-0)",
size="sm"
variant="light"
radius="xl"
color={hasSubAttrError ? "red" : "gray"}>
{objectCount}
</Badge>
<ActionIcon
variant="subtle"
size="sm"
onClick={() => {
updateOperationStatus(
activeImage + operationId,
"COLLAPSE",
!isCollapsed
)
}}> }}>
{!isCollapsed ? ( {renderOperationIcon(operationSchema)}
<ChevronUp size={16} /> <Text
) : ( size="xs"
<ChevronDown size={16} /> fw={700}
)} c={hasSubAttrError ? "red" : "dimmed"}
</ActionIcon> 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> </Group>
</Flex> </Flex>
</Paper> <Group
<Collapse in={!isCollapsed}> gap={6}
<Stack gap="xs" mt="xs" ml="xs"> wrap="nowrap"
{visibleChildren.map((child, childIndex) => { style={{ flexShrink: 0, marginLeft: "auto" }}>
const { comment } = child[2] <TextInput
const isSelected = selectedPath[ w={88}
activeImage size="xs"
]?.includes(child[0]) placeholder="搜索子属性"
const isChildHide = value={searchSubAttrs[operationId] || ""}
pathStatus[activeImage + child[0]]?.["HIDE"] onChange={(e) => {
const hasChildError = getSubAttrStatus( setSearchSubAttrs((searchValues) => ({
getOperationSchema(operationId), ...searchValues,
child[2]?.sub_attributes [operationId]: e.target.value,
) }))
const isLastVisibleChild = }}
childIndex === visibleChildren.length - 1 onKeyDown={(e) =>
handleSelectedBySearchSubAttr(e, operationId)
let textColor = undefined
let connectorColor = "var(--mantine-color-gray-4)"
if (hasChildError) {
textColor = "red"
connectorColor = "var(--mantine-color-red-3)"
} else if (isSelected) {
textColor = "white"
connectorColor =
"var(--mantine-primary-color-filled)"
} }
/>
<Badge
size="sm"
variant="light"
radius="xl"
color={hasSubAttrError ? "red" : "gray"}>
{objectCount}
</Badge>
<ActionIcon
variant="subtle"
size="sm"
onClick={() => {
updateOperationStatus(
activeImage + operationId,
"COLLAPSE",
!isCollapsed
)
}}>
{!isCollapsed ? (
<ChevronUp size={16} />
) : (
<ChevronDown size={16} />
)}
</ActionIcon>
</Group>
</Flex>
</Paper>
<Collapse in={!isCollapsed}>
<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
return ( let textColor = undefined
let connectorColor = "var(--mantine-color-gray-4)"
if (hasChildError) {
textColor = "red"
connectorColor = "var(--mantine-color-red-3)"
} else if (isSelected) {
textColor = "white"
connectorColor =
"var(--mantine-primary-color-filled)"
}
return (
<Box
key={child[0]}
pl="lg"
style={{ position: "relative" }}>
<Box <Box
key={child[0]} style={{
pl="lg" position: "absolute",
style={{ position: "relative" }}> left: 7,
top: 0,
bottom: isLastVisibleChild ? "75%" : 0,
width: 1.5,
borderRadius: 999,
background: connectorColor,
opacity: isSelected ? 0.9 : 0.35,
}}
/>
{isLastVisibleChild ? (
<Box <Box
style={{ style={{
position: "absolute", position: "absolute",
left: 7, left: 7,
top: 0, top: "calc(50% - 10px)",
bottom: isLastVisibleChild ? "75%" : 0, width: 14,
width: 1.5, height: 10,
borderRadius: 999, borderLeft: `1.5px solid ${connectorColor}`,
background: connectorColor, borderBottom: `1.5px solid ${connectorColor}`,
opacity: isSelected ? 0.9 : 0.35, borderBottomLeftRadius: 10,
opacity: isSelected ? 0.9 : 0.55,
}} }}
/> />
{isLastVisibleChild ? ( ) : (
<>
<Box <Box
style={{ style={{
position: "absolute", position: "absolute",
left: 7, left: 7,
top: "calc(50% - 10px)", top: "50%",
width: 14, width: 14,
height: 10, borderTop: `1.5px solid ${connectorColor}`,
borderLeft: `1.5px solid ${connectorColor}`, opacity: isSelected ? 0.9 : 0.45,
borderBottom: `1.5px solid ${connectorColor}`,
borderBottomLeftRadius: 10,
opacity: isSelected ? 0.9 : 0.55,
}} }}
/> />
) : ( <Box
<> style={{
<Box position: "absolute",
style={{ left: 4,
position: "absolute", top: "calc(50% - 3px)",
left: 7, width: 6,
top: "50%", height: 6,
width: 14, borderRadius: 999,
borderTop: `1.5px solid ${connectorColor}`, background: connectorColor,
opacity: isSelected ? 0.9 : 0.45, boxShadow:
}} "0 0 0 2px var(--mantine-color-body)",
/> opacity: isSelected ? 1 : 0.85,
<Box }}
style={{ />
position: "absolute", </>
left: 4, )}
top: "calc(50% - 3px)", <Paper
width: 6, withBorder
height: 6, p="xs"
borderRadius: 999, radius="md"
background: connectorColor, shadow="sm"
boxShadow: bg={
"0 0 0 2px var(--mantine-color-body)", isSelected
opacity: isSelected ? 1 : 0.85, ? "var(--mantine-primary-color-filled)"
}} : "var(--mantine-color-gray-0)"
/> }
</> c={isSelected ? "white" : undefined}
)} onClick={() =>
<Paper handleSelect(child[0], operationId)
withBorder }
p="xs" style={{ cursor: "pointer" }}>
radius="md" <Flex justify="space-between" align="center">
shadow="sm" <Group gap="xs">
bg={ <ActionIcon
isSelected variant="subtle"
? "var(--mantine-primary-color-filled)" size="sm"
: "var(--mantine-color-gray-0)" c={isSelected ? "white" : undefined}
} onClick={(e) => {
c={isSelected ? "white" : undefined} e.stopPropagation()
onClick={() => objectHide(
handleSelect(child[0], operationId) operationId,
} child[0],
style={{ cursor: "pointer" }}> !isChildHide
<Flex justify="space-between" align="center"> )
<Group gap="xs"> }}>
<ActionIcon {!isChildHide ? (
variant="subtle" <Eye size={16} />
size="sm" ) : (
c={isSelected ? "white" : undefined} <EyeOff size={16} />
onClick={(e) => { )}
e.stopPropagation() </ActionIcon>
objectHide( <Text size="sm" c={textColor}>
operationId, {child[0]?.toString()}
child[0], </Text>
!isChildHide <Tooltip
) label={
}}> <Stack gap={0}>
{!isChildHide ? ( <Group gap={4}>
<Eye size={16} /> <Text size="xs"></Text>
) : ( <Text size="xs">
<EyeOff size={16} /> {
)} userOpts.find(
</ActionIcon> (item) =>
<Text size="sm" c={textColor}> item.value === child[2]?.uid
{child[0]?.toString()} )?.label
</Text> }
<Tooltip </Text>
label={ <Text size="xs">
<Stack gap={0}> {dayjs(
(child[2]?.create_timestamp ||
0) * 1000
).format("YYYY-MM-DD HH:mm:ss")}
</Text>
</Group>
{child[2]?.first_modified_uid && (
<Group gap={4}> <Group gap={4}>
<Text size="xs"></Text> <Text size="xs"></Text>
<Text size="xs"> <Text size="xs">
{ {
userOpts.find( userOpts.find(
(item) => (item) =>
item.value === item.value ===
child[2]?.uid child[2]
?.first_modified_uid
)?.label )?.label
} }
</Text> </Text>
<Text size="xs"> <Text size="xs">
{dayjs( {dayjs(
(child[2]?.create_timestamp || (child[2]
?.first_modified_timestamp ||
0) * 1000 0) * 1000
).format("YYYY-MM-DD HH:mm:ss")} ).format("YYYY-MM-DD HH:mm:ss")}
</Text> </Text>
</Group> </Group>
{child[2]?.first_modified_uid && ( )}
<Group gap={4}> {child[2]?.last_modified_uid && (
<Text size="xs"> <Group gap={4}>
<Text size="xs"></Text>
</Text> <Text size="xs">
<Text size="xs"> {
{ userOpts.find(
userOpts.find( (item) =>
(item) => item.value ===
item.value === child[2]
child[2] ?.last_modified_uid
?.first_modified_uid )?.label
)?.label }
} </Text>
</Text> <Text size="xs">
<Text size="xs"> {dayjs(
{dayjs( (child[2]
(child[2] ?.last_modified_timestamp ||
?.first_modified_timestamp || 0) * 1000
0) * 1000 ).format("YYYY-MM-DD HH:mm:ss")}
).format( </Text>
"YYYY-MM-DD HH:mm:ss" </Group>
)} )}
</Text> </Stack>
</Group> }
)} multiline
{child[2]?.last_modified_uid && ( bg="white"
<Group gap={4}> c="black"
<Text size="xs"> withArrow>
<Info
</Text> size={16}
<Text size="xs"> style={{ cursor: "pointer" }}
{ color={isSelected ? "white" : "black"}
userOpts.find( />
(item) => </Tooltip>
item.value === </Group>
child[2] {comment ? (
?.last_modified_uid <Group gap={4}>
)?.label {comment.map((c: any) => {
} const count =
</Text> c.check_box_comments.length +
<Text size="xs"> c.text_comments.length
{dayjs( const { last_modified_timestamp } =
(child[2] child[2]
?.last_modified_timestamp || let flag = false
0) * 1000 if (
).format( count > 0 &&
"YYYY-MM-DD HH:mm:ss" last_modified_timestamp &&
)} last_modified_timestamp > c.date
</Text> )
</Group> flag = true
)}
</Stack> let badgeColor = "gray"
} if (c.comment_type === 1)
multiline badgeColor = "green"
bg="white" if (c.comment_type === 2)
c="black" badgeColor = "red"
withArrow> if (flag) badgeColor = "yellow"
<Info
size={16} return (
style={{ cursor: "pointer" }} <Popover
color={isSelected ? "white" : "black"} key={`${child[0]}${c.turn}${c.times}`}
/> position="left"
</Tooltip> withArrow>
<Popover.Target>
<Badge
size="xs"
circle
color={badgeColor}
style={{
cursor: "pointer",
}}>
{count}
</Badge>
</Popover.Target>
<Popover.Dropdown>
<Text size="xs" c={badgeColor}>
{`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"
)
: ""
}`}
</Text>
</Popover.Dropdown>
</Popover>
)
})}
</Group> </Group>
{comment ? ( ) : null}
<Group gap={4}> </Flex>
{comment.map((c: any) => { </Paper>
const count = </Box>
c.check_box_comments.length + )
c.text_comments.length })}
const { last_modified_timestamp } = </Stack>
child[2] </Collapse>
let flag = false </Box>
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 (
<Popover
key={`${child[0]}${c.turn}${c.times}`}
position="left"
withArrow>
<Popover.Target>
<Badge
size="xs"
circle
color={badgeColor}
style={{
cursor: "pointer",
}}>
{count}
</Badge>
</Popover.Target>
<Popover.Dropdown>
<Text size="xs" c={badgeColor}>
{`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"
)
: ""
}`}
</Text>
</Popover.Dropdown>
</Popover>
)
})}
</Group>
) : null}
</Flex>
</Paper>
</Box>
)
})}
</Stack>
</Collapse>
</Box>
)
})}
</Stack> </Stack>
</ScrollArea> </ScrollArea>
</Collapse> </Collapse>