From 2f4bea1c11da9c29527b777d6369939914722b12 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 23 Mar 2020 17:23:46 +0300 Subject: [PATCH 1/2] Task creator validators & help message --- .../advanced-configuration-form.tsx | 120 ++++++++++++++++-- .../create-task-page/create-task-content.tsx | 4 +- 2 files changed, 111 insertions(+), 13 deletions(-) diff --git a/cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx b/cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx index b8d85eef2105..7ffcbbc08909 100644 --- a/cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx +++ b/cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx @@ -30,7 +30,7 @@ export interface AdvancedConfiguration { lfs: boolean; repository?: string; useZipChunks: boolean; - dataChunkSize: number; + dataChunkSize?: number; } type Props = FormComponentProps & { @@ -38,6 +38,52 @@ type Props = FormComponentProps & { installedGit: boolean; }; +function isPositiveInteger(_: any, value: any, callback: any): void { + if (!value) { + callback(); + return; + } + + const intValue = +value; + if (Number.isNaN(intValue) + || !Number.isInteger(intValue) || intValue < 1) { + callback('Value must be a positive integer'); + } + + callback(); +} + +function isNonNegativeInteger(_: any, value: any, callback: any): void { + if (!value) { + callback(); + return; + } + + const intValue = +value; + if (Number.isNaN(intValue) || intValue < 0) { + callback('Value must be a non negative integer'); + } + + callback(); +} + +function isIntegerRange(min: number, max: number, _: any, value: any, callback: any): void { + if (!value) { + callback(); + return; + } + + const intValue = +value; + if (Number.isNaN(intValue) + || !Number.isInteger(intValue) + || intValue < min || intValue > max + ) { + callback(`Value must be an integer [${min}, ${max}]`); + } + + callback(); +} + class AdvancedConfigurationForm extends React.PureComponent { public submit(): Promise { return new Promise((resolve, reject) => { @@ -51,6 +97,16 @@ class AdvancedConfigurationForm extends React.PureComponent { const filteredValues = { ...values }; delete filteredValues.frameStep; + if (values.overlapSize && +values.segmentSize <= +values.overlapSize) { + reject(new Error('Overlap size must be more than segment size')); + } + + if (typeof (values.startFrame) !== 'undefined' && typeof (values.stopFrame) !== 'undefined' + && +values.stopFrame < +values.startFrame + ) { + reject(new Error('Stop frame must be more or equal start frame')); + } + onSubmit({ ...values, frameFilter: values.frameStep ? `step=${values.frameStep}` : undefined, @@ -96,14 +152,14 @@ class AdvancedConfigurationForm extends React.PureComponent { initialValue: 70, rules: [{ required: true, - message: 'This field is required', + message: 'The field is required.', + }, { + validator: isIntegerRange.bind(null, 5, 100), }], })( } />, )} @@ -118,7 +174,11 @@ class AdvancedConfigurationForm extends React.PureComponent { return ( Overlap size}> - {form.getFieldDecorator('overlapSize')( + {form.getFieldDecorator('overlapSize', { + rules: [{ + validator: isNonNegativeInteger, + }], + })( , )} @@ -132,7 +192,11 @@ class AdvancedConfigurationForm extends React.PureComponent { return ( Segment size}> - {form.getFieldDecorator('segmentSize')( + {form.getFieldDecorator('segmentSize', { + rules: [{ + validator: isPositiveInteger, + }], + })( , )} @@ -145,7 +209,11 @@ class AdvancedConfigurationForm extends React.PureComponent { return ( Start frame}> - {form.getFieldDecorator('startFrame')( + {form.getFieldDecorator('startFrame', { + rules: [{ + validator: isNonNegativeInteger, + }], + })( { return ( Stop frame}> - {form.getFieldDecorator('stopFrame')( + {form.getFieldDecorator('stopFrame', { + rules: [{ + validator: isNonNegativeInteger, + }], + })( { return ( Frame step}> - {form.getFieldDecorator('frameStep')( + {form.getFieldDecorator('frameStep', { + rules: [{ + validator: isPositiveInteger, + }], + })( { return ( Chunk size}> - - {form.getFieldDecorator('dataChunkSize')( + + Defines a number of frames to be packed in + a chunk when send from client to server. + Server defines automatically if empty. +
+ Recommended values: +
+ 1080p or less: 36 +
+ 2k or less: 8 - 16 +
+ 4k or less: 4 - 8 +
+ More: 1 - 4 + + )} + > + {form.getFieldDecorator('dataChunkSize', { + rules: [{ + validator: isPositiveInteger, + }], + })( , )}
diff --git a/cvat-ui/src/components/create-task-page/create-task-content.tsx b/cvat-ui/src/components/create-task-page/create-task-content.tsx index 1432dcd87474..ca81c2d72540 100644 --- a/cvat-ui/src/components/create-task-page/create-task-content.tsx +++ b/cvat-ui/src/components/create-task-page/create-task-content.tsx @@ -142,10 +142,10 @@ export default class CreateTaskContent extends React.PureComponent }).then((): void => { const { onCreate } = this.props; onCreate(this.state); - }).catch((): void => { + }).catch((error: Error): void => { notification.error({ message: 'Could not create a task', - description: 'Please, check configuration you specified', + description: error.toString(), }); }); }; From f1488fc886e9bc23fef1941719a3d93a883794cd Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 24 Mar 2020 17:48:36 +0300 Subject: [PATCH 2/2] Fixed preview position --- cvat-ui/src/components/task-page/details.tsx | 30 ++++++++++++++++---- cvat-ui/src/components/task-page/styles.scss | 13 ++++----- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/cvat-ui/src/components/task-page/details.tsx b/cvat-ui/src/components/task-page/details.tsx index 2c690330e75d..ff7083765b0a 100644 --- a/cvat-ui/src/components/task-page/details.tsx +++ b/cvat-ui/src/components/task-page/details.tsx @@ -44,6 +44,8 @@ interface State { export default class DetailsComponent extends React.PureComponent { private mounted: boolean; + private previewImageElement: HTMLImageElement; + private previewWrapperRef: React.RefObject; constructor(props: Props) { super(props); @@ -51,6 +53,8 @@ export default class DetailsComponent extends React.PureComponent const { taskInstance } = props; this.mounted = false; + this.previewImageElement = new Image(); + this.previewWrapperRef = React.createRef(); this.state = { name: taskInstance.name, bugTracker: taskInstance.bugTracker, @@ -60,9 +64,25 @@ export default class DetailsComponent extends React.PureComponent } public componentDidMount(): void { - const { taskInstance } = this.props; + const { taskInstance, previewImage } = this.props; + const { previewImageElement, previewWrapperRef } = this; this.mounted = true; + previewImageElement.onload = () => { + const { height, width } = previewImageElement; + if (width > height) { + previewImageElement.style.width = '100%'; + } else { + previewImageElement.style.height = '100%'; + } + }; + + previewImageElement.src = previewImage; + previewImageElement.alt = 'Preview'; + if (previewWrapperRef.current) { + previewWrapperRef.current.appendChild(previewImageElement); + } + getReposData(taskInstance.id) .then((data): void => { if (data !== null && this.mounted) { @@ -135,11 +155,11 @@ export default class DetailsComponent extends React.PureComponent } private renderPreview(): JSX.Element { - const { previewImage } = this.props; + const { previewWrapperRef } = this; + + // Add image on mount after get its width and height to fit it into wrapper return ( -
- Preview -
+
); } diff --git a/cvat-ui/src/components/task-page/styles.scss b/cvat-ui/src/components/task-page/styles.scss index e7e850686f53..7feb0344f489 100644 --- a/cvat-ui/src/components/task-page/styles.scss +++ b/cvat-ui/src/components/task-page/styles.scss @@ -76,15 +76,14 @@ } .cvat-task-preview-wrapper { - display: flex; - justify-content: flex-start; overflow: hidden; margin-bottom: 20px; - - > .cvat-task-preview { - max-width: 252px; - max-height: 144px; - } + width: 252px; + height: 144px; + display: table-cell; + text-align: center; + vertical-align: middle; + background-color: $background-color-2; } .cvat-user-selector {