From 85cd64694638e17efee32fdd841b9be7dd281dbf Mon Sep 17 00:00:00 2001 From: lijingchi Date: Tue, 31 May 2022 20:24:56 +0800 Subject: [PATCH] feat: [lb-component] Add TagToolInstanceAdaptor to implement toolInstance --- .../src/components/videoAnnotate/index.tsx | 42 ++++- .../videoPlayer/TagToolInstanceAdaptor.tsx | 177 ++++++++++++++++++ .../components/videoPlayer/VideoTagLayer.tsx | 77 ++++++++ .../src/components/videoPlayer/index.tsx | 18 +- packages/lb-components/src/store/Actions.ts | 1 + .../src/store/annotation/reducer.ts | 19 +- .../MainView/sidebar/TagSidebar/index.tsx | 1 + .../src/views/MainView/sidebar/index.tsx | 5 +- 8 files changed, 322 insertions(+), 18 deletions(-) create mode 100644 packages/lb-components/src/components/videoPlayer/TagToolInstanceAdaptor.tsx create mode 100644 packages/lb-components/src/components/videoPlayer/VideoTagLayer.tsx diff --git a/packages/lb-components/src/components/videoAnnotate/index.tsx b/packages/lb-components/src/components/videoAnnotate/index.tsx index bb6caae79..4f01a3781 100644 --- a/packages/lb-components/src/components/videoAnnotate/index.tsx +++ b/packages/lb-components/src/components/videoAnnotate/index.tsx @@ -1,22 +1,46 @@ import React from 'react'; -import VideoPlayer from '@/components/VideoPlayer'; import { connect, useDispatch } from 'react-redux'; import { AppState } from '@/store'; import { AnnotationState } from '@/store/annotation/types'; import { PageBackward, PageForward, PageJump } from '@/store/annotation/actionCreators'; +import { ANNOTATION_ACTIONS } from '@/store/Actions'; +import { TagToolInstanceAdaptor } from '../VideoPlayer/TagToolInstanceAdaptor'; const VideoAnnotate: React.FC<{ annotation: AnnotationState }> = (props) => { - const { imgList, imgIndex } = props.annotation; + const { imgList, imgIndex, stepList, step } = props.annotation; const dispatch = useDispatch(); + const onMounted = (instance: TagToolInstanceAdaptor) => { + dispatch({ + type: ANNOTATION_ACTIONS.SET_TOOL, + payload: { + instance, + }, + }); + }; + + const onUnmounted = () => { + dispatch({ + type: ANNOTATION_ACTIONS.SET_TOOL, + payload: { + instance: undefined, + }, + }); + }; return ( - dispatch(PageBackward())} - pageForward={() => dispatch(PageForward())} - pageJump={(page) => dispatch(PageJump(page))} - /> + <> + dispatch(PageBackward())} + pageForward={() => dispatch(PageForward())} + pageJump={(page) => dispatch(PageJump(page))} + onMounted={onMounted} + onUnmounted={onUnmounted} + stepList={stepList} + step={step} + /> + ); }; diff --git a/packages/lb-components/src/components/videoPlayer/TagToolInstanceAdaptor.tsx b/packages/lb-components/src/components/videoPlayer/TagToolInstanceAdaptor.tsx new file mode 100644 index 000000000..01986f3a7 --- /dev/null +++ b/packages/lb-components/src/components/videoPlayer/TagToolInstanceAdaptor.tsx @@ -0,0 +1,177 @@ +/** + * @file 视频标签工具实现标签工具的方法 + * @author lijingchi + * @date 2022-05-31 + */ + +import React from 'react'; +import { CommonToolUtils, uuid } from '@labelbee/lb-annotation'; +import StepUtils from '@/utils/StepUtils'; +import { jsonParser } from '@/utils'; +import { VideoPlayer } from './index'; +import { VideoTagLayer } from './VideoTagLayer'; +import { IStepInfo } from '@/types/step'; + +export interface IZProp { + imgIndex: number; + imgList: any[]; + pageForward: () => void; + pageJump: (page: number) => void; + pageBackward: () => void; + onMounted: (instance: TagToolInstanceAdaptor) => void; + onUnmounted: () => void; + step: number; + stepList: IStepInfo[]; +} +export interface IZState { + tagResult: any[]; + labelSelectedList: any; +} + +export class TagToolInstanceAdaptor extends React.Component { + constructor(props: IZProp) { + super(props); + this.state = { + tagResult: [], + labelSelectedList: [], + }; + } + + get config() { + const stepInfo = StepUtils.getCurrentStepInfo(this.props.step, this.props.stepList); + return jsonParser(stepInfo?.config); + } + + // TODO 标签记录 + get labelSelectedList() { + return []; + } + + get history() { + return { initRecord: () => {} }; + } + + get currentTagResult() { + return this.state.tagResult; + } + + set labelSelectedList(labelSelectedList: any) { + this.setState({ + labelSelectedList, + }); + } + + public clearResult = (sendMsg: boolean, value?: string) => { + const newTag = value + ? this.state.tagResult.map((v) => { + if (v?.result[value]) { + delete v.result[value]; + } + return v; + }) + : []; + + this.setState({ + tagResult: newTag, + }); + }; + + public exportData = () => { + return [this.state.tagResult, { valid: true }]; + }; + + public singleOn() {} + + public getTagResultByCode(num1: number, num2?: number) { + try { + const inputList = this.config?.inputList ?? []; + const mulitTags = inputList.length > 1; + const keycode1 = num2 !== undefined ? num1 : 0; + const keycode2 = num2 !== undefined ? num2 : num1; + const primaryTagConfig = mulitTags ? inputList[keycode1] : inputList[0]; + const secondaryTagConfig = (primaryTagConfig.subSelected ?? [])[keycode2]; + + if (primaryTagConfig && secondaryTagConfig) { + return { + value: { + key: primaryTagConfig.value, + value: secondaryTagConfig.value, + }, + isMulti: primaryTagConfig.isMulti, + }; + } + } catch { + return; + } + } + + public setLabelBySelectedList(num1: number, num2?: number) { + const newTagConfig = this.getTagResultByCode(num1, num2); + const currentRes = this.state.tagResult.length > 0 ? this.state.tagResult[0].result : {}; + + if (newTagConfig) { + const inputValue = { [newTagConfig.value.key]: newTagConfig.value.value }; + // todo: 合并输入逻辑 + const tagRes = newTagConfig.isMulti ? Object.assign(currentRes, inputValue) : inputValue; + + const tagResult = [ + { + sourceID: CommonToolUtils.getSourceID(), + id: uuid(8, 62), + result: tagRes, + }, + ]; + + this.setState({ + tagResult, + }); + } + } + + public setResult = (tagResult: any[]) => { + this.setState({ + tagResult, + }); + }; + + public setLabel = (num1: number, num2: number) => { + this.setLabelBySelectedList(num1, num2); + }; + + public componentDidMount() { + // document.addEventListener('keydown', this.keydown); + this.props.onMounted(this); + } + + public componentWillMount() { + // document.addEventListener('keydown', this.keydown); + this.props.onUnmounted(); + } + + public shouldComponentUpdate({ imgIndex, imgList, step }: IZProp) { + if (imgIndex !== this.props.imgIndex) { + this.setState({ + tagResult: jsonParser(imgList[imgIndex].result)[`step_${step}`]?.result ?? [], + }); + } + return true; + } + + public render() { + const { imgIndex, imgList, pageForward, pageJump, pageBackward } = this.props; + const { tagResult } = this.state; + + return ( +
+ + +
+ ); + } +} diff --git a/packages/lb-components/src/components/videoPlayer/VideoTagLayer.tsx b/packages/lb-components/src/components/videoPlayer/VideoTagLayer.tsx new file mode 100644 index 000000000..c6e031d8a --- /dev/null +++ b/packages/lb-components/src/components/videoPlayer/VideoTagLayer.tsx @@ -0,0 +1,77 @@ +import React from 'react'; + +/** + * 通过 key和value在inputList找到对应的标签 + * @param key + * @param value + * @param inputList + */ +const findTagLabel = (key: string, value: string, inputList: any[]) => { + const primaryTagConfig = inputList.find((i) => i.value === key); + const secondaryTagConfig = primaryTagConfig.subSelected.find((i) => i.value === value); + return { keyLabel: primaryTagConfig.key, valueLabel: secondaryTagConfig.key }; +}; + +const result2LabelKey = (result: any[], inputList: any[]) => { + try { + return ( + result?.reduce( + ( + exitsTags: Array<{ keyLabel: string; valueLabel: string }>, + res: { result: { [key: string]: string } }, + ) => { + Object.keys(res.result).forEach((key) => { + const value = res.result[key]; + const { keyLabel, valueLabel } = findTagLabel(key, value, inputList); + if (!exitsTags.find((i) => i.keyLabel === keyLabel && i.valueLabel === valueLabel)) { + exitsTags.push({ keyLabel, valueLabel }); + } + }); + return exitsTags; + }, + [], + ) ?? [] + ); + } catch (error) { + return []; + } +}; + +export const VideoTagLayer = ({ + result, + inputList, +}: { + result: Array<{ result: { [key: string]: string } }>; + inputList: any[]; +}) => { + const cssProperty: React.CSSProperties = { + position: 'absolute', + zIndex: 20, + padding: '0 20px', + color: 'white', + fontSize: 15, + lineHeight: '32px', + background: 'rgba(0, 255, 255, 0.32)', + top: 0, + right: 0, + maxHeight: 'calc(100% - 80px)', + overflowY: 'scroll', + }; + + const tags: Array<{ keyLabel: string; valueLabel: string }> = result2LabelKey(result, inputList); + + return ( +
+ + + {tags.map(({ keyLabel, valueLabel }) => ( + + + + + ))} + +
{`${keyLabel}:`}{`${valueLabel}`}
+
+ ); +}; diff --git a/packages/lb-components/src/components/videoPlayer/index.tsx b/packages/lb-components/src/components/videoPlayer/index.tsx index 6002a73ae..602822331 100644 --- a/packages/lb-components/src/components/videoPlayer/index.tsx +++ b/packages/lb-components/src/components/videoPlayer/index.tsx @@ -60,7 +60,19 @@ interface IState { export const decimalReserved = (num: number, places: number = 2) => typeof num === 'number' ? parseFloat(num.toFixed(places)) : num; -class VideoPlayer extends React.Component { +const getKeyCodeNumber = (keyCode: number) => { + if (keyCode <= 57 && keyCode >= 49) { + return keyCode - 49; + } + + if (keyCode <= 105 && keyCode >= 97) { + return keyCode - 97; + } + + return 0; +}; + +export class VideoPlayer extends React.Component { public videoRef?: React.RefObject; public timeInterval?: number; @@ -217,10 +229,6 @@ class VideoPlayer extends React.Component { window.removeEventListener('keyup', this.keyUpEvents); } - public shouldComponentUpdate() { - return true; - } - public render() { const { isPlay, playbackRate, currentTime, duration, buffered } = this.state; const { imgList, imgIndex, pageBackward, pageJump, pageForward } = this.props; diff --git a/packages/lb-components/src/store/Actions.ts b/packages/lb-components/src/store/Actions.ts index b1b4d0f61..f7bdd8ef1 100644 --- a/packages/lb-components/src/store/Actions.ts +++ b/packages/lb-components/src/store/Actions.ts @@ -6,6 +6,7 @@ export const ANNOTATION_ACTIONS = { SUBMIT_FILE_DATA: '@@SUBMIT_FILE_DATA', SET_TASK_CONFIG: '@@SET_TASK_CONFIG', INIT_TOOL: '@@INIT_TOOL', + SET_TOOL: '@@SET_TOOL', UPDATE_ON_SUBMIT: '@@UPDATE_ON_SUBMIT', UPDATE_ON_SAVE: '@@UPDATE_ON_SAVE', UPDATE_ON_PAGE_CHANGE: '@@UPDATE_ON_PAGE_CHANGE', diff --git a/packages/lb-components/src/store/annotation/reducer.ts b/packages/lb-components/src/store/annotation/reducer.ts index fd4b7b36d..894c194c8 100644 --- a/packages/lb-components/src/store/annotation/reducer.ts +++ b/packages/lb-components/src/store/annotation/reducer.ts @@ -240,12 +240,12 @@ export const annotationReducer = ( } case ANNOTATION_ACTIONS.SUBMIT_RESULT: { - const { imgList, basicIndex, resultList, annotationEngine, basicResultList } = state; - if (!annotationEngine) { + const { imgList, basicIndex, resultList, toolInstance, basicResultList } = state; + if (!toolInstance) { return state; } - const [exportResult] = annotationEngine?.toolInstance?.exportData(); + const [exportResult] = toolInstance?.exportData(); let previousResultList = exportResult; @@ -430,6 +430,19 @@ export const annotationReducer = ( }; } + case ANNOTATION_ACTIONS.SET_TOOL: { + if (action.payload.instance) { + return { + ...state, + toolInstance: action.payload.instance, + }; + } + + return { + ...state, + }; + } + case ANNOTATION_ACTIONS.UPDATE_ON_SUBMIT: { return { ...state, diff --git a/packages/lb-components/src/views/MainView/sidebar/TagSidebar/index.tsx b/packages/lb-components/src/views/MainView/sidebar/TagSidebar/index.tsx index a5d0d3b81..ae5b645f2 100644 --- a/packages/lb-components/src/views/MainView/sidebar/TagSidebar/index.tsx +++ b/packages/lb-components/src/views/MainView/sidebar/TagSidebar/index.tsx @@ -137,6 +137,7 @@ const TagSidebar: React.FC = ({ toolInstance, imgIndex }) => { display: 'flex', justifyContent: 'space-between', alignItems: 'center', + flex: 1, }} > diff --git a/packages/lb-components/src/views/MainView/sidebar/index.tsx b/packages/lb-components/src/views/MainView/sidebar/index.tsx index 4f0b50899..0b523c404 100644 --- a/packages/lb-components/src/views/MainView/sidebar/index.tsx +++ b/packages/lb-components/src/views/MainView/sidebar/index.tsx @@ -19,6 +19,9 @@ import SwitchAttributeList from './SwitchAttributeList'; import TagSidebar, { expandIconFuc } from './TagSidebar'; import TextToolSidebar from './TextToolSidebar'; import ToolStyle from './ToolStyle'; +import { cTool } from '@labelbee/lb-annotation'; + +const { EVideoToolName } = cTool; const { Panel } = Collapse; @@ -182,7 +185,7 @@ const Sidebar: React.FC = ({ sider }) => { ); } - if (toolName === EToolName.Tag) { + if (toolName === EToolName.Tag || toolName === EVideoToolName.VideoTagTool) { return (