Merge branch 'zh' into 'main'
Zh See merge request prism/lable/labelimage!7
This commit is contained in:
@@ -24,6 +24,7 @@ import {
|
|||||||
Collapse,
|
Collapse,
|
||||||
Flex,
|
Flex,
|
||||||
Group,
|
Group,
|
||||||
|
Modal,
|
||||||
MultiSelect,
|
MultiSelect,
|
||||||
Select,
|
Select,
|
||||||
SimpleGrid,
|
SimpleGrid,
|
||||||
@@ -73,6 +74,8 @@ function createInitialFilters(): FilterFormRecord {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_RECORDS_PER_PAGE_OPTIONS = [10, 15, 20]
|
||||||
|
|
||||||
export interface selectedDataProps {
|
export interface selectedDataProps {
|
||||||
status: number
|
status: number
|
||||||
uid: number | null
|
uid: number | null
|
||||||
@@ -148,6 +151,12 @@ export default function TaskTableContainer(props: {
|
|||||||
|
|
||||||
const [pageNumber, setPageNumber] = useState(1)
|
const [pageNumber, setPageNumber] = useState(1)
|
||||||
const [pageSize, setPageSize] = useState(15)
|
const [pageSize, setPageSize] = useState(15)
|
||||||
|
const [recordsPerPageOptions, setRecordsPerPageOptions] = useState<number[]>(
|
||||||
|
() => [...DEFAULT_RECORDS_PER_PAGE_OPTIONS]
|
||||||
|
)
|
||||||
|
const [customPageSizeInput, setCustomPageSizeInput] = useState("")
|
||||||
|
const [customPageSizeModalOpened, setCustomPageSizeModalOpened] =
|
||||||
|
useState(false)
|
||||||
|
|
||||||
const [searchExpanded, setSearchExpanded] = useState(false)
|
const [searchExpanded, setSearchExpanded] = useState(false)
|
||||||
|
|
||||||
@@ -259,6 +268,53 @@ export default function TaskTableContainer(props: {
|
|||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const openCustomPageSizeModal = useCallback(() => {
|
||||||
|
setCustomPageSizeModalOpened(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const closeCustomPageSizeModal = useCallback(() => {
|
||||||
|
setCustomPageSizeModalOpened(false)
|
||||||
|
setCustomPageSizeInput("")
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleAddCustomPageSize = useCallback(() => {
|
||||||
|
const rawValue = customPageSizeInput.trim()
|
||||||
|
if (!rawValue) return
|
||||||
|
const size = Number(rawValue)
|
||||||
|
if (!Number.isInteger(size) || size <= 0) {
|
||||||
|
notifications.show({
|
||||||
|
color: "red",
|
||||||
|
title: "输入无效",
|
||||||
|
message: "请填写大于 0 的整数条数",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (recordsPerPageOptions.includes(size)) {
|
||||||
|
notifications.show({
|
||||||
|
color: "blue",
|
||||||
|
title: "已存在",
|
||||||
|
message: `每页 ${size} 条已在可选项中`,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setRecordsPerPageOptions((prev) => [...prev, size].sort((a, b) => a - b))
|
||||||
|
closeCustomPageSizeModal()
|
||||||
|
notifications.show({
|
||||||
|
color: "green",
|
||||||
|
title: "添加成功",
|
||||||
|
message: `已新增每页 ${size} 条`,
|
||||||
|
})
|
||||||
|
}, [closeCustomPageSizeModal, customPageSizeInput, recordsPerPageOptions])
|
||||||
|
|
||||||
|
const handleCustomPageSizeEnter = useCallback(
|
||||||
|
(event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (event.key !== "Enter") return
|
||||||
|
event.preventDefault()
|
||||||
|
handleAddCustomPageSize()
|
||||||
|
},
|
||||||
|
[handleAddCustomPageSize]
|
||||||
|
)
|
||||||
|
|
||||||
const clickDispatchTask = () => {
|
const clickDispatchTask = () => {
|
||||||
if (!selectedRecords.length) {
|
if (!selectedRecords.length) {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
@@ -992,12 +1048,58 @@ export default function TaskTableContainer(props: {
|
|||||||
}}
|
}}
|
||||||
selectedRecords={selectedRecords}
|
selectedRecords={selectedRecords}
|
||||||
onSelectedRecordsChange={setSelectedRecords}
|
onSelectedRecordsChange={setSelectedRecords}
|
||||||
recordsPerPageOptions={[10, 15, 20]}
|
recordsPerPageOptions={recordsPerPageOptions}
|
||||||
|
renderPagination={({ Controls }) => (
|
||||||
|
<Group
|
||||||
|
justify="space-between"
|
||||||
|
align="center"
|
||||||
|
w="100%"
|
||||||
|
gap="sm"
|
||||||
|
wrap="wrap">
|
||||||
|
<Group gap="xs" align="center" wrap="wrap">
|
||||||
|
<Controls.Text />
|
||||||
|
<Controls.PageSizeSelector />
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="gray"
|
||||||
|
size="xs"
|
||||||
|
onClick={openCustomPageSizeModal}>
|
||||||
|
自定义每页条数
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
<Controls.Pagination />
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</SettingContentPanel>
|
</SettingContentPanel>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
opened={customPageSizeModalOpened}
|
||||||
|
onClose={closeCustomPageSizeModal}
|
||||||
|
title="添加每页条数"
|
||||||
|
centered>
|
||||||
|
<Stack gap="sm">
|
||||||
|
<TextInput
|
||||||
|
data-autofocus
|
||||||
|
label="每页条数"
|
||||||
|
placeholder="请输入大于 0 的整数,例如 99"
|
||||||
|
value={customPageSizeInput}
|
||||||
|
onChange={(event) =>
|
||||||
|
setCustomPageSizeInput(event.currentTarget.value)
|
||||||
|
}
|
||||||
|
onKeyDown={handleCustomPageSizeEnter}
|
||||||
|
/>
|
||||||
|
<Group justify="flex-end">
|
||||||
|
<Button variant="default" onClick={closeCustomPageSizeModal}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleAddCustomPageSize}>添加</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<WorkflowModal
|
<WorkflowModal
|
||||||
opened={workflowTaskId !== null}
|
opened={workflowTaskId !== null}
|
||||||
taskId={workflowTaskId}
|
taskId={workflowTaskId}
|
||||||
|
|||||||
@@ -80,6 +80,9 @@ const initialState: LabelState = new Map()
|
|||||||
const operationTypeList = [101, 102, 103, 104]
|
const operationTypeList = [101, 102, 103, 104]
|
||||||
let latestProjectDetailRequestSeq = 0
|
let latestProjectDetailRequestSeq = 0
|
||||||
let latestTaskDetailRequestSeq = 0
|
let latestTaskDetailRequestSeq = 0
|
||||||
|
const RIGHT_PANEL_TRANSITION =
|
||||||
|
"width 160ms ease, min-width 160ms ease, opacity 160ms ease"
|
||||||
|
const RIGHT_PANEL_WILL_CHANGE = "width, min-width, opacity"
|
||||||
|
|
||||||
// utils
|
// utils
|
||||||
const getRectPoints = ([sx, sy, w, h]: number[]) => {
|
const getRectPoints = ([sx, sy, w, h]: number[]) => {
|
||||||
@@ -95,6 +98,19 @@ const getSegmentPoints = (arr: Array<number[]>) => {
|
|||||||
return arr.map((item) => [item[0], item[1]])
|
return arr.map((item) => [item[0], item[1]])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getRightPanelViewportStyle = (width: number) => {
|
||||||
|
const stableWidth = Math.max(0, Math.round(width))
|
||||||
|
return {
|
||||||
|
position: "absolute" as const,
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: stableWidth,
|
||||||
|
minWidth: stableWidth,
|
||||||
|
height: "100%",
|
||||||
|
contain: "layout paint",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const videoExtPattern = /\.(mp4|mov|avi|mkv|webm|h264|264|ts|m4v)$/i
|
const videoExtPattern = /\.(mp4|mov|avi|mkv|webm|h264|264|ts|m4v)$/i
|
||||||
const GLOBAL_LOADING_Z_INDEX = 900
|
const GLOBAL_LOADING_Z_INDEX = 900
|
||||||
const videoMimeByExt: Record<string, string> = {
|
const videoMimeByExt: Record<string, string> = {
|
||||||
@@ -2170,7 +2186,13 @@ const LabelPage = ({
|
|||||||
|
|
||||||
if (e.altKey && e.key.toLowerCase() === "x") {
|
if (e.altKey && e.key.toLowerCase() === "x") {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (!isView) topRef.current?.commitTaskByActionKey?.()
|
if (!isView) {
|
||||||
|
if (topRef.current?.requestSubmitTaskConfirm) {
|
||||||
|
topRef.current.requestSubmitTaskConfirm()
|
||||||
|
} else {
|
||||||
|
topRef.current?.commitTaskByActionKey?.()
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2551,17 +2573,16 @@ const LabelPage = ({
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const mainBoxHeight = useMemo(() => {
|
const bottomToolsHeight = useMemo(() => {
|
||||||
if (projectDetail?.label_type === 5 && metaOperation) {
|
if (projectDetail?.label_type === 5 && metaOperation) {
|
||||||
let h = `calc(100% - ${headerHeight}px - 198px)`
|
return 128
|
||||||
return h
|
|
||||||
} else if (showMultiFrame) {
|
|
||||||
let h = `calc(100% - ${headerHeight}px - 150px)`
|
|
||||||
return h
|
|
||||||
}
|
}
|
||||||
let h = `calc(100% - ${headerHeight}px - 70px)`
|
return 80
|
||||||
return h
|
}, [metaOperation, projectDetail?.label_type])
|
||||||
}, [projectDetail?.label_type, metaOperation, showMultiFrame, headerHeight])
|
|
||||||
|
const mainBoxHeight = useMemo(() => {
|
||||||
|
return `calc(100% - ${headerHeight}px - ${70 + bottomToolsHeight}px)`
|
||||||
|
}, [bottomToolsHeight, headerHeight])
|
||||||
|
|
||||||
const selectedTagPreviewList = useMemo(() => {
|
const selectedTagPreviewList = useMemo(() => {
|
||||||
if (!showTags) return []
|
if (!showTags) return []
|
||||||
@@ -2741,48 +2762,139 @@ const LabelPage = ({
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
{showTaskList && (
|
<Box
|
||||||
<Box w={sizes[1]} miw={300} style={{ flexShrink: 0 }}>
|
w={sizes[1]}
|
||||||
|
miw={showTaskList ? 300 : 0}
|
||||||
|
style={{
|
||||||
|
position: "relative",
|
||||||
|
flexShrink: 0,
|
||||||
|
overflow: "hidden",
|
||||||
|
opacity: showTaskList ? 1 : 0,
|
||||||
|
visibility: showTaskList ? "visible" : "hidden",
|
||||||
|
pointerEvents: showTaskList ? "auto" : "none",
|
||||||
|
transition: RIGHT_PANEL_TRANSITION,
|
||||||
|
willChange: RIGHT_PANEL_WILL_CHANGE,
|
||||||
|
}}>
|
||||||
|
<Box style={getRightPanelViewportStyle(Math.max(sizes[1], 300))}>
|
||||||
<RightTaskTools
|
<RightTaskTools
|
||||||
projectDetail={projectDetail}
|
projectDetail={projectDetail}
|
||||||
project_id={projectId}
|
project_id={projectId}
|
||||||
task_id={taskId}
|
task_id={taskId}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
</Box>
|
||||||
{showGroupList && (
|
<Box
|
||||||
<Box w={sizes[2]} miw={200} style={{ flexShrink: 0 }}>
|
w={sizes[2]}
|
||||||
|
miw={showGroupList ? 200 : 0}
|
||||||
|
style={{
|
||||||
|
position: "relative",
|
||||||
|
flexShrink: 0,
|
||||||
|
overflow: "hidden",
|
||||||
|
opacity: showGroupList ? 1 : 0,
|
||||||
|
visibility: showGroupList ? "visible" : "hidden",
|
||||||
|
pointerEvents: showGroupList ? "auto" : "none",
|
||||||
|
transition: RIGHT_PANEL_TRANSITION,
|
||||||
|
willChange: RIGHT_PANEL_WILL_CHANGE,
|
||||||
|
}}>
|
||||||
|
<Box style={getRightPanelViewportStyle(Math.max(sizes[2], 200))}>
|
||||||
<RightGroupTools
|
<RightGroupTools
|
||||||
labelState={labelState}
|
labelState={labelState}
|
||||||
projectDetail={projectDetail}
|
projectDetail={projectDetail}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
</Box>
|
||||||
{showDescList && projectDetail && projectDetail.label_type === 5 && (
|
<Box
|
||||||
<Box w={sizes[3]} miw={350} style={{ flexShrink: 0 }}>
|
w={sizes[3]}
|
||||||
|
miw={
|
||||||
|
showDescList && projectDetail && projectDetail.label_type === 5
|
||||||
|
? 350
|
||||||
|
: 0
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
position: "relative",
|
||||||
|
flexShrink: 0,
|
||||||
|
overflow: "hidden",
|
||||||
|
opacity:
|
||||||
|
showDescList && projectDetail && projectDetail.label_type === 5
|
||||||
|
? 1
|
||||||
|
: 0,
|
||||||
|
visibility:
|
||||||
|
showDescList && projectDetail && projectDetail.label_type === 5
|
||||||
|
? "visible"
|
||||||
|
: "hidden",
|
||||||
|
pointerEvents:
|
||||||
|
showDescList && projectDetail && projectDetail.label_type === 5
|
||||||
|
? "auto"
|
||||||
|
: "none",
|
||||||
|
transition: RIGHT_PANEL_TRANSITION,
|
||||||
|
willChange: RIGHT_PANEL_WILL_CHANGE,
|
||||||
|
}}>
|
||||||
|
<Box style={getRightPanelViewportStyle(Math.max(sizes[3], 350))}>
|
||||||
<RightDescTools taskDetail={taskDetail} />
|
<RightDescTools taskDetail={taskDetail} />
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
</Box>
|
||||||
{showDescList && projectDetail && projectDetail.label_type === 6 && (
|
<Box
|
||||||
<Box w={sizes[4]} miw={350} style={{ flexShrink: 0 }}>
|
w={sizes[4]}
|
||||||
|
miw={
|
||||||
|
showDescList && projectDetail && projectDetail.label_type === 6
|
||||||
|
? 350
|
||||||
|
: 0
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
position: "relative",
|
||||||
|
flexShrink: 0,
|
||||||
|
overflow: "hidden",
|
||||||
|
opacity:
|
||||||
|
showDescList && projectDetail && projectDetail.label_type === 6
|
||||||
|
? 1
|
||||||
|
: 0,
|
||||||
|
visibility:
|
||||||
|
showDescList && projectDetail && projectDetail.label_type === 6
|
||||||
|
? "visible"
|
||||||
|
: "hidden",
|
||||||
|
pointerEvents:
|
||||||
|
showDescList && projectDetail && projectDetail.label_type === 6
|
||||||
|
? "auto"
|
||||||
|
: "none",
|
||||||
|
transition: RIGHT_PANEL_TRANSITION,
|
||||||
|
willChange: RIGHT_PANEL_WILL_CHANGE,
|
||||||
|
}}>
|
||||||
|
<Box style={getRightPanelViewportStyle(Math.max(sizes[4], 350))}>
|
||||||
<RightQATools ref={qaToolContainerRef} taskDetail={taskDetail} />
|
<RightQATools ref={qaToolContainerRef} taskDetail={taskDetail} />
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
</Box>
|
||||||
{showObjectList && (
|
<Box
|
||||||
<Box w={sizes[5]} miw={350} style={{ flexShrink: 0 }}>
|
w={sizes[5]}
|
||||||
|
miw={showObjectList ? 350 : 0}
|
||||||
|
style={{
|
||||||
|
position: "relative",
|
||||||
|
flexShrink: 0,
|
||||||
|
overflow: "hidden",
|
||||||
|
opacity: showObjectList ? 1 : 0,
|
||||||
|
visibility: showObjectList ? "visible" : "hidden",
|
||||||
|
pointerEvents: showObjectList ? "auto" : "none",
|
||||||
|
transition: RIGHT_PANEL_TRANSITION,
|
||||||
|
willChange: RIGHT_PANEL_WILL_CHANGE,
|
||||||
|
}}>
|
||||||
|
<Box style={getRightPanelViewportStyle(Math.max(sizes[5], 350))}>
|
||||||
<RightObjectTools
|
<RightObjectTools
|
||||||
taskDetail={taskDetail}
|
taskDetail={taskDetail}
|
||||||
labelState={labelState}
|
labelState={labelState}
|
||||||
projectDetail={projectDetail}
|
projectDetail={projectDetail}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
</Box>
|
||||||
{/* </Flex> */}
|
{/* </Flex> */}
|
||||||
</Flex>
|
</Flex>
|
||||||
<Box
|
<Box
|
||||||
w="100%"
|
w="100%"
|
||||||
h={projectDetail?.label_type === 5 && metaOperation ? 128 : 80}
|
h={bottomToolsHeight}
|
||||||
display={showMultiFrame ? "block" : "none"}>
|
style={{
|
||||||
|
overflow: "hidden",
|
||||||
|
opacity: showMultiFrame ? 1 : 0,
|
||||||
|
pointerEvents: showMultiFrame ? "auto" : "none",
|
||||||
|
transition: "opacity 160ms ease",
|
||||||
|
}}>
|
||||||
<BottomTools
|
<BottomTools
|
||||||
labelState={labelState}
|
labelState={labelState}
|
||||||
projectDetail={projectDetail}
|
projectDetail={projectDetail}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export const BASE_LABEL_API =
|
export const BASE_LABEL_API =
|
||||||
process.env.NEXT_PUBLIC_ENV === "production"
|
process.env.NEXT_PUBLIC_ENV === "production"
|
||||||
? "http://172.16.104.95:9110"
|
? "http://172.16.103.224:9110"
|
||||||
: process.env.NEXT_PUBLIC_ENV === "staging"
|
: process.env.NEXT_PUBLIC_ENV === "staging"
|
||||||
? "http://172.16.115.128:9110"
|
? "http://172.16.115.128:9110"
|
||||||
: "https://label.softtest.cowarobot.com"
|
: "https://label.softtest.cowarobot.com"
|
||||||
|
|||||||
@@ -120,9 +120,16 @@ export const getAuxiliaryAnnotation = (data: any) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const BASE_SAM_API =
|
||||||
|
process.env.NEXT_PUBLIC_ENV === "production"
|
||||||
|
? "http://172.16.103.224:9120"
|
||||||
|
: process.env.NEXT_PUBLIC_ENV === "staging"
|
||||||
|
? "http://172.30.21.211:8000"
|
||||||
|
: "http://172.30.21.211:8000"
|
||||||
|
|
||||||
export const getAuxiliaryAnnotation2 = (data: {
|
export const getAuxiliaryAnnotation2 = (data: {
|
||||||
project_id: string
|
project_id: number
|
||||||
image: string
|
image_name: string
|
||||||
prompt: {
|
prompt: {
|
||||||
foreground_points?: Array<[number, number]>
|
foreground_points?: Array<[number, number]>
|
||||||
background_points?: Array<[number, number]>
|
background_points?: Array<[number, number]>
|
||||||
@@ -133,7 +140,7 @@ export const getAuxiliaryAnnotation2 = (data: {
|
|||||||
contours: Array<Array<[[number, number]]>>
|
contours: Array<Array<[[number, number]]>>
|
||||||
hollows: Array<boolean>
|
hollows: Array<boolean>
|
||||||
}>({
|
}>({
|
||||||
url: "http://172.30.21.211:9000/image_predictor",
|
url: BASE_SAM_API + "/api/sam2/image_predictor",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -64,8 +64,11 @@ const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
|
|||||||
inputNumber,
|
inputNumber,
|
||||||
setInputNumber,
|
setInputNumber,
|
||||||
activeImage,
|
activeImage,
|
||||||
|
pendingImage,
|
||||||
|
frameSwitchStatus,
|
||||||
setActiveImage,
|
setActiveImage,
|
||||||
setActiveImageId,
|
setActiveImageId,
|
||||||
|
requestFrameSwitch,
|
||||||
onlyKeyFrame,
|
onlyKeyFrame,
|
||||||
setOnlyKeyFrame,
|
setOnlyKeyFrame,
|
||||||
keyFrameData,
|
keyFrameData,
|
||||||
@@ -168,24 +171,32 @@ const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
|
|||||||
if (index < 0 || index >= currentKeys.length) return false
|
if (index < 0 || index >= currentKeys.length) return false
|
||||||
|
|
||||||
const nextImage = currentKeys[index]
|
const nextImage = currentKeys[index]
|
||||||
const absoluteIndex = allKeys.findIndex((k) => k === nextImage)
|
if (!nextImage) return false
|
||||||
|
|
||||||
scrollToIndex(index)
|
scrollToIndex(index)
|
||||||
setInputNumber(String(index + 1))
|
if (
|
||||||
setActiveImageId(absoluteIndex >= 0 ? absoluteIndex + 1 : 0)
|
frameSwitchStatus === "preparing" &&
|
||||||
setActiveImage(nextImage)
|
pendingImage &&
|
||||||
|
pendingImage === nextImage
|
||||||
|
) {
|
||||||
|
closeRightContext()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (nextImage !== activeImage) {
|
||||||
|
requestFrameSwitch(nextImage)
|
||||||
|
}
|
||||||
closeRightContext()
|
closeRightContext()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
allKeys,
|
activeImage,
|
||||||
closeRightContext,
|
closeRightContext,
|
||||||
currentKeys,
|
currentKeys,
|
||||||
|
frameSwitchStatus,
|
||||||
|
pendingImage,
|
||||||
|
requestFrameSwitch,
|
||||||
scrollToIndex,
|
scrollToIndex,
|
||||||
setActiveImage,
|
|
||||||
setActiveImageId,
|
|
||||||
setInputNumber,
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -569,9 +580,14 @@ const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
|
|||||||
radius={"sm"}
|
radius={"sm"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
let flag = false
|
let flag = false
|
||||||
keyFrameData.entries().forEach(([key, value]) => {
|
keyFrameData
|
||||||
if (labelState.has(key) && value?.key_frame) flag = true
|
.entries()
|
||||||
})
|
.forEach(
|
||||||
|
([key, value]: [string, { key_frame?: boolean }]) => {
|
||||||
|
if (labelState.has(key) && value?.key_frame)
|
||||||
|
flag = true
|
||||||
|
}
|
||||||
|
)
|
||||||
if (!flag) {
|
if (!flag) {
|
||||||
showNotification({
|
showNotification({
|
||||||
title: "警告",
|
title: "警告",
|
||||||
@@ -711,6 +727,10 @@ const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
|
|||||||
<Flex gap={0} w="max-content" h="100%" align="center">
|
<Flex gap={0} w="max-content" h="100%" align="center">
|
||||||
{currentKeys.map((key, index) => {
|
{currentKeys.map((key, index) => {
|
||||||
const isActive = activeImage === key
|
const isActive = activeImage === key
|
||||||
|
const isPending =
|
||||||
|
frameSwitchStatus === "preparing" &&
|
||||||
|
pendingImage === key &&
|
||||||
|
!isActive
|
||||||
const isSelected =
|
const isSelected =
|
||||||
selectedItems.length && selectedItems.includes(key)
|
selectedItems.length && selectedItems.includes(key)
|
||||||
const isHighlight = isActive || isSelected
|
const isHighlight = isActive || isSelected
|
||||||
@@ -735,12 +755,17 @@ const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
|
|||||||
style={{
|
style={{
|
||||||
border: isHighlight
|
border: isHighlight
|
||||||
? "1px solid var(--mantine-color-blue-6)"
|
? "1px solid var(--mantine-color-blue-6)"
|
||||||
: "1px solid var(--mantine-color-gray-3)",
|
: isPending
|
||||||
|
? "1px dashed var(--mantine-color-blue-4)"
|
||||||
|
: "1px solid var(--mantine-color-gray-3)",
|
||||||
borderRadius: "6px",
|
borderRadius: "6px",
|
||||||
backgroundColor: isHighlight
|
backgroundColor: isHighlight
|
||||||
? "var(--mantine-color-blue-6)"
|
? "var(--mantine-color-blue-6)"
|
||||||
: undefined,
|
: isPending
|
||||||
|
? "rgba(34, 139, 230, 0.08)"
|
||||||
|
: undefined,
|
||||||
color: isHighlight ? "white" : undefined,
|
color: isHighlight ? "white" : undefined,
|
||||||
|
opacity: isPending ? 0.85 : 1,
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -158,7 +158,7 @@ const TopTools = (
|
|||||||
renderPolygons,
|
renderPolygons,
|
||||||
} = props
|
} = props
|
||||||
const { backUrl, basePath } = useBackUrlStore()
|
const { backUrl, basePath } = useBackUrlStore()
|
||||||
const { mode, loadingData } = usePaperStore()
|
const { mode, loadingData, requestResizeGhost } = usePaperStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const user_id = usePermissionStore.getState().user_id
|
const user_id = usePermissionStore.getState().user_id
|
||||||
|
|
||||||
@@ -169,6 +169,7 @@ const TopTools = (
|
|||||||
const [duplicateName, setDuplicateName] = useState("")
|
const [duplicateName, setDuplicateName] = useState("")
|
||||||
const [recoverOpen, setRecoverOpen] = useState(false)
|
const [recoverOpen, setRecoverOpen] = useState(false)
|
||||||
const [taskOpen, setTaskOpen] = useState(false)
|
const [taskOpen, setTaskOpen] = useState(false)
|
||||||
|
const [submitConfirmOpen, setSubmitConfirmOpen] = useState(false)
|
||||||
const [operationPickerOpened, setOperationPickerOpened] = useState(false)
|
const [operationPickerOpened, setOperationPickerOpened] = useState(false)
|
||||||
const [operationKeyword, setOperationKeyword] = useState("")
|
const [operationKeyword, setOperationKeyword] = useState("")
|
||||||
const labelData = useLabelStore((state) => state.label)
|
const labelData = useLabelStore((state) => state.label)
|
||||||
@@ -232,6 +233,32 @@ const TopTools = (
|
|||||||
const autoSaveGap = useIntervalStore((state) => state.autoSaveGap)
|
const autoSaveGap = useIntervalStore((state) => state.autoSaveGap)
|
||||||
const setAutoSaveGap = useIntervalStore((state) => state.setAutoSaveGap)
|
const setAutoSaveGap = useIntervalStore((state) => state.setAutoSaveGap)
|
||||||
|
|
||||||
|
const visibleRightPanelCount = useMemo(() => {
|
||||||
|
return [
|
||||||
|
showTaskList,
|
||||||
|
showGroupList,
|
||||||
|
showDescList &&
|
||||||
|
!!projectDetail &&
|
||||||
|
[5, 6].includes(projectDetail.label_type),
|
||||||
|
showObjectList,
|
||||||
|
].filter(Boolean).length
|
||||||
|
}, [projectDetail, showDescList, showGroupList, showObjectList, showTaskList])
|
||||||
|
|
||||||
|
const toggleRightPanel = useCallback(
|
||||||
|
(visible: boolean, setter: (val: boolean) => void) => {
|
||||||
|
const nextVisible = !visible
|
||||||
|
const nextCount = visibleRightPanelCount + (nextVisible ? 1 : -1)
|
||||||
|
if (
|
||||||
|
(visibleRightPanelCount === 0 && nextCount === 1) ||
|
||||||
|
(visibleRightPanelCount === 1 && nextCount === 0)
|
||||||
|
) {
|
||||||
|
requestResizeGhost()
|
||||||
|
}
|
||||||
|
setter(nextVisible)
|
||||||
|
},
|
||||||
|
[requestResizeGhost, visibleRightPanelCount]
|
||||||
|
)
|
||||||
|
|
||||||
// 当前是否可以操作功能按键
|
// 当前是否可以操作功能按键
|
||||||
const currentEditAuth = useMemo(() => {
|
const currentEditAuth = useMemo(() => {
|
||||||
if (!taskDetail) return false
|
if (!taskDetail) return false
|
||||||
@@ -1389,6 +1416,10 @@ const TopTools = (
|
|||||||
getNextTaskData()
|
getNextTaskData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const requestSubmitTaskConfirm = useCallback(() => {
|
||||||
|
setSubmitConfirmOpen(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (objectOperations.length) {
|
if (objectOperations.length) {
|
||||||
setActiveOperation(objectOperations[0].category_id.toString())
|
setActiveOperation(objectOperations[0].category_id.toString())
|
||||||
@@ -1645,6 +1676,7 @@ const TopTools = (
|
|||||||
handleSave,
|
handleSave,
|
||||||
handleBackup,
|
handleBackup,
|
||||||
commitTaskByActionKey,
|
commitTaskByActionKey,
|
||||||
|
requestSubmitTaskConfirm,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const renderEditIcon = useMemo(() => {
|
const renderEditIcon = useMemo(() => {
|
||||||
@@ -1900,7 +1932,7 @@ const TopTools = (
|
|||||||
disabled={isView}
|
disabled={isView}
|
||||||
style={{ cursor: isView ? "default" : "pointer" }}
|
style={{ cursor: isView ? "default" : "pointer" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!isView) commitTaskByActionKey()
|
if (!isView) requestSubmitTaskConfirm()
|
||||||
}}>
|
}}>
|
||||||
提交
|
提交
|
||||||
</UnstyledButton>
|
</UnstyledButton>
|
||||||
@@ -2432,7 +2464,7 @@ const TopTools = (
|
|||||||
c={!showTaskList ? "gray" : "var(--mantine-color-text)"}
|
c={!showTaskList ? "gray" : "var(--mantine-color-text)"}
|
||||||
style={{ width: "auto", display: "flex", gap: 4 }}
|
style={{ width: "auto", display: "flex", gap: 4 }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowTaskList(!showTaskList)
|
toggleRightPanel(showTaskList, setShowTaskList)
|
||||||
}}>
|
}}>
|
||||||
<ClipboardList style={{ width: 20, height: 20 }} />
|
<ClipboardList style={{ width: 20, height: 20 }} />
|
||||||
<Text size="xs">任务</Text>
|
<Text size="xs">任务</Text>
|
||||||
@@ -2452,7 +2484,7 @@ const TopTools = (
|
|||||||
c={!showGroupList ? "gray" : "var(--mantine-color-text)"}
|
c={!showGroupList ? "gray" : "var(--mantine-color-text)"}
|
||||||
style={{ width: "auto", display: "flex", gap: 4 }}
|
style={{ width: "auto", display: "flex", gap: 4 }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowGroupList(!showGroupList)
|
toggleRightPanel(showGroupList, setShowGroupList)
|
||||||
}}>
|
}}>
|
||||||
<GroupIcon style={{ width: 20, height: 20 }} />
|
<GroupIcon style={{ width: 20, height: 20 }} />
|
||||||
<Text size="xs">群组</Text>
|
<Text size="xs">群组</Text>
|
||||||
@@ -2463,7 +2495,7 @@ const TopTools = (
|
|||||||
c={!showDescList ? "gray" : "var(--mantine-color-text)"}
|
c={!showDescList ? "gray" : "var(--mantine-color-text)"}
|
||||||
style={{ width: "auto", display: "flex", gap: 4 }}
|
style={{ width: "auto", display: "flex", gap: 4 }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowDescList(!showDescList)
|
toggleRightPanel(showDescList, setShowDescList)
|
||||||
}}>
|
}}>
|
||||||
<BadgeInfoIcon style={{ width: 20, height: 20 }} />
|
<BadgeInfoIcon style={{ width: 20, height: 20 }} />
|
||||||
<Text size="xs">描述</Text>
|
<Text size="xs">描述</Text>
|
||||||
@@ -2474,7 +2506,7 @@ const TopTools = (
|
|||||||
c={!showObjectList ? "gray" : "var(--mantine-color-text)"}
|
c={!showObjectList ? "gray" : "var(--mantine-color-text)"}
|
||||||
style={{ width: "auto", display: "flex", gap: 4 }}
|
style={{ width: "auto", display: "flex", gap: 4 }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowObjectList(!showObjectList)
|
toggleRightPanel(showObjectList, setShowObjectList)
|
||||||
}}>
|
}}>
|
||||||
<Component style={{ width: 20, height: 20 }} />
|
<Component style={{ width: 20, height: 20 }} />
|
||||||
<Text size="xs">对象</Text>
|
<Text size="xs">对象</Text>
|
||||||
@@ -2528,6 +2560,20 @@ const TopTools = (
|
|||||||
handleOk={handleBackup}
|
handleOk={handleBackup}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{submitConfirmOpen && (
|
||||||
|
<ConfirmModal
|
||||||
|
open={submitConfirmOpen}
|
||||||
|
title="提交任务"
|
||||||
|
content="是否确定提交当前任务?"
|
||||||
|
onOk={async () => {
|
||||||
|
setSubmitConfirmOpen(false)
|
||||||
|
await commitTaskByActionKey()
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
setSubmitConfirmOpen(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{duplicateConfirmOpen && (
|
{duplicateConfirmOpen && (
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
open={duplicateConfirmOpen}
|
open={duplicateConfirmOpen}
|
||||||
|
|||||||
56
components/label/config/performance.ts
Normal file
56
components/label/config/performance.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
const parseEnvInt = (
|
||||||
|
value: string | undefined,
|
||||||
|
fallback: number,
|
||||||
|
min: number,
|
||||||
|
max: number
|
||||||
|
) => {
|
||||||
|
if (!value) return fallback
|
||||||
|
const parsed = Number.parseInt(value, 10)
|
||||||
|
if (!Number.isFinite(parsed)) return fallback
|
||||||
|
return Math.min(max, Math.max(min, parsed))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const labelimagePerformanceConfig = {
|
||||||
|
// 单次渲染的路径切片大小。越小越丝滑,越大越快完成。
|
||||||
|
polygonRenderBatchSize: parseEnvInt(
|
||||||
|
process.env.NEXT_PUBLIC_LABELIMAGE_POLYGON_BATCH_SIZE,
|
||||||
|
12,
|
||||||
|
1,
|
||||||
|
200
|
||||||
|
),
|
||||||
|
// 每帧渲染预算(毫秒)。越小越不卡操作,越大越快完成绘制。
|
||||||
|
polygonRenderBatchBudgetMs: parseEnvInt(
|
||||||
|
process.env.NEXT_PUBLIC_LABELIMAGE_POLYGON_BATCH_BUDGET_MS,
|
||||||
|
6,
|
||||||
|
1,
|
||||||
|
33
|
||||||
|
),
|
||||||
|
// 切帧准备超过该阈值后才显示轻量提示,避免短切换频繁闪动提示。
|
||||||
|
frameSwitchLoadingDelayMs: parseEnvInt(
|
||||||
|
process.env.NEXT_PUBLIC_LABELIMAGE_FRAME_SWITCH_LOADING_DELAY_MS,
|
||||||
|
120,
|
||||||
|
0,
|
||||||
|
1000
|
||||||
|
),
|
||||||
|
// 切帧时新旧图层的交叉淡入时长(毫秒)。设为 0 可关闭淡入。
|
||||||
|
rasterCrossfadeDurationMs: parseEnvInt(
|
||||||
|
process.env.NEXT_PUBLIC_LABELIMAGE_RASTER_CROSSFADE_MS,
|
||||||
|
90,
|
||||||
|
0,
|
||||||
|
400
|
||||||
|
),
|
||||||
|
// 容器尺寸连续变化时,延迟提交 canvas 实际宽高,减少频繁重置导致的白闪。
|
||||||
|
resizeCommitDebounceMs: parseEnvInt(
|
||||||
|
process.env.NEXT_PUBLIC_LABELIMAGE_RESIZE_COMMIT_DEBOUNCE_MS,
|
||||||
|
90,
|
||||||
|
0,
|
||||||
|
500
|
||||||
|
),
|
||||||
|
// 容器尺寸变更后的视觉缓动时长(毫秒),用于降低“跳变感”。
|
||||||
|
resizeInterpolationDurationMs: parseEnvInt(
|
||||||
|
process.env.NEXT_PUBLIC_LABELIMAGE_RESIZE_INTERPOLATION_MS,
|
||||||
|
100,
|
||||||
|
0,
|
||||||
|
400
|
||||||
|
),
|
||||||
|
} as const
|
||||||
@@ -13,6 +13,12 @@ interface KeyFrameData {
|
|||||||
interface BottomToolsState {
|
interface BottomToolsState {
|
||||||
activeImage: string
|
activeImage: string
|
||||||
setActiveImage: (val: string) => void
|
setActiveImage: (val: string) => void
|
||||||
|
pendingImage: string
|
||||||
|
frameSwitchStatus: "idle" | "preparing"
|
||||||
|
frameSwitchToken: number
|
||||||
|
requestFrameSwitch: (val: string) => number
|
||||||
|
commitFrameSwitch: (val: string, token: number) => void
|
||||||
|
clearPendingFrameSwitch: (token?: number) => void
|
||||||
inputNumber: string
|
inputNumber: string
|
||||||
setInputNumber: (val: string) => void
|
setInputNumber: (val: string) => void
|
||||||
activeImageId: number
|
activeImageId: number
|
||||||
@@ -36,13 +42,48 @@ const initialBottomToolsState = {
|
|||||||
inputNumber: "",
|
inputNumber: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useBottomToolsStore = create<BottomToolsState>((set) => ({
|
export const useBottomToolsStore = create<BottomToolsState>()((set, get) => ({
|
||||||
activeImage: initialBottomToolsState.activeImage,
|
activeImage: initialBottomToolsState.activeImage,
|
||||||
setActiveImage: (val: string) =>
|
setActiveImage: (val: string) =>
|
||||||
set((state: BottomToolsState) => ({
|
set((state: BottomToolsState) => ({
|
||||||
...state,
|
...state,
|
||||||
activeImage: val,
|
activeImage: val,
|
||||||
|
pendingImage: "",
|
||||||
|
frameSwitchStatus: "idle",
|
||||||
})),
|
})),
|
||||||
|
pendingImage: "",
|
||||||
|
frameSwitchStatus: "idle",
|
||||||
|
frameSwitchToken: 0,
|
||||||
|
requestFrameSwitch: (val: string): number => {
|
||||||
|
const nextToken = get().frameSwitchToken + 1
|
||||||
|
set((state: BottomToolsState) => ({
|
||||||
|
...state,
|
||||||
|
pendingImage: val,
|
||||||
|
frameSwitchStatus:
|
||||||
|
val && val !== state.activeImage ? "preparing" : "idle",
|
||||||
|
frameSwitchToken: nextToken,
|
||||||
|
}))
|
||||||
|
return nextToken
|
||||||
|
},
|
||||||
|
commitFrameSwitch: (val: string, token: number) =>
|
||||||
|
set((state: BottomToolsState) => {
|
||||||
|
if (state.frameSwitchToken !== token) return state
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activeImage: val,
|
||||||
|
pendingImage: "",
|
||||||
|
frameSwitchStatus: "idle",
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
clearPendingFrameSwitch: (token?: number) =>
|
||||||
|
set((state: BottomToolsState) => {
|
||||||
|
if (token != null && state.frameSwitchToken !== token) return state
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
pendingImage: "",
|
||||||
|
frameSwitchStatus: "idle",
|
||||||
|
}
|
||||||
|
}),
|
||||||
inputNumber: initialBottomToolsState.inputNumber,
|
inputNumber: initialBottomToolsState.inputNumber,
|
||||||
setInputNumber: (val: string) =>
|
setInputNumber: (val: string) =>
|
||||||
set((state: BottomToolsState) => ({
|
set((state: BottomToolsState) => ({
|
||||||
@@ -63,7 +104,7 @@ export const useBottomToolsStore = create<BottomToolsState>((set) => ({
|
|||||||
})),
|
})),
|
||||||
keyFrameData: new Map(),
|
keyFrameData: new Map(),
|
||||||
setKeyFrameData: (imageId, value) => {
|
setKeyFrameData: (imageId, value) => {
|
||||||
const data = useBottomToolsStore.getState().keyFrameData
|
const data = get().keyFrameData
|
||||||
data.set(imageId, value)
|
data.set(imageId, value)
|
||||||
return set((state) => ({
|
return set((state) => ({
|
||||||
...state,
|
...state,
|
||||||
@@ -78,15 +119,13 @@ export const useBottomToolsStore = create<BottomToolsState>((set) => ({
|
|||||||
})),
|
})),
|
||||||
selectedItems: [],
|
selectedItems: [],
|
||||||
setSelectedItems: (id, ctrlKey, shiftKey) => {
|
setSelectedItems: (id, ctrlKey, shiftKey) => {
|
||||||
let arr = [...useBottomToolsStore.getState().selectedItems]
|
let arr = [...get().selectedItems]
|
||||||
if ((ctrlKey && shiftKey) || (!ctrlKey && !shiftKey)) arr = []
|
if ((ctrlKey && shiftKey) || (!ctrlKey && !shiftKey)) arr = []
|
||||||
else if (ctrlKey && !shiftKey) {
|
else if (ctrlKey && !shiftKey) {
|
||||||
arr.push(id)
|
arr.push(id)
|
||||||
} else if (!ctrlKey && shiftKey) {
|
} else if (!ctrlKey && shiftKey) {
|
||||||
const allItems = useBottomToolsStore.getState().allItems
|
const allItems = get().allItems
|
||||||
const prevId = arr.length
|
const prevId = arr.length ? arr[arr.length - 1] : get().activeImage
|
||||||
? arr[arr.length - 1]
|
|
||||||
: useBottomToolsStore.getState().activeImage
|
|
||||||
const prevIndex = allItems.findIndex((v) => v === prevId)
|
const prevIndex = allItems.findIndex((v) => v === prevId)
|
||||||
const currentIndex = allItems.findIndex((v) => v === id)
|
const currentIndex = allItems.findIndex((v) => v === id)
|
||||||
arr =
|
arr =
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ interface PaperState {
|
|||||||
initScope: paper.PaperScope,
|
initScope: paper.PaperScope,
|
||||||
initGroup: paper.Group
|
initGroup: paper.Group
|
||||||
) => void
|
) => void
|
||||||
|
setGroup: (group: paper.Group | null) => void
|
||||||
setRasterPath: (paperRaster: paper.Path) => void
|
setRasterPath: (paperRaster: paper.Path) => void
|
||||||
setRasterScale: (id: string, scale: number) => void
|
setRasterScale: (id: string, scale: number) => void
|
||||||
setRasterSize: (id: string, size: [number, number]) => void
|
setRasterSize: (id: string, size: [number, number]) => void
|
||||||
@@ -180,6 +181,8 @@ interface PaperState {
|
|||||||
getItemsById: (id: number) => Array<paper.Path | paper.CompoundPath>
|
getItemsById: (id: number) => Array<paper.Path | paper.CompoundPath>
|
||||||
setContinuePolygonTarget: (target: ContinuePolygonTarget | null) => void
|
setContinuePolygonTarget: (target: ContinuePolygonTarget | null) => void
|
||||||
setLoadingData: (flag: boolean) => void
|
setLoadingData: (flag: boolean) => void
|
||||||
|
resizeGhostRequestToken: number
|
||||||
|
requestResizeGhost: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PositionStore {
|
interface PositionStore {
|
||||||
@@ -208,6 +211,7 @@ const initialPaperState = {
|
|||||||
rasterScale: {},
|
rasterScale: {},
|
||||||
rasterSize: {},
|
rasterSize: {},
|
||||||
reciprocalRasterScale: {},
|
reciprocalRasterScale: {},
|
||||||
|
resizeGhostRequestToken: 0,
|
||||||
}
|
}
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
const group = usePaperStore.getState().group!
|
const group = usePaperStore.getState().group!
|
||||||
@@ -529,6 +533,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
|||||||
rasterScale: initialPaperState.rasterScale,
|
rasterScale: initialPaperState.rasterScale,
|
||||||
rasterSize: initialPaperState.rasterSize,
|
rasterSize: initialPaperState.rasterSize,
|
||||||
reciprocalRasterScale: initialPaperState.reciprocalRasterScale,
|
reciprocalRasterScale: initialPaperState.reciprocalRasterScale,
|
||||||
|
resizeGhostRequestToken: initialPaperState.resizeGhostRequestToken,
|
||||||
continuePolygonTarget: null,
|
continuePolygonTarget: null,
|
||||||
currentMousePosition: [0, 0],
|
currentMousePosition: [0, 0],
|
||||||
loadingData: false,
|
loadingData: false,
|
||||||
@@ -544,6 +549,12 @@ export const usePaperStore = create<PaperState>((set) => ({
|
|||||||
el: initEl,
|
el: initEl,
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
|
setGroup: (group) => {
|
||||||
|
set((state: PaperState) => ({
|
||||||
|
...state,
|
||||||
|
group,
|
||||||
|
}))
|
||||||
|
},
|
||||||
setRasterPath: (rasterPath) => {
|
setRasterPath: (rasterPath) => {
|
||||||
set((state: PaperState) => ({
|
set((state: PaperState) => ({
|
||||||
...state,
|
...state,
|
||||||
@@ -577,6 +588,11 @@ export const usePaperStore = create<PaperState>((set) => ({
|
|||||||
...state,
|
...state,
|
||||||
loadingData: flag,
|
loadingData: flag,
|
||||||
})),
|
})),
|
||||||
|
requestResizeGhost: () =>
|
||||||
|
set((state: PaperState) => ({
|
||||||
|
...state,
|
||||||
|
resizeGhostRequestToken: state.resizeGhostRequestToken + 1,
|
||||||
|
})),
|
||||||
setContinuePolygonTarget: (target) =>
|
setContinuePolygonTarget: (target) =>
|
||||||
set((state: PaperState) => ({
|
set((state: PaperState) => ({
|
||||||
...state,
|
...state,
|
||||||
@@ -5328,6 +5344,15 @@ export const usePaperStore = create<PaperState>((set) => ({
|
|||||||
}) as paper.Item[]
|
}) as paper.Item[]
|
||||||
tagItems.forEach((item) => {
|
tagItems.forEach((item) => {
|
||||||
if (item.data?.tag_category === "mask") return
|
if (item.data?.tag_category === "mask") return
|
||||||
|
const anchor = item.data?.tag_anchor
|
||||||
|
if (Array.isArray(anchor) && anchor.length >= 2) {
|
||||||
|
const anchorX = Number(anchor[0])
|
||||||
|
const anchorY = Number(anchor[1])
|
||||||
|
if (Number.isFinite(anchorX) && Number.isFinite(anchorY)) {
|
||||||
|
item.scale(1 / newScale, new paper.Point(anchorX, anchorY))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
item.scale(1 / newScale)
|
item.scale(1 / newScale)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,20 @@ const useRenderTag = () => {
|
|||||||
const dataMap = useLabelStore.getState().label
|
const dataMap = useLabelStore.getState().label
|
||||||
const activeImage = useBottomToolsStore.getState().activeImage
|
const activeImage = useBottomToolsStore.getState().activeImage
|
||||||
const group = usePaperStore.getState().group
|
const group = usePaperStore.getState().group
|
||||||
|
if (!group) return
|
||||||
const visible = useTopToolsStore.getState().showTags
|
const visible = useTopToolsStore.getState().showTags
|
||||||
const configs = useTopToolsStore.getState().showTagsConfigs
|
const configs = useTopToolsStore.getState().showTagsConfigs
|
||||||
|
const groupScale =
|
||||||
|
Number.isFinite(group.scaling?.x) && group.scaling.x !== 0
|
||||||
|
? group.scaling.x
|
||||||
|
: 1
|
||||||
|
const applyTagCompensation = (
|
||||||
|
item: paper.Item,
|
||||||
|
anchor: [number, number]
|
||||||
|
) => {
|
||||||
|
if (Math.abs(groupScale - 1) < 0.000001) return
|
||||||
|
item.scale(1 / groupScale, new paper.Point(anchor[0], anchor[1]))
|
||||||
|
}
|
||||||
|
|
||||||
// 清空之前渲染的标签
|
// 清空之前渲染的标签
|
||||||
removeTagTexts()
|
removeTagTexts()
|
||||||
@@ -38,7 +50,8 @@ const useRenderTag = () => {
|
|||||||
const path = usePaperStore.getState().getItemById(id)
|
const path = usePaperStore.getState().getItemById(id)
|
||||||
if (path) {
|
if (path) {
|
||||||
const bounds = path.bounds
|
const bounds = path.bounds
|
||||||
let pos = [bounds.left, bounds.top - 6]
|
const tagAnchor: [number, number] = [bounds.left, bounds.top - 6]
|
||||||
|
const pos = [...tagAnchor]
|
||||||
let pathData = path.data
|
let pathData = path.data
|
||||||
let content = ``
|
let content = ``
|
||||||
if (configs.includes("ID"))
|
if (configs.includes("ID"))
|
||||||
@@ -68,10 +81,12 @@ const useRenderTag = () => {
|
|||||||
id,
|
id,
|
||||||
type: "tag",
|
type: "tag",
|
||||||
tag_category: "text",
|
tag_category: "text",
|
||||||
|
tag_anchor: tagAnchor,
|
||||||
},
|
},
|
||||||
parent: group,
|
parent: group,
|
||||||
visible,
|
visible,
|
||||||
})
|
})
|
||||||
|
applyTagCompensation(text, tagAnchor)
|
||||||
|
|
||||||
// 文本框
|
// 文本框
|
||||||
const textMask = new paper.Path.Rectangle(text.bounds)
|
const textMask = new paper.Path.Rectangle(text.bounds)
|
||||||
@@ -84,10 +99,12 @@ const useRenderTag = () => {
|
|||||||
id,
|
id,
|
||||||
type: "tag",
|
type: "tag",
|
||||||
tag_category: "text_mask",
|
tag_category: "text_mask",
|
||||||
|
tag_anchor: tagAnchor,
|
||||||
},
|
},
|
||||||
parent: group,
|
parent: group,
|
||||||
visible,
|
visible,
|
||||||
})
|
})
|
||||||
|
applyTagCompensation(textMask, tagAnchor)
|
||||||
// 标注对象外接框
|
// 标注对象外接框
|
||||||
const mask = new paper.Path.Rectangle(bounds)
|
const mask = new paper.Path.Rectangle(bounds)
|
||||||
mask.set({
|
mask.set({
|
||||||
@@ -108,7 +125,7 @@ const useRenderTag = () => {
|
|||||||
path.children.forEach((child) => {
|
path.children.forEach((child) => {
|
||||||
const segments = (child as paper.Path).segments
|
const segments = (child as paper.Path).segments
|
||||||
segments.forEach((segment) => {
|
segments.forEach((segment) => {
|
||||||
let pointTextPosition = [
|
const pointTextPosition: [number, number] = [
|
||||||
segment.point.x - 10,
|
segment.point.x - 10,
|
||||||
segment.point.y + 5,
|
segment.point.y + 5,
|
||||||
]
|
]
|
||||||
@@ -122,17 +139,19 @@ const useRenderTag = () => {
|
|||||||
id,
|
id,
|
||||||
type: "tag",
|
type: "tag",
|
||||||
tag_category: "point_text",
|
tag_category: "point_text",
|
||||||
|
tag_anchor: pointTextPosition,
|
||||||
},
|
},
|
||||||
parent: group,
|
parent: group,
|
||||||
visible: visible && configs.includes("点ID"),
|
visible: visible && configs.includes("点ID"),
|
||||||
})
|
})
|
||||||
|
applyTagCompensation(pointText, pointTextPosition)
|
||||||
index++
|
index++
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const segments = (path as paper.Path).segments
|
const segments = (path as paper.Path).segments
|
||||||
segments.forEach((segment, index) => {
|
segments.forEach((segment, index) => {
|
||||||
let pointTextPosition = [
|
const pointTextPosition: [number, number] = [
|
||||||
segment.point.x - 10,
|
segment.point.x - 10,
|
||||||
segment.point.y + 5,
|
segment.point.y + 5,
|
||||||
]
|
]
|
||||||
@@ -146,10 +165,12 @@ const useRenderTag = () => {
|
|||||||
id,
|
id,
|
||||||
type: "tag",
|
type: "tag",
|
||||||
tag_category: "point_text",
|
tag_category: "point_text",
|
||||||
|
tag_anchor: pointTextPosition,
|
||||||
},
|
},
|
||||||
parent: group,
|
parent: group,
|
||||||
visible: visible && configs.includes("点ID"),
|
visible: visible && configs.includes("点ID"),
|
||||||
})
|
})
|
||||||
|
applyTagCompensation(pointText, pointTextPosition)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,20 +11,14 @@ import {
|
|||||||
ScrollArea,
|
ScrollArea,
|
||||||
Space,
|
Space,
|
||||||
Stack,
|
Stack,
|
||||||
|
Text,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
UnstyledButton,
|
UnstyledButton,
|
||||||
useComputedColorScheme,
|
useComputedColorScheme,
|
||||||
useMantineColorScheme,
|
useMantineColorScheme,
|
||||||
} from "@mantine/core"
|
} from "@mantine/core"
|
||||||
import { useDisclosure, useMediaQuery } from "@mantine/hooks"
|
import { useDisclosure, useMediaQuery } from "@mantine/hooks"
|
||||||
import {
|
import { IconLogout, IconMoon, IconSun, IconUser } from "@tabler/icons-react"
|
||||||
IconLogout,
|
|
||||||
IconMessageCircle,
|
|
||||||
IconMoon,
|
|
||||||
IconSettings,
|
|
||||||
IconSun,
|
|
||||||
IconUser,
|
|
||||||
} from "@tabler/icons-react"
|
|
||||||
import cx from "clsx"
|
import cx from "clsx"
|
||||||
import { Suspense, useEffect, useMemo, useState } from "react"
|
import { Suspense, useEffect, useMemo, useState } from "react"
|
||||||
import actionToggleClasses from "./ActionToggle.module.css"
|
import actionToggleClasses from "./ActionToggle.module.css"
|
||||||
@@ -32,6 +26,7 @@ import headerLinkClasses from "./HeaderLink.module.css"
|
|||||||
|
|
||||||
import { useUserStore } from "@/app/store/user"
|
import { useUserStore } from "@/app/store/user"
|
||||||
import { usePathname, useRouter } from "next/navigation"
|
import { usePathname, useRouter } from "next/navigation"
|
||||||
|
import { usePermissionStore } from "../label/store/auth"
|
||||||
import { deleteCookie } from "../login/api"
|
import { deleteCookie } from "../login/api"
|
||||||
import { MenuItem, withoutLayoutRoutes } from "./common"
|
import { MenuItem, withoutLayoutRoutes } from "./common"
|
||||||
import { ClientIcon } from "./components/ClientIcon"
|
import { ClientIcon } from "./components/ClientIcon"
|
||||||
@@ -147,6 +142,7 @@ export default function AppLayout({
|
|||||||
window.location.reload()
|
window.location.reload()
|
||||||
}
|
}
|
||||||
const matches = useMediaQuery("(min-width: 48em)")
|
const matches = useMediaQuery("(min-width: 48em)")
|
||||||
|
const userName = usePermissionStore((s) => s.user_name)
|
||||||
|
|
||||||
return isClient ? (
|
return isClient ? (
|
||||||
withoutLayout ? (
|
withoutLayout ? (
|
||||||
@@ -199,7 +195,7 @@ export default function AppLayout({
|
|||||||
/>
|
/>
|
||||||
</UnstyledButton>
|
</UnstyledButton>
|
||||||
<Divider orientation="vertical" mx={"0.5rem"} display="none" />
|
<Divider orientation="vertical" mx={"0.5rem"} display="none" />
|
||||||
<Menu shadow="md" width={200}>
|
<Menu shadow="md" width={220}>
|
||||||
<Menu.Target>
|
<Menu.Target>
|
||||||
<UnstyledButton>
|
<UnstyledButton>
|
||||||
<IconUser
|
<IconUser
|
||||||
@@ -210,13 +206,12 @@ export default function AppLayout({
|
|||||||
</UnstyledButton>
|
</UnstyledButton>
|
||||||
</Menu.Target>
|
</Menu.Target>
|
||||||
<Menu.Dropdown>
|
<Menu.Dropdown>
|
||||||
<Menu.Label>个人信息</Menu.Label>
|
<Menu.Label>用户信息</Menu.Label>
|
||||||
<Menu.Item leftSection={<IconSettings size={14} />}>
|
<Box px="sm" py={4}>
|
||||||
设置
|
<Text size="sm" fw={500} style={{ wordBreak: "break-all" }}>
|
||||||
</Menu.Item>
|
{userName || "-"}
|
||||||
<Menu.Item leftSection={<IconMessageCircle size={14} />}>
|
</Text>
|
||||||
通知
|
</Box>
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Divider />
|
<Menu.Divider />
|
||||||
<Menu.Label>操作</Menu.Label>
|
<Menu.Label>操作</Menu.Label>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Login } from "./types"
|
|||||||
|
|
||||||
const BASE_PATH =
|
const BASE_PATH =
|
||||||
process.env.NEXT_PUBLIC_ENV === "production"
|
process.env.NEXT_PUBLIC_ENV === "production"
|
||||||
? "http://172.16.104.95:9110"
|
? "http://172.16.103.224:9110"
|
||||||
: process.env.NEXT_PUBLIC_ENV === "staging"
|
: process.env.NEXT_PUBLIC_ENV === "staging"
|
||||||
? "http://172.16.115.128:9110"
|
? "http://172.16.115.128:9110"
|
||||||
: "https://label.softtest.cowarobot.com"
|
: "https://label.softtest.cowarobot.com"
|
||||||
|
|||||||
Reference in New Issue
Block a user