Added switcher to maintain poylgon crop behaviour (#2961) (#3021)

* Added switcher to maintain poylgon crop behaviour (#2961)

* Updated versions & changelog
main
Boris Sekachev 5 years ago committed by GitHub
parent 57385fa762
commit 1f56fd286c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Ability of upload manifest for dataset with images (<https://github.com/openvinotoolkit/cvat/pull/2763>) - Ability of upload manifest for dataset with images (<https://github.com/openvinotoolkit/cvat/pull/2763>)
- Annotations filters UI using react-awesome-query-builder (https://github.com/openvinotoolkit/cvat/issues/1418) - Annotations filters UI using react-awesome-query-builder (https://github.com/openvinotoolkit/cvat/issues/1418)
- [ICDAR](https://rrc.cvc.uab.es/?ch=2) format support (<https://github.com/openvinotoolkit/cvat/pull/2866>) - [ICDAR](https://rrc.cvc.uab.es/?ch=2) format support (<https://github.com/openvinotoolkit/cvat/pull/2866>)
- Added switcher to maintain poylgon crop behaviour (<https://github.com/openvinotoolkit/cvat/pull/3021>)
### Changed ### Changed

@ -1,6 +1,6 @@
{ {
"name": "cvat-canvas", "name": "cvat-canvas",
"version": "2.3.2", "version": "2.4.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

@ -1,6 +1,6 @@
{ {
"name": "cvat-canvas", "name": "cvat-canvas",
"version": "2.3.2", "version": "2.4.0",
"description": "Part of Computer Vision Annotation Tool which presents its canvas library", "description": "Part of Computer Vision Annotation Tool which presents its canvas library",
"main": "src/canvas.ts", "main": "src/canvas.ts",
"scripts": { "scripts": {

@ -57,6 +57,7 @@ export interface Configuration {
undefinedAttrValue?: string; undefinedAttrValue?: string;
showProjections?: boolean; showProjections?: boolean;
forceDisableEditing?: boolean; forceDisableEditing?: boolean;
intelligentPolygonCrop?: boolean;
} }
export interface DrawData { export interface DrawData {
@ -621,25 +622,29 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
} }
public configure(configuration: Configuration): void { public configure(configuration: Configuration): void {
if (typeof configuration.displayAllText !== 'undefined') { if (typeof configuration.displayAllText === 'boolean') {
this.data.configuration.displayAllText = configuration.displayAllText; this.data.configuration.displayAllText = configuration.displayAllText;
} }
if (typeof configuration.showProjections !== 'undefined') { if (typeof configuration.showProjections === 'boolean') {
this.data.configuration.showProjections = configuration.showProjections; this.data.configuration.showProjections = configuration.showProjections;
} }
if (typeof configuration.autoborders !== 'undefined') { if (typeof configuration.autoborders === 'boolean') {
this.data.configuration.autoborders = configuration.autoborders; this.data.configuration.autoborders = configuration.autoborders;
} }
if (typeof configuration.undefinedAttrValue !== 'undefined') { if (typeof configuration.undefinedAttrValue === 'string') {
this.data.configuration.undefinedAttrValue = configuration.undefinedAttrValue; this.data.configuration.undefinedAttrValue = configuration.undefinedAttrValue;
} }
if (typeof configuration.forceDisableEditing !== 'undefined') { if (typeof configuration.forceDisableEditing === 'boolean') {
this.data.configuration.forceDisableEditing = configuration.forceDisableEditing; this.data.configuration.forceDisableEditing = configuration.forceDisableEditing;
} }
if (typeof configuration.intelligentPolygonCrop === 'boolean') {
this.data.configuration.intelligentPolygonCrop = configuration.intelligentPolygonCrop;
}
this.notify(UpdateReasons.CONFIG_UPDATED); this.notify(UpdateReasons.CONFIG_UPDATED);
} }

@ -27,6 +27,7 @@ export class EditHandlerImpl implements EditHandler {
private editLine: SVG.PolyLine; private editLine: SVG.PolyLine;
private clones: SVG.Polygon[]; private clones: SVG.Polygon[];
private autobordersEnabled: boolean; private autobordersEnabled: boolean;
private intelligentCutEnabled: boolean;
private setupTrailingPoint(circle: SVG.Circle): void { private setupTrailingPoint(circle: SVG.Circle): void {
const head = this.editedShape.attr('points').split(' ').slice(0, this.editData.pointID).join(' '); const head = this.editedShape.attr('points').split(' ').slice(0, this.editData.pointID).join(' ');
@ -259,11 +260,11 @@ export class EditHandlerImpl implements EditHandler {
this.editLine.remove(); this.editLine.remove();
this.editLine = null; this.editLine = null;
if (pointsCriteria && lengthCriteria) { if (pointsCriteria && lengthCriteria && this.intelligentCutEnabled) {
this.clones.push(this.canvas.polygon(firstPart.join(' '))); this.clones.push(this.canvas.polygon(firstPart.join(' ')));
this.selectPolygon(this.clones[0]); this.selectPolygon(this.clones[0]);
// left indexes1 and // left indexes1 and
} else if (!pointsCriteria && !lengthCriteria) { } else if (!pointsCriteria && !lengthCriteria && this.intelligentCutEnabled) {
this.clones.push(this.canvas.polygon(secondPart.join(' '))); this.clones.push(this.canvas.polygon(secondPart.join(' ')));
this.selectPolygon(this.clones[0]); this.selectPolygon(this.clones[0]);
} else { } else {
@ -384,6 +385,7 @@ export class EditHandlerImpl implements EditHandler {
) { ) {
this.autoborderHandler = autoborderHandler; this.autoborderHandler = autoborderHandler;
this.autobordersEnabled = false; this.autobordersEnabled = false;
this.intelligentCutEnabled = false;
this.onEditDone = onEditDone; this.onEditDone = onEditDone;
this.canvas = canvas; this.canvas = canvas;
this.editData = null; this.editData = null;
@ -423,6 +425,10 @@ export class EditHandlerImpl implements EditHandler {
} }
} }
} }
if (typeof configuration.intelligentPolygonCrop === 'boolean') {
this.intelligentCutEnabled = configuration.intelligentPolygonCrop;
}
} }
public transform(geometry: Geometry): void { public transform(geometry: Geometry): void {

@ -1,6 +1,6 @@
{ {
"name": "cvat-ui", "name": "cvat-ui",
"version": "1.16.1", "version": "1.17.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

@ -1,6 +1,6 @@
{ {
"name": "cvat-ui", "name": "cvat-ui",
"version": "1.16.1", "version": "1.17.0",
"description": "CVAT single-page application", "description": "CVAT single-page application",
"main": "src/index.tsx", "main": "src/index.tsx",
"scripts": { "scripts": {

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation // Copyright (C) 2020-2021 Intel Corporation
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@ -27,6 +27,7 @@ export enum SettingsActionTypes {
CHANGE_AUTO_SAVE_INTERVAL = 'CHANGE_AUTO_SAVE_INTERVAL', CHANGE_AUTO_SAVE_INTERVAL = 'CHANGE_AUTO_SAVE_INTERVAL',
CHANGE_AAM_ZOOM_MARGIN = 'CHANGE_AAM_ZOOM_MARGIN', CHANGE_AAM_ZOOM_MARGIN = 'CHANGE_AAM_ZOOM_MARGIN',
SWITCH_AUTOMATIC_BORDERING = 'SWITCH_AUTOMATIC_BORDERING', SWITCH_AUTOMATIC_BORDERING = 'SWITCH_AUTOMATIC_BORDERING',
SWITCH_INTELLIGENT_POLYGON_CROP = 'SWITCH_INTELLIGENT_POLYGON_CROP',
SWITCH_SHOWNIG_INTERPOLATED_TRACKS = 'SWITCH_SHOWNIG_INTERPOLATED_TRACKS', SWITCH_SHOWNIG_INTERPOLATED_TRACKS = 'SWITCH_SHOWNIG_INTERPOLATED_TRACKS',
SWITCH_SHOWING_OBJECTS_TEXT_ALWAYS = 'SWITCH_SHOWING_OBJECTS_TEXT_ALWAYS', SWITCH_SHOWING_OBJECTS_TEXT_ALWAYS = 'SWITCH_SHOWING_OBJECTS_TEXT_ALWAYS',
CHANGE_CANVAS_BACKGROUND_COLOR = 'CHANGE_CANVAS_BACKGROUND_COLOR', CHANGE_CANVAS_BACKGROUND_COLOR = 'CHANGE_CANVAS_BACKGROUND_COLOR',
@ -241,6 +242,15 @@ export function switchAutomaticBordering(automaticBordering: boolean): AnyAction
}; };
} }
export function switchIntelligentPolygonCrop(intelligentPolygonCrop: boolean): AnyAction {
return {
type: SettingsActionTypes.SWITCH_INTELLIGENT_POLYGON_CROP,
payload: {
intelligentPolygonCrop,
},
};
}
export function changeCanvasBackgroundColor(color: string): AnyAction { export function changeCanvasBackgroundColor(color: string): AnyAction {
return { return {
type: SettingsActionTypes.CHANGE_CANVAS_BACKGROUND_COLOR, type: SettingsActionTypes.CHANGE_CANVAS_BACKGROUND_COLOR,

@ -3,12 +3,12 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react';
import Layout from 'antd/lib/layout'; import Layout from 'antd/lib/layout';
import Slider from 'antd/lib/slider'; import Slider from 'antd/lib/slider';
import Dropdown from 'antd/lib/dropdown'; import Dropdown from 'antd/lib/dropdown';
import { PlusCircleOutlined, UpOutlined } from '@ant-design/icons'; import { PlusCircleOutlined, UpOutlined } from '@ant-design/icons';
import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react';
import { import {
ColorBy, GridColor, ObjectType, ContextMenuType, Workspace, ShapeType, ColorBy, GridColor, ObjectType, ContextMenuType, Workspace, ShapeType,
} from 'reducers/interfaces'; } from 'reducers/interfaces';
@ -61,6 +61,7 @@ interface Props {
showAllInterpolationTracks: boolean; showAllInterpolationTracks: boolean;
workspace: Workspace; workspace: Workspace;
automaticBordering: boolean; automaticBordering: boolean;
intelligentPolygonCrop: boolean;
keyMap: KeyMap; keyMap: KeyMap;
canvasBackgroundColor: string; canvasBackgroundColor: string;
switchableAutomaticBordering: boolean; switchableAutomaticBordering: boolean;
@ -98,7 +99,12 @@ interface Props {
export default class CanvasWrapperComponent extends React.PureComponent<Props> { export default class CanvasWrapperComponent extends React.PureComponent<Props> {
public componentDidMount(): void { public componentDidMount(): void {
const { const {
automaticBordering, showObjectsTextAlways, canvasInstance, workspace, automaticBordering,
intelligentPolygonCrop,
showObjectsTextAlways,
canvasInstance,
workspace,
showProjections,
} = this.props; } = this.props;
// It's awful approach from the point of view React // It's awful approach from the point of view React
@ -111,6 +117,8 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE, undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE,
displayAllText: showObjectsTextAlways, displayAllText: showObjectsTextAlways,
forceDisableEditing: workspace === Workspace.REVIEW_WORKSPACE, forceDisableEditing: workspace === Workspace.REVIEW_WORKSPACE,
intelligentPolygonCrop,
showProjections,
}); });
this.initialSetup(); this.initialSetup();
@ -147,6 +155,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
showObjectsTextAlways, showObjectsTextAlways,
showAllInterpolationTracks, showAllInterpolationTracks,
automaticBordering, automaticBordering,
intelligentPolygonCrop,
showProjections, showProjections,
canvasBackgroundColor, canvasBackgroundColor,
onFetchAnnotation, onFetchAnnotation,
@ -155,13 +164,15 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
if ( if (
prevProps.showObjectsTextAlways !== showObjectsTextAlways || prevProps.showObjectsTextAlways !== showObjectsTextAlways ||
prevProps.automaticBordering !== automaticBordering || prevProps.automaticBordering !== automaticBordering ||
prevProps.showProjections !== showProjections prevProps.showProjections !== showProjections ||
prevProps.intelligentPolygonCrop !== intelligentPolygonCrop
) { ) {
canvasInstance.configure({ canvasInstance.configure({
undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE, undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE,
displayAllText: showObjectsTextAlways, displayAllText: showObjectsTextAlways,
autoborders: automaticBordering, autoborders: automaticBordering,
showProjections, showProjections,
intelligentPolygonCrop,
}); });
} }

@ -25,6 +25,7 @@
.cvat-workspace-settings-auto-save, .cvat-workspace-settings-auto-save,
.cvat-workspace-settings-autoborders, .cvat-workspace-settings-autoborders,
.cvat-workspace-settings-intelligent-polygon-cropping,
.cvat-workspace-settings-show-text-always, .cvat-workspace-settings-show-text-always,
.cvat-workspace-settings-show-interpolated { .cvat-workspace-settings-show-interpolated {
margin-bottom: 25px; margin-bottom: 25px;

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation // Copyright (C) 2020-2021 Intel Corporation
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@ -18,12 +18,14 @@ interface Props {
showAllInterpolationTracks: boolean; showAllInterpolationTracks: boolean;
showObjectsTextAlways: boolean; showObjectsTextAlways: boolean;
automaticBordering: boolean; automaticBordering: boolean;
intelligentPolygonCrop: boolean;
onSwitchAutoSave(enabled: boolean): void; onSwitchAutoSave(enabled: boolean): void;
onChangeAutoSaveInterval(interval: number): void; onChangeAutoSaveInterval(interval: number): void;
onChangeAAMZoomMargin(margin: number): void; onChangeAAMZoomMargin(margin: number): void;
onSwitchShowingInterpolatedTracks(enabled: boolean): void; onSwitchShowingInterpolatedTracks(enabled: boolean): void;
onSwitchShowingObjectsTextAlways(enabled: boolean): void; onSwitchShowingObjectsTextAlways(enabled: boolean): void;
onSwitchAutomaticBordering(enabled: boolean): void; onSwitchAutomaticBordering(enabled: boolean): void;
onSwitchIntelligentPolygonCrop(enabled: boolean): void;
} }
export default function WorkspaceSettingsComponent(props: Props): JSX.Element { export default function WorkspaceSettingsComponent(props: Props): JSX.Element {
@ -34,12 +36,14 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element {
showAllInterpolationTracks, showAllInterpolationTracks,
showObjectsTextAlways, showObjectsTextAlways,
automaticBordering, automaticBordering,
intelligentPolygonCrop,
onSwitchAutoSave, onSwitchAutoSave,
onChangeAutoSaveInterval, onChangeAutoSaveInterval,
onChangeAAMZoomMargin, onChangeAAMZoomMargin,
onSwitchShowingInterpolatedTracks, onSwitchShowingInterpolatedTracks,
onSwitchShowingObjectsTextAlways, onSwitchShowingObjectsTextAlways,
onSwitchAutomaticBordering, onSwitchAutomaticBordering,
onSwitchIntelligentPolygonCrop,
} = props; } = props;
const minAutoSaveInterval = 1; const minAutoSaveInterval = 1;
@ -111,9 +115,7 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element {
</Col> </Col>
<Col span={24}> <Col span={24}>
<Text type='secondary'> <Text type='secondary'>
{' '}
Show text for an object on the canvas not only when the object is activated Show text for an object on the canvas not only when the object is activated
{' '}
</Text> </Text>
</Col> </Col>
</Row> </Row>
@ -131,12 +133,26 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element {
</Col> </Col>
<Col span={24}> <Col span={24}>
<Text type='secondary'> <Text type='secondary'>
{' '}
Enable automatic bordering for polygons and polylines during drawing/editing Enable automatic bordering for polygons and polylines during drawing/editing
{' '}
</Text> </Text>
</Col> </Col>
</Row> </Row>
<Row className='cvat-workspace-settings-intelligent-polygon-cropping'>
<Col span={24}>
<Checkbox
className='cvat-text-color'
checked={intelligentPolygonCrop}
onChange={(event: CheckboxChangeEvent): void => {
onSwitchIntelligentPolygonCrop(event.target.checked);
}}
>
Intelligent polygon cropping
</Checkbox>
</Col>
<Col span={24}>
<Text type='secondary'>Try to crop polygons automatically when editing</Text>
</Col>
</Row>
<Row className='cvat-workspace-settings-aam-zoom-margin'> <Row className='cvat-workspace-settings-aam-zoom-margin'>
<Col> <Col>
<Text className='cvat-text-color'> Attribute annotation mode (AAM) zoom margin </Text> <Text className='cvat-text-color'> Attribute annotation mode (AAM) zoom margin </Text>

@ -2,9 +2,9 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { KeyMap } from 'utils/mousetrap-react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { KeyMap } from 'utils/mousetrap-react';
import CanvasWrapperComponent from 'components/annotation-page/canvas/canvas-wrapper'; import CanvasWrapperComponent from 'components/annotation-page/canvas/canvas-wrapper';
import { import {
confirmCanvasReady, confirmCanvasReady,
@ -159,7 +159,11 @@ function mapStateToProps(state: CombinedState): StateToProps {
resetZoom, resetZoom,
}, },
workspace: { workspace: {
aamZoomMargin, showObjectsTextAlways, showAllInterpolationTracks, automaticBordering, aamZoomMargin,
showObjectsTextAlways,
showAllInterpolationTracks,
automaticBordering,
intelligentPolygonCrop,
}, },
shapes: { shapes: {
opacity, colorBy, selectedOpacity, outlined, outlineColor, showBitmap, showProjections, opacity, colorBy, selectedOpacity, outlined, outlineColor, showBitmap, showProjections,
@ -207,6 +211,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
minZLayer, minZLayer,
maxZLayer, maxZLayer,
automaticBordering, automaticBordering,
intelligentPolygonCrop,
workspace, workspace,
keyMap, keyMap,
canvasBackgroundColor, canvasBackgroundColor,

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation // Copyright (C) 2020-2021 Intel Corporation
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@ -12,6 +12,7 @@ import {
switchShowingInterpolatedTracks, switchShowingInterpolatedTracks,
switchShowingObjectsTextAlways, switchShowingObjectsTextAlways,
switchAutomaticBordering, switchAutomaticBordering,
switchIntelligentPolygonCrop,
} from 'actions/settings-actions'; } from 'actions/settings-actions';
import { CombinedState } from 'reducers/interfaces'; import { CombinedState } from 'reducers/interfaces';
@ -25,6 +26,7 @@ interface StateToProps {
showAllInterpolationTracks: boolean; showAllInterpolationTracks: boolean;
showObjectsTextAlways: boolean; showObjectsTextAlways: boolean;
automaticBordering: boolean; automaticBordering: boolean;
intelligentPolygonCrop: boolean;
} }
interface DispatchToProps { interface DispatchToProps {
@ -34,6 +36,7 @@ interface DispatchToProps {
onSwitchShowingInterpolatedTracks(enabled: boolean): void; onSwitchShowingInterpolatedTracks(enabled: boolean): void;
onSwitchShowingObjectsTextAlways(enabled: boolean): void; onSwitchShowingObjectsTextAlways(enabled: boolean): void;
onSwitchAutomaticBordering(enabled: boolean): void; onSwitchAutomaticBordering(enabled: boolean): void;
onSwitchIntelligentPolygonCrop(enabled: boolean): void;
} }
function mapStateToProps(state: CombinedState): StateToProps { function mapStateToProps(state: CombinedState): StateToProps {
@ -45,6 +48,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
showAllInterpolationTracks, showAllInterpolationTracks,
showObjectsTextAlways, showObjectsTextAlways,
automaticBordering, automaticBordering,
intelligentPolygonCrop,
} = workspace; } = workspace;
return { return {
@ -54,6 +58,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
showAllInterpolationTracks, showAllInterpolationTracks,
showObjectsTextAlways, showObjectsTextAlways,
automaticBordering, automaticBordering,
intelligentPolygonCrop,
}; };
} }
@ -77,6 +82,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
onSwitchAutomaticBordering(enabled: boolean): void { onSwitchAutomaticBordering(enabled: boolean): void {
dispatch(switchAutomaticBordering(enabled)); dispatch(switchAutomaticBordering(enabled));
}, },
onSwitchIntelligentPolygonCrop(enabled: boolean): void {
dispatch(switchIntelligentPolygonCrop(enabled));
},
}; };
} }

@ -518,6 +518,7 @@ export interface WorkspaceSettingsState {
automaticBordering: boolean; automaticBordering: boolean;
showObjectsTextAlways: boolean; showObjectsTextAlways: boolean;
showAllInterpolationTracks: boolean; showAllInterpolationTracks: boolean;
intelligentPolygonCrop: boolean;
} }
export interface ShapesSettingsState { export interface ShapesSettingsState {

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation // Copyright (C) 2020-2021 Intel Corporation
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@ -9,7 +9,9 @@ import { AuthActionTypes } from 'actions/auth-actions';
import { SettingsActionTypes } from 'actions/settings-actions'; import { SettingsActionTypes } from 'actions/settings-actions';
import { AnnotationActionTypes } from 'actions/annotation-actions'; import { AnnotationActionTypes } from 'actions/annotation-actions';
import { SettingsState, GridColor, FrameSpeed, ColorBy } from './interfaces'; import {
SettingsState, GridColor, FrameSpeed, ColorBy,
} from './interfaces';
const defaultState: SettingsState = { const defaultState: SettingsState = {
shapes: { shapes: {
@ -28,6 +30,7 @@ const defaultState: SettingsState = {
automaticBordering: false, automaticBordering: false,
showObjectsTextAlways: false, showObjectsTextAlways: false,
showAllInterpolationTracks: false, showAllInterpolationTracks: false,
intelligentPolygonCrop: true,
}, },
player: { player: {
canvasBackgroundColor: '#ffffff', canvasBackgroundColor: '#ffffff',
@ -256,6 +259,15 @@ export default (state = defaultState, action: AnyAction): SettingsState => {
}, },
}; };
} }
case SettingsActionTypes.SWITCH_INTELLIGENT_POLYGON_CROP: {
return {
...state,
workspace: {
...state.workspace,
intelligentPolygonCrop: action.payload.intelligentPolygonCrop,
},
};
}
case SettingsActionTypes.CHANGE_CANVAS_BACKGROUND_COLOR: { case SettingsActionTypes.CHANGE_CANVAS_BACKGROUND_COLOR: {
return { return {
...state, ...state,

Loading…
Cancel
Save