feat(project): init
This commit is contained in:
716
components/label/components/BottomTools.tsx
Normal file
716
components/label/components/BottomTools.tsx
Normal file
@@ -0,0 +1,716 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
ActionIcon,
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
Group,
|
||||
ScrollArea,
|
||||
Text,
|
||||
TextInput,
|
||||
} from "@mantine/core"
|
||||
import { showNotification } from "@mantine/notifications"
|
||||
import {
|
||||
IconChevronLeft,
|
||||
IconChevronRight,
|
||||
IconChevronsLeft,
|
||||
IconChevronsRight,
|
||||
IconFlag,
|
||||
} from "@tabler/icons-react"
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import { Project } from "../api/project/typing"
|
||||
import { Task } from "../api/task/typing"
|
||||
import { LabelState } from "../LabelNossr"
|
||||
import { useLabelStore } from "../store"
|
||||
import { usePermissionStore } from "../store/auth"
|
||||
import { useBottomToolsStore } from "../useBottomToolsStore"
|
||||
import { useDescToolsStore } from "../useDescToolsStore"
|
||||
import { useKeyboardStore } from "../useKeyBoardStore"
|
||||
import { useOtherToolsStore } from "../useOtherToolsStore"
|
||||
import { usePaperStore } from "../usePaperStore"
|
||||
import { useTopToolsStore } from "../useTopToolsStore"
|
||||
import { getSubAttrStatus } from "../util"
|
||||
import CustomModal from "./CustomModal"
|
||||
import MetaOperationTool from "./MetaOperationTool"
|
||||
|
||||
interface BottomToolsComponentProps {
|
||||
labelState: LabelState
|
||||
taskDetail?: Task.DataProps
|
||||
projectDetail?: Project.DetailResponse
|
||||
renderPolygons: (
|
||||
operationId: string,
|
||||
imageData: Map<string, [number, any[], any, any[]][]> | undefined
|
||||
) => void | null
|
||||
}
|
||||
|
||||
const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
|
||||
const { labelState, projectDetail, taskDetail, renderPolygons } = props
|
||||
|
||||
const itemRefs = useRef<Record<string, HTMLDivElement | null>>({})
|
||||
|
||||
const { label: storeLabel, setLabel, pushStateStack } = useLabelStore()
|
||||
const {
|
||||
inputNumber,
|
||||
setInputNumber,
|
||||
activeImage,
|
||||
setActiveImage,
|
||||
setActiveImageId,
|
||||
onlyKeyFrame,
|
||||
setOnlyKeyFrame,
|
||||
keyFrameData,
|
||||
setKeyFrameData,
|
||||
selectedItems,
|
||||
setSelectedItems,
|
||||
} = useBottomToolsStore()
|
||||
const { metaOperation } = useDescToolsStore()
|
||||
const { ctrl: ctrlKey, shift: shiftKey } = useKeyboardStore()
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
const [conflictOpen, setConflictOpen] = useState(false)
|
||||
const isShowKeyFrame =
|
||||
projectDetail &&
|
||||
[5, 6].includes(projectDetail.label_type) &&
|
||||
Array.from(labelState.keys()).some(
|
||||
(k) => keyFrameData.has(k) && keyFrameData.get(k)?.key_frame
|
||||
) &&
|
||||
onlyKeyFrame
|
||||
const currentKeys = Array.from(labelState.keys()).filter((k) =>
|
||||
isShowKeyFrame
|
||||
? keyFrameData.has(k) && keyFrameData.get(k)?.key_frame
|
||||
: true
|
||||
)
|
||||
// 上一帧图片id
|
||||
const previousFrameKey = useMemo(() => {
|
||||
let key = ""
|
||||
const keys = Array.from(labelState.keys()).filter((k) =>
|
||||
isShowKeyFrame
|
||||
? keyFrameData.has(k) && keyFrameData.get(k)?.key_frame
|
||||
: true
|
||||
)
|
||||
keys.forEach((k, index) => {
|
||||
if (k === activeImage && index > 0) key = keys[index - 1]
|
||||
})
|
||||
return key
|
||||
}, [activeImage, isShowKeyFrame, keyFrameData, labelState])
|
||||
|
||||
useEffect(() => {
|
||||
const [firstKey] = labelState.keys()
|
||||
setActiveImage(firstKey)
|
||||
setInputNumber("1")
|
||||
setActiveImageId(1)
|
||||
}, [labelState, setActiveImage, setActiveImageId, setInputNumber])
|
||||
|
||||
const scrollToIndex = (index: number) => {
|
||||
const key = currentKeys[index]
|
||||
if (key && itemRefs.current[key]) {
|
||||
itemRefs.current[key]?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
inline: "center",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const closeRightContext = useCallback(() => {
|
||||
useOtherToolsStore.getState().setShowContextMenu({
|
||||
showContextMenu: false,
|
||||
position: {
|
||||
top: 0,
|
||||
left: 0,
|
||||
},
|
||||
imageId: useOtherToolsStore.getState().imageId,
|
||||
operationId: useOtherToolsStore.getState().operationId,
|
||||
id: useOtherToolsStore.getState().id,
|
||||
})
|
||||
}, [])
|
||||
|
||||
const getOperationSchema = useCallback(
|
||||
(operationId: string) => {
|
||||
return (
|
||||
projectDetail?.label_schema_list?.find(
|
||||
(item) => item.category_id === +operationId
|
||||
) || null
|
||||
)
|
||||
},
|
||||
[projectDetail?.label_schema_list]
|
||||
)
|
||||
|
||||
// child 是否存在不符合要求的子属性
|
||||
const getOperationSubAttrStatus = useCallback(
|
||||
(imageId: string) => {
|
||||
let parentBool = labelState.get(imageId)?.some((operationId) => {
|
||||
let bool = storeLabel
|
||||
.get(imageId)
|
||||
?.get(operationId)
|
||||
?.some((child) => {
|
||||
return getSubAttrStatus(
|
||||
getOperationSchema(operationId),
|
||||
child[2]?.sub_attributes
|
||||
)
|
||||
})
|
||||
return bool
|
||||
})
|
||||
return parentBool
|
||||
},
|
||||
[getOperationSchema, labelState, storeLabel]
|
||||
)
|
||||
|
||||
// 获取上一帧和当前帧标注对象数组
|
||||
const getLabelObjectsData = () => {
|
||||
let map1 =
|
||||
storeLabel.get(previousFrameKey) ??
|
||||
new Map<string, [number, any[], any, any[]][]>()
|
||||
let arr1 = []
|
||||
for (const value of map1.values()) arr1.push(...value)
|
||||
let map2 =
|
||||
storeLabel.get(activeImage) ??
|
||||
new Map<string, [number, any[], any, any[]][]>()
|
||||
let arr2 = []
|
||||
for (const value of map2.values()) arr2.push(...value)
|
||||
return [arr1, arr2]
|
||||
}
|
||||
|
||||
// 上一帧是否有文本描述
|
||||
const prevFramehasTextDesc = () => {
|
||||
if (projectDetail?.label_type === 5) {
|
||||
let flag = false
|
||||
const currentDescData = structuredClone(
|
||||
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 prevData2 = currentMetaData.get(previousFrameKey) ?? []
|
||||
flag = prevData2.length ? true : false
|
||||
return flag
|
||||
} else if (projectDetail?.label_type === 6) {
|
||||
const currentQaData = structuredClone(useDescToolsStore.getState().qaData)
|
||||
const prevData = currentQaData.get(previousFrameKey) ?? {}
|
||||
return JSON.stringify(prevData) === "{}" ? false : true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 更新数据绘制
|
||||
const updateDataRender = (
|
||||
data: Map<string, [number, any[], any, any[]][]>
|
||||
) => {
|
||||
if (!renderPolygons) return
|
||||
const list = usePaperStore.getState().group?.getItems({})
|
||||
list?.forEach((item) => {
|
||||
if (item.data.id) {
|
||||
item.remove()
|
||||
}
|
||||
})
|
||||
|
||||
useTopToolsStore.getState().objectOperations?.forEach((item) => {
|
||||
renderPolygons(item.category_id.toString(), data)
|
||||
})
|
||||
}
|
||||
|
||||
// 大语言标注继承上一帧时数据处理
|
||||
const handleLLMData = () => {
|
||||
// LLM
|
||||
if (projectDetail?.label_type === 5) {
|
||||
// 文本描述
|
||||
const currentDescData = structuredClone(
|
||||
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 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 prevData = currentQaData.get(previousFrameKey) ?? {}
|
||||
currentQaData.set(activeImage, prevData)
|
||||
useDescToolsStore.getState().setQaData(currentQaData)
|
||||
useDescToolsStore.getState().updateFlag(true)
|
||||
}
|
||||
}
|
||||
|
||||
// 继承上一帧的数据
|
||||
const handleCopyData = () => {
|
||||
// 图形绘制
|
||||
const [perviousData] = getLabelObjectsData()
|
||||
let map =
|
||||
storeLabel.get(activeImage) ??
|
||||
new Map<string, [number, any[], any, any[]][]>()
|
||||
perviousData.forEach((item) => {
|
||||
const id = item[2].category_id.toString()
|
||||
if (map.has(id)) {
|
||||
let data = map.get(id)
|
||||
data!.push(item)
|
||||
map.set(id, data as any)
|
||||
} else {
|
||||
map.set(id, [item])
|
||||
}
|
||||
})
|
||||
let updateData = new Map()
|
||||
for (const [key, value] of storeLabel) {
|
||||
if (key === activeImage) updateData.set(key, map)
|
||||
else updateData.set(key, value)
|
||||
}
|
||||
if (!storeLabel.has(activeImage)) updateData.set(activeImage, map)
|
||||
updateDataRender(updateData.get(activeImage)!)
|
||||
setLabel(updateData)
|
||||
pushStateStack(updateData)
|
||||
handleLLMData()
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const handleCopyConflictData = (flag: boolean) => {
|
||||
const [perviousData, nowData] = getLabelObjectsData()
|
||||
|
||||
let map = new Map<string, [number, any[], any, any[]][]>()
|
||||
let arr = flag
|
||||
? [
|
||||
...perviousData,
|
||||
...nowData.filter(
|
||||
(item) => !perviousData.map((p) => p[0]).includes(item[0])
|
||||
),
|
||||
]
|
||||
: [
|
||||
...perviousData.filter(
|
||||
(item) => !nowData.map((d) => d[0]).includes(item[0])
|
||||
),
|
||||
...nowData,
|
||||
]
|
||||
arr.forEach((item) => {
|
||||
const id = item[2].category_id.toString()
|
||||
if (map.has(id)) {
|
||||
let data = map.get(id)
|
||||
data!.push(item)
|
||||
map.set(id, data as any)
|
||||
} else {
|
||||
map.set(id, [item])
|
||||
}
|
||||
})
|
||||
let updateData = new Map()
|
||||
for (const [key, value] of storeLabel) {
|
||||
if (key === activeImage) updateData.set(key, map)
|
||||
else updateData.set(key, value)
|
||||
}
|
||||
updateDataRender(updateData.get(activeImage)!)
|
||||
setLabel(updateData)
|
||||
pushStateStack(updateData)
|
||||
handleLLMData()
|
||||
setConflictOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box w="100%" h="100%">
|
||||
{projectDetail?.label_type === 5 && metaOperation ? (
|
||||
<Flex
|
||||
h={48}
|
||||
w="100%"
|
||||
align="center"
|
||||
style={{
|
||||
borderTop:
|
||||
"1px solid light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-4))",
|
||||
}}>
|
||||
<Box px="md" fw={600}>
|
||||
元操作序列
|
||||
</Box>
|
||||
<Box
|
||||
style={{
|
||||
maxWidth: "calc(100% - 98px)",
|
||||
overflowX: "auto",
|
||||
height: "100%",
|
||||
}}>
|
||||
<MetaOperationTool />
|
||||
</Box>
|
||||
</Flex>
|
||||
) : null}
|
||||
<Box
|
||||
h={28}
|
||||
style={{
|
||||
border:
|
||||
"1px solid light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-4))",
|
||||
}}>
|
||||
<Flex justify="space-between" align="center" h="100%">
|
||||
<Box flex={1} />
|
||||
<Flex flex={1} align="center" justify="center" gap="xs">
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
scrollToIndex(0)
|
||||
currentKeys.length && setActiveImage(currentKeys[0])
|
||||
setInputNumber("1")
|
||||
setActiveImageId(1)
|
||||
closeRightContext()
|
||||
}}>
|
||||
<IconChevronsLeft size={16} />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
if (inputNumber !== "1") {
|
||||
let newActiveIndex = +inputNumber - 1
|
||||
setInputNumber(newActiveIndex.toString())
|
||||
setActiveImageId(newActiveIndex)
|
||||
scrollToIndex(newActiveIndex - 1)
|
||||
|
||||
setActiveImage(currentKeys?.[newActiveIndex - 1])
|
||||
closeRightContext()
|
||||
}
|
||||
}}>
|
||||
<IconChevronLeft size={16} />
|
||||
</ActionIcon>
|
||||
<TextInput
|
||||
w={42}
|
||||
size="compact-xs"
|
||||
radius={"sm"}
|
||||
styles={{
|
||||
input: {
|
||||
height: 20,
|
||||
fontSize: "14px",
|
||||
textAlign: "center",
|
||||
},
|
||||
}}
|
||||
value={inputNumber}
|
||||
onChange={(e) => {
|
||||
setInputNumber(e.target.value)
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
scrollToIndex(+inputNumber - 1)
|
||||
setActiveImage(currentKeys?.[+inputNumber - 1])
|
||||
setActiveImageId(+inputNumber)
|
||||
closeRightContext()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Text size="xs">/ {currentKeys.length}</Text>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
if (+inputNumber < currentKeys.length) {
|
||||
let newActiveIndex = +inputNumber + 1
|
||||
setInputNumber(newActiveIndex.toString())
|
||||
setActiveImageId(newActiveIndex)
|
||||
scrollToIndex(newActiveIndex - 1)
|
||||
setActiveImage(currentKeys?.[newActiveIndex - 1])
|
||||
closeRightContext()
|
||||
}
|
||||
}}>
|
||||
<IconChevronRight size={16} />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
scrollToIndex(currentKeys.length - 1)
|
||||
setInputNumber(currentKeys.length.toString())
|
||||
setActiveImageId(currentKeys.length)
|
||||
setActiveImage(currentKeys?.[currentKeys.length - 1])
|
||||
closeRightContext()
|
||||
}}>
|
||||
<IconChevronsRight size={16} />
|
||||
</ActionIcon>
|
||||
</Flex>
|
||||
<Flex flex={1} justify="flex-end" align="center" gap="xs" pr="xs">
|
||||
{projectDetail && [5, 6].includes(projectDetail.label_type) ? (
|
||||
<>
|
||||
{isShowKeyFrame ? (
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="compact-xs"
|
||||
radius={"sm"}
|
||||
onClick={() => {
|
||||
const [firstKey] = labelState.keys()
|
||||
setActiveImage(firstKey)
|
||||
setInputNumber("1")
|
||||
setActiveImageId(1)
|
||||
setOnlyKeyFrame(false)
|
||||
}}>
|
||||
查看全部帧
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="compact-xs"
|
||||
radius={"sm"}
|
||||
onClick={() => {
|
||||
let flag = false
|
||||
keyFrameData.entries().forEach(([key, value]) => {
|
||||
if (labelState.has(key) && value?.key_frame) flag = true
|
||||
})
|
||||
if (!flag) {
|
||||
showNotification({
|
||||
title: "警告",
|
||||
message: "当前任务暂未设定关键帧",
|
||||
color: "yellow",
|
||||
})
|
||||
return
|
||||
}
|
||||
const filterKeys = Array.from(labelState.keys()).filter(
|
||||
(key) =>
|
||||
keyFrameData.has(key) &&
|
||||
keyFrameData.get(key)?.key_frame
|
||||
)
|
||||
setActiveImage(filterKeys[0])
|
||||
setInputNumber("1")
|
||||
setActiveImageId(1)
|
||||
setOnlyKeyFrame(true)
|
||||
}}>
|
||||
仅保留关键帧
|
||||
</Button>
|
||||
)}
|
||||
{keyFrameData.has(activeImage) &&
|
||||
keyFrameData.get(activeImage)?.key_frame ? (
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="compact-xs"
|
||||
radius={"sm"}
|
||||
disabled={
|
||||
!taskDetail ||
|
||||
![2, 4, 6].includes(taskDetail.label_status) ||
|
||||
useTopToolsStore.getState().isView
|
||||
}
|
||||
onClick={() => {
|
||||
if (!taskDetail) return
|
||||
const data = keyFrameData.get(activeImage)!
|
||||
data.key_frame = false
|
||||
if (taskDetail.label_status === 2) {
|
||||
data.label1_ts = 0
|
||||
data.label1_uid = 0
|
||||
} else if (taskDetail.label_status === 4) {
|
||||
data.review1_ts = 0
|
||||
data.review1_uid = 0
|
||||
} else if (taskDetail.label_status === 6) {
|
||||
data.review2_ts = 0
|
||||
data.review2_uid = 0
|
||||
}
|
||||
setKeyFrameData(activeImage, data)
|
||||
}}>
|
||||
设定为非关键帧
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="compact-xs"
|
||||
radius={"sm"}
|
||||
disabled={
|
||||
!taskDetail ||
|
||||
![2, 4, 6].includes(taskDetail.label_status) ||
|
||||
useTopToolsStore.getState().isView
|
||||
}
|
||||
onClick={() => {
|
||||
if (!taskDetail) return
|
||||
const data = keyFrameData.get(activeImage) || {
|
||||
key_frame: false,
|
||||
label1_ts: 0,
|
||||
label1_uid: 0,
|
||||
review1_ts: 0,
|
||||
review1_uid: 0,
|
||||
review2_ts: 0,
|
||||
review2_uid: 0,
|
||||
}
|
||||
data.key_frame = true
|
||||
if (taskDetail.label_status === 2) {
|
||||
data.label1_ts =
|
||||
useLabelStore.getState().labelTime.label
|
||||
data.label1_uid = usePermissionStore.getState().user_id
|
||||
} else if (taskDetail.label_status === 4) {
|
||||
data.review1_ts =
|
||||
useLabelStore.getState().labelTime.review1
|
||||
data.review1_uid = usePermissionStore.getState().user_id
|
||||
} else if (taskDetail.label_status === 6) {
|
||||
data.review2_ts =
|
||||
useLabelStore.getState().labelTime.review2
|
||||
data.review2_uid = usePermissionStore.getState().user_id
|
||||
}
|
||||
setKeyFrameData(activeImage, data)
|
||||
}}>
|
||||
设定为关键帧
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="compact-xs"
|
||||
radius={"sm"}
|
||||
disabled={
|
||||
(!getLabelObjectsData()[0].length && !prevFramehasTextDesc()) ||
|
||||
useTopToolsStore.getState().isView
|
||||
}
|
||||
onClick={() => {
|
||||
const [perviousData, nowData] = getLabelObjectsData()
|
||||
// check merge conflict
|
||||
if (!nowData.length) {
|
||||
setOpen(true)
|
||||
return
|
||||
}
|
||||
let flag = false
|
||||
const perviousIds = perviousData.map((item) => item[0])
|
||||
const nowIds = nowData.map((item) => item[0])
|
||||
perviousIds.forEach((id) => {
|
||||
if (nowIds.includes(id)) flag = true
|
||||
})
|
||||
if (flag) setConflictOpen(true)
|
||||
else setOpen(true)
|
||||
}}>
|
||||
继承上一帧
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box
|
||||
h={
|
||||
projectDetail?.label_type === 5 && metaOperation
|
||||
? "calc(100% - 76px)"
|
||||
: "calc(100% - 28px)"
|
||||
}
|
||||
style={{
|
||||
border:
|
||||
"1px solid light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-4))",
|
||||
}}>
|
||||
<ScrollArea.Autosize
|
||||
mah="100%"
|
||||
h={"100%"}
|
||||
w="100%"
|
||||
scrollbars="x"
|
||||
style={{ display: "flex", alignItems: "center" }}>
|
||||
<Flex gap={0} w="max-content" h="100%" align="center">
|
||||
{currentKeys.map((key, index) => {
|
||||
const isActive = activeImage === key
|
||||
const isSelected =
|
||||
selectedItems.length && selectedItems.includes(key)
|
||||
const isHighlight = isActive || isSelected
|
||||
|
||||
let textColor = "inherit"
|
||||
if (getOperationSubAttrStatus(key)) {
|
||||
textColor = "red"
|
||||
} else if (isHighlight) {
|
||||
textColor = "white"
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={key}
|
||||
w={120}
|
||||
h="100%"
|
||||
p={6}
|
||||
ref={(el) => {
|
||||
itemRefs.current[key] = el
|
||||
}}>
|
||||
<Box
|
||||
style={{
|
||||
border: isHighlight
|
||||
? "1px solid var(--mantine-color-blue-6)"
|
||||
: "1px solid var(--mantine-color-gray-3)",
|
||||
borderRadius: "6px",
|
||||
backgroundColor: isHighlight
|
||||
? "var(--mantine-color-blue-6)"
|
||||
: undefined,
|
||||
color: isHighlight ? "white" : undefined,
|
||||
cursor: "pointer",
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
onClick={(e) => {
|
||||
if (e.shiftKey) {
|
||||
e.preventDefault()
|
||||
}
|
||||
if (ctrlKey || shiftKey) {
|
||||
setSelectedItems(key, ctrlKey, shiftKey)
|
||||
} else {
|
||||
setInputNumber(index + 1 + "")
|
||||
setActiveImage(key)
|
||||
setActiveImageId(index + 1)
|
||||
setSelectedItems("", false, false)
|
||||
}
|
||||
closeRightContext()
|
||||
}}>
|
||||
{keyFrameData.has(key) &&
|
||||
keyFrameData.get(key)?.key_frame ? (
|
||||
<Box
|
||||
pos="absolute"
|
||||
top={0}
|
||||
right={8}
|
||||
c={isHighlight ? "white" : "gray"}>
|
||||
<IconFlag size={16} />
|
||||
</Box>
|
||||
) : null}
|
||||
<Text fw={600} size="lg" c={textColor}>
|
||||
{index + 1}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
})}
|
||||
</Flex>
|
||||
</ScrollArea.Autosize>
|
||||
</Box>
|
||||
<CustomModal
|
||||
title="提示"
|
||||
open={open}
|
||||
centered
|
||||
onCancel={() => {
|
||||
setOpen(false)
|
||||
}}
|
||||
onOk={handleCopyData}>
|
||||
确实要复制上一帧数据吗?
|
||||
</CustomModal>
|
||||
<CustomModal
|
||||
title="提示"
|
||||
open={conflictOpen}
|
||||
centered
|
||||
onCancel={() => {
|
||||
setConflictOpen(false)
|
||||
}}
|
||||
footer={
|
||||
<Group justify="flex-end" gap="xs" mt="md">
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={() => {
|
||||
setConflictOpen(false)
|
||||
}}>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
color="red"
|
||||
onClick={() => {
|
||||
handleCopyConflictData(false)
|
||||
}}>
|
||||
否
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleCopyConflictData(true)
|
||||
}}>
|
||||
是
|
||||
</Button>
|
||||
</Group>
|
||||
}>
|
||||
与上一帧数据存在冲突,是否要复制上一帧数据并覆盖吗?
|
||||
</CustomModal>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default BottomTools
|
||||
Reference in New Issue
Block a user