From 63749763a6e2f3a3bfdca1466f31d940b4ce5925 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 26 Feb 2020 18:10:09 +0300 Subject: [PATCH 1/2] Pinned option was added --- cvat-canvas/src/typescript/canvasView.ts | 73 +++++++++++-------- cvat-core/src/annotations-objects.js | 30 ++++++++ cvat-core/src/enums.js | 1 + cvat-core/src/object-state.js | 72 +++++++++++++----- .../objects-side-bar/object-item.tsx | 32 +++++++- .../objects-side-bar/object-item.tsx | 15 ++++ 6 files changed, 171 insertions(+), 52 deletions(-) diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index ebda629c183b..3b55971514e8 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -840,6 +840,7 @@ export class CanvasViewImpl implements CanvasView, Listener { points: [...state.points], attributes: { ...state.attributes }, zOrder: state.zOrder, + pinned: state.pinned, }; } @@ -875,6 +876,12 @@ export class CanvasViewImpl implements CanvasView, Listener { } } + if (drawnState.pinned !== state.pinned && this.activeElement.clientID !== null) { + const activeElement = { ...this.activeElement }; + this.deactivate(); + this.activate(activeElement); + } + if (state.points .some((p: number, id: number): boolean => p !== drawnState.points[id]) ) { @@ -1006,9 +1013,11 @@ export class CanvasViewImpl implements CanvasView, Listener { shape.removeClass('cvat_canvas_shape_activated'); - (shape as any).off('dragstart'); - (shape as any).off('dragend'); - (shape as any).draggable(false); + if (!drawnState.pinned) { + (shape as any).off('dragstart'); + (shape as any).off('dragend'); + (shape as any).draggable(false); + } if (drawnState.shapeType !== 'points') { this.selectize(false, shape); @@ -1090,36 +1099,38 @@ export class CanvasViewImpl implements CanvasView, Listener { this.content.append(shape.node); } - (shape as any).draggable().on('dragstart', (): void => { - this.mode = Mode.DRAG; - if (text) { - text.addClass('cvat_canvas_hidden'); - } - }).on('dragend', (e: CustomEvent): void => { - if (text) { - text.removeClass('cvat_canvas_hidden'); - self.updateTextPosition( - text, - shape, - ); - } - - this.mode = Mode.IDLE; + if (!state.pinned) { + (shape as any).draggable().on('dragstart', (): void => { + this.mode = Mode.DRAG; + if (text) { + text.addClass('cvat_canvas_hidden'); + } + }).on('dragend', (e: CustomEvent): void => { + if (text) { + text.removeClass('cvat_canvas_hidden'); + self.updateTextPosition( + text, + shape, + ); + } - const p1 = e.detail.handler.startPoints.point; - const p2 = e.detail.p; - const delta = 1; - const { offset } = this.controller.geometry; - if (Math.sqrt(((p1.x - p2.x) ** 2) + ((p1.y - p2.y) ** 2)) >= delta) { - const points = pointsToArray( - shape.attr('points') || `${shape.attr('x')},${shape.attr('y')} ` - + `${shape.attr('x') + shape.attr('width')},` - + `${shape.attr('y') + shape.attr('height')}`, - ).map((x: number): number => x - offset); + this.mode = Mode.IDLE; - this.onEditDone(state, points); - } - }); + const p1 = e.detail.handler.startPoints.point; + const p2 = e.detail.p; + const delta = 1; + const { offset } = this.controller.geometry; + if (Math.sqrt(((p1.x - p2.x) ** 2) + ((p1.y - p2.y) ** 2)) >= delta) { + const points = pointsToArray( + shape.attr('points') || `${shape.attr('x')},${shape.attr('y')} ` + + `${shape.attr('x') + shape.attr('width')},` + + `${shape.attr('y') + shape.attr('height')}`, + ).map((x: number): number => x - offset); + + this.onEditDone(state, points); + } + }); + } if (state.shapeType !== 'points') { this.selectize(true, shape); diff --git a/cvat-core/src/annotations-objects.js b/cvat-core/src/annotations-objects.js index e5de9685e3a9..7970b51712ff 100644 --- a/cvat-core/src/annotations-objects.js +++ b/cvat-core/src/annotations-objects.js @@ -269,11 +269,25 @@ this.frameMeta = injection.frameMeta; this.hidden = false; + this.pinned = true; this.color = color; this.shapeType = null; } + _savePinned(pinned) { + const undoPinned = this.pinned; + const redoPinned = pinned; + + this.history.do(HistoryActions.CHANGED_PINNED, () => { + this.pinned = undoPinned; + }, () => { + this.pinned = redoPinned; + }, [this.clientID]); + + this.pinned = pinned; + } + _validateStateBeforeSave(frame, data, updated) { let fittedPoints = []; @@ -343,6 +357,10 @@ checkObjectType('lock', data.lock, 'boolean', null); } + if (updated.pinned) { + checkObjectType('pinned', data.pinned, 'boolean', null); + } + if (updated.color) { checkObjectType('color', data.color, 'string', null); if (/^#[0-9A-F]{6}$/i.test(data.color)) { @@ -439,6 +457,7 @@ color: this.color, hidden: this.hidden, updated: this.updated, + pinned: this.pinned, frame, }; } @@ -521,6 +540,10 @@ this._saveLock(data.lock); } + if (updated.pinned) { + this._savePinned(data.pinned); + } + if (updated.color) { this._saveColor(data.color); } @@ -631,6 +654,7 @@ hidden: this.hidden, updated: this.updated, label: this.label, + pinned: this.pinned, keyframes: { prev, next, @@ -971,6 +995,10 @@ this._saveLock(data.lock); } + if (updated.pinned) { + this._savePinned(data.pinned); + } + if (updated.color) { this._saveColor(data.color); } @@ -1135,6 +1163,7 @@ constructor(data, clientID, color, injection) { super(data, clientID, color, injection); this.shapeType = ObjectShape.RECTANGLE; + this.pinned = false; checkNumberOfPoints(this.shapeType, this.points); } @@ -1302,6 +1331,7 @@ constructor(data, clientID, color, injection) { super(data, clientID, color, injection); this.shapeType = ObjectShape.RECTANGLE; + this.pinned = false; for (const shape of Object.values(this.shapes)) { checkNumberOfPoints(this.shapeType, shape.points); } diff --git a/cvat-core/src/enums.js b/cvat-core/src/enums.js index df7c5ca4e9fa..2c34290876bf 100644 --- a/cvat-core/src/enums.js +++ b/cvat-core/src/enums.js @@ -196,6 +196,7 @@ CHANGED_ZORDER: 'Changed z-order', CHANGED_KEYFRAME: 'Changed keyframe', CHANGED_LOCK: 'Changed lock', + CHANGED_PINNED: 'Changed pinned', CHANGED_COLOR: 'Changed color', CHANGED_HIDDEN: 'Changed hidden', MERGED_OBJECTS: 'Merged objects', diff --git a/cvat-core/src/object-state.js b/cvat-core/src/object-state.js index 282ab344e619..806a29099796 100644 --- a/cvat-core/src/object-state.js +++ b/cvat-core/src/object-state.js @@ -19,10 +19,10 @@ /** * @param {Object} serialized - is an dictionary which contains * initial information about an ObjectState; - * Necessary fields: objectType, shapeType, frame, updated - * Optional fields: points, group, zOrder, outside, occluded, hidden, - * attributes, lock, label, mode, color, keyframe, keyframes, clientID, serverID - * These fields can be set later via setters + *
Necessary fields: objectType, shapeType, frame, updated, group + *
Optional fields: keyframes, clientID, serverID + *
Optional fields which can be set later: points, zOrder, outside, + * occluded, hidden, attributes, lock, label, color, keyframe */ constructor(serialized) { const data = { @@ -34,12 +34,13 @@ occluded: null, keyframe: null, - zOrder: undefined, + zOrder: null, lock: null, color: null, hidden: null, + pinned: null, + keyframes: null, group: serialized.group, - keyframes: serialized.keyframes, updated: serialized.updated, clientID: serialized.clientID, @@ -63,6 +64,7 @@ this.keyframe = false; this.zOrder = false; + this.pinned = false; this.lock = false; this.color = false; this.hidden = false; @@ -202,7 +204,7 @@ zOrder: { /** * @name zOrder - * @type {integer} + * @type {integer | null} * @memberof module:API.cvat.classes.ObjectState * @instance */ @@ -242,15 +244,16 @@ /** * Object of keyframes { first, prev, next, last } * @name keyframes - * @type {object} + * @type {object | null} * @memberof module:API.cvat.classes.ObjectState * @readonly * @instance */ get: () => { - if (data.keyframes) { + if (typeof (data.keyframes) === 'object') { return { ...data.keyframes }; } + return null; }, }, @@ -280,6 +283,25 @@ data.lock = lock; }, }, + pinned: { + /** + * @name pinned + * @type {boolean | null} + * @memberof module:API.cvat.classes.ObjectState + * @instance + */ + get: () => { + if (typeof (data.pinned) === 'boolean') { + return data.pinned; + } + + return null; + }, + set: (pinned) => { + data.updateFlags.pinned = true; + data.pinned = pinned; + }, + }, updated: { /** * Timestamp of the latest updated of the object @@ -320,19 +342,33 @@ })); this.label = serialized.label; - this.zOrder = serialized.zOrder; - this.outside = serialized.outside; - this.keyframe = serialized.keyframe; - this.occluded = serialized.occluded; - this.color = serialized.color; this.lock = serialized.lock; - this.hidden = serialized.hidden; - // It can be undefined in a constructor and it can be defined later - if (typeof (serialized.points) !== 'undefined') { + if (typeof (serialized.zOrder) === 'number') { + this.zOrder = serialized.zOrder; + } + if (typeof (serialized.occluded) === 'boolean') { + this.occluded = serialized.occluded; + } + if (typeof (serialized.outside) === 'boolean') { + this.outside = serialized.outside; + } + if (typeof (serialized.keyframe) === 'boolean') { + this.keyframe = serialized.keyframe; + } + if (typeof (serialized.pinned) === 'boolean') { + this.pinned = serialized.pinned; + } + if (typeof (serialized.hidden) === 'boolean') { + this.hidden = serialized.hidden; + } + if (typeof (serialized.color) === 'string') { + this.color = serialized.color; + } + if (Array.isArray(serialized.points)) { this.points = serialized.points; } - if (typeof (serialized.attributes) !== 'undefined') { + if (typeof (serialized.attributes) === 'object') { this.attributes = serialized.attributes; } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx index 5dc79c1ef2a0..26af88ac1083 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx @@ -175,6 +175,7 @@ interface ItemButtonsComponentProps { occluded: boolean; outside: boolean | undefined; locked: boolean; + pinned: boolean; hidden: boolean; keyframe: boolean | undefined; @@ -191,6 +192,8 @@ interface ItemButtonsComponentProps { unsetKeyframe(): void; lock(): void; unlock(): void; + pin(): void; + unpin(): void; hide(): void; show(): void; } @@ -201,6 +204,7 @@ function ItemButtonsComponent(props: ItemButtonsComponentProps): JSX.Element { occluded, outside, locked, + pinned, hidden, keyframe, @@ -217,6 +221,8 @@ function ItemButtonsComponent(props: ItemButtonsComponentProps): JSX.Element { unsetKeyframe, lock, unlock, + pin, + unpin, hide, show, } = props; @@ -273,6 +279,11 @@ function ItemButtonsComponent(props: ItemButtonsComponentProps): JSX.Element { ? : } + + { pinned + ? + : } + @@ -283,21 +294,26 @@ function ItemButtonsComponent(props: ItemButtonsComponentProps): JSX.Element { - + { locked ? : } - + { occluded ? : } - + { hidden ? : } + + { pinned + ? + : } + @@ -540,6 +556,7 @@ interface Props { occluded: boolean; outside: boolean | undefined; locked: boolean; + pinned: boolean; hidden: boolean; keyframe: boolean | undefined; attrValues: Record; @@ -568,6 +585,8 @@ interface Props { unsetKeyframe(): void; lock(): void; unlock(): void; + pin(): void; + unpin(): void; hide(): void; show(): void; changeLabel(labelID: string): void; @@ -578,6 +597,7 @@ interface Props { function objectItemsAreEqual(prevProps: Props, nextProps: Props): boolean { return nextProps.activated === prevProps.activated && nextProps.locked === prevProps.locked + && nextProps.pinned === prevProps.pinned && nextProps.occluded === prevProps.occluded && nextProps.outside === prevProps.outside && nextProps.hidden === prevProps.hidden @@ -608,6 +628,7 @@ function ObjectItemComponent(props: Props): JSX.Element { occluded, outside, locked, + pinned, hidden, keyframe, attrValues, @@ -637,6 +658,8 @@ function ObjectItemComponent(props: Props): JSX.Element { unsetKeyframe, lock, unlock, + pin, + unpin, hide, show, changeLabel, @@ -677,6 +700,7 @@ function ObjectItemComponent(props: Props): JSX.Element { occluded={occluded} outside={outside} locked={locked} + pinned={pinned} hidden={hidden} keyframe={keyframe} navigateFirstKeyframe={navigateFirstKeyframe} @@ -691,6 +715,8 @@ function ObjectItemComponent(props: Props): JSX.Element { unsetKeyframe={unsetKeyframe} lock={lock} unlock={unlock} + pin={pin} + unpin={unpin} hide={hide} show={show} /> diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx index 2461837d51fc..5f26173e779b 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx @@ -277,6 +277,18 @@ class ObjectItemContainer extends React.PureComponent { this.commit(); }; + private pin = (): void => { + const { objectState } = this.props; + objectState.pinned = true; + this.commit(); + }; + + private unpin = (): void => { + const { objectState } = this.props; + objectState.pinned = false; + this.commit(); + }; + private show = (): void => { const { objectState } = this.props; objectState.hidden = false; @@ -407,6 +419,7 @@ class ObjectItemContainer extends React.PureComponent { occluded={objectState.occluded} outside={objectState.outside} locked={objectState.lock} + pinned={objectState.pinned} hidden={objectState.hidden} keyframe={objectState.keyframe} attrValues={{ ...objectState.attributes }} @@ -446,6 +459,8 @@ class ObjectItemContainer extends React.PureComponent { unsetKeyframe={this.unsetKeyframe} lock={this.lock} unlock={this.unlock} + pin={this.pin} + unpin={this.unpin} hide={this.hide} show={this.show} changeLabel={this.changeLabel} From d0e467f52e412fc90dc839c10adfe90a94d8bf44 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 2 Mar 2020 11:00:05 +0300 Subject: [PATCH 2/2] Do not show for points --- .../objects-side-bar/object-item.tsx | 55 +++++++++++-------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx index 92418aa6f73f..284fc6130094 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx @@ -178,6 +178,7 @@ const ItemTop = React.memo(ItemTopComponent); interface ItemButtonsComponentProps { objectType: ObjectType; + shapeType: ShapeType; occluded: boolean; outside: boolean | undefined; locked: boolean; @@ -207,6 +208,7 @@ interface ItemButtonsComponentProps { function ItemButtonsComponent(props: ItemButtonsComponentProps): JSX.Element { const { objectType, + shapeType, occluded, outside, locked, @@ -238,58 +240,62 @@ function ItemButtonsComponent(props: ItemButtonsComponentProps): JSX.Element { - + { navigateFirstKeyframe ? : } - + { navigatePrevKeyframe ? : } - + { navigateNextKeyframe ? : } - + { navigateLastKeyframe ? : } - + { outside ? : } - + { locked ? : } - + { occluded ? : } - + { hidden ? : } - + { keyframe ? : } - - { pinned - ? - : } - + { + shapeType !== ShapeType.POINTS && ( + + { pinned + ? + : } + + ) + } @@ -300,26 +306,30 @@ function ItemButtonsComponent(props: ItemButtonsComponentProps): JSX.Element { - + { locked ? : } - + { occluded ? : } - + { hidden ? : } - - { pinned - ? - : } - + { + shapeType !== ShapeType.POINTS && ( + + { pinned + ? + : } + + ) + } @@ -727,6 +737,7 @@ function ObjectItemComponent(props: Props): JSX.Element { toForeground={toForeground} />