fix(structuredclone): change

This commit is contained in:
2026-02-28 16:54:50 +08:00
parent 717b7237a2
commit cc9bea54e5
12 changed files with 101 additions and 103 deletions

View File

@@ -51,6 +51,7 @@ import { useRightToolsStore } from "./useRightToolsStore"
import { useIntervalStore, useTimerStore } from "./useTimerStore"
import { useTopToolsStore } from "./useTopToolsStore"
import { findGroupKey } from "./util"
import { safeClone } from "./utils/clone"
import { labelTypeMap } from "./utils/constants"
// Splitter component - will need custom implementation or alternative
@@ -662,19 +663,19 @@ const LabelPage = ({
if (JSON.stringify(current_data) === "{}") {
current_data = qaData.get("init")
}
const qaMap = structuredClone(qaData)
const qaMap = safeClone(qaData)
qaMap.set(key, current_data)
useDescToolsStore.getState().setQaData(qaMap)
}
} else if (useDescToolsStore.getState().descOperations.length) {
if (desc) {
const { meta_operation, other_desc } = desc
const currentMetaMap = structuredClone(
const currentMetaMap = safeClone(
useDescToolsStore.getState().metaData
)
currentMetaMap.set(key, meta_operation || [])
useDescToolsStore.getState().setMetaData(currentMetaMap)
const currentDescMap = structuredClone(
const currentDescMap = safeClone(
useDescToolsStore.getState().descData
)
let descMap = new Map()
@@ -1084,7 +1085,7 @@ const LabelPage = ({
// }
// }
// 更新当前数据
useLabelStore.getState().setLabel(structuredClone(data))
useLabelStore.getState().setLabel(safeClone(data))
}
}

View File

@@ -31,6 +31,7 @@ import { useOtherToolsStore } from "../useOtherToolsStore"
import { usePaperStore } from "../usePaperStore"
import { useTopToolsStore } from "../useTopToolsStore"
import { getSubAttrStatus } from "../util"
import { safeClone } from "../utils/clone"
import CustomModal from "./CustomModal"
import MetaOperationTool from "./MetaOperationTool"
@@ -175,20 +176,16 @@ const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
const prevFramehasTextDesc = () => {
if (projectDetail?.label_type === 5) {
let flag = false
const currentDescData = structuredClone(
useDescToolsStore.getState().descData
)
const currentDescData = safeClone(useDescToolsStore.getState().descData)
const prevData1 = currentDescData.get(previousFrameKey) ?? new Map()
flag = prevData1.size ? true : false
if (flag) return true
const currentMetaData = structuredClone(
useDescToolsStore.getState().metaData
)
const currentMetaData = safeClone(useDescToolsStore.getState().metaData)
const prevData2 = currentMetaData.get(previousFrameKey) ?? []
flag = prevData2.length ? true : false
return flag
} else if (projectDetail?.label_type === 6) {
const currentQaData = structuredClone(useDescToolsStore.getState().qaData)
const currentQaData = safeClone(useDescToolsStore.getState().qaData)
const prevData = currentQaData.get(previousFrameKey) ?? {}
return JSON.stringify(prevData) === "{}" ? false : true
}
@@ -217,24 +214,20 @@ const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
// LLM
if (projectDetail?.label_type === 5) {
// 文本描述
const currentDescData = structuredClone(
useDescToolsStore.getState().descData
)
const currentDescData = safeClone(useDescToolsStore.getState().descData)
const prevData1 = currentDescData.get(previousFrameKey) ?? new Map()
currentDescData.set(activeImage, prevData1)
useDescToolsStore.getState().setDescData(currentDescData)
useDescToolsStore.getState().updateFlag(true)
// 元操作序列
const currentMetaData = structuredClone(
useDescToolsStore.getState().metaData
)
const currentMetaData = safeClone(useDescToolsStore.getState().metaData)
const prevData2 = currentMetaData.get(previousFrameKey) ?? []
currentMetaData.set(activeImage, prevData2)
useDescToolsStore.getState().setMetaData(currentMetaData)
}
// QA
if (projectDetail?.label_type === 6) {
const currentQaData = structuredClone(useDescToolsStore.getState().qaData)
const currentQaData = safeClone(useDescToolsStore.getState().qaData)
const prevData = currentQaData.get(previousFrameKey) ?? {}
currentQaData.set(activeImage, prevData)
useDescToolsStore.getState().setQaData(currentQaData)

View File

@@ -55,6 +55,7 @@ import { useRightToolsStore } from "../useRightToolsStore"
import { useTopToolsStore } from "../useTopToolsStore"
import { checkCommentsIsSame } from "../util"
import { labelTypeMap } from "../utils/constants"
import { safeClone } from "../utils/clone"
import { adjustPoints } from "../utils/paperjs"
import CrosshairComponent from "./CrosshairComponent"
import { renderOperationIcon } from "./RightObjectTools"
@@ -456,9 +457,7 @@ const PaperContainer = (
hollows: boolean[]
}[]
} = resData
const nowTaskData = structuredClone(
useLabelStore.getState().label
)
const nowTaskData = safeClone(useLabelStore.getState().label)
future_contours.forEach(({ contours, hollows }, index) => {
const fileName = file_names[index + 1]
const imgScale =
@@ -1013,7 +1012,7 @@ const PaperContainer = (
.selectedItems.filter((k) => k !== activeImage)
const copyArr = useKeyboardStore.getState().copyDataIds
if (picArr.length && copyArr.length) {
const data = structuredClone(useLabelStore.getState().label)
const data = safeClone(useLabelStore.getState().label)
// 先判断是否有重复的id
let existedIds: number[] = []
picArr.forEach((imageId) => {

View File

@@ -7,6 +7,7 @@ import { useLabelStore } from "../store"
import { useBottomToolsStore } from "../useBottomToolsStore"
import { usePaperStore } from "../usePaperStore"
import { useRightToolsStore } from "../useRightToolsStore"
import { safeClone } from "../utils/clone"
interface ComponentProps {
open: boolean
@@ -59,7 +60,7 @@ const RightGroupEditModal = ({
if (groupMap?.has(id)) return
const oldIds = groupMap?.get(editId) ?? []
const labelData = structuredClone(useLabelStore.getState().label)
const labelData = safeClone(useLabelStore.getState().label)
// update path
let operationIds: any[] = []
oldIds.forEach((childId) => {

View File

@@ -26,6 +26,7 @@ import { renderGroupPath } from "../useRenderGroupPath"
import { useRightToolsStore } from "../useRightToolsStore"
import { useTopToolsStore } from "../useTopToolsStore"
import { labelTypeMap } from "../utils/constants"
import { safeClone } from "../utils/clone"
import RightGroupEditModal from "./RightGroupEditModal"
interface RightGroupToolsComponentProps {
@@ -212,7 +213,7 @@ const RightGroupTools: React.FC<RightGroupToolsComponentProps> = (props) => {
needUpdateOperationIds[index] = childPath.data.operationId
})
// handle labeldata update
const currentData = structuredClone(useLabelStore.getState().label)
const currentData = safeClone(useLabelStore.getState().label)
needUpdateOperationIds.forEach((operationId, index) => {
if (currentData.get(imageId)?.get(operationId)?.length) {
let result =
@@ -260,7 +261,7 @@ const RightGroupTools: React.FC<RightGroupToolsComponentProps> = (props) => {
needRemoveOperationIds[index] = p.data.operationId
})
})
const currentData = structuredClone(useLabelStore.getState().label)
const currentData = safeClone(useLabelStore.getState().label)
needRemoveOperationIds.forEach((operationId, index) => {
if (currentData.get(imageId)?.get(operationId)?.length) {
let result =

View File

@@ -34,6 +34,7 @@ import { usePermissionStore } from "../store/auth"
import { useBottomToolsStore } from "../useBottomToolsStore"
import { useDescToolsStore } from "../useDescToolsStore"
import { useTopToolsStore } from "../useTopToolsStore"
import { safeClone } from "../utils/clone"
import CustomModal from "./CustomModal"
import EditorContainer, { splitWord } from "./EditorContainer"
@@ -147,7 +148,7 @@ const RightQAContent = forwardRef(
}
})
try {
const finalData = structuredClone(currentQaData)
const finalData = safeClone(currentQaData)
finalData[category] = newArr
qaData.set(activeImage, finalData)
setQaData(new Map(qaData)) // Force update
@@ -192,7 +193,7 @@ const RightQAContent = forwardRef(
// 处理问题上移
const handleQuestionUp = (category: string, index: number) => {
const finalData = structuredClone(currentQaData)
const finalData = safeClone(currentQaData)
let arr = finalData[category]
let temp = arr[index]
arr[index] = arr[index - 1]
@@ -205,7 +206,7 @@ const RightQAContent = forwardRef(
// 处理问题置顶
const handleQuestionUpToTop = (category: string, index: number) => {
const finalData = structuredClone(currentQaData)
const finalData = safeClone(currentQaData)
let arr = finalData[category]
const element = arr.splice(index, 1)[0]
arr.unshift(element)
@@ -226,7 +227,7 @@ const RightQAContent = forwardRef(
const currentImg = useBottomToolsStore.getState().activeImage
const qaData = useDescToolsStore.getState().qaData
const currentQaData = qaData.get(currentImg) ?? qaData.get("init") ?? {}
const newQaData = structuredClone(currentQaData)
const newQaData = safeClone(currentQaData)
const handleText = (v: string) => {
// 提取文本内容
@@ -370,9 +371,8 @@ const RightQAContent = forwardRef(
size="compact-xs"
onClick={() => {
const finalData =
structuredClone(currentQaData)
const updateData =
structuredClone(qaDataArr)
safeClone(currentQaData)
const updateData = safeClone(qaDataArr)
updateData.splice(index, 1)
finalData[qaCategory] = updateData
qaData.set(activeImage, finalData)
@@ -396,9 +396,8 @@ const RightQAContent = forwardRef(
size="compact-xs"
onClick={() => {
const finalData =
structuredClone(currentQaData)
const updateData =
structuredClone(qaDataArr)
safeClone(currentQaData)
const updateData = safeClone(qaDataArr)
updateData[index] = {
[question_name]: {
...detail,
@@ -599,8 +598,8 @@ const RightQAContent = forwardRef(
}
}
const finalData = structuredClone(currentQaData)
const updateData = structuredClone(currentOperation)
const finalData = safeClone(currentQaData)
const updateData = safeClone(currentOperation)
let obj = {
value: formData.value ?? "<p></p>",
id: formData.round_id,

View File

@@ -6,6 +6,7 @@ import { Skeleton, Stack, Flex, Box, Text } from "@mantine/core"
import { FolderPen, ImageDown, Trash2 } from "lucide-react"
import { useCallback, useEffect, useState } from "react"
import { storage, useImagesStore } from "../store"
import { safeClone } from "../utils/clone"
interface ComponentProps {
open: boolean
@@ -65,7 +66,7 @@ const TaskCacheModal = ({ open, handleClose, taskDetail }: ComponentProps) => {
// 删除图片缓存
if (data1 === "0.00 B") return
const imgNames = taskDetail?.data_name.map((item) => item.name[0]) ?? []
const imageData = structuredClone(useImagesStore.getState().images)
const imageData = safeClone(useImagesStore.getState().images)
imgNames.forEach((name) => {
imageData.delete(name)
})

View File

@@ -93,6 +93,7 @@ import { useIntervalStore } from "../useTimerStore"
import { useTopToolsStore } from "../useTopToolsStore"
import { getSubAttrStatus } from "../util"
import { labelTypeMap, TaskStatusEnum } from "../utils/constants"
import { safeClone } from "../utils/clone"
import { adjustAllPoints } from "../utils/paperjs"
import BackConfirmModal from "./BackConfirmModal"
import BackupModal from "./BackupModal"
@@ -562,7 +563,7 @@ const TopTools = (
}
// 处理QA数据
if (projectDetail?.label_type === 6) {
const currentQaData = structuredClone(
const currentQaData = safeClone(
useDescToolsStore.getState().qaData.get(name) || {}
)
let other_desc: any = {}
@@ -977,7 +978,7 @@ const TopTools = (
useDescToolsStore.getState().setMetaData(meta)
} else if (projectDetail?.label_type === 6) {
const { text } = data
const updateQaData = structuredClone(
const updateQaData = safeClone(
useDescToolsStore.getState().qaData
)
text.entries().forEach(([key, value]: any) => {
@@ -1542,7 +1543,7 @@ const TopTools = (
}}
c={showTagsConfigs.includes(config) ? "blue" : undefined}
onClick={() => {
let arr = structuredClone(showTagsConfigs)
let arr = safeClone(showTagsConfigs)
const index = arr.findIndex((item) => item === config)
if (index !== -1) {
arr.splice(index, 1)

View File

@@ -3,6 +3,7 @@ import superjson from "superjson"
import { create } from "zustand"
import { persist, PersistStorage, StorageValue } from "zustand/middleware"
import { Comment } from "./api/label/typing"
import { safeClone } from "./utils/clone"
type LabelData = Map<string, Map<string, [number, any[], any, any[]][]>>
interface LabelState {
@@ -145,56 +146,6 @@ const initialLabelState = {
},
}
const deepCloneFallback = <T>(value: T, cache = new WeakMap()): T => {
if (value === null || typeof value !== "object") return value
if (cache.has(value as object)) return cache.get(value as object)
if (value instanceof Date) return new Date(value.getTime()) as T
if (value instanceof Map) {
const clonedMap = new Map()
cache.set(value as object, clonedMap)
value.forEach((mapValue, mapKey) => {
clonedMap.set(
deepCloneFallback(mapKey, cache),
deepCloneFallback(mapValue, cache)
)
})
return clonedMap as T
}
if (value instanceof Set) {
const clonedSet = new Set()
cache.set(value as object, clonedSet)
value.forEach((setValue) => {
clonedSet.add(deepCloneFallback(setValue, cache))
})
return clonedSet as T
}
if (Array.isArray(value)) {
const clonedArr: any[] = []
cache.set(value as object, clonedArr)
value.forEach((item, index) => {
clonedArr[index] = deepCloneFallback(item, cache)
})
return clonedArr as T
}
const clonedObj: Record<string, any> = {}
cache.set(value as object, clonedObj)
Object.keys(value as object).forEach((key) => {
clonedObj[key] = deepCloneFallback((value as any)[key], cache)
})
return clonedObj as T
}
const safeClone = <T>(value: T): T => {
try {
return structuredClone(value)
} catch (error) {
console.warn("structuredClone failed, fallback to deep clone", error)
return deepCloneFallback(value)
}
}
export const initialDetail = {
comment: [],
create_timestamp: 0,

View File

@@ -16,6 +16,7 @@ import { useOtherToolsStore } from "./useOtherToolsStore"
import { usePaperSupportStore } from "./usePaperSupportStore"
import { useRightToolsStore } from "./useRightToolsStore"
import { useTopToolsStore } from "./useTopToolsStore"
import { safeClone } from "./utils/clone"
interface PaperState {
paperScope: paper.PaperScope | null
@@ -239,7 +240,7 @@ const handleDelete = () => {
}
})
}
const currentLabelData = structuredClone(useLabelStore.getState().label)
const currentLabelData = safeClone(useLabelStore.getState().label)
operationIds.forEach((operationId, index) => {
let result =
currentLabelData
@@ -1808,7 +1809,7 @@ export const usePaperStore = create<PaperState>((set) => ({
!child.data ||
(child.data && !Object.keys(child.data).length)
)
child.data = structuredClone(newDrawPath?.data || {})
child.data = safeClone(newDrawPath?.data || {})
})
oldPolygon.remove()
oldPolygon = null
@@ -1886,7 +1887,7 @@ export const usePaperStore = create<PaperState>((set) => ({
// if (!newDrawPath?.clockwise) newDrawPath?.reverse();
if (newDrawPath instanceof paper.CompoundPath) {
;(newDrawPath.children as paper.Path[]).forEach((child) => {
child.data = structuredClone(
child.data = safeClone(
Object.assign({}, newDrawPath?.data || {}, {
isHollowPolygon: !child.clockwise,
})
@@ -1894,7 +1895,7 @@ export const usePaperStore = create<PaperState>((set) => ({
})
if (oldParent instanceof paper.CompoundPath) {
;(oldParent.children as paper.Path[]).forEach((child) => {
child.data = structuredClone(
child.data = safeClone(
Object.assign({}, newDrawPath?.data || {}, {
isHollowPolygon: !child.clockwise,
})
@@ -1946,7 +1947,7 @@ export const usePaperStore = create<PaperState>((set) => ({
// if (!newDrawPath?.clockwise) newDrawPath?.reverse();
if (newDrawPath instanceof paper.CompoundPath)
(newDrawPath.children as paper.Path[]).forEach((child) => {
child.data = structuredClone(newDrawPath?.data || {})
child.data = safeClone(newDrawPath?.data || {})
})
oldPolygon.remove()
oldPolygon = null
@@ -2153,7 +2154,7 @@ export const usePaperStore = create<PaperState>((set) => ({
return
}
// hollowPath.path.clockwise = false;
hollowPath.path.data = structuredClone(
hollowPath.path.data = safeClone(
Object.assign({}, hollowPath.path?.data || {}, {
isHollowPolygon: true,
})
@@ -2168,7 +2169,7 @@ export const usePaperStore = create<PaperState>((set) => ({
// if (finnalPath instanceof paper.CompoundPath) {
// (finnalPath.children as paper.Path[]).forEach((child) => {
// // child.clockwise = false;
// child.data = structuredClone(
// child.data = safeClone(
// Object.assign({}, oldPolygon?.data || {}, {
// isHollowPolygon: true,
// })
@@ -2176,7 +2177,7 @@ export const usePaperStore = create<PaperState>((set) => ({
// });
// } else if (finnalPath instanceof paper.Path) {
// // finnalPath.clockwise = false;
// finnalPath.data = structuredClone(
// finnalPath.data = safeClone(
// Object.assign({}, oldPolygon?.data || {}, {
// isHollowPolygon: true,
// })
@@ -2656,7 +2657,7 @@ export const usePaperStore = create<PaperState>((set) => ({
if (polygon instanceof paper.CompoundPath)
(polygon.children as paper.Path[]).forEach((child) => {
if (!child.data || (child.data && !Object.keys(child.data).length))
child.data = structuredClone(
child.data = safeClone(
Object.assign(
{ isHollowPolygon: !child.clockwise },
polygon?.data || {}
@@ -2674,7 +2675,7 @@ export const usePaperStore = create<PaperState>((set) => ({
polygon = oldPolygon?.subtract(eachPath) as any
if (polygon instanceof paper.CompoundPath)
(polygon.children as paper.Path[]).forEach((child) => {
child.data = structuredClone(
child.data = safeClone(
Object.assign(
{ isHollowPolygon: !child.clockwise },
polygon?.data || {}
@@ -2716,7 +2717,7 @@ export const usePaperStore = create<PaperState>((set) => ({
polygon = oldPolygon.unite(eachPath) as any
if (polygon instanceof paper.CompoundPath)
(polygon.children as paper.Path[]).forEach((child) => {
child.data = structuredClone(polygon?.data || {})
child.data = safeClone(polygon?.data || {})
})
oldPolygon?.remove()
oldPolygon = null

View File

@@ -2,6 +2,7 @@ import { create } from "zustand"
import { persist } from "zustand/middleware"
import { storage, useLabelStore } from "./store"
import { usePaperStore } from "./usePaperStore"
import { safeClone } from "./utils/clone"
interface RightToolsState {
pathIds: number[]
@@ -94,7 +95,7 @@ export const useRightToolsStore = create(
}
state.pathGroupMap.get(imageId)?.set(groupId, pathIds)
// update LabelData
const data = structuredClone(useLabelStore.getState().label)
const data = safeClone(useLabelStore.getState().label)
// handle delete
if (deleteIds.length) {
let operationIds: any[] = []

View File

@@ -0,0 +1,49 @@
const deepCloneFallback = <T>(value: T, cache = new WeakMap()): T => {
if (value === null || typeof value !== "object") return value
if (cache.has(value as object)) return cache.get(value as object)
if (value instanceof Date) return new Date(value.getTime()) as T
if (value instanceof Map) {
const clonedMap = new Map()
cache.set(value as object, clonedMap)
value.forEach((mapValue, mapKey) => {
clonedMap.set(
deepCloneFallback(mapKey, cache),
deepCloneFallback(mapValue, cache)
)
})
return clonedMap as T
}
if (value instanceof Set) {
const clonedSet = new Set()
cache.set(value as object, clonedSet)
value.forEach((setValue) => {
clonedSet.add(deepCloneFallback(setValue, cache))
})
return clonedSet as T
}
if (Array.isArray(value)) {
const clonedArr: any[] = []
cache.set(value as object, clonedArr)
value.forEach((item, index) => {
clonedArr[index] = deepCloneFallback(item, cache)
})
return clonedArr as T
}
const clonedObj: Record<string, any> = {}
cache.set(value as object, clonedObj)
Object.keys(value as object).forEach((key) => {
clonedObj[key] = deepCloneFallback((value as any)[key], cache)
})
return clonedObj as T
}
export const safeClone = <T>(value: T): T => {
try {
return structuredClone(value)
} catch (error) {
console.warn("structuredClone failed, fallback to deep clone", error)
return deepCloneFallback(value)
}
}