feat(shortcut): h/a/d
This commit is contained in:
@@ -56,6 +56,7 @@ import {
|
||||
import { useTopToolsStore } from "./useTopToolsStore"
|
||||
import { findGroupKey } from "./util"
|
||||
import { safeClone } from "./utils/clone"
|
||||
import { toggleObjectHideByShortcut } from "./utils/objectVisibility"
|
||||
import { labelTypeMap } from "./utils/constants"
|
||||
|
||||
// Splitter component - will need custom implementation or alternative
|
||||
@@ -233,9 +234,11 @@ const LabelPage = ({
|
||||
const {
|
||||
editMode,
|
||||
setEditMode,
|
||||
pressA,
|
||||
setPressA,
|
||||
activeOperation,
|
||||
setActiveOperation,
|
||||
setDrawOption,
|
||||
showObjectList,
|
||||
showGroupList,
|
||||
showTaskList,
|
||||
@@ -1357,6 +1360,12 @@ const LabelPage = ({
|
||||
}, [editMode, setDrawType])
|
||||
|
||||
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(
|
||||
(key: string, ctrl: boolean) => {
|
||||
@@ -1376,6 +1385,132 @@ const LabelPage = ({
|
||||
[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 activeImage = useBottomToolsStore.getState().activeImage
|
||||
const selectedIds =
|
||||
@@ -1518,6 +1653,27 @@ const LabelPage = ({
|
||||
|
||||
console.log(e.key)
|
||||
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") {
|
||||
e.preventDefault()
|
||||
@@ -1561,11 +1717,6 @@ const LabelPage = ({
|
||||
paperContainerRef.current.handleCreatePathGroup()
|
||||
}
|
||||
|
||||
if (e.key === "a") {
|
||||
e.preventDefault()
|
||||
setPressA(true)
|
||||
}
|
||||
|
||||
if (e.key === "Control") {
|
||||
e.preventDefault()
|
||||
useKeyboardStore.getState().setCtrl(true)
|
||||
@@ -1746,19 +1897,27 @@ const LabelPage = ({
|
||||
}
|
||||
}, [
|
||||
editMode,
|
||||
enterSelectedObjectDrawMode,
|
||||
focusInput,
|
||||
getOperationSchema,
|
||||
handleToggleHideSelectedObject,
|
||||
handleMergeSelectedObjects,
|
||||
handleShortcut,
|
||||
isView,
|
||||
projectDetail?.label_type,
|
||||
setEditMode,
|
||||
setGroupScale,
|
||||
setDrawOption,
|
||||
setPressA,
|
||||
setScale,
|
||||
setShift,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (drawAction === "none" || pressA) return
|
||||
restoreDrawShortcutSnapshot()
|
||||
}, [drawAction, pressA, restoreDrawShortcutSnapshot])
|
||||
|
||||
const setGlobalLoading = useCallback((flag: boolean) => {
|
||||
setLoading(flag)
|
||||
}, [])
|
||||
@@ -1766,6 +1925,8 @@ const LabelPage = ({
|
||||
// 切换图片时清空复制保存的数组
|
||||
useEffect(() => {
|
||||
useKeyboardStore.getState().setCopyDataIds([])
|
||||
useKeyboardStore.getState().setDrawAction("none")
|
||||
drawShortcutSnapshotRef.current = null
|
||||
}, [activeImage])
|
||||
|
||||
const handleBeforeUnload = (e: any) => {
|
||||
|
||||
@@ -42,6 +42,7 @@ import { getShowContextMenuPosition, usePaperStore } from "../usePaperStore"
|
||||
import { usePaperSupportStore } from "../usePaperSupportStore"
|
||||
import { useTopToolsStore } from "../useTopToolsStore"
|
||||
import { getSubAttrStatus } from "../util"
|
||||
import { toggleObjectHideByShortcut } from "../utils/objectVisibility"
|
||||
import { labelTypeMap } from "../utils/constants"
|
||||
|
||||
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(
|
||||
(operationId: string, hide: boolean) => {
|
||||
updateOperationStatus(activeImage + operationId, "HIDE", hide)
|
||||
@@ -414,51 +388,17 @@ const RightObjectTools: React.FC<RightObjectToolsComponentProps> = (props) => {
|
||||
.get(activeImage)
|
||||
?.get(operationId)
|
||||
?.map((child) => {
|
||||
updatePathStatus(activeImage + child[0], "HIDE", hide)
|
||||
syncObjectItemsVisibility(child[0], hide)
|
||||
// 隐藏时清空选中状态
|
||||
if (hide) setSelectedItemFalse(child[0])
|
||||
toggleObjectHideByShortcut(activeImage, operationId, child[0], hide)
|
||||
})
|
||||
},
|
||||
[
|
||||
activeImage,
|
||||
storeLabel,
|
||||
syncObjectItemsVisibility,
|
||||
updateOperationStatus,
|
||||
updatePathStatus,
|
||||
]
|
||||
[activeImage, storeLabel, updateOperationStatus]
|
||||
)
|
||||
|
||||
const objectHide = useCallback(
|
||||
(operationId: string, objectId: number, hide: boolean) => {
|
||||
if (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)
|
||||
}
|
||||
toggleObjectHideByShortcut(activeImage, operationId, objectId, hide)
|
||||
},
|
||||
[
|
||||
activeImage,
|
||||
getItemById,
|
||||
storeLabel,
|
||||
syncObjectItemsVisibility,
|
||||
updateOperationStatus,
|
||||
updatePathStatus,
|
||||
]
|
||||
[activeImage]
|
||||
)
|
||||
|
||||
// const renderObjectName = useCallback((name: string, input: string) => {
|
||||
|
||||
@@ -87,6 +87,7 @@ import {
|
||||
import { useBackUrlStore, usePermissionStore } from "../store/auth"
|
||||
import { useBottomToolsStore } from "../useBottomToolsStore"
|
||||
import { useDescToolsStore } from "../useDescToolsStore"
|
||||
import { useKeyboardStore } from "../useKeyBoardStore"
|
||||
import { usePaperStore } from "../usePaperStore"
|
||||
import { useRightToolsStore } from "../useRightToolsStore"
|
||||
import { useIntervalStore, useLabelTimeStore } from "../useTimerStore"
|
||||
@@ -1531,6 +1532,7 @@ const TopTools = (
|
||||
}, [activeOperation, mode])
|
||||
|
||||
const operationSchema = getOperationSchema(activeOperation)
|
||||
const drawAction = useKeyboardStore((state) => state.drawAction)
|
||||
|
||||
const subAttrForm = (
|
||||
<Box w={256}>
|
||||
@@ -1645,6 +1647,12 @@ const TopTools = (
|
||||
const iconStyle = { width: 20, height: 20 }
|
||||
if (mode === "support") return <Paintbrush style={iconStyle} />
|
||||
if (editMode) {
|
||||
if (pressA && drawAction === "subtract") {
|
||||
return <CopyMinus style={iconStyle} />
|
||||
}
|
||||
if (pressA && drawAction === "add") {
|
||||
return <CopyPlus style={iconStyle} />
|
||||
}
|
||||
return pressA ? (
|
||||
<CirclePlus style={iconStyle} />
|
||||
) : (
|
||||
@@ -1653,7 +1661,7 @@ const TopTools = (
|
||||
} else {
|
||||
return <MousePointer style={iconStyle} />
|
||||
}
|
||||
}, [mode, editMode, pressA])
|
||||
}, [mode, editMode, pressA, drawAction])
|
||||
|
||||
const showTagConfigContainer = useMemo(() => {
|
||||
const configs = ["ID", "类别", "外框", "点ID", "子属性", "属性值"]
|
||||
|
||||
@@ -5,6 +5,8 @@ interface KeyboardStore {
|
||||
setCtrl: (v: boolean) => void
|
||||
shift: boolean
|
||||
setShift: (v: boolean) => void
|
||||
drawAction: "none" | "add" | "subtract"
|
||||
setDrawAction: (mode: "none" | "add" | "subtract") => void
|
||||
copyDataIds: number[] // ctrl + c 复制数据
|
||||
setCopyDataIds: (ids: number[]) => void
|
||||
}
|
||||
@@ -12,8 +14,10 @@ interface KeyboardStore {
|
||||
export const useKeyboardStore = create<KeyboardStore>((set) => ({
|
||||
ctrl: false,
|
||||
shift: false,
|
||||
drawAction: "none",
|
||||
copyDataIds: [],
|
||||
setCtrl: (v) => set((state) => ({ ...state, ctrl: v })),
|
||||
setShift: (v) => set((state) => ({ ...state, shift: v })),
|
||||
setDrawAction: (mode) => set((state) => ({ ...state, drawAction: mode })),
|
||||
setCopyDataIds: (ids) => set((state) => ({ ...state, copyDataIds: ids })),
|
||||
}))
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
useObjectStore,
|
||||
} from "./store"
|
||||
import { useBottomToolsStore } from "./useBottomToolsStore"
|
||||
import { useKeyboardStore } from "./useKeyBoardStore"
|
||||
import { useOtherToolsStore } from "./useOtherToolsStore"
|
||||
import { usePaperSupportStore } from "./usePaperSupportStore"
|
||||
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: {
|
||||
event: MouseEvent
|
||||
point: paper.Point
|
||||
@@ -2897,35 +3031,83 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
// 初始化 compoundPolygon
|
||||
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 模式下添加多边形
|
||||
if (useTopToolsStore.getState().pressA) {
|
||||
let ids =
|
||||
useObjectStore.getState().selectedPath[
|
||||
useBottomToolsStore.getState().activeImage
|
||||
]
|
||||
const selectedId = ids?.[0]
|
||||
if (!selectedId) {
|
||||
resetPolygonDraft()
|
||||
return
|
||||
}
|
||||
let paths = usePaperStore.getState().getItemsById(ids[0])
|
||||
let oldPolygon = polygon
|
||||
// 闭合绘制路径
|
||||
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 prevPath = paths[0]
|
||||
const basePathData = getSafePathData(
|
||||
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(
|
||||
renderCompoundPath,
|
||||
[prevPath, oldPolygon] as paper.Path[],
|
||||
[...prevPaths, oldPolygon] as paper.Path[],
|
||||
{
|
||||
// option
|
||||
...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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ export const useTopToolsStore = create<TopToolsState>()(
|
||||
}
|
||||
let path = usePaperStore.getState().getItemById(ids[0])
|
||||
if (path && path.data.type !== "rectangle") {
|
||||
let flag = val && usePaperStore.getState().mode !== "rectangle"
|
||||
let flag = val
|
||||
|
||||
set((state: TopToolsState) => ({
|
||||
...state,
|
||||
|
||||
71
components/label/utils/objectVisibility.ts
Normal file
71
components/label/utils/objectVisibility.ts
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user