feat(shortcut): h/a/d

This commit is contained in:
zhangheng
2026-04-03 17:00:09 +08:00
parent b6ae2a6a29
commit bb727d52be
7 changed files with 449 additions and 83 deletions

View File

@@ -56,6 +56,7 @@ import {
import { useTopToolsStore } from "./useTopToolsStore" import { useTopToolsStore } from "./useTopToolsStore"
import { findGroupKey } from "./util" import { findGroupKey } from "./util"
import { safeClone } from "./utils/clone" import { safeClone } from "./utils/clone"
import { toggleObjectHideByShortcut } from "./utils/objectVisibility"
import { labelTypeMap } from "./utils/constants" import { labelTypeMap } from "./utils/constants"
// Splitter component - will need custom implementation or alternative // Splitter component - will need custom implementation or alternative
@@ -233,9 +234,11 @@ const LabelPage = ({
const { const {
editMode, editMode,
setEditMode, setEditMode,
pressA,
setPressA, setPressA,
activeOperation, activeOperation,
setActiveOperation, setActiveOperation,
setDrawOption,
showObjectList, showObjectList,
showGroupList, showGroupList,
showTaskList, showTaskList,
@@ -1357,6 +1360,12 @@ const LabelPage = ({
}, [editMode, setDrawType]) }, [editMode, setDrawType])
const { setShift, focusInput } = useKeyEventStore() const { setShift, focusInput } = useKeyEventStore()
const drawAction = useKeyboardStore((state) => state.drawAction)
const drawShortcutSnapshotRef = useRef<{
activeOperation: string
drawOption: "default" | "intersect" | "unite"
editMode: boolean
} | null>(null)
const handleShortcut = useCallback( const handleShortcut = useCallback(
(key: string, ctrl: boolean) => { (key: string, ctrl: boolean) => {
@@ -1376,6 +1385,132 @@ const LabelPage = ({
[getOperationSchema, setActiveOperation] [getOperationSchema, setActiveOperation]
) )
const resolveSelectedObjectContext = useCallback(() => {
const imageId = useBottomToolsStore.getState().activeImage
const selectedIds = useObjectStore.getState().selectedPath[imageId] || []
if (selectedIds.length !== 1) return null
const objectId = selectedIds[0]
const imageLabel = useLabelStore.getState().label.get(imageId)
if (!imageLabel) return null
let operationId = ""
for (const [categoryId, paths] of imageLabel.entries()) {
if (paths.some((path) => path[0] === objectId)) {
operationId = categoryId
break
}
}
if (!operationId) return null
const selectedItem = usePaperStore.getState().getItemById(objectId)
if (!selectedItem?.data?.type) return null
return {
imageId,
objectId,
operationId,
objectType: selectedItem.data.type as string,
}
}, [])
const restoreDrawShortcutSnapshot = useCallback(() => {
useKeyboardStore.getState().setDrawAction("none")
const snapshot = drawShortcutSnapshotRef.current
if (!snapshot) return
setActiveOperation(snapshot.activeOperation)
setDrawOption(snapshot.drawOption)
setEditMode(snapshot.editMode)
drawShortcutSnapshotRef.current = null
}, [setActiveOperation, setDrawOption, setEditMode])
const enterSelectedObjectDrawMode = useCallback(
(mode: "add" | "subtract") => {
const selectedContext = resolveSelectedObjectContext()
if (!selectedContext) {
notifications.show({
color: "yellow",
message: "请先选中单个对象",
})
return
}
if (
mode === "add" &&
!["polygon", "brush"].includes(selectedContext.objectType)
) {
notifications.show({
color: "yellow",
message: "新增区域仅支持 polygon/brush 类型对象",
})
return
}
if (mode === "subtract" && selectedContext.objectType !== "polygon") {
notifications.show({
color: "yellow",
message: "删除区域仅支持 polygon 类型对象",
})
return
}
if (!drawShortcutSnapshotRef.current) {
const topState = useTopToolsStore.getState()
drawShortcutSnapshotRef.current = {
activeOperation: topState.activeOperation,
drawOption: topState.drawOption,
editMode: topState.editMode,
}
}
setActiveOperation(selectedContext.operationId)
setEditMode(true)
setDrawOption("default")
useKeyboardStore.getState().setDrawAction(mode)
setPressA(true)
if (!useTopToolsStore.getState().pressA) {
notifications.show({
color: "yellow",
message: "当前对象无法进入区域编辑模式",
})
restoreDrawShortcutSnapshot()
}
},
[
resolveSelectedObjectContext,
restoreDrawShortcutSnapshot,
setActiveOperation,
setDrawOption,
setEditMode,
setPressA,
]
)
const handleToggleHideSelectedObject = useCallback(() => {
const selectedContext = resolveSelectedObjectContext()
if (!selectedContext) {
notifications.show({
color: "yellow",
message: "请先选中单个对象",
})
return
}
const currentHideStatus =
useObjectStore.getState().pathStatus[
selectedContext.imageId + selectedContext.objectId
]?.HIDE || false
toggleObjectHideByShortcut(
selectedContext.imageId,
selectedContext.operationId,
selectedContext.objectId,
!currentHideStatus
)
}, [resolveSelectedObjectContext])
const handleMergeSelectedObjects = useCallback(() => { const handleMergeSelectedObjects = useCallback(() => {
const activeImage = useBottomToolsStore.getState().activeImage const activeImage = useBottomToolsStore.getState().activeImage
const selectedIds = const selectedIds =
@@ -1518,6 +1653,27 @@ const LabelPage = ({
console.log(e.key) console.log(e.key)
const mode = usePaperStore.getState().mode const mode = usePaperStore.getState().mode
const lowerKey = e.key.toLowerCase()
if (e.repeat && ["a", "d", "h"].includes(lowerKey)) return
if (!e.ctrlKey && !e.metaKey && !e.altKey && lowerKey === "h") {
e.preventDefault()
handleToggleHideSelectedObject()
return
}
if (!e.ctrlKey && !e.metaKey && !e.altKey && lowerKey === "a") {
e.preventDefault()
enterSelectedObjectDrawMode("add")
return
}
if (!e.ctrlKey && !e.metaKey && !e.altKey && lowerKey === "d") {
e.preventDefault()
enterSelectedObjectDrawMode("subtract")
return
}
if (e.altKey && e.key.toLowerCase() === "x") { if (e.altKey && e.key.toLowerCase() === "x") {
e.preventDefault() e.preventDefault()
@@ -1561,11 +1717,6 @@ const LabelPage = ({
paperContainerRef.current.handleCreatePathGroup() paperContainerRef.current.handleCreatePathGroup()
} }
if (e.key === "a") {
e.preventDefault()
setPressA(true)
}
if (e.key === "Control") { if (e.key === "Control") {
e.preventDefault() e.preventDefault()
useKeyboardStore.getState().setCtrl(true) useKeyboardStore.getState().setCtrl(true)
@@ -1746,19 +1897,27 @@ const LabelPage = ({
} }
}, [ }, [
editMode, editMode,
enterSelectedObjectDrawMode,
focusInput, focusInput,
getOperationSchema, getOperationSchema,
handleToggleHideSelectedObject,
handleMergeSelectedObjects, handleMergeSelectedObjects,
handleShortcut, handleShortcut,
isView, isView,
projectDetail?.label_type, projectDetail?.label_type,
setEditMode, setEditMode,
setGroupScale, setGroupScale,
setDrawOption,
setPressA, setPressA,
setScale, setScale,
setShift, setShift,
]) ])
useEffect(() => {
if (drawAction === "none" || pressA) return
restoreDrawShortcutSnapshot()
}, [drawAction, pressA, restoreDrawShortcutSnapshot])
const setGlobalLoading = useCallback((flag: boolean) => { const setGlobalLoading = useCallback((flag: boolean) => {
setLoading(flag) setLoading(flag)
}, []) }, [])
@@ -1766,6 +1925,8 @@ const LabelPage = ({
// 切换图片时清空复制保存的数组 // 切换图片时清空复制保存的数组
useEffect(() => { useEffect(() => {
useKeyboardStore.getState().setCopyDataIds([]) useKeyboardStore.getState().setCopyDataIds([])
useKeyboardStore.getState().setDrawAction("none")
drawShortcutSnapshotRef.current = null
}, [activeImage]) }, [activeImage])
const handleBeforeUnload = (e: any) => { const handleBeforeUnload = (e: any) => {

View File

@@ -42,6 +42,7 @@ import { getShowContextMenuPosition, usePaperStore } from "../usePaperStore"
import { usePaperSupportStore } from "../usePaperSupportStore" import { usePaperSupportStore } from "../usePaperSupportStore"
import { useTopToolsStore } from "../useTopToolsStore" import { useTopToolsStore } from "../useTopToolsStore"
import { getSubAttrStatus } from "../util" import { getSubAttrStatus } from "../util"
import { toggleObjectHideByShortcut } from "../utils/objectVisibility"
import { labelTypeMap } from "../utils/constants" import { labelTypeMap } from "../utils/constants"
interface RightObjectToolsComponentProps { interface RightObjectToolsComponentProps {
@@ -379,33 +380,6 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
}) })
} }
const getTagVisibility = (tagCategory?: string) => {
const { showTags, showTagsConfigs } = useTopToolsStore.getState()
if (!showTags) return false
if (tagCategory === "mask") return showTagsConfigs.includes("外框")
if (tagCategory === "point_text") return showTagsConfigs.includes("点ID")
return true
}
const syncObjectItemsVisibility = useCallback(
(objectId: number, hide: boolean) => {
const sameIdItems = getItemsById(objectId)
if (!sameIdItems?.length) return
sameIdItems.forEach((item) => {
if (hide) {
item.visible = false
return
}
if (item.data?.type === "tag") {
item.visible = getTagVisibility(item.data?.tag_category)
return
}
item.visible = true
})
},
[getItemsById]
)
const operationHide = useCallback( const operationHide = useCallback(
(operationId: string, hide: boolean) => { (operationId: string, hide: boolean) => {
updateOperationStatus(activeImage + operationId, "HIDE", hide) updateOperationStatus(activeImage + operationId, "HIDE", hide)
@@ -414,51 +388,17 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
.get(activeImage) .get(activeImage)
?.get(operationId) ?.get(operationId)
?.map((child) => { ?.map((child) => {
updatePathStatus(activeImage + child[0], "HIDE", hide) toggleObjectHideByShortcut(activeImage, operationId, child[0], hide)
syncObjectItemsVisibility(child[0], hide)
// 隐藏时清空选中状态
if (hide) setSelectedItemFalse(child[0])
}) })
}, },
[ [activeImage, storeLabel, updateOperationStatus]
activeImage,
storeLabel,
syncObjectItemsVisibility,
updateOperationStatus,
updatePathStatus,
]
) )
const objectHide = useCallback( const objectHide = useCallback(
(operationId: string, objectId: number, hide: boolean) => { (operationId: string, objectId: number, hide: boolean) => {
if (hide) { toggleObjectHideByShortcut(activeImage, operationId, objectId, hide)
updatePathStatus(activeImage + objectId, "HIDE", hide)
syncObjectItemsVisibility(objectId, 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)
syncObjectItemsVisibility(objectId, hide)
}
}, },
[ [activeImage]
activeImage,
getItemById,
storeLabel,
syncObjectItemsVisibility,
updateOperationStatus,
updatePathStatus,
]
) )
// const renderObjectName = useCallback((name: string, input: string) => { // const renderObjectName = useCallback((name: string, input: string) => {

View File

@@ -87,6 +87,7 @@ import {
import { useBackUrlStore, usePermissionStore } from "../store/auth" import { useBackUrlStore, usePermissionStore } from "../store/auth"
import { useBottomToolsStore } from "../useBottomToolsStore" import { useBottomToolsStore } from "../useBottomToolsStore"
import { useDescToolsStore } from "../useDescToolsStore" import { useDescToolsStore } from "../useDescToolsStore"
import { useKeyboardStore } from "../useKeyBoardStore"
import { usePaperStore } from "../usePaperStore" import { usePaperStore } from "../usePaperStore"
import { useRightToolsStore } from "../useRightToolsStore" import { useRightToolsStore } from "../useRightToolsStore"
import { useIntervalStore, useLabelTimeStore } from "../useTimerStore" import { useIntervalStore, useLabelTimeStore } from "../useTimerStore"
@@ -1531,6 +1532,7 @@ const TopTools = (
}, [activeOperation, mode]) }, [activeOperation, mode])
const operationSchema = getOperationSchema(activeOperation) const operationSchema = getOperationSchema(activeOperation)
const drawAction = useKeyboardStore((state) => state.drawAction)
const subAttrForm = ( const subAttrForm = (
<Box w={256}> <Box w={256}>
@@ -1645,6 +1647,12 @@ const TopTools = (
const iconStyle = { width: 20, height: 20 } const iconStyle = { width: 20, height: 20 }
if (mode === "support") return <Paintbrush style={iconStyle} /> if (mode === "support") return <Paintbrush style={iconStyle} />
if (editMode) { if (editMode) {
if (pressA && drawAction === "subtract") {
return <CopyMinus style={iconStyle} />
}
if (pressA && drawAction === "add") {
return <CopyPlus style={iconStyle} />
}
return pressA ? ( return pressA ? (
<CirclePlus style={iconStyle} /> <CirclePlus style={iconStyle} />
) : ( ) : (
@@ -1653,7 +1661,7 @@ const TopTools = (
} else { } else {
return <MousePointer style={iconStyle} /> return <MousePointer style={iconStyle} />
} }
}, [mode, editMode, pressA]) }, [mode, editMode, pressA, drawAction])
const showTagConfigContainer = useMemo(() => { const showTagConfigContainer = useMemo(() => {
const configs = ["ID", "类别", "外框", "点ID", "子属性", "属性值"] const configs = ["ID", "类别", "外框", "点ID", "子属性", "属性值"]

View File

@@ -5,6 +5,8 @@ interface KeyboardStore {
setCtrl: (v: boolean) => void setCtrl: (v: boolean) => void
shift: boolean shift: boolean
setShift: (v: boolean) => void setShift: (v: boolean) => void
drawAction: "none" | "add" | "subtract"
setDrawAction: (mode: "none" | "add" | "subtract") => void
copyDataIds: number[] // ctrl + c 复制数据 copyDataIds: number[] // ctrl + c 复制数据
setCopyDataIds: (ids: number[]) => void setCopyDataIds: (ids: number[]) => void
} }
@@ -12,8 +14,10 @@ interface KeyboardStore {
export const useKeyboardStore = create<KeyboardStore>((set) => ({ export const useKeyboardStore = create<KeyboardStore>((set) => ({
ctrl: false, ctrl: false,
shift: false, shift: false,
drawAction: "none",
copyDataIds: [], copyDataIds: [],
setCtrl: (v) => set((state) => ({ ...state, ctrl: v })), setCtrl: (v) => set((state) => ({ ...state, ctrl: v })),
setShift: (v) => set((state) => ({ ...state, shift: v })), setShift: (v) => set((state) => ({ ...state, shift: v })),
setDrawAction: (mode) => set((state) => ({ ...state, drawAction: mode })),
setCopyDataIds: (ids) => set((state) => ({ ...state, copyDataIds: ids })), setCopyDataIds: (ids) => set((state) => ({ ...state, copyDataIds: ids })),
})) }))

View File

@@ -12,6 +12,7 @@ import {
useObjectStore, useObjectStore,
} from "./store" } from "./store"
import { useBottomToolsStore } from "./useBottomToolsStore" import { useBottomToolsStore } from "./useBottomToolsStore"
import { useKeyboardStore } from "./useKeyBoardStore"
import { useOtherToolsStore } from "./useOtherToolsStore" import { useOtherToolsStore } from "./useOtherToolsStore"
import { usePaperSupportStore } from "./usePaperSupportStore" import { usePaperSupportStore } from "./usePaperSupportStore"
import { useRightToolsStore } from "./useRightToolsStore" import { useRightToolsStore } from "./useRightToolsStore"
@@ -2855,6 +2856,139 @@ export const usePaperStore = create<PaperState>((set) => ({
}) })
} }
// paper path data里可能带函数(例如事件回调), 不能直接structuredClone
const getSafePathData = (data: any) => {
const nextData: Record<string, any> = {}
Object.entries(data || {}).forEach(([key, value]) => {
if (typeof value === "function") return
nextData[key] = value
})
return nextData
}
const handleSelectedPolygonSubtract = (draftPath: paper.Path) => {
const activeImage = useBottomToolsStore.getState().activeImage
const selectedIds =
useObjectStore.getState().selectedPath[activeImage] || []
if (selectedIds.length !== 1) return false
const selectedId = selectedIds[0]
const targetShapes = usePaperStore
.getState()
.getItemsById(selectedId)
.filter((item) => item.data?.type === "polygon")
const targetShape =
targetShapes.find((item) => item instanceof paper.CompoundPath) ||
targetShapes.find(
(item) => item instanceof paper.Path && !item.data?.isHollowPolygon
) ||
targetShapes.find((item) => item instanceof paper.Path)
if (!targetShape) return false
let clippedDraft: paper.Path | paper.CompoundPath | null =
draftPath.intersect(usePaperStore.getState().rasterPath!, {
insert: false,
trace: false,
}) as paper.Path | paper.CompoundPath
draftPath.remove()
const draftHasSegments =
clippedDraft instanceof paper.CompoundPath
? (clippedDraft.children as paper.Path[]).some(
(child) => child.segments?.length
)
: clippedDraft.segments?.length
if (!draftHasSegments) {
clippedDraft?.remove()
clippedDraft = null
return false
}
const targetData = getSafePathData(targetShape.data || {})
let nextPolygon = (
targetShape as paper.Path | paper.CompoundPath
).subtract(clippedDraft) as paper.Path | paper.CompoundPath
clippedDraft?.remove()
clippedDraft = null
targetShape.remove()
if (nextPolygon instanceof paper.CompoundPath) {
nextPolygon.data = Object.assign({}, targetData)
;(nextPolygon.children as paper.Path[]).forEach((child) => {
child.data = Object.assign({}, targetData, {
isHollowPolygon: !child.clockwise,
})
})
} else {
nextPolygon.data = Object.assign({}, targetData)
}
const points: any[] = []
const hollowPoints: any[] = []
const collectPathPoints = (path: paper.Path) => {
if (!path.segments?.length) return
const pathData = JSON.parse(path.exportJSON({ precision: 20 }))
if (path.clockwise) {
points.push(pathData[1]?.segments)
} else {
hollowPoints.push(pathData[1]?.segments)
}
}
if (nextPolygon instanceof paper.CompoundPath) {
;(nextPolygon.children as paper.Path[]).forEach((child) => {
collectPathPoints(child)
})
} else {
collectPathPoints(nextPolygon)
}
const operationId = (
targetData.operationId ||
useTopToolsStore.getState().activeOperation ||
""
).toString()
if (!operationId) {
nextPolygon.remove()
return false
}
const prevDetail = safeClone(
useLabelStore
.getState()
.getLabelDetailDataByKeys(activeImage, operationId, selectedId) ||
initialDetail
)
if (points.length) {
useLabelStore
.getState()
.setOperation(activeImage, operationId, [
selectedId,
points,
prevDetail,
hollowPoints,
])
useObjectStore
.getState()
.updateSelectedPath(activeImage, "ADD", selectedId)
} else {
nextPolygon.remove()
useLabelStore
.getState()
.deleteOperation(activeImage, operationId, selectedId)
useObjectStore.getState().updateSelectedPath(activeImage, "ADD")
const nextLabel = safeClone(useLabelStore.getState().label)
useLabelStore.getState().setLabel(nextLabel)
useLabelStore.getState().pushStateStack(nextLabel)
}
forceUpdate()
return true
}
polygonTool.onMouseDown = (e: { polygonTool.onMouseDown = (e: {
event: MouseEvent event: MouseEvent
point: paper.Point point: paper.Point
@@ -2897,35 +3031,83 @@ export const usePaperStore = create<PaperState>((set) => ({
// 初始化 compoundPolygon // 初始化 compoundPolygon
compoundPolygon = new paper.Path() compoundPolygon = new paper.Path()
if (useKeyboardStore.getState().drawAction === "subtract") {
;(polygon as paper.Path).closePath()
handleSelectedPolygonSubtract(polygon as paper.Path)
selectedCircles.forEach((item) => {
item.remove()
})
selectedCircles = []
clearPolygonMagnet()
useTopToolsStore.getState().setPressA(false)
draftId = null
lastPoint = null
lastTime = 0
points = []
polygon = null
return
}
// pressA 模式下添加多边形 // pressA 模式下添加多边形
if (useTopToolsStore.getState().pressA) { if (useTopToolsStore.getState().pressA) {
let ids = let ids =
useObjectStore.getState().selectedPath[ useObjectStore.getState().selectedPath[
useBottomToolsStore.getState().activeImage useBottomToolsStore.getState().activeImage
] ]
const selectedId = ids?.[0]
if (!selectedId) {
resetPolygonDraft()
return
}
let paths = usePaperStore.getState().getItemsById(ids[0]) let paths = usePaperStore.getState().getItemsById(ids[0])
let oldPolygon = polygon let oldPolygon = polygon
// 闭合绘制路径 // 闭合绘制路径
oldPolygon.closePath() oldPolygon.closePath()
const polygonPaths = paths.filter(
(path) => path.data?.type === "polygon"
)
const prevCompoundPath =
(polygonPaths.find(
(path) => path instanceof paper.CompoundPath
) as paper.CompoundPath | undefined) ||
((polygonPaths.find(
(path) => path.parent instanceof paper.CompoundPath
)?.parent as paper.CompoundPath | undefined) ??
undefined)
if (paths.length === 1) { const basePathData = getSafePathData(
const prevPath = paths[0] polygonPaths.find((path) => !!path.data)?.data || {}
)
oldPolygon.data = Object.assign({}, basePathData, oldPolygon.data, {
id: selectedId,
type: "polygon",
isHollowPolygon: !oldPolygon.clockwise,
})
if (prevCompoundPath) {
// 已是多区域对象, 直接追加 child
prevCompoundPath.addChild(oldPolygon)
prevCompoundPath.data = Object.assign(
{},
getSafePathData(prevCompoundPath.data || {}),
basePathData,
{ id: selectedId, type: "polygon" }
)
polygon = prevCompoundPath
} else {
const prevPaths = polygonPaths.filter(
(path): path is paper.Path => path instanceof paper.Path
)
polygon = usePaperStore.getState().addCompoundPath( polygon = usePaperStore.getState().addCompoundPath(
renderCompoundPath, renderCompoundPath,
[prevPath, oldPolygon] as paper.Path[], [...prevPaths, oldPolygon] as paper.Path[],
{ {
// option // option
...usePaperStore.getState().toolOption, ...usePaperStore.getState().toolOption,
}, },
{ ...oldPolygon.data, id: prevPath?.data.id } { ...basePathData, ...oldPolygon.data, id: selectedId }
) )
} else if (paths.length > 1) {
const prevPath = paths.filter(
(path) => path instanceof paper.CompoundPath
)[0]
// 直接将当前路径添加到原有父路径
prevPath.addChild(oldPolygon)
polygon = prevPath
} }
} }

View File

@@ -136,7 +136,7 @@ export const useTopToolsStore = create<TopToolsState>()(
} }
let path = usePaperStore.getState().getItemById(ids[0]) let path = usePaperStore.getState().getItemById(ids[0])
if (path && path.data.type !== "rectangle") { if (path && path.data.type !== "rectangle") {
let flag = val && usePaperStore.getState().mode !== "rectangle" let flag = val
set((state: TopToolsState) => ({ set((state: TopToolsState) => ({
...state, ...state,

View File

@@ -0,0 +1,71 @@
import paper from "paper"
import { useLabelStore, useObjectStore } from "../store"
import { usePaperStore } from "../usePaperStore"
import { useTopToolsStore } from "../useTopToolsStore"
const getTagVisibility = (tagCategory?: string) => {
const { showTags, showTagsConfigs } = useTopToolsStore.getState()
if (!showTags) return false
if (tagCategory === "mask") return showTagsConfigs.includes("外框")
if (tagCategory === "point_text") return showTagsConfigs.includes("点ID")
return true
}
const setSelectedItemFalse = (id: number) => {
if (!id) return
const sameIdItems = usePaperStore.getState().getItemsById(id)
sameIdItems.forEach((item) => {
if (item.data.type === "mask" || item.data.type === "text") {
item.remove()
}
if (item.data.selected) item.data.selected = false
})
}
const syncObjectItemsVisibility = (objectId: number, hide: boolean) => {
const sameIdItems = usePaperStore.getState().getItemsById(objectId)
if (!sameIdItems?.length) return
sameIdItems.forEach((item) => {
if (hide) {
item.visible = false
return
}
if (item.data?.type === "tag") {
item.visible = getTagVisibility(item.data?.tag_category)
return
}
item.visible = true
})
}
export const toggleObjectHideByShortcut = (
imageId: string,
operationId: string,
objectId: number,
hide: boolean
) => {
const { updatePathStatus, updateOperationStatus } = useObjectStore.getState()
const storeLabel = useLabelStore.getState().label
if (hide) {
updatePathStatus(imageId + objectId, "HIDE", hide)
syncObjectItemsVisibility(objectId, hide)
setSelectedItemFalse(objectId)
const operationChildren = storeLabel.get(imageId)?.get(operationId) || []
const hasVisibleChild = operationChildren.some((child) => {
const item = usePaperStore
.getState()
.getItemById(child[0]) as paper.Item | null
return !!item?.visible
})
if (!hasVisibleChild) {
updateOperationStatus(imageId + operationId, "HIDE", hide)
}
return
}
updateOperationStatus(imageId + operationId, "HIDE", hide)
updatePathStatus(imageId + objectId, "HIDE", hide)
syncObjectItemsVisibility(objectId, hide)
}