From 16c0c57359858aefd9759a50e3d21658064ff693 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 5 Dec 2019 13:41:58 +0300 Subject: [PATCH 01/17] Installed airbnb fullsettings --- .vscode/settings.json | 3 +- cvat-ui/.eslintrc.js | 2 +- cvat-ui/package-lock.json | 194 +++++++++++++++++++++++++++++++------- cvat-ui/package.json | 7 +- 4 files changed, 166 insertions(+), 40 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 971c0a063eec..f83561156008 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,8 @@ "eslint.enable": true, "eslint.validate": [ "javascript", - "typescript" + "typescript", + "typescriptreact", ], "eslint.workingDirectories": [ { diff --git a/cvat-ui/.eslintrc.js b/cvat-ui/.eslintrc.js index 48445ed0d297..31fededabe4e 100644 --- a/cvat-ui/.eslintrc.js +++ b/cvat-ui/.eslintrc.js @@ -19,7 +19,7 @@ module.exports = { ], 'extends': [ 'plugin:@typescript-eslint/recommended', - 'airbnb-typescript/base', + 'airbnb-typescript', 'plugin:import/errors', 'plugin:import/warnings', 'plugin:import/typescript', diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index 9422a2bc8456..a10d1935690b 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -899,6 +899,24 @@ } } }, + "@babel/runtime-corejs3": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.7.4.tgz", + "integrity": "sha512-BBIEhzk8McXDcB3IbOi8zQPzzINUp4zcLesVlBSOcyGhzPUU8Xezk5GAG7Sy5GVhGmAO0zGd2qRSeY2g4Obqxw==", + "dev": true, + "requires": { + "core-js-pure": "^3.0.0", + "regenerator-runtime": "^0.13.2" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", + "dev": true + } + } + }, "@babel/template": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz", @@ -1059,27 +1077,39 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.13.0.tgz", - "integrity": "sha512-WQHCozMnuNADiqMtsNzp96FNox5sOVpU8Xt4meaT4em8lOG1SrOv92/mUbEHQVh90sldKSfcOc/I0FOb/14G1g==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.10.0.tgz", + "integrity": "sha512-rT51fNLW0u3fnDGnAHVC5nu+Das+y2CpW10yqvf6/j5xbuUV3FxA3mBaIbM24CXODXjbgUznNb4Kg9XZOUxKAw==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "1.13.0", - "eslint-utils": "^1.3.1", + "@typescript-eslint/experimental-utils": "2.10.0", + "eslint-utils": "^1.4.3", "functional-red-black-tree": "^1.0.1", - "regexpp": "^2.0.1", - "tsutils": "^3.7.0" + "regexpp": "^3.0.0", + "tsutils": "^3.17.1" } }, "@typescript-eslint/experimental-utils": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz", - "integrity": "sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.10.0.tgz", + "integrity": "sha512-FZhWq6hWWZBP76aZ7bkrfzTMP31CCefVIImrwP3giPLcoXocmLTmr92NLZxuIcTL4GTEOE33jQMWy9PwelL+yQ==", "dev": true, "requires": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/typescript-estree": "1.13.0", - "eslint-scope": "^4.0.0" + "@typescript-eslint/typescript-estree": "2.10.0", + "eslint-scope": "^5.0.0" + }, + "dependencies": { + "eslint-scope": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + } } }, "@typescript-eslint/parser": { @@ -1092,22 +1122,70 @@ "@typescript-eslint/experimental-utils": "1.13.0", "@typescript-eslint/typescript-estree": "1.13.0", "eslint-visitor-keys": "^1.0.0" + }, + "dependencies": { + "@typescript-eslint/experimental-utils": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz", + "integrity": "sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "1.13.0", + "eslint-scope": "^4.0.0" + } + }, + "@typescript-eslint/typescript-estree": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz", + "integrity": "sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==", + "dev": true, + "requires": { + "lodash.unescape": "4.0.1", + "semver": "5.5.0" + } + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true + } } }, "@typescript-eslint/typescript-estree": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz", - "integrity": "sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.10.0.tgz", + "integrity": "sha512-oOYnplddQNm/LGVkqbkAwx4TIBuuZ36cAQq9v3nFIU9FmhemHuVzAesMSXNQDdAzCa5bFgCrfD3JWhYVKlRN2g==", "dev": true, "requires": { + "debug": "^4.1.1", + "eslint-visitor-keys": "^1.1.0", + "glob": "^7.1.6", + "is-glob": "^4.0.1", "lodash.unescape": "4.0.1", - "semver": "5.5.0" + "semver": "^6.3.0", + "tsutils": "^3.17.1" }, "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } @@ -1634,12 +1712,30 @@ "dev": true }, "axobject-query": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", - "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.1.1.tgz", + "integrity": "sha512-lF98xa/yvy6j3fBHAgQXIYl+J4eZadOSqsPojemUqClzNbBV38wWGpUbQbVEyf4eUF5yF7eHmGgGA2JiHyjeqw==", "dev": true, "requires": { - "ast-types-flow": "0.0.7" + "@babel/runtime": "^7.7.4", + "@babel/runtime-corejs3": "^7.7.4" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.4.tgz", + "integrity": "sha512-r24eVUUr0QqNZa+qrImUk8fn5SPhHq+IfYvIoIMg0do3GdK9sMdiLKP3GYVVaxpPKORgm8KRKaNTEhAjgIpLMw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", + "dev": true + } } }, "babel": { @@ -2580,6 +2676,12 @@ } } }, + "core-js-pure": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.4.7.tgz", + "integrity": "sha512-Am3uRS8WCdTFA3lP7LtKR0PxgqYzjAMGKXaZKSNSC/8sqU0Wfq8R/YzoRs2rqtOVEunfgH+0q3O0BKOg0AvjPw==", + "dev": true + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -3484,6 +3586,12 @@ } } }, + "eslint-plugin-eslint-plugin": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-eslint-plugin/-/eslint-plugin-eslint-plugin-2.1.0.tgz", + "integrity": "sha512-kT3A/ZJftt28gbl/Cv04qezb/NQ1dwYIbi8lyf806XMxkus7DvOVCLIfTXMrorp322Pnoez7+zabXH29tADIDg==", + "dev": true + }, "eslint-plugin-import": { "version": "2.18.2", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz", @@ -3538,20 +3646,21 @@ } }, "eslint-plugin-react": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.16.0.tgz", - "integrity": "sha512-GacBAATewhhptbK3/vTP09CbFrgUJmBSaaRcWdbQLFvUZy9yVcQxigBNHGPU/KE2AyHpzj3AWXpxoMTsIDiHug==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.17.0.tgz", + "integrity": "sha512-ODB7yg6lxhBVMeiH1c7E95FLD4E/TwmFjltiU+ethv7KPdCwgiFuOZg9zNRHyufStTDLl/dEFqI2Q1VPmCd78A==", "dev": true, "requires": { "array-includes": "^3.0.3", "doctrine": "^2.1.0", + "eslint-plugin-eslint-plugin": "^2.1.0", "has": "^1.0.3", - "jsx-ast-utils": "^2.2.1", + "jsx-ast-utils": "^2.2.3", "object.entries": "^1.1.0", - "object.fromentries": "^2.0.0", + "object.fromentries": "^2.0.1", "object.values": "^1.1.0", "prop-types": "^15.7.2", - "resolve": "^1.12.0" + "resolve": "^1.13.1" }, "dependencies": { "doctrine": { @@ -3562,9 +3671,24 @@ "requires": { "esutils": "^2.0.2" } + }, + "resolve": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.13.1.tgz", + "integrity": "sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } } } }, + "eslint-plugin-react-hooks": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz", + "integrity": "sha512-iXTCFcOmlWvw4+TOE8CLWj6yX1GwzT0Y6cUfHHZqWnSk144VmVIRcVGtUAzrLES7C798lmvnt02C7rxaOX1HNA==", + "dev": true + }, "eslint-scope": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", @@ -8141,9 +8265,9 @@ } }, "regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.0.0.tgz", + "integrity": "sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g==", "dev": true }, "regexpu-core": { @@ -9444,9 +9568,9 @@ "dev": true }, "typescript": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.4.tgz", - "integrity": "sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==", + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.3.tgz", + "integrity": "sha512-Mcr/Qk7hXqFBXMN7p7Lusj1ktCBydylfQM/FZCk5glCNQJrCUKPkMHdo9R0MTFWsC/4kPFvDS0fDPvukfCkFsw==", "dev": true }, "ua-parser-js": { diff --git a/cvat-ui/package.json b/cvat-ui/package.json index 50d78f82c35b..59b3196f2529 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -14,7 +14,7 @@ "@babel/preset-env": "^7.6.0", "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.6.0", - "@typescript-eslint/eslint-plugin": "^1.13.0", + "@typescript-eslint/eslint-plugin": "^2.10.0", "babel": "^6.23.0", "babel-loader": "^8.0.6", "babel-plugin-import": "^1.12.2", @@ -22,11 +22,12 @@ "eslint-config-airbnb-typescript": "^4.0.1", "eslint-plugin-import": "^2.18.2", "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-react": "^7.14.3", + "eslint-plugin-react": "^7.17.0", + "eslint-plugin-react-hooks": "^1.7.0", "html-webpack-plugin": "^3.2.0", "nodemon": "^1.19.2", "style-loader": "^1.0.0", - "typescript": "^3.6.3", + "typescript": "^3.7.3", "webpack": "^4.41.2", "webpack-cli": "^3.3.8", "webpack-dev-server": "^3.8.0" From 50ab7e77f987cbb724517baab3e543a0078ba4ee Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 5 Dec 2019 14:10:53 +0300 Subject: [PATCH 02/17] Fixed actions menu --- cvat-ui/.eslintrc.js | 4 ++ cvat-ui/src/actions/models-actions.ts | 2 +- .../components/actions-menu/actions-menu.tsx | 60 +++++++++++++------ .../components/actions-menu/dumper-item.tsx | 28 +++++---- .../components/actions-menu/export-item.tsx | 31 ++++++---- .../components/actions-menu/loader-item.tsx | 28 +++++---- 6 files changed, 99 insertions(+), 54 deletions(-) diff --git a/cvat-ui/.eslintrc.js b/cvat-ui/.eslintrc.js index 31fededabe4e..41e6a107fef1 100644 --- a/cvat-ui/.eslintrc.js +++ b/cvat-ui/.eslintrc.js @@ -26,6 +26,10 @@ module.exports = { ], 'rules': { '@typescript-eslint/indent': ['warn', 4], + 'react/jsx-indent': ['warn', 4], + 'react/jsx-indent-props': ['warn', 4], + 'jsx-quotes': ['error', 'prefer-single'], + 'arrow-parens': ['error', 'always'], '@typescript-eslint/no-explicit-any': [0], 'no-restricted-syntax': [0, {'selector': 'ForOfStatement'}], 'no-plusplus': [0], diff --git a/cvat-ui/src/actions/models-actions.ts b/cvat-ui/src/actions/models-actions.ts index 50fc54a9ad8c..e8b60da56096 100644 --- a/cvat-ui/src/actions/models-actions.ts +++ b/cvat-ui/src/actions/models-actions.ts @@ -431,7 +431,7 @@ async function timeoutCallback( dispatch(getInferenceStatusSuccess(taskID, activeInference)); } catch (error) { dispatch(getInferenceStatusFailed(taskID, new Error( - `Server request for the task ${taskID} was failed` + `Server request for the task ${taskID} was failed`, ))); } } diff --git a/cvat-ui/src/components/actions-menu/actions-menu.tsx b/cvat-ui/src/components/actions-menu/actions-menu.tsx index f6057cb8574b..49a32a1c99ef 100644 --- a/cvat-ui/src/components/actions-menu/actions-menu.tsx +++ b/cvat-ui/src/components/actions-menu/actions-menu.tsx @@ -36,14 +36,16 @@ interface MinActionsMenuProps { onOpenRunWindow: (taskInstance: any) => void; } -export function handleMenuClick(props: MinActionsMenuProps, params: ClickParam) { +export function handleMenuClick(props: MinActionsMenuProps, params: ClickParam): void { const { taskInstance } = props; const tracker = taskInstance.bugTracker; if (params.keyPath.length !== 2) { switch (params.key) { case 'tracker': { - window.open(`${tracker}`, '_blank') + // false positive eslint(security/detect-non-literal-fs-filename) + // eslint-disable-next-line + window.open(`${tracker}`, '_blank'); return; } case 'auto_annotation': { props.onOpenRunWindow(taskInstance); @@ -57,36 +59,51 @@ export function handleMenuClick(props: MinActionsMenuProps, params: ClickParam) props.onDeleteTask(taskInstance); }, }); - return; + break; } default: { - return; + // do nothing } } } } -export default function ActionsMenuComponent(props: ActionsMenuComponentProps) { - const tracker = props.taskInstance.bugTracker; - const renderModelRunner = props.installedAutoAnnotation || - props.installedTFAnnotation || props.installedTFSegmentation; +export default function ActionsMenuComponent(props: ActionsMenuComponentProps): JSX.Element { + const { + taskInstance, + installedAutoAnnotation, + installedTFAnnotation, + installedTFSegmentation, + dumpers, + loaders, + exporters, + inferenceIsActive, + } = props; + const tracker = taskInstance.bugTracker; + const renderModelRunner = installedAutoAnnotation + || installedTFAnnotation || installedTFSegmentation; return ( - handleMenuClick(props, params) - }> + handleMenuClick(props, params) + } + > { - props.dumpers.map((dumper) => DumperItemComponent({ + dumpers.map((dumper) => DumperItemComponent({ dumper, taskInstance: props.taskInstance, dumpActivity: (props.dumpActivities || []) .filter((_dumper: string) => _dumper === dumper.name)[0] || null, onDumpAnnotation: props.onDumpAnnotation, - } ))} + })) + } { - props.loaders.map((loader) => LoaderItemComponent({ + loaders.map((loader) => LoaderItemComponent({ loader, taskInstance: props.taskInstance, loadActivity: props.loadActivity, @@ -96,7 +113,7 @@ export default function ActionsMenuComponent(props: ActionsMenuComponentProps) { { - props.exporters.map((exporter) => ExportItemComponent({ + exporters.map((exporter) => ExportItemComponent({ exporter, taskInstance: props.taskInstance, exportActivity: (props.exportActivities || []) @@ -107,10 +124,17 @@ export default function ActionsMenuComponent(props: ActionsMenuComponentProps) { {tracker && Open bug tracker} { - renderModelRunner && - Automatic annotation + renderModelRunner + && ( + + Automatic annotation + + ) } -
+
Delete
); diff --git a/cvat-ui/src/components/actions-menu/dumper-item.tsx b/cvat-ui/src/components/actions-menu/dumper-item.tsx index ca862a8ec327..37db1a6c7f8a 100644 --- a/cvat-ui/src/components/actions-menu/dumper-item.tsx +++ b/cvat-ui/src/components/actions-menu/dumper-item.tsx @@ -20,25 +20,31 @@ function isDefaultFormat(dumperName: string, taskMode: string): boolean { || (dumperName === 'CVAT XML 1.1 for images' && taskMode === 'annotation'); } -export default function DumperItemComponent(props: DumperItemComponentProps) { - const task = props.taskInstance; - const { mode } = task; +export default function DumperItemComponent(props: DumperItemComponentProps): JSX.Element { + const { + taskInstance, + dumpActivity, + } = props; + const { mode } = taskInstance; const { dumper } = props; - const pending = !!props.dumpActivity; + const pending = !!dumpActivity; return ( - ); } - diff --git a/cvat-ui/src/components/actions-menu/export-item.tsx b/cvat-ui/src/components/actions-menu/export-item.tsx index 157fd495b227..9dba300bdfaa 100644 --- a/cvat-ui/src/components/actions-menu/export-item.tsx +++ b/cvat-ui/src/components/actions-menu/export-item.tsx @@ -15,24 +15,31 @@ interface DumperItemComponentProps { onExportDataset: (task: any, exporter: any) => void; } -export default function DumperItemComponent(props: DumperItemComponentProps) { - const task = props.taskInstance; - const { exporter } = props; - const pending = !!props.exportActivity; +export default function DumperItemComponent(props: DumperItemComponentProps): JSX.Element { + const { + taskInstance, + exporter, + exportActivity, + } = props; + + const pending = !!exportActivity; return ( - ); } - diff --git a/cvat-ui/src/components/actions-menu/loader-item.tsx b/cvat-ui/src/components/actions-menu/loader-item.tsx index f1e4ea46ce2c..5f736c7e8702 100644 --- a/cvat-ui/src/components/actions-menu/loader-item.tsx +++ b/cvat-ui/src/components/actions-menu/loader-item.tsx @@ -17,12 +17,15 @@ interface LoaderItemComponentProps { onLoadAnnotation: (taskInstance: any, loader: any, file: File) => void; } -export default function LoaderItemComponent(props: LoaderItemComponentProps) { - const { loader } = props; +export default function LoaderItemComponent(props: LoaderItemComponentProps): JSX.Element { + const { + loader, + loadActivity, + } = props; - const loadingWithThisLoader = props.loadActivity - && props.loadActivity === loader.name - ? props.loadActivity : null; + const loadingWithThisLoader = loadActivity + && loadActivity === loader.name + ? loadActivity : null; const pending = !!loadingWithThisLoader; @@ -31,8 +34,8 @@ export default function LoaderItemComponent(props: LoaderItemComponentProps) { { + showUploadList={false} + beforeUpload={(file: RcFile): boolean => { props.onLoadAnnotation( props.taskInstance, loader, @@ -40,13 +43,14 @@ export default function LoaderItemComponent(props: LoaderItemComponentProps) { ); return false; - }}> - ); -} \ No newline at end of file +} From 7c682d8f898afb34a7ee6cdd5ecd65c54f61f03b Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 5 Dec 2019 15:11:37 +0300 Subject: [PATCH 03/17] Create model/task page --- cvat-ui/.eslintrc.js | 2 + .../create-model-content.tsx | 96 +++++++----- .../create-model-page/create-model-form.tsx | 35 ++--- .../create-model-page/create-model-page.tsx | 16 +- .../advanced-configuration-form.tsx | 138 ++++++++++------- .../basic-configuration-form.tsx | 31 ++-- .../create-task-page/create-task-content.tsx | 143 ++++++++++-------- .../create-task-page/create-task-page.tsx | 14 +- 8 files changed, 281 insertions(+), 194 deletions(-) diff --git a/cvat-ui/.eslintrc.js b/cvat-ui/.eslintrc.js index 41e6a107fef1..90bb054240ad 100644 --- a/cvat-ui/.eslintrc.js +++ b/cvat-ui/.eslintrc.js @@ -33,6 +33,8 @@ module.exports = { '@typescript-eslint/no-explicit-any': [0], 'no-restricted-syntax': [0, {'selector': 'ForOfStatement'}], 'no-plusplus': [0], + 'lines-between-class-members': 0, + 'react/no-did-update-set-state': 0, // https://github.com/airbnb/javascript/issues/1875 }, 'settings': { 'import/resolver': { diff --git a/cvat-ui/src/components/create-model-page/create-model-content.tsx b/cvat-ui/src/components/create-model-page/create-model-content.tsx index ba40a87050ff..5951c2a17f16 100644 --- a/cvat-ui/src/components/create-model-page/create-model-content.tsx +++ b/cvat-ui/src/components/create-model-page/create-model-content.tsx @@ -14,10 +14,10 @@ import { import Text from 'antd/lib/typography/Text'; import CreateModelForm, { - CreateModelForm as WrappedCreateModelForm + CreateModelForm as WrappedCreateModelForm, } from './create-model-form'; import ConnectedFileManager, { - FileManagerContainer + FileManagerContainer, } from '../../containers/file-manager/file-manager'; import { ModelFiles } from '../../reducers/interfaces'; @@ -30,19 +30,32 @@ interface Props { export default class CreateModelContent extends React.PureComponent { private modelForm: WrappedCreateModelForm; private fileManagerContainer: FileManagerContainer; + public constructor(props: Props) { super(props); this.modelForm = null as any as WrappedCreateModelForm; this.fileManagerContainer = null as any as FileManagerContainer; } - private handleSubmitClick = () => { + public componentDidUpdate(prevProps: Props): void { + const { modelCreatingStatus } = this.props; + + if (prevProps.modelCreatingStatus !== 'CREATED' + && modelCreatingStatus === 'CREATED') { + message.success('The model has been uploaded'); + this.modelForm.resetFields(); + this.fileManagerContainer.reset(); + } + } + + private handleSubmitClick = (): void => { + const { createModel } = this.props; this.modelForm.submit() .then((data) => { const { local, share, - } = this.fileManagerContainer.getFiles(); + } = this.fileManagerContainer.getFiles(); const files = local.length ? local : share; const grouppedFiles: ModelFiles = { @@ -65,50 +78,51 @@ export default class CreateModelContent extends React.PureComponent { if (Object.keys(grouppedFiles) .map((key: string) => grouppedFiles[key]) .filter((val) => !!val).length !== 4) { - notification.error({ - message: 'Could not upload a model', - description: 'Please, specify correct files', - }); - } else { - this.props.createModel(data.name, grouppedFiles, data.global); - } + notification.error({ + message: 'Could not upload a model', + description: 'Please, specify correct files', + }); + } else { + createModel(data.name, grouppedFiles, data.global); + } }).catch(() => { notification.error({ message: 'Could not upload a model', description: 'Please, check input fields', }); - }) - } + }); + }; - public componentDidUpdate(prevProps: Props) { - if (prevProps.modelCreatingStatus !== 'CREATED' - && this.props.modelCreatingStatus === 'CREATED') { - message.success('The model has been uploaded'); - this.modelForm.resetFields(); - this.fileManagerContainer.reset(); - } - } - - public render() { - const loading = !!this.props.modelCreatingStatus - && this.props.modelCreatingStatus !== 'CREATED'; - const status = this.props.modelCreatingStatus - && this.props.modelCreatingStatus !== 'CREATED' ? this.props.modelCreatingStatus : ''; + public render(): JSX.Element { + const { + modelCreatingStatus, + } = this.props; + const loading = !!modelCreatingStatus + && modelCreatingStatus !== 'CREATED'; + const status = modelCreatingStatus + && modelCreatingStatus !== 'CREATED' ? modelCreatingStatus : ''; const guideLink = 'https://github.com/opencv/cvat/blob/develop/cvat/apps/auto_annotation/README.md'; return ( - { - window.open(guideLink, '_blank') - }} type='question-circle'/> + { + // false positive + // eslint-disable-next-line + window.open(guideLink, '_blank'); + }} + type='question-circle' + /> this.modelForm = ref + (ref: WrappedCreateModelForm): void => { + this.modelForm = ref; + } } /> @@ -117,13 +131,17 @@ export default class CreateModelContent extends React.PureComponent { Select files: - - this.fileManagerContainer = container - } withRemote={true}/> + { + this.fileManagerContainer = container; + } + } + withRemote + /> - {status && } + {status && } + > + Submit + ); } -} \ No newline at end of file +} diff --git a/cvat-ui/src/components/create-model-page/create-model-form.tsx b/cvat-ui/src/components/create-model-page/create-model-form.tsx index 1a1965933468..df42cdda16fe 100644 --- a/cvat-ui/src/components/create-model-page/create-model-form.tsx +++ b/cvat-ui/src/components/create-model-page/create-model-form.tsx @@ -15,13 +15,10 @@ import Text from 'antd/lib/typography/Text'; type Props = FormComponentProps; export class CreateModelForm extends React.PureComponent { - public constructor(props: Props) { - super(props); - } - - public submit(): Promise<{name: string, global: boolean}> { + public submit(): Promise<{name: string; global: boolean}> { + const { form } = this.props; return new Promise((resolve, reject) => { - this.props.form.validateFields((errors, values) => { + form.validateFields((errors, values) => { if (!errors) { resolve({ name: values.name, @@ -34,15 +31,17 @@ export class CreateModelForm extends React.PureComponent { }); } - public resetFields() { - this.props.form.resetFields(); + public resetFields(): void { + const { form } = this.props; + form.resetFields(); } - public render() { - const { getFieldDecorator } = this.props.form; + public render(): JSX.Element { + const { form } = this.props; + const { getFieldDecorator } = form; return ( -
e.preventDefault()}> + e.preventDefault()}> * @@ -55,7 +54,7 @@ export class CreateModelForm extends React.PureComponent { required: true, message: 'Please, specify a model name', }], - })()} + })()} @@ -64,11 +63,13 @@ export class CreateModelForm extends React.PureComponent { { getFieldDecorator('global', { initialValue: false, valuePropName: 'checked', - })( - - Load globally - - )} + })( + + + Load globally + + , + )} diff --git a/cvat-ui/src/components/create-model-page/create-model-page.tsx b/cvat-ui/src/components/create-model-page/create-model-page.tsx index e58a8ff1c928..0d9d663dc846 100644 --- a/cvat-ui/src/components/create-model-page/create-model-page.tsx +++ b/cvat-ui/src/components/create-model-page/create-model-page.tsx @@ -16,17 +16,23 @@ interface Props { modelCreatingStatus: string; } -export default function CreateModelPageComponent(props: Props) { +export default function CreateModelPageComponent(props: Props): JSX.Element { + const { + isAdmin, + modelCreatingStatus, + createModel, + } = props; + return ( Upload a new model ); -} \ No newline at end of file +} 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 56cc88da846a..29fcadcc4c39 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 @@ -28,19 +28,24 @@ export interface AdvancedConfiguration { } type Props = FormComponentProps & { - onSubmit(values: AdvancedConfiguration): void + onSubmit(values: AdvancedConfiguration): void; installedGit: boolean; }; class AdvancedConfigurationForm extends React.PureComponent { - public submit() { + public submit(): Promise { return new Promise((resolve, reject) => { - this.props.form.validateFields((error, values) => { + const { + form, + onSubmit, + } = this.props; + + form.validateFields((error, values) => { if (!error) { const filteredValues = { ...values }; delete filteredValues.frameStep; - this.props.onSubmit({ + onSubmit({ ...values, frameFilter: values.frameStep ? `step=${values.frameStep}` : undefined, }); @@ -49,17 +54,19 @@ class AdvancedConfigurationForm extends React.PureComponent { reject(); } }); - }) + }); } - public resetFields() { - this.props.form.resetFields(); + public resetFields(): void { + const { form } = this.props; + form.resetFields(); } - private renderZOrder() { + private renderZOrder(): JSX.Element { + const { form } = this.props; return ( - {this.props.form.getFieldDecorator('zOrder', { + {form.getFieldDecorator('zOrder', { initialValue: false, valuePropName: 'checked', })( @@ -67,21 +74,23 @@ class AdvancedConfigurationForm extends React.PureComponent { Z-order - + , )} ); } - private renderImageQuality() { + private renderImageQuality(): JSX.Element { + const { form } = this.props; + return ( - {this.props.form.getFieldDecorator('imageQuality', { + {form.getFieldDecorator('imageQuality', { initialValue: 70, rules: [{ required: true, - message: 'This field is required' + message: 'This field is required', }], })( { type='number' min={5} max={100} - suffix={} - /> + suffix={} + />, )} ); } - private renderOverlap() { + private renderOverlap(): JSX.Element { + const { form } = this.props; + return ( - {this.props.form.getFieldDecorator('overlapSize')( - + {form.getFieldDecorator('overlapSize')( + , )} ); } - private renderSegmentSize() { + private renderSegmentSize(): JSX.Element { + const { form } = this.props; + return ( - {this.props.form.getFieldDecorator('segmentSize')( - + {form.getFieldDecorator('segmentSize')( + , )} ); } - private renderStartFrame() { + private renderStartFrame(): JSX.Element { + const { form } = this.props; + return ( - {this.props.form.getFieldDecorator('startFrame')( + {form.getFieldDecorator('startFrame')( + />, )} ); } - private renderStopFrame() { + private renderStopFrame(): JSX.Element { + const { form } = this.props; + return ( - {this.props.form.getFieldDecorator('stopFrame')( + {form.getFieldDecorator('stopFrame')( + />, )} ); } - private renderFrameStep() { + private renderFrameStep(): JSX.Element { + const { form } = this.props; + return ( - {this.props.form.getFieldDecorator('frameStep')( + {form.getFieldDecorator('frameStep')( + />, )} ); } - private renderGitLFSBox() { + private renderGitLFSBox(): JSX.Element { + const { form } = this.props; + return ( - {this.props.form.getFieldDecorator('lfs', { + {form.getFieldDecorator('lfs', { valuePropName: 'checked', initialValue: false, })( @@ -177,22 +198,24 @@ class AdvancedConfigurationForm extends React.PureComponent { Use LFS (Large File Support): - + , )} ); } - private renderGitRepositoryURL() { + private renderGitRepositoryURL(): JSX.Element { + const { form } = this.props; + return ( - {this.props.form.getFieldDecorator('repository', { + {form.getFieldDecorator('repository', { rules: [{ - validator: (_, value, callback) => { + validator: (_, value, callback): void => { if (!value) { callback(); } else { @@ -207,18 +230,19 @@ class AdvancedConfigurationForm extends React.PureComponent { callback(); } - } - }] + }, + }], })( - + />, )} ); } - private renderGit() { + private renderGit(): JSX.Element { return ( <> @@ -235,36 +259,42 @@ class AdvancedConfigurationForm extends React.PureComponent { ); } - private renderBugTracker() { + private renderBugTracker(): JSX.Element { + const { form } = this.props; + return ( - {this.props.form.getFieldDecorator('bugTracker', { + {form.getFieldDecorator('bugTracker', { rules: [{ - validator: (_, value, callback) => { + validator: (_, value, callback): void => { if (value && !patterns.validateURL.pattern.test(value)) { callback('Issue tracker must be URL'); } else { callback(); } - } - }] + }, + }], })( - + , )} - ) + ); } - public render() { + public render(): JSX.Element { + const { installedGit } = this.props; + return ( - - {this.renderZOrder()} - + + + {this.renderZOrder()} + + @@ -290,11 +320,11 @@ class AdvancedConfigurationForm extends React.PureComponent { - { this.props.installedGit ? this.renderGit() : null} + { installedGit ? this.renderGit() : null} - {this.renderBugTracker()} + {this.renderBugTracker()} diff --git a/cvat-ui/src/components/create-task-page/basic-configuration-form.tsx b/cvat-ui/src/components/create-task-page/basic-configuration-form.tsx index e0b877a45072..805000f4b842 100644 --- a/cvat-ui/src/components/create-task-page/basic-configuration-form.tsx +++ b/cvat-ui/src/components/create-task-page/basic-configuration-form.tsx @@ -4,7 +4,6 @@ import { Input, } from 'antd'; -import Text from 'antd/lib/typography/Text'; import Form, { FormComponentProps } from 'antd/lib/form/Form'; export interface BaseConfiguration { @@ -16,11 +15,16 @@ type Props = FormComponentProps & { }; class BasicConfigurationForm extends React.PureComponent { - public submit() { + public submit(): Promise { return new Promise((resolve, reject) => { - this.props.form.validateFields((error, values) => { + const { + form, + onSubmit, + } = this.props; + + form.validateFields((error, values) => { if (!error) { - this.props.onSubmit({ + onSubmit({ name: values.name, }); resolve(); @@ -28,25 +32,28 @@ class BasicConfigurationForm extends React.PureComponent { reject(); } }); - }) + }); } - public resetFields() { - this.props.form.resetFields(); + public resetFields(): void { + const { form } = this.props; + form.resetFields(); } - public render() { - const { getFieldDecorator } = this.props.form; + public render(): JSX.Element { + const { form } = this.props; + const { getFieldDecorator } = form; + return ( -
e.preventDefault()}> + e.preventDefault()}> { getFieldDecorator('name', { rules: [{ required: true, message: 'Please, specify a name', - }] + }], })( - + , ) }
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 57801a1b8ba8..07f3fb798ac9 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 @@ -21,7 +21,7 @@ export interface CreateTaskData { basic: BaseConfiguration; advanced: AdvancedConfiguration; labels: any[]; - files: Files, + files: Files; } interface Props { @@ -58,11 +58,33 @@ export default class CreateTaskContent extends React.PureComponent this.state = { ...defaultState }; } - private validateLabels = () => { - return !!this.state.labels.length; + public componentDidUpdate(prevProps: Props): void { + const { status } = this.props; + + if (status === 'CREATED' && prevProps.status !== 'CREATED') { + notification.info({ + message: 'The task has been created', + }); + + this.basicConfigurationComponent.resetFields(); + if (this.advancedConfigurationComponent) { + this.advancedConfigurationComponent.resetFields(); + } + + this.fileManagerContainer.reset(); + + this.setState({ + ...defaultState, + }); + } } - private validateFiles = () => { + private validateLabels = (): boolean => { + const { labels } = this.state; + return !!labels.length; + }; + + private validateFiles = (): boolean => { const files = this.fileManagerContainer.getFiles(); this.setState({ files, @@ -72,21 +94,21 @@ export default class CreateTaskContent extends React.PureComponent ); return !!totalLen; - } + }; - private handleSubmitBasicConfiguration = (values: BaseConfiguration) => { + private handleSubmitBasicConfiguration = (values: BaseConfiguration): void => { this.setState({ - basic: {...values}, + basic: { ...values }, }); }; - private handleSubmitAdvancedConfiguration = (values: AdvancedConfiguration) => { + private handleSubmitAdvancedConfiguration = (values: AdvancedConfiguration): void => { this.setState({ - advanced: {...values}, + advanced: { ...values }, }); }; - private handleSubmitClick = () => { + private handleSubmitClick = (): void => { if (!this.validateLabels()) { notification.error({ message: 'Could not create a task', @@ -105,44 +127,50 @@ export default class CreateTaskContent extends React.PureComponent this.basicConfigurationComponent.submit() .then(() => { - return this.advancedConfigurationComponent ? - this.advancedConfigurationComponent.submit() : - new Promise((resolve) => { - resolve(); - }) - }) - .then(() => { - this.props.onCreate(this.state); - }) - .catch((_: any) => { + if (this.advancedConfigurationComponent) { + return this.advancedConfigurationComponent.submit(); + } + + return new Promise((resolve) => { + resolve(); + }); + }).then(() => { + const { onCreate } = this.props; + onCreate(this.state); + }).catch(() => { notification.error({ message: 'Could not create a task', description: 'Please, check configuration you specified', }); }); - } + }; - private renderBasicBlock() { + private renderBasicBlock(): JSX.Element { return ( - { this.basicConfigurationComponent = component } - } onSubmit={this.handleSubmitBasicConfiguration}/> + { this.basicConfigurationComponent = component; } + } + onSubmit={this.handleSubmitBasicConfiguration} + /> ); } - private renderLabelsBlock() { + private renderLabelsBlock(): JSX.Element { + const { labels } = this.state; + return ( * Labels: { + (newLabels): void => { this.setState({ - labels, + labels: newLabels, }); } } @@ -151,32 +179,37 @@ export default class CreateTaskContent extends React.PureComponent ); } - private renderFilesBlock() { + private renderFilesBlock(): JSX.Element { return ( * Select files: - - this.fileManagerContainer = container - } withRemote={true}/> + { this.fileManagerContainer = container; } + } + withRemote + /> ); } - private renderAdvancedBlock() { + private renderAdvancedBlock(): JSX.Element { + const { installedGit } = this.props; return ( Advanced configuration - } key='1'> + } + > { - this.advancedConfigurationComponent = component + (component: any): void => { + this.advancedConfigurationComponent = component; } } onSubmit={this.handleSubmitAdvancedConfiguration} @@ -187,29 +220,9 @@ export default class CreateTaskContent extends React.PureComponent ); } - public componentDidUpdate(prevProps: Props) { - if (this.props.status === 'CREATED' && prevProps.status !== 'CREATED') { - notification.info({ - message: 'The task has been created', - }); - - this.basicConfigurationComponent.resetFields(); - if (this.advancedConfigurationComponent) { - this.advancedConfigurationComponent.resetFields(); - } - - this.fileManagerContainer.reset(); - - this.setState({ - ...defaultState, - }); - } - } - - public render() { - const loading = !!this.props.status - && this.props.status !== 'CREATED' - && this.props.status !== 'FAILED'; + public render(): JSX.Element { + const { status } = this.props; + const loading = !!status && status !== 'CREATED' && status !== 'FAILED'; return ( @@ -223,7 +236,7 @@ export default class CreateTaskContent extends React.PureComponent { this.renderAdvancedBlock() } - {loading ? : null} + {loading ? : null} + > + Submit + ); diff --git a/cvat-ui/src/components/create-task-page/create-task-page.tsx b/cvat-ui/src/components/create-task-page/create-task-page.tsx index 2287ab5c51a2..ad4ae6d723ce 100644 --- a/cvat-ui/src/components/create-task-page/create-task-page.tsx +++ b/cvat-ui/src/components/create-task-page/create-task-page.tsx @@ -15,15 +15,21 @@ interface Props { installedGit: boolean; } -export default function CreateTaskPage(props: Props) { +export default function CreateTaskPage(props: Props): JSX.Element { + const { + status, + onCreate, + installedGit, + } = props; + return ( Create a new task From 608fbe3e92ecc0415113cb7061da12d3ddbc95e9 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 5 Dec 2019 15:47:25 +0300 Subject: [PATCH 04/17] File manager, header --- .../components/file-manager/file-manager.tsx | 207 +++++++++++------- cvat-ui/src/components/header/header.tsx | 142 ++++++++---- 2 files changed, 221 insertions(+), 128 deletions(-) diff --git a/cvat-ui/src/components/file-manager/file-manager.tsx b/cvat-ui/src/components/file-manager/file-manager.tsx index e34aeaba6673..3c3eca0d5bbb 100644 --- a/cvat-ui/src/components/file-manager/file-manager.tsx +++ b/cvat-ui/src/components/file-manager/file-manager.tsx @@ -44,35 +44,61 @@ export default class FileManager extends React.PureComponent { }; this.loadData('/'); - }; + } - private loadData = (key: string) => { - const promise = new Promise((resolve, reject) => { - const success = () => resolve(); - const failure = () => reject(); - this.props.onLoadData(key, success, failure); - }); + public getFiles(): Files { + const { + active, + files, + } = this.state; + return { + local: active === 'local' ? files.local : [], + share: active === 'share' ? files.share : [], + remote: active === 'remote' ? files.remote : [], + }; + } + + private loadData = (key: string): Promise => new Promise( + (resolve, reject) => { + const { onLoadData } = this.props; + + const success = (): void => resolve(); + const failure = (): void => reject(); + onLoadData(key, success, failure); + }, + ); - return promise; + public reset(): void { + this.setState({ + expandedKeys: [], + active: 'local', + files: { + local: [], + share: [], + remote: [], + }, + }); } - private renderLocalSelector() { + private renderLocalSelector(): JSX.Element { + const { files } = this.state; + return ( { + beforeUpload={(_: RcFile, newLocalFiles: RcFile[]): boolean => { this.setState({ files: { - ...this.state.files, - local: files + ...files, + local: newLocalFiles, }, }); return false; - } - }> + }} + >

@@ -81,115 +107,126 @@ export default class FileManager extends React.PureComponent { Support for a bulk images or a single video

- { !!this.state.files.local.length && - <> -
- - {`${this.state.files.local.length} file(s) selected`} - - + { !!files.local.length + && ( + <> +
+ + {`${files.local.length} file(s) selected`} + + + ) }
); } - private renderShareSelector() { - function renderTreeNodes(data: TreeNodeNormal[]) { + private renderShareSelector(): JSX.Element { + function renderTreeNodes(data: TreeNodeNormal[]): JSX.Element[] { return data.map((item: TreeNodeNormal) => { if (item.children) { - return ( - - {renderTreeNodes(item.children)} - - ); + return ( + + {renderTreeNodes(item.children)} + + ); } return ; }); } + const { treeData } = this.props; + const { + expandedKeys, + files, + } = this.state; + return ( - { this.props.treeData.length ? - { - return this.loadData(node.props.dataRef.key); - }} - onExpand={(expandedKeys: string[]) => { - this.setState({ - expandedKeys, - }); - }} - onCheck={(checkedKeys: string[] | {checked: string[], halfChecked: string[]}) => { - const keys = checkedKeys as string[]; - this.setState({ - files: { - ...this.state.files, - share: keys, - }, - }); - }}> - { renderTreeNodes(this.props.treeData) } - : {'No data found'} + { treeData.length + ? ( + => this.loadData( + node.props.dataRef.key, + )} + onExpand={(newExpandedKeys: string[]): void => { + this.setState({ + expandedKeys: newExpandedKeys, + }); + }} + onCheck={ + (checkedKeys: string[] | { + checked: string[]; + halfChecked: string[]; + }): void => { + const keys = checkedKeys as string[]; + this.setState({ + files: { + ...files, + share: keys, + }, + }); + }} + > + { renderTreeNodes(treeData) } + + ) : No data found } ); } - private renderRemoteSelector() { + private renderRemoteSelector(): JSX.Element { + const { files } = this.state; + return ( ) => { + value={[...files.remote].join('\n')} + onChange={(event: React.ChangeEvent): void => { this.setState({ files: { - ...this.state.files, + ...files, remote: event.target.value.split('\n'), }, }); - }}/> + }} + /> ); } - public getFiles(): Files { - return { - local: this.state.active === 'local' ? this.state.files.local : [], - share: this.state.active === 'share' ? this.state.files.share : [], - remote: this.state.active === 'remote' ? this.state.files.remote : [], - }; - } - - public reset() { - this.setState({ - expandedKeys: [], - active: 'local', - files: { - local: [], - share: [], - remote: [], - }, - }); - } + public render(): JSX.Element { + const { withRemote } = this.props; - public render() { return ( <> - this.setState({ - active: activeKey as any, - })}> + this.setState({ + active: activeKey as any, + }) + } + > { this.renderLocalSelector() } { this.renderShareSelector() } - { this.props.withRemote && this.renderRemoteSelector() } + { withRemote && this.renderRemoteSelector() } ); diff --git a/cvat-ui/src/components/header/header.tsx b/cvat-ui/src/components/header/header.tsx index 451d93fd9333..707d7de6a48d 100644 --- a/cvat-ui/src/components/header/header.tsx +++ b/cvat-ui/src/components/header/header.tsx @@ -15,6 +15,7 @@ import Text from 'antd/lib/typography/Text'; import getCore from '../../core'; const core = getCore(); +const serverHost = core.config.backendAPI.slice(0, -7); interface HeaderContainerProps { onLogout: () => void; @@ -28,66 +29,121 @@ interface HeaderContainerProps { type Props = HeaderContainerProps & RouteComponentProps; -const cvatLogo = () => ; -const userLogo = () => ; +const cvatLogo = (): JSX.Element => ; +const userLogo = (): JSX.Element => ; + +function HeaderContainer(props: Props): JSX.Element { + const { + installedTFSegmentation, + installedAutoAnnotation, + installedTFAnnotation, + installedAnalytics, + username, + onLogout, + logoutFetching, + } = props; + + const renderModels = installedAutoAnnotation + || installedTFAnnotation + || installedTFSegmentation; -function HeaderContainer(props: Props) { - const renderModels = props.installedAutoAnnotation - || props.installedTFAnnotation - || props.installedTFSegmentation; return (
- + - - { renderModels ? - : null + + { renderModels + && ( + + ) } - { props.installedAnalytics ? - : null + { installedAnalytics + && ( + + ) }
- - + } + > + + Help + - - - - - {props.username.length > 14 ? `${props.username.slice(0, 10)} ...` : props.username} - - - - - }> + + + + + {username.length > 14 ? `${username.slice(0, 10)} ...` : username} + + + + + ) + } + > - {props.logoutFetching && } Logout + {logoutFetching && } + Logout From be79210db6e45b13b0920aeedc51c2c7fe6ad057 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 5 Dec 2019 16:55:22 +0300 Subject: [PATCH 05/17] Labels editor --- .../labels-editor/constructor-creator.tsx | 27 +- .../labels-editor/constructor-updater.tsx | 28 +- .../labels-editor/constructor-viewer-item.tsx | 41 ++- .../labels-editor/constructor-viewer.tsx | 16 +- .../components/labels-editor/label-form.tsx | 308 ++++++++++-------- .../labels-editor/labels-editor.tsx | 281 +++++++++------- .../components/labels-editor/raw-viewer.tsx | 81 +++-- 7 files changed, 440 insertions(+), 342 deletions(-) diff --git a/cvat-ui/src/components/labels-editor/constructor-creator.tsx b/cvat-ui/src/components/labels-editor/constructor-creator.tsx index f3f189d38ccd..d49c04140aea 100644 --- a/cvat-ui/src/components/labels-editor/constructor-creator.tsx +++ b/cvat-ui/src/components/labels-editor/constructor-creator.tsx @@ -1,26 +1,17 @@ import React from 'react'; -import LabelForm from './label-form'; -import { Label, Attribute } from './common'; +import LabelForm from './label-form'; +import { Label } from './common'; interface Props { onCreate: (label: Label | null) => void; } -interface State { - attributes: Attribute[]; -} - -export default class ConstructorCreator extends React.PureComponent { - public constructor(props: Props) { - super(props); - } - - public render() { - return ( -
- -
- ); - } +export default function ConstructorCreator(props: Props): JSX.Element { + const { onCreate } = props; + return ( +
+ +
+ ); } diff --git a/cvat-ui/src/components/labels-editor/constructor-updater.tsx b/cvat-ui/src/components/labels-editor/constructor-updater.tsx index c87e3d758ea0..048b31075c40 100644 --- a/cvat-ui/src/components/labels-editor/constructor-updater.tsx +++ b/cvat-ui/src/components/labels-editor/constructor-updater.tsx @@ -1,28 +1,22 @@ import React from 'react'; import LabelForm from './label-form'; -import { Label, Attribute } from './common'; +import { Label } from './common'; interface Props { label: Label; onUpdate: (label: Label | null) => void; } -interface State { - savedAttributes: Attribute[]; - unsavedAttributes: Attribute[]; -} - -export default class ConstructorUpdater extends React.PureComponent { - constructor(props: Props) { - super(props); - } +export default function ConstructorUpdater(props: Props): JSX.Element { + const { + label, + onUpdate, + } = props; - public render() { - return ( -
- -
- ); - } + return ( +
+ +
+ ); } diff --git a/cvat-ui/src/components/labels-editor/constructor-viewer-item.tsx b/cvat-ui/src/components/labels-editor/constructor-viewer-item.tsx index 272b380f3072..983cf814a3b8 100644 --- a/cvat-ui/src/components/labels-editor/constructor-viewer-item.tsx +++ b/cvat-ui/src/components/labels-editor/constructor-viewer-item.tsx @@ -16,21 +16,40 @@ interface ConstructorViewerItemProps { onDelete: (label: Label) => void; } -export default function ConstructorViewerItem(props: ConstructorViewerItemProps) { +export default function ConstructorViewerItem(props: ConstructorViewerItemProps): JSX.Element { + const { + color, + label, + onUpdate, + onDelete, + } = props; + return ( -
- { props.label.name } +
+ {label.name} - props.onUpdate(props.label)}> - + onUpdate(label)} + onKeyPress={(): boolean => false} + > + - { props.label.id >= 0 ? null : - - props.onDelete(props.label)}> - - - + { label.id < 0 + && ( + + onDelete(label)} + onKeyPress={(): boolean => false} + > + + + + ) }
); diff --git a/cvat-ui/src/components/labels-editor/constructor-viewer.tsx b/cvat-ui/src/components/labels-editor/constructor-viewer.tsx index 7c12983e590e..138ef61f7d59 100644 --- a/cvat-ui/src/components/labels-editor/constructor-viewer.tsx +++ b/cvat-ui/src/components/labels-editor/constructor-viewer.tsx @@ -23,7 +23,7 @@ const colors = [ let currentColor = 0; -function nextColor() { +function nextColor(): string { const color = colors[currentColor]; currentColor += 1; if (currentColor >= colors.length) { @@ -32,12 +32,14 @@ function nextColor() { return color; } -export default function ConstructorViewer(props: ConstructorViewerProps) { +export default function ConstructorViewer(props: ConstructorViewerProps): JSX.Element { + const { onCreate } = props; currentColor = 0; const list = [ - ]; for (const label of props.labels) { list.push( @@ -47,8 +49,8 @@ export default function ConstructorViewer(props: ConstructorViewerProps) { label={label} key={label.id} color={nextColor()} - /> - ) + />, + ); } return ( @@ -56,4 +58,4 @@ export default function ConstructorViewer(props: ConstructorViewerProps) { { list }
); -} \ No newline at end of file +} diff --git a/cvat-ui/src/components/labels-editor/label-form.tsx b/cvat-ui/src/components/labels-editor/label-form.tsx index f9870c653ab3..8f463c6b2e0c 100644 --- a/cvat-ui/src/components/labels-editor/label-form.tsx +++ b/cvat-ui/src/components/labels-editor/label-form.tsx @@ -35,73 +35,76 @@ type Props = FormComponentProps & { onSubmit: (label: Label | null) => void; }; -interface State { - -} - -class LabelForm extends React.PureComponent { +class LabelForm extends React.PureComponent { private continueAfterSubmit: boolean; constructor(props: Props) { super(props); - this.continueAfterSubmit = false; } - private handleSubmit = (e: React.FormEvent) => { + private handleSubmit = (e: React.FormEvent): void => { e.preventDefault(); - this.props.form.validateFields((error, values) => { + + const { + form, + label, + onSubmit, + } = this.props; + + form.validateFields((error, values) => { if (!error) { - this.props.onSubmit({ + onSubmit({ name: values.labelName, - id: this.props.label ? this.props.label.id : idGenerator(), - attributes: values.keys.map((key: number, index: number) => { - return { + id: label ? label.id : idGenerator(), + attributes: values.keys.map((key: number, index: number) => ( + { name: values.attrName[key], type: values.type[key], mutable: values.mutable[key], - id: this.props.label && index < this.props.label.attributes.length - ? this.props.label.attributes[index].id : key, + id: label && index < label.attributes.length + ? label.attributes[index].id : key, values: Array.isArray(values.values[key]) - ? values.values[key] : [values.values[key]] - }; - }), + ? values.values[key] : [values.values[key]], + } + )), }); - this.props.form.resetFields(); + form.resetFields(); if (!this.continueAfterSubmit) { - this.props.onSubmit(null); + onSubmit(null); } } }); - } + }; - private addAttribute = () => { + private addAttribute = (): void => { const { form } = this.props; const keys = form.getFieldValue('keys'); const nextKeys = keys.concat(idGenerator()); form.setFieldsValue({ keys: nextKeys, }); - } + }; - private removeAttribute = (key: number) => { + private removeAttribute = (key: number): void => { const { form } = this.props; const keys = form.getFieldValue('keys'); form.setFieldsValue({ keys: keys.filter((_key: number) => _key !== key), }); - } + }; - private renderAttributeNameInput(key: number, attr: Attribute | null) { + private renderAttributeNameInput(key: number, attr: Attribute | null): JSX.Element { const locked = attr ? attr.id >= 0 : false; const value = attr ? attr.name : ''; + const { form } = this.props; return ( - { - this.props.form.getFieldDecorator(`attrName[${key}]`, { + + {form.getFieldDecorator(`attrName[${key}]`, { initialValue: value, rules: [{ required: true, @@ -110,42 +113,54 @@ class LabelForm extends React.PureComponent { pattern: patterns.validateAttributeName.pattern, message: patterns.validateAttributeName.message, }], - })() - } + })()} + ); } - private renderAttributeTypeInput(key: number, attr: Attribute | null) { + private renderAttributeTypeInput(key: number, attr: Attribute | null): JSX.Element { const locked = attr ? attr.id >= 0 : false; const type = attr ? attr.type.toUpperCase() : AttributeType.SELECT; + const { form } = this.props; return ( - {this.props.form.getFieldDecorator(`type[${key}]`, { + { form.getFieldDecorator(`type[${key}]`, { initialValue: type, })( - ) - } + + Select + + + Radio + + + Checkbox + + + Text + + + Number + + , + )} + ); } - private renderAttributeValuesInput(key: number, attr: Attribute | null) { + private renderAttributeValuesInput(key: number, attr: Attribute | null): JSX.Element { const locked = attr ? attr.id >= 0 : false; const existedValues = attr ? attr.values : []; + const { form } = this.props; - const validator = (_: any, values: string[], callback: any) => { + const validator = (_: any, values: string[], callback: any): void => { if (locked && existedValues) { if (!equalArrayHead(existedValues, values)) { callback('You can only append new values'); @@ -159,11 +174,11 @@ class LabelForm extends React.PureComponent { } callback(); - } + }; return ( - { this.props.form.getFieldDecorator(`values[${key}]`, { + { form.getFieldDecorator(`values[${key}]`, { initialValue: existedValues, rules: [{ required: true, @@ -174,37 +189,41 @@ class LabelForm extends React.PureComponent { })( False True - + , )} ); } - private renderNumberRangeInput(key: number, attr: Attribute | null) { + private renderNumberRangeInput(key: number, attr: Attribute | null): JSX.Element { const locked = attr ? attr.id >= 0 : false; const value = attr ? attr.values[0] : ''; + const { form } = this.props; - const validator = (_: any, value: string, callback: any) => { - const numbers = value.split(';').map((number) => Number.parseFloat(number)); + const validator = (_: any, strNumbers: string, callback: any): void => { + const numbers = strNumbers + .split(';') + .map((number) => Number.parseFloat(number)); if (numbers.length !== 3) { callback('Invalid input'); } @@ -224,58 +243,60 @@ class LabelForm extends React.PureComponent { } callback(); - } + }; return ( - { this.props.form.getFieldDecorator(`values[${key}]`, { + { form.getFieldDecorator(`values[${key}]`, { initialValue: value, rules: [{ required: true, message: 'Please set a range', }, { validator, - }] + }], })( - + , )} ); } - private renderDefaultValueInput(key: number, attr: Attribute | null) { + private renderDefaultValueInput(key: number, attr: Attribute | null): JSX.Element { const value = attr ? attr.values[0] : ''; + const { form } = this.props; return ( - { this.props.form.getFieldDecorator(`values[${key}]`, { + { form.getFieldDecorator(`values[${key}]`, { initialValue: value, })( - + , )} ); } - private renderMutableAttributeInput(key: number, attr: Attribute | null) { + private renderMutableAttributeInput(key: number, attr: Attribute | null): JSX.Element { const locked = attr ? attr.id >= 0 : false; const value = attr ? attr.mutable : false; + const { form } = this.props; return ( - { this.props.form.getFieldDecorator(`mutable[${key}]`, { + { form.getFieldDecorator(`mutable[${key}]`, { initialValue: value, valuePropName: 'checked', })( - Mutable + Mutable , )} ); } - private renderDeleteAttributeButton(key: number, attr: Attribute | null) { + private renderDeleteAttributeButton(key: number, attr: Attribute | null): JSX.Element { const locked = attr ? attr.id >= 0 : false; return ( @@ -285,20 +306,25 @@ class LabelForm extends React.PureComponent { type='link' className='cvat-delete-attribute-button' disabled={locked} - onClick={() => { + onClick={(): void => { this.removeAttribute(key); }} > - + ); } - private renderAttribute = (key: number, index: number) => { - const attr = (this.props.label && index < this.props.label.attributes.length - ? this.props.label.attributes[index] + private renderAttribute = (key: number, index: number): JSX.Element => { + const { + label, + form, + } = this.props; + + const attr = (label && index < label.attributes.length + ? label.attributes[index] : null); return ( @@ -306,23 +332,23 @@ class LabelForm extends React.PureComponent { { this.renderAttributeNameInput(key, attr) } { this.renderAttributeTypeInput(key, attr) } - { - (() => { - const type = this.props.form.getFieldValue(`type[${key}]`); + + {((): JSX.Element => { + const type = form.getFieldValue(`type[${key}]`); let element = null; - - [AttributeType.SELECT, AttributeType.RADIO] - .includes(type) ? - element = this.renderAttributeValuesInput(key, attr) - : type === AttributeType.CHECKBOX ? - element = this.renderBooleanValueInput(key, attr) - : type === AttributeType.NUMBER ? - element = this.renderNumberRangeInput(key, attr) - : element = this.renderDefaultValueInput(key, attr) + if ([AttributeType.SELECT, AttributeType.RADIO].includes(type)) { + element = this.renderAttributeValuesInput(key, attr); + } else if (type === AttributeType.CHECKBOX) { + element = this.renderBooleanValueInput(key, attr); + } else if (type === AttributeType.NUMBER) { + element = this.renderNumberRangeInput(key, attr); + } else { + element = this.renderDefaultValueInput(key, attr); + } return element; - })() - } + })()} + { this.renderMutableAttributeInput(key, attr) } @@ -332,16 +358,20 @@ class LabelForm extends React.PureComponent { ); - } + }; - private renderLabelNameInput() { - const value = this.props.label ? this.props.label.name : ''; - const locked = this.props.label ? this.props.label.id >= 0 : false; + private renderLabelNameInput(): JSX.Element { + const { + label, + form, + } = this.props; + const value = label ? label.name : ''; + const locked = label ? label.id >= 0 : false; return ( - { - this.props.form.getFieldDecorator('labelName', { + + {form.getFieldDecorator('labelName', { initialValue: value, rules: [{ required: true, @@ -349,99 +379,119 @@ class LabelForm extends React.PureComponent { }, { pattern: patterns.validateAttributeName.pattern, message: patterns.validateAttributeName.message, - }] - })() - } + }], + })()} + ); } - private renderNewAttributeButton() { + private renderNewAttributeButton(): JSX.Element { return ( ); } - private renderDoneButton() { + private renderDoneButton(): JSX.Element { return ( + > + Done + ); } - private renderContinueButton() { + private renderContinueButton(): JSX.Element { + const { label } = this.props; + return ( - this.props.label ?
: - - - - - - ); + label ?
+ : ( + + + + + + ) + ); } - private renderCancelButton() { + private renderCancelButton(): JSX.Element { + const { onSubmit } = this.props; + return ( + > + Cancel + ); } - public render() { - this.props.form.getFieldDecorator('keys', { - initialValue: this.props.label - ? this.props.label.attributes.map((attr: Attribute) => attr.id) - : [] + public render(): JSX.Element { + const { + label, + form, + } = this.props; + + form.getFieldDecorator('keys', { + initialValue: label + ? label.attributes.map((attr: Attribute) => attr.id) + : [], }); - let keys = this.props.form.getFieldValue('keys'); + const keys = form.getFieldValue('keys'); const attributeItems = keys.map(this.renderAttribute); return (
{ this.renderLabelNameInput() } - + { this.renderNewAttributeButton() } - { attributeItems.length > 0 ? - - - Attributes - - : null + { attributeItems.length > 0 + && ( + + + Attributes + + + ) } { attributeItems.reverse() } diff --git a/cvat-ui/src/components/labels-editor/labels-editor.tsx b/cvat-ui/src/components/labels-editor/labels-editor.tsx index f49b2e2236d0..545dd5bbb0f2 100644 --- a/cvat-ui/src/components/labels-editor/labels-editor.tsx +++ b/cvat-ui/src/components/labels-editor/labels-editor.tsx @@ -39,7 +39,6 @@ interface LabelsEditorState { export default class LabelsEditor extends React.PureComponent { - public constructor(props: LabelsEditortProps) { super(props); @@ -51,37 +50,46 @@ export default class LabelsEditor }; } - private handleSubmit(savedLabels: Label[], unsavedLabels: Label[]) { - function transformLabel(label: Label): any { + public componentDidMount(): void { + // just need performe the same code + this.componentDidUpdate(null as any as LabelsEditortProps); + } + + public componentDidUpdate(prevProps: LabelsEditortProps): void { + function transformLabel(label: any): Label { return { name: label.name, - id: label.id < 0 ? undefined : label.id, - attributes: label.attributes.map((attr: Attribute): any => { - return { + id: label.id || idGenerator(), + attributes: label.attributes.map((attr: any): Attribute => ( + { + id: attr.id || idGenerator(), name: attr.name, - id: attr.id < 0 ? undefined : attr.id, - input_type: attr.type.toLowerCase(), - default_value: attr.values[0], + type: attr.input_type, mutable: attr.mutable, values: [...attr.values], - }; - }), - } + } + )), + }; } - const output = []; - for (const label of savedLabels.concat(unsavedLabels)) { - output.push(transformLabel(label)); - } + const { labels } = this.props; - this.props.onSubmit(output); + if (!prevProps || prevProps.labels !== labels) { + const transformedLabels = labels.map(transformLabel); + this.setState({ + savedLabels: transformedLabels + .filter((label: Label) => label.id >= 0), + unsavedLabels: transformedLabels + .filter((label: Label) => label.id < 0), + }); + } } - private handleRawSubmit = (labels: Label[]) => { + private handleRawSubmit = (labels: Label[]): void => { const unsavedLabels = []; const savedLabels = []; - for (let label of labels) { + for (const label of labels) { if (label.id >= 0) { savedLabels.push(label); } else { @@ -95,29 +103,60 @@ export default class LabelsEditor }); this.handleSubmit(savedLabels, unsavedLabels); - } + }; + + private handleCreate = (label: Label | null): void => { + if (label === null) { + this.setState({ + constructorMode: ConstructorMode.SHOW, + }); + } else { + const { + unsavedLabels, + savedLabels, + } = this.state; + const newUnsavedLabels = [ + ...unsavedLabels, + { + ...label, + id: idGenerator(), + }, + ]; + + this.setState({ + unsavedLabels: newUnsavedLabels, + }); + + this.handleSubmit(savedLabels, newUnsavedLabels); + } + }; + + private handleUpdate = (label: Label | null): void => { + const { + savedLabels, + unsavedLabels, + } = this.state; - private handleUpdate = (label: Label | null) => { if (label) { - const savedLabels = this.state.savedLabels + const filteredSavedLabels = savedLabels .filter((_label: Label) => _label.id !== label.id); - const unsavedLabels = this.state.unsavedLabels + const filteredUnsavedLabels = unsavedLabels .filter((_label: Label) => _label.id !== label.id); if (label.id >= 0) { - savedLabels.push(label); + filteredSavedLabels.push(label); this.setState({ - savedLabels, + savedLabels: filteredSavedLabels, constructorMode: ConstructorMode.SHOW, }); } else { - unsavedLabels.push(label); + filteredUnsavedLabels.push(label); this.setState({ - unsavedLabels, + unsavedLabels: filteredUnsavedLabels, constructorMode: ConstructorMode.SHOW, }); } - this.handleSubmit(savedLabels, unsavedLabels); + this.handleSubmit(filteredSavedLabels, filteredUnsavedLabels); } else { this.setState({ constructorMode: ConstructorMode.SHOW, @@ -125,128 +164,132 @@ export default class LabelsEditor } }; - private handleDelete = (label: Label) => { + private handleDelete = (label: Label): void => { // the label is saved on the server, cannot delete it - if (typeof(label.id) !== 'undefined' && label.id >= 0) { + if (typeof (label.id) !== 'undefined' && label.id >= 0) { notification.error({ message: 'Could not delete the label', description: 'It has been already saved on the server', }); } - const unsavedLabels = this.state.unsavedLabels.filter( - (_label: Label) => _label.id !== label.id + const { + unsavedLabels, + savedLabels, + } = this.state; + + const filteredUnsavedLabels = unsavedLabels.filter( + (_label: Label) => _label.id !== label.id, ); this.setState({ - unsavedLabels: [...unsavedLabels], + unsavedLabels: filteredUnsavedLabels, }); - this.handleSubmit(this.state.savedLabels, unsavedLabels); - }; - - private handleCreate = (label: Label | null) => { - if (label === null) { - this.setState({ - constructorMode: ConstructorMode.SHOW, - }); - } else { - const unsavedLabels = [...this.state.unsavedLabels, - { - ...label, - id: idGenerator() - } - ]; - - this.setState({ - unsavedLabels, - }); - - this.handleSubmit(this.state.savedLabels, unsavedLabels); - } + this.handleSubmit(savedLabels, filteredUnsavedLabels); }; - public componentDidMount() { - this.componentDidUpdate(null as any as LabelsEditortProps); - } - - public componentDidUpdate(prevProps: LabelsEditortProps) { - function transformLabel(label: any): Label { + private handleSubmit(savedLabels: Label[], unsavedLabels: Label[]): void { + function transformLabel(label: Label): any { return { name: label.name, - id: label.id || idGenerator(), - attributes: label.attributes.map((attr: any): Attribute => { - return { - id: attr.id || idGenerator(), + id: label.id < 0 ? undefined : label.id, + attributes: label.attributes.map((attr: Attribute): any => ( + { name: attr.name, - type: attr.input_type, + id: attr.id < 0 ? undefined : attr.id, + input_type: attr.type.toLowerCase(), + default_value: attr.values[0], mutable: attr.mutable, values: [...attr.values], - }; - }), - } + } + )), + }; } - if (!prevProps || prevProps.labels !== this.props.labels) { - const transformedLabels = this.props.labels.map(transformLabel); - this.setState({ - savedLabels: transformedLabels - .filter((label: Label) => label.id >= 0), - unsavedLabels: transformedLabels - .filter((label: Label) => label.id < 0), - }); + const { onSubmit } = this.props; + const output = []; + for (const label of savedLabels.concat(unsavedLabels)) { + output.push(transformLabel(label)); } + + onSubmit(output); } - public render() { + public render(): JSX.Element { + const { + savedLabels, + unsavedLabels, + constructorMode, + labelForUpdate, + } = this.state; + return ( - - - - Raw - - } key='1'> + + + + Raw + + ) + } + key='1' + > - - - Constructor - - } key='2'> + + + Constructor + + ) + } + key='2' + > + { + constructorMode === ConstructorMode.SHOW + && ( + { + this.setState({ + constructorMode: ConstructorMode.UPDATE, + labelForUpdate: label, + }); + }} + onDelete={this.handleDelete} + onCreate={(): void => { + this.setState({ + constructorMode: ConstructorMode.CREATE, + }); + }} + /> + ) + } + { + constructorMode === ConstructorMode.UPDATE + && labelForUpdate !== null && ( + + ) + } { - this.state.constructorMode === ConstructorMode.SHOW ? - { - this.setState({ - constructorMode: ConstructorMode.UPDATE, - labelForUpdate: label, - }); - }} - onDelete={this.handleDelete} - onCreate={() => { - this.setState({ - constructorMode: ConstructorMode.CREATE, - }) - }} - /> : - - this.state.constructorMode === ConstructorMode.UPDATE - && this.state.labelForUpdate !== null ? - : - - + constructorMode === ConstructorMode.CREATE + && ( + + ) } diff --git a/cvat-ui/src/components/labels-editor/raw-viewer.tsx b/cvat-ui/src/components/labels-editor/raw-viewer.tsx index c18f92a234ca..0c7f5cd1dd32 100644 --- a/cvat-ui/src/components/labels-editor/raw-viewer.tsx +++ b/cvat-ui/src/components/labels-editor/raw-viewer.tsx @@ -18,22 +18,10 @@ import { type Props = FormComponentProps & { labels: Label[]; onSubmit: (labels: Label[]) => void; -} - -interface State { - valid: boolean; -} - -class RawViewer extends React.PureComponent { - public constructor(props: Props) { - super(props); - - this.state = { - valid: true, - }; - } +}; - private validateLabels = (_: any, value: string, callback: any) => { +class RawViewer extends React.PureComponent { + private validateLabels = (_: any, value: string, callback: any): void => { try { JSON.parse(value); } catch (error) { @@ -41,62 +29,73 @@ class RawViewer extends React.PureComponent { } callback(); - } + }; + + private handleSubmit = (e: React.FormEvent): void => { + const { + form, + onSubmit, + } = this.props; - private handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - this.props.form.validateFields((error, values) => { + form.validateFields((error, values) => { if (!error) { - this.props.onSubmit(JSON.parse(values.labels)); + onSubmit(JSON.parse(values.labels)); } }); - } + }; - public render() { - const labels = this.props.labels.map((label: any) => { - return { + public render(): JSX.Element { + const { labels } = this.props; + const convertedLabels = labels.map((label: any) => ( + { ...label, id: label.id < 0 ? undefined : label.id, - attributes: label.attributes.map((attribute: any) => { - return { + attributes: label.attributes.map((attribute: any) => ( + { ...attribute, id: attribute.id < 0 ? undefined : attribute.id, - }; - }), - }; - }); + } + )), + } + )); - const textLabels = JSON.stringify(labels, null, 2); + const textLabels = JSON.stringify(convertedLabels, null, 2); + const { form } = this.props; return ( - { - this.props.form.getFieldDecorator('labels', { + + {form.getFieldDecorator('labels', { initialValue: textLabels, rules: [{ validator: this.validateLabels, - }] - })( ) - } + }], + })()} + + > + Done + + > + Reset + From a8664694646309e69e5d7a1b6b912612b2c2b05a Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 5 Dec 2019 17:06:46 +0300 Subject: [PATCH 06/17] Login, register --- .../src/components/login-page/login-form.tsx | 46 +++---- .../src/components/login-page/login-page.tsx | 21 +++- .../register-page/register-form.tsx | 115 ++++++++++-------- .../register-page/register-page.tsx | 39 +++--- 4 files changed, 129 insertions(+), 92 deletions(-) diff --git a/cvat-ui/src/components/login-page/login-form.tsx b/cvat-ui/src/components/login-page/login-form.tsx index b3536797b94c..510747184368 100644 --- a/cvat-ui/src/components/login-page/login-form.tsx +++ b/cvat-ui/src/components/login-page/login-form.tsx @@ -18,21 +18,23 @@ type LoginFormProps = { } & FormComponentProps; class LoginFormComponent extends React.PureComponent { - constructor(props: LoginFormProps) { - super(props); - } - - private handleSubmit = (e: React.FormEvent) => { + private handleSubmit = (e: React.FormEvent): void => { e.preventDefault(); - this.props.form.validateFields((error, values) => { + const { + form, + onSubmit, + } = this.props; + + form.validateFields((error, values) => { if (!error) { - this.props.onSubmit(values); + onSubmit(values); } }); - } + }; - private renderUsernameField() { - const { getFieldDecorator } = this.props.form; + private renderUsernameField(): JSX.Element { + const { form } = this.props; + const { getFieldDecorator } = form; return ( @@ -44,16 +46,17 @@ class LoginFormComponent extends React.PureComponent { })( } + prefix={} placeholder='Username' - /> + />, )} - ) + ); } - private renderPasswordField() { - const { getFieldDecorator } = this.props.form; + private renderPasswordField(): JSX.Element { + const { form } = this.props; + const { getFieldDecorator } = form; return ( @@ -65,16 +68,17 @@ class LoginFormComponent extends React.PureComponent { })( } + prefix={} placeholder='Password' type='password' - /> + />, )} - ) + ); } - public render() { + public render(): JSX.Element { + const { fetching } = this.props; return ( {this.renderUsernameField()} @@ -83,8 +87,8 @@ class LoginFormComponent extends React.PureComponent { @@ -228,4 +241,4 @@ class RegisterFormComponent extends React.PureComponent { } } -export default Form.create()(RegisterFormComponent); \ No newline at end of file +export default Form.create()(RegisterFormComponent); diff --git a/cvat-ui/src/components/register-page/register-page.tsx b/cvat-ui/src/components/register-page/register-page.tsx index 5fd4025e63ad..ea82f7995d36 100644 --- a/cvat-ui/src/components/register-page/register-page.tsx +++ b/cvat-ui/src/components/register-page/register-page.tsx @@ -10,7 +10,7 @@ import { Row, } from 'antd'; -import RegisterForm, { RegisterData } from '../../components/register-page/register-form'; +import RegisterForm, { RegisterData } from './register-form'; interface RegisterPageComponentProps { fetching: boolean; @@ -19,33 +19,44 @@ interface RegisterPageComponentProps { password1: string, password2: string) => void; } -function RegisterPageComponent(props: RegisterPageComponentProps & RouteComponentProps) { +function RegisterPageComponent( + props: RegisterPageComponentProps & RouteComponentProps, +): JSX.Element { const sizes = { xs: { span: 14 }, sm: { span: 14 }, md: { span: 10 }, lg: { span: 4 }, xl: { span: 4 }, - } + }; + + const { + fetching, + onRegister, + } = props; return ( Create an account - { - props.onRegister( - registerData.username, - registerData.firstName, - registerData.lastName, - registerData.email, - registerData.password1, - registerData.password2, - ); - }}/> + { + onRegister( + registerData.username, + registerData.firstName, + registerData.lastName, + registerData.email, + registerData.password1, + registerData.password2, + ); + }} + /> - Already have an account? Login + Already have an account? + Login From 3eb544c569fa7c3789264e7dcec42739eead20d0 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 5 Dec 2019 17:49:37 +0300 Subject: [PATCH 07/17] Models page & model runner --- .../create-model-content.tsx | 2 +- .../model-runner-modal/model-runner-modal.tsx | 350 +++++++++++------- .../models-page/built-model-item.tsx | 22 +- .../models-page/built-models-list.tsx | 13 +- .../src/components/models-page/empty-list.tsx | 19 +- .../components/models-page/models-page.tsx | 68 ++-- .../src/components/models-page/top-bar.tsx | 34 +- .../models-page/uploaded-model-item.tsx | 46 ++- .../models-page/uploaded-models-list.tsx | 24 +- 9 files changed, 346 insertions(+), 232 deletions(-) diff --git a/cvat-ui/src/components/create-model-page/create-model-content.tsx b/cvat-ui/src/components/create-model-page/create-model-content.tsx index 5951c2a17f16..228cf9803c64 100644 --- a/cvat-ui/src/components/create-model-page/create-model-content.tsx +++ b/cvat-ui/src/components/create-model-page/create-model-content.tsx @@ -137,7 +137,7 @@ export default class CreateModelContent extends React.PureComponent { this.fileManagerContainer = container; } } - withRemote + withRemote={false} /> diff --git a/cvat-ui/src/components/model-runner-modal/model-runner-modal.tsx b/cvat-ui/src/components/model-runner-modal/model-runner-modal.tsx index 5b347713af42..bc5d5cd3edb9 100644 --- a/cvat-ui/src/components/model-runner-modal/model-runner-modal.tsx +++ b/cvat-ui/src/components/model-runner-modal/model-runner-modal.tsx @@ -41,25 +41,31 @@ interface State { mapping: StringObject; colors: StringObject; matching: { - model: string, - task: string, + model: string; + task: string; }; } -const nextColor = (function *() { +function colorGenerator(): () => string { const values = [ 'magenta', 'green', 'geekblue', 'orange', 'red', 'cyan', 'blue', 'volcano', 'purple', ]; - for (let i = 0; i < values.length; i++) { - yield values[i]; - if (i === values.length) { - i = 0; + let index = 0; + + return (): string => { + const color = values[index++]; + if (index >= values.length) { + index = 0; } - } -})(); + + return color; + }; +} + +const nextColor = colorGenerator(); export default class ModelRunnerModalComponent extends React.PureComponent { public constructor(props: Props) { @@ -76,51 +82,115 @@ export default class ModelRunnerModalComponent extends React.PureComponent model.name === selectedModel)[0]; + + if (!selectedModelInstance.primary) { + let taskLabels: string[] = taskInstance.labels + .map((label: any) => label.name); + const [defaultMapping, defaultColors]: StringObject[] = selectedModelInstance.labels + .reduce((acc: StringObject[], label) => { + if (taskLabels.includes(label)) { + acc[0][label] = label; + acc[1][label] = nextColor(); + taskLabels = taskLabels.filter((_label) => _label !== label); + } + + return acc; + }, [{}, {}]); + + this.setState({ + mapping: defaultMapping, + colors: defaultColors, + }); + } + } + } + + private renderModelSelector(): JSX.Element { + const { models } = this.props; + return ( Model: ); } - private renderMappingTag(modelLabel: string, taskLabel: string) { + private renderMappingTag(modelLabel: string, taskLabel: string): JSX.Element { + const { + colors, + mapping, + } = this.state; + return ( - {modelLabel} + {modelLabel} - {taskLabel} + {taskLabel} { - const mapping = {...this.state.mapping}; - delete mapping[modelLabel]; - this.setState({ - mapping, - }); - }}/> + onClick={(): void => { + const newMapping = { ...mapping }; + delete newMapping[modelLabel]; + this.setState({ + mapping: newMapping, + }); + }} + /> @@ -130,106 +200,126 @@ export default class ModelRunnerModalComponent extends React.PureComponent { - const anotherValue = current === 'Model' ? - this.state.matching.task : this.state.matching.model; + style={{ width: '100%' }} + onChange={(selectedValue: string): void => { + const anotherValue = current === 'Model' + ? matching.task : matching.model; if (!anotherValue) { - const matching = { ...this.state.matching }; + const newMatching = { ...matching }; if (current === 'Model') { - matching.model = value; + newMatching.model = selectedValue; } else { - matching.task = value; + newMatching.task = selectedValue; } this.setState({ - matching, + matching: newMatching, }); } else { - const colors = {...this.state.colors}; - const mapping = {...this.state.mapping}; + const newColors = { ...colors }; + const newMapping = { ...mapping }; if (current === 'Model') { - colors[value] = nextColor.next().value; - mapping[value] = anotherValue; + newColors[selectedValue] = nextColor(); + newMapping[selectedValue] = anotherValue; } else { - colors[anotherValue] = nextColor.next().value; - mapping[anotherValue] = value; + newColors[anotherValue] = nextColor(); + newMapping[anotherValue] = selectedValue; } this.setState({ - colors, - mapping, + colors: newColors, + mapping: newMapping, matching: { task: '', model: '', }, - }) + }); } }} > - {options.map((label: string) => + {options.map((label: string) => ( {label} - )} + ))} ); } - private renderMappingInput(availableModelLabels: string[], availableTaskLabels: string[]) { + private renderMappingInput( + availableModelLabels: string[], + availableTaskLabels: string[], + ): JSX.Element { + const { matching } = this.state; return ( {this.renderMappingInputSelector( - this.state.matching.model, + matching.model, 'Model', availableModelLabels, )} {this.renderMappingInputSelector( - this.state.matching.task, + matching.task, 'Task', availableTaskLabels, )} - + ); } - private renderContent() { - const model = this.state.selectedModel && this.props.models - .filter((model) => model.name === this.state.selectedModel)[0]; + private renderContent(): JSX.Element { + const { + selectedModel, + cleanOut, + mapping, + } = this.state; + const { + models, + taskInstance, + } = this.props; + + const model = selectedModel && models + .filter((_model) => _model.name === selectedModel)[0]; const excludedLabels: { - model: string[], - task: string[], + model: string[]; + task: string[]; } = { model: [], task: [], }; const withMapping = model && !model.primary; - const tags = withMapping ? Object.keys(this.state.mapping) + const tags = withMapping ? Object.keys(mapping) .map((modelLabel: string) => { - const taskLabel = this.state.mapping[modelLabel]; + const taskLabel = mapping[modelLabel]; excludedLabels.model.push(modelLabel); excludedLabels.task.push(taskLabel); return this.renderMappingTag( modelLabel, - this.state.mapping[modelLabel], + mapping[modelLabel], ); }) : []; @@ -237,10 +327,10 @@ export default class ModelRunnerModalComponent extends React.PureComponent !excludedLabels.model.includes(label), ) : []; - const availableTaskLabels = this.props.taskInstance.labels + const availableTaskLabels = taskInstance.labels .map( (label: any) => label.name, - ).filter((label: string) => !excludedLabels.task.includes(label)) + ).filter((label: string) => !excludedLabels.task.includes(label)); const mappingISAvailable = !!availableModelLabels.length && !!availableTaskLabels.length; @@ -253,97 +343,73 @@ export default class ModelRunnerModalComponent extends React.PureComponent - this.setState({ - cleanOut: e.target.checked, - })} - > Clean old annotations -
+ { withMapping + && ( +
+ this.setState({ + cleanOut: e.target.checked, + })} + > + Clean old annotations + +
+ ) }
); } - private renderSpin() { - return ( - - ); - } + public render(): JSX.Element | false { + const { + selectedModel, + mapping, + cleanOut, + } = this.state; - public componentDidUpdate(prevProps: Props, prevState: State) { - if (!this.props.modelsInitialized && !this.props.modelsFetching) { - this.props.getModels(); - } + const { + models, + visible, + taskInstance, + modelsInitialized, + runInference, + closeDialog, + } = this.props; - if (!prevProps.visible && this.props.visible) { - this.setState({ - selectedModel: null, - mapping: {}, - matching: { - model: '', - task: '', - }, - cleanOut: false, - }); - } - - if (this.state.selectedModel && prevState.selectedModel !== this.state.selectedModel) { - const model = this.props.models - .filter((model) => model.name === this.state.selectedModel)[0]; - if (!model.primary) { - let taskLabels: string[] = this.props.taskInstance.labels - .map((label: any) => label.name); - const defaultMapping: StringObject = model.labels - .reduce((acc: StringObject, label) => { - if (taskLabels.includes(label)) { - acc[label] = label; - taskLabels = taskLabels.filter((_label) => _label !== label) - } - - return acc; - }, {}); - - this.setState({ - mapping: defaultMapping, - }); - } - } - } - - public render() { - const activeModel = this.props.models.filter( - (model) => model.name === this.state.selectedModel + const activeModel = models.filter( + (model) => model.name === selectedModel, )[0]; - let enabledSubmit = !!activeModel - && activeModel.primary || !!Object.keys(this.state.mapping).length; + const enabledSubmit = (!!activeModel + && activeModel.primary) || !!Object.keys(mapping).length; return ( - this.props.visible && { - this.props.runInference( - this.props.taskInstance, - this.props.models - .filter((model) => model.name === this.state.selectedModel)[0], - this.state.mapping, - this.state.cleanOut, - ); - this.props.closeDialog() - }} - onCancel={() => this.props.closeDialog()} - okButtonProps={{disabled: !enabledSubmit}} - title='Automatic annotation' - visible={true} - > - {!this.props.modelsInitialized && this.renderSpin()} - {this.props.modelsInitialized && this.renderContent()} - + visible && ( + { + runInference( + taskInstance, + models + .filter((model) => model.name === selectedModel)[0], + mapping, + cleanOut, + ); + closeDialog(); + }} + onCancel={(): void => closeDialog()} + okButtonProps={{ disabled: !enabledSubmit }} + title='Automatic annotation' + visible + > + {!modelsInitialized + && } + {modelsInitialized && this.renderContent()} + + ) ); } -} \ No newline at end of file +} diff --git a/cvat-ui/src/components/models-page/built-model-item.tsx b/cvat-ui/src/components/models-page/built-model-item.tsx index 0bfd1bb71501..d03021546753 100644 --- a/cvat-ui/src/components/models-page/built-model-item.tsx +++ b/cvat-ui/src/components/models-page/built-model-item.tsx @@ -15,7 +15,9 @@ interface Props { model: Model; } -export default function BuiltModelItemComponent(props: Props) { +export default function BuiltModelItemComponent(props: Props): JSX.Element { + const { model } = props; + return ( @@ -23,24 +25,26 @@ export default function BuiltModelItemComponent(props: Props) { - {props.model.name} + {model.name} - + ); } diff --git a/cvat-ui/src/components/models-page/built-models-list.tsx b/cvat-ui/src/components/models-page/built-models-list.tsx index 50e76de58111..c0ca093c8c00 100644 --- a/cvat-ui/src/components/models-page/built-models-list.tsx +++ b/cvat-ui/src/components/models-page/built-models-list.tsx @@ -14,10 +14,9 @@ interface Props { models: Model[]; } -export default function IntegratedModelsListComponent(props: Props) { - const items = props.models.map((model) => - - ); +export default function IntegratedModelsListComponent(props: Props): JSX.Element { + const { models } = props; + const items = models.map((model) => ); return ( <> @@ -28,12 +27,12 @@ export default function IntegratedModelsListComponent(props: Props) { - + - {'Framework'} + Framework - {'Name'} + Name Labels diff --git a/cvat-ui/src/components/models-page/empty-list.tsx b/cvat-ui/src/components/models-page/empty-list.tsx index 1746eaa8471a..b48417a03d29 100644 --- a/cvat-ui/src/components/models-page/empty-list.tsx +++ b/cvat-ui/src/components/models-page/empty-list.tsx @@ -8,32 +8,31 @@ import { Icon, } from 'antd'; -export default function EmptyListComponent() { - const emptyTasksIcon = () => (); +export default function EmptyListComponent(): JSX.Element { + const emptyTasksIcon = (): JSX.Element => (); return (
- + - {'No models uploaded yet ...'} + No models uploaded yet ... - {'To annotate your tasks automatically'} + To annotate your tasks automatically - + - {'upload a new model'} + upload a new model
- - ) -} \ No newline at end of file + ); +} diff --git a/cvat-ui/src/components/models-page/models-page.tsx b/cvat-ui/src/components/models-page/models-page.tsx index 8db88abedf09..3aca57e49bc9 100644 --- a/cvat-ui/src/components/models-page/models-page.tsx +++ b/cvat-ui/src/components/models-page/models-page.tsx @@ -22,36 +22,48 @@ interface Props { deleteModel(id: number): void; } -export default function ModelsPageComponent(props: Props) { - if (!props.modelsInitialized && !props.modelsFetching) { - props.getModels(); - return ( - - ); - } else { - const uploadedModels = props.models.filter((model) => model.id !== null); - const integratedModels = props.models.filter((model) => model.id === null); +export default function ModelsPageComponent(props: Props): JSX.Element { + const { + installedAutoAnnotation, + installedTFSegmentation, + installedTFAnnotation, + modelsInitialized, + modelsFetching, + registeredUsers, + models, + + deleteModel, + } = props; + if (!modelsInitialized && !modelsFetching) { + props.getModels(); return ( -
- - { !!integratedModels.length && - - } - { !!uploadedModels.length && - - } - { props.installedAutoAnnotation && - !uploadedModels.length && - !props.installedTFAnnotation && - !props.installedTFSegmentation && - - } -
+ ); } + + const uploadedModels = models.filter((model) => model.id !== null); + const integratedModels = models.filter((model) => model.id === null); + + return ( +
+ + { !!integratedModels.length + && + } + { !!uploadedModels.length && ( + + )} + { installedAutoAnnotation + && !uploadedModels.length + && !installedTFAnnotation + && !installedTFSegmentation + && + } +
+ ); } diff --git a/cvat-ui/src/components/models-page/top-bar.tsx b/cvat-ui/src/components/models-page/top-bar.tsx index 2b394cd24c7e..b28c7d024710 100644 --- a/cvat-ui/src/components/models-page/top-bar.tsx +++ b/cvat-ui/src/components/models-page/top-bar.tsx @@ -14,26 +14,40 @@ type Props = { installedAutoAnnotation: boolean; } & RouteComponentProps; -function TopBarComponent(props: Props) { +function TopBarComponent(props: Props): JSX.Element { + const { + installedAutoAnnotation, + history, + } = props; + return ( Models - { props.installedAutoAnnotation && - + { installedAutoAnnotation + && ( + + ) } - ) + ); } export default withRouter(TopBarComponent); diff --git a/cvat-ui/src/components/models-page/uploaded-model-item.tsx b/cvat-ui/src/components/models-page/uploaded-model-item.tsx index bad3f1a63cae..bbd6c565f3dd 100644 --- a/cvat-ui/src/components/models-page/uploaded-model-item.tsx +++ b/cvat-ui/src/components/models-page/uploaded-model-item.tsx @@ -21,8 +21,13 @@ interface Props { onDelete(): void; } -export default function UploadedModelItem(props: Props) { - const subMenuIcon = () => (); +export default function UploadedModelItem(props: Props): JSX.Element { + const subMenuIcon = (): JSX.Element => (); + const { + model, + owner, + onDelete, + } = props; return ( @@ -31,43 +36,52 @@ export default function UploadedModelItem(props: Props) { - {props.model.name} + {model.name} - {props.owner ? props.owner.username : 'undefined'} + {owner ? owner.username : 'undefined'} - {moment(props.model.uploadDate).format('MMMM Do YYYY')} + {moment(model.uploadDate).format('MMMM Do YYYY')} Actions - { - props.onDelete(); - }}key='delete'>Delete + { + onDelete(); + }} + key='delete' + > + Delete +
- }> - + )} + > + diff --git a/cvat-ui/src/components/models-page/uploaded-models-list.tsx b/cvat-ui/src/components/models-page/uploaded-models-list.tsx index 6cc6a1391fb9..f5eca0df1887 100644 --- a/cvat-ui/src/components/models-page/uploaded-models-list.tsx +++ b/cvat-ui/src/components/models-page/uploaded-models-list.tsx @@ -16,15 +16,21 @@ interface Props { deleteModel(id: number): void; } -export default function UploadedModelsListComponent(props: Props) { - const items = props.models.map((model) => { - const owner = props.registeredUsers.filter((user) => user.id === model.ownerID)[0]; +export default function UploadedModelsListComponent(props: Props): JSX.Element { + const { + models, + registeredUsers, + deleteModel, + } = props; + + const items = models.map((model) => { + const owner = registeredUsers.filter((user) => user.id === model.ownerID)[0]; return ( props.deleteModel(model.id as number)} + onDelete={(): void => deleteModel(model.id as number)} /> ); }); @@ -33,17 +39,17 @@ export default function UploadedModelsListComponent(props: Props) { <> - {'Uploaded by a user'} + Uploaded by a user - + - {'Framework'} + Framework - {'Name'} + Name Owner @@ -54,7 +60,7 @@ export default function UploadedModelsListComponent(props: Props) { Labels - + { items } From ba52e3e55628fda85925dae7631e5b051d4f7a12 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 5 Dec 2019 18:24:24 +0300 Subject: [PATCH 08/17] Tasks page --- cvat-ui/src/components/task-page/details.tsx | 362 ++++++++++-------- cvat-ui/src/components/task-page/job-list.tsx | 51 ++- .../src/components/task-page/task-page.tsx | 62 +-- cvat-ui/src/components/task-page/top-bar.tsx | 21 +- .../components/task-page/user-selector.tsx | 39 +- 5 files changed, 313 insertions(+), 222 deletions(-) diff --git a/cvat-ui/src/components/task-page/details.tsx b/cvat-ui/src/components/task-page/details.tsx index 355f2e279df9..71e59bc392dc 100644 --- a/cvat-ui/src/components/task-page/details.tsx +++ b/cvat-ui/src/components/task-page/details.tsx @@ -55,36 +55,91 @@ export default class DetailsComponent extends React.PureComponent }; } - private renderTaskName() { + public componentDidMount(): void { const { taskInstance } = this.props; + this.mounted = true; + + getReposData(taskInstance.id) + .then((data) => { + if (data !== null && this.mounted) { + if (data.status.error) { + notification.error({ + message: 'Could not receive repository status', + description: data.status.error, + }); + } else { + this.setState({ + repositoryStatus: data.status.value, + }); + } + + this.setState({ + repository: data.url, + }); + } + }).catch((error) => { + if (this.mounted) { + notification.error({ + message: 'Could not receive repository status', + description: error.toString(), + }); + } + }); + } + + + public componentDidUpdate(prevProps: Props): void { + const { taskInstance } = this.props; + + if (prevProps !== this.props) { + this.setState({ + name: taskInstance.name, + bugTracker: taskInstance.bugTracker, + }); + } + } + + public componentWillUnmount(): void { + this.mounted = false; + } + + private renderTaskName(): JSX.Element { const { name } = this.state; + const { + taskInstance, + onTaskUpdate, + } = this.props; + return ( { + onChange: (value: string): void => { this.setState({ name: value, }); taskInstance.name = value; - this.props.onTaskUpdate(taskInstance); + onTaskUpdate(taskInstance); }, }} className='cvat-black-color' - >{name} + > + {name} + ); } - private renderPreview() { + private renderPreview(): JSX.Element { + const { previewImage } = this.props; return (
- Preview + Preview
); } - private renderParameters() { + private renderParameters(): JSX.Element { const { taskInstance } = this.props; const { overlap } = taskInstance; const { segmentSize } = taskInstance; @@ -95,25 +150,25 @@ export default class DetailsComponent extends React.PureComponent <> - {'Overlap size'} -
+ Overlap size +
{overlap} - {'Segment size'} -
+ Segment size +
{segmentSize}
- {'Image quality'} -
+ Image quality +
{imageQuality} - {'Z-order'} -
+ Z-order +
{zOrder}
@@ -121,39 +176,47 @@ export default class DetailsComponent extends React.PureComponent ); } - private renderUsers() { - const { taskInstance } = this.props; + private renderUsers(): JSX.Element { + const { + taskInstance, + registeredUsers, + onTaskUpdate, + } = this.props; const owner = taskInstance.owner ? taskInstance.owner.username : null; const assignee = taskInstance.assignee ? taskInstance.assignee.username : null; const created = moment(taskInstance.createdDate).format('MMMM Do YYYY'); - const assigneeSelect = { - let [userInstance] = this.props.registeredUsers - .filter((user: any) => user.username === value); - - if (userInstance === undefined) { - userInstance = null; - } + const assigneeSelect = ( + { + let [userInstance] = registeredUsers + .filter((user: any) => user.username === value); + + if (userInstance === undefined) { + userInstance = null; + } - taskInstance.assignee = userInstance; - this.props.onTaskUpdate(taskInstance); + taskInstance.assignee = userInstance; + onTaskUpdate(taskInstance); + } } - } - /> + /> + ); return ( - { owner ? - Created by {owner} on {created} - : null } + { owner && ( + + {`Created by ${owner} on ${created}`} + + )} - {'Assigned to'} + Assigned to { assigneeSelect } @@ -161,57 +224,89 @@ export default class DetailsComponent extends React.PureComponent ); } - private renderDatasetRepository() { - const { repository } = this.state; - const { repositoryStatus } = this.state; + private renderDatasetRepository(): JSX.Element | boolean { + const { taskInstance } = this.props; + const { + repository, + repositoryStatus, + } = this.state; return ( - repository ? - - - {'Dataset Repository'} -
- {repository} - {repositoryStatus === 'sync' ? - - Synchronized - : repositoryStatus === 'merged' ? - - Merged - : repositoryStatus === 'syncing' ? - - Syncing : - { - this.setState({ - repositoryStatus: 'syncing', - }); - - syncRepos(this.props.taskInstance.id).then(() => { - if (this.mounted) { - this.setState({ - repositoryStatus: 'sync', - }); - } - }).catch(() => { - if (this.mounted) { - this.setState({ - repositoryStatus: '!sync', - }); - } - }); - }}> Synchronize - } - -
: null + !!repository + && ( + + + Dataset Repository +
+ {repository} + {repositoryStatus === 'sync' + && ( + + + Synchronized + + ) + } + {repositoryStatus === 'merged' + && ( + + + Merged + + ) + } + {repositoryStatus === 'syncing' + && ( + + + Syncing + + ) + } + {repositoryStatus === '!sync' + && ( + { + this.setState({ + repositoryStatus: 'syncing', + }); + + syncRepos(taskInstance.id).then(() => { + if (this.mounted) { + this.setState({ + repositoryStatus: 'sync', + }); + } + }).catch(() => { + if (this.mounted) { + this.setState({ + repositoryStatus: '!sync', + }); + } + }); + }} + > + + Synchronize + + ) + } + +
+ ) ); } - private renderBugTracker() { - const { taskInstance } = this.props; + private renderBugTracker(): JSX.Element { + const { + taskInstance, + onTaskUpdate, + } = this.props; const { bugTracker } = this.state; let shown = false; - const onChangeValue = (value: string) => { + const onChangeValue = (value: string): void => { if (value && !patterns.validateURL.pattern.test(value)) { if (!shown) { Modal.error({ @@ -229,52 +324,62 @@ export default class DetailsComponent extends React.PureComponent }); taskInstance.bugTracker = value; - this.props.onTaskUpdate(taskInstance); + onTaskUpdate(taskInstance); } - } + }; if (bugTracker) { return ( - {'Issue Tracker'} -
- {bugTracker} - - -
- ); - } else { - return ( - - - {'Issue Tracker'} -
- {'Not specified'} + Issue Tracker +
+ {bugTracker} +
); } + + return ( + + + Issue Tracker +
+ Not specified + +
+ ); } - private renderLabelsEditor() { - const { taskInstance } = this.props; + private renderLabelsEditor(): JSX.Element { + const { + taskInstance, + onTaskUpdate, + } = this.props; return ( label.toJSON() + (label: any) => label.toJSON(), )} - onSubmit={(labels: any[]) => { - taskInstance.labels = labels.map((labelData) => { - return new core.classes.Label(labelData); - }); - - this.props.onTaskUpdate(taskInstance); + onSubmit={(labels: any[]): void => { + taskInstance.labels = labels + .map((labelData) => new core.classes.Label(labelData)); + onTaskUpdate(taskInstance); }} /> @@ -282,50 +387,7 @@ export default class DetailsComponent extends React.PureComponent ); } - public componentDidMount() { - this.mounted = true; - getReposData(this.props.taskInstance.id) - .then((data) => { - if (data !== null && this.mounted) { - if (data.status.error) { - notification.error({ - message: 'Could not receive repository status', - description: data.status.error - }); - } else { - this.setState({ - repositoryStatus: data.status.value, - }); - } - - this.setState({ - repository: data.url, - }); - } - }).catch((error) => { - if (this.mounted) { - notification.error({ - message: 'Could not receive repository status', - description: error.toString(), - }); - } - }); - } - - public componentWillUnmount() { - this.mounted = false; - } - - public componentDidUpdate(prevProps: Props) { - if (prevProps !== this.props) { - this.setState({ - name: this.props.taskInstance.name, - bugTracker: this.props.taskInstance.bugTracker, - }); - } - } - - public render() { + public render(): JSX.Element { return (
diff --git a/cvat-ui/src/components/task-page/job-list.tsx b/cvat-ui/src/components/task-page/job-list.tsx index 3f8319fa2a92..01086e28d371 100644 --- a/cvat-ui/src/components/task-page/job-list.tsx +++ b/cvat-ui/src/components/task-page/job-list.tsx @@ -24,17 +24,19 @@ interface Props { onJobUpdate(jobInstance: any): void; } -export default function JobListComponent(props: Props) { - const { jobs } = props.taskInstance; +export default function JobListComponent(props: Props): JSX.Element { + const { + taskInstance, + registeredUsers, + onJobUpdate, + } = props; + + const { jobs } = taskInstance; const columns = [{ title: 'Job', dataIndex: 'job', key: 'job', - render: (id: number) => { - return ( - { `Job #${id++}` } - ); - } + render: (id: number): JSX.Element => ({ `Job #${id}` }), }, { title: 'Frames', dataIndex: 'frames', @@ -44,14 +46,20 @@ export default function JobListComponent(props: Props) { title: 'Status', dataIndex: 'status', key: 'status', - render: (status: string) => { - const progressColor = status === 'completed' ? 'cvat-job-completed-color': - status === 'validation' ? 'cvat-job-validation-color' : 'cvat-job-annotation-color'; + render: (status: string): JSX.Element => { + let progressColor = null; + if (status === 'completed') { + progressColor = 'cvat-job-completed-color'; + } else if (status === 'validation') { + progressColor = 'cvat-job-validation-color'; + } else { + progressColor = 'cvat-job-annotation-color'; + } return ( { status } ); - } + }, }, { title: 'Started on', dataIndex: 'started', @@ -66,22 +74,25 @@ export default function JobListComponent(props: Props) { title: 'Assignee', dataIndex: 'assignee', key: 'assignee', - render: (jobInstance: any) => { - const assignee = jobInstance.assignee ? jobInstance.assignee.username : null + render: (jobInstance: any): JSX.Element => { + const assignee = jobInstance.assignee ? jobInstance.assignee.username : null; + return ( { - let [userInstance] = props.registeredUsers - .filter((user: any) => user.username === value); + onChange={(value: string): void => { + let [userInstance] = [...registeredUsers] + .filter((user: any) => user.username === value); if (userInstance === undefined) { userInstance = null; } - jobInstance.assignee = userInstance; - props.onJobUpdate(jobInstance); + onJobUpdate({ + ...jobInstance, + assignee: userInstance, + }); }} /> ); @@ -129,4 +140,4 @@ export default function JobListComponent(props: Props) { />
); -} \ No newline at end of file +} diff --git a/cvat-ui/src/components/task-page/task-page.tsx b/cvat-ui/src/components/task-page/task-page.tsx index 615788996d82..81eda43f1a09 100644 --- a/cvat-ui/src/components/task-page/task-page.tsx +++ b/cvat-ui/src/components/task-page/task-page.tsx @@ -25,52 +25,64 @@ interface TaskPageComponentProps { type Props = TaskPageComponentProps & RouteComponentProps<{id: string}>; class TaskPageComponent extends React.PureComponent { - private attempts: number = 0; + private attempts = 0; - public componentDidUpdate() { - if (this.props.deleteActivity) { - this.props.history.replace('/tasks'); + public componentDidUpdate(): void { + const { + deleteActivity, + history, + } = this.props; + + if (deleteActivity) { + history.replace('/tasks'); } - if (this.attempts == 2) { + if (this.attempts === 2) { notification.warning({ message: 'Something wrong with the task. It cannot be fetched from the server', }); } } - public render() { - const { id } = this.props.match.params; - const fetchTask = !this.props.task; + public render(): JSX.Element { + const { + match, + task, + fetching, + onFetchTask, + } = this.props; + const { id } = match.params; + const fetchTask = !task; if (fetchTask) { - if (!this.props.fetching) { + if (!fetching) { if (!this.attempts) { - this.attempts ++; - this.props.onFetchTask(+id); + this.attempts++; + onFetchTask(+id); } else { - this.attempts ++; + this.attempts++; } } return ( - + ); - } else if (typeof(this.props.task) === 'undefined') { + } + + if (typeof (task) === 'undefined') { return (
- ) - } else { - const task = this.props.task as Task; - return ( - - - - - - - ); } + + return ( + + + + + + + + ); } } diff --git a/cvat-ui/src/components/task-page/top-bar.tsx b/cvat-ui/src/components/task-page/top-bar.tsx index 3cbd31393048..8c368771f764 100644 --- a/cvat-ui/src/components/task-page/top-bar.tsx +++ b/cvat-ui/src/components/task-page/top-bar.tsx @@ -16,10 +16,11 @@ interface DetailsComponentProps { taskInstance: any; } -export default function DetailsComponent(props: DetailsComponentProps) { - const subMenuIcon = () => (); +export default function DetailsComponent(props: DetailsComponentProps): JSX.Element { + const subMenuIcon = (): JSX.Element => (); - const { id } = props.taskInstance; + const { taskInstance } = props; + const { id } = taskInstance; return ( @@ -28,16 +29,18 @@ export default function DetailsComponent(props: DetailsComponentProps) { - }> + ( + + )} + > ); -} \ No newline at end of file +} diff --git a/cvat-ui/src/components/task-page/user-selector.tsx b/cvat-ui/src/components/task-page/user-selector.tsx index b02870faeca3..07338f524968 100644 --- a/cvat-ui/src/components/task-page/user-selector.tsx +++ b/cvat-ui/src/components/task-page/user-selector.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { - Icon, Select, } from 'antd'; @@ -11,23 +10,27 @@ interface Props { onChange: (user: string) => void; } -export default function UserSelector(props: Props) { +export default function UserSelector(props: Props): JSX.Element { + const { + value, + users, + onChange, + } = props; + return ( + defaultValue={value || '—'} + size='small' + showSearch + className='cvat-user-selector' + onChange={onChange} + > + + { users.map((user) => ( + + {user.username} + + ))} + ); -} \ No newline at end of file +} From 2583817566ddf7aa63726e977d50d2ddca9636cd Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 5 Dec 2019 18:43:52 +0300 Subject: [PATCH 09/17] Feedback and base app --- cvat-ui/src/components/cvat-app.tsx | 206 +++++++++++++++++----------- cvat-ui/src/components/feedback.tsx | 161 +++++++++++----------- cvat-ui/src/index.tsx | 35 ++--- 3 files changed, 211 insertions(+), 191 deletions(-) diff --git a/cvat-ui/src/components/cvat-app.tsx b/cvat-ui/src/components/cvat-app.tsx index 09e3d4762300..7b954d1b9fd5 100644 --- a/cvat-ui/src/components/cvat-app.tsx +++ b/cvat-ui/src/components/cvat-app.tsx @@ -46,28 +46,73 @@ type CVATAppProps = { installedTFSegmentation: boolean; notifications: NotificationsState; user: any; -} +}; export default class CVATApplication extends React.PureComponent { - constructor(props: any) { - super(props); + public componentDidMount(): void { + const { verifyAuthorized } = this.props; + verifyAuthorized(); } - private showMessages() { - function showMessage(title: string) { + public componentDidUpdate(): void { + const { + loadFormats, + loadUsers, + initPlugins, + userInitialized, + formatsInitialized, + formatsFetching, + usersInitialized, + usersFetching, + pluginsInitialized, + pluginsFetching, + user, + } = this.props; + + this.showErrors(); + this.showMessages(); + + if (!userInitialized || user == null) { + // not authorized user + return; + } + + if (!formatsInitialized && !formatsFetching) { + loadFormats(); + } + + if (!usersInitialized && !usersFetching) { + loadUsers(); + } + + if (!pluginsInitialized && !pluginsFetching) { + initPlugins(); + } + } + + private showMessages(): void { + function showMessage(title: string): void { notification.info({ message: ( -
+
), duration: null, }); } - const { tasks } = this.props.notifications.messages; - const { models } = this.props.notifications.messages; - let shown = !!tasks.loadingDone || !!models.inferenceDone; + const { + notifications, + resetMessages, + } = this.props; + + const { tasks } = notifications.messages; + const { models } = notifications.messages; + const shown = !!tasks.loadingDone || !!models.inferenceDone; if (tasks.loadingDone) { showMessage(tasks.loadingDone); @@ -77,18 +122,21 @@ export default class CVATApplication extends React.PureComponent { } if (shown) { - this.props.resetMessages(); + resetMessages(); } } - private showErrors() { - function showError(title: string, _error: any) { + private showErrors(): void { + function showError(title: string, _error: any): void { const error = _error.toString(); notification.error({ message: ( -
+
), duration: null, description: error.length > 200 ? '' : error, @@ -97,14 +145,19 @@ export default class CVATApplication extends React.PureComponent { console.error(error); } - const { auth } = this.props.notifications.errors; - const { tasks } = this.props.notifications.errors; - const { formats } = this.props.notifications.errors; - const { users } = this.props.notifications.errors; - const { share } = this.props.notifications.errors; - const { models } = this.props.notifications.errors; + const { + notifications, + resetErrors, + } = this.props; + + const { auth } = notifications.errors; + const { tasks } = notifications.errors; + const { formats } = notifications.errors; + const { users } = notifications.errors; + const { share } = notifications.errors; + const { models } = notifications.errors; - let shown = !!auth.authorized || !!auth.login || !!auth.logout || !!auth.register + const shown = !!auth.authorized || !!auth.login || !!auth.logout || !!auth.register || !!tasks.fetching || !!tasks.updating || !!tasks.dumping || !!tasks.loading || !!tasks.exporting || !!tasks.deleting || !!tasks.creating || !!formats.fetching || !!users.fetching || !!share.fetching || !!models.creating || !!models.starting @@ -169,89 +222,76 @@ export default class CVATApplication extends React.PureComponent { showError(models.metaFetching.message, models.metaFetching.reason); } if (models.inferenceStatusFetching) { - showError(models.inferenceStatusFetching.message, models.inferenceStatusFetching.reason); + showError( + models.inferenceStatusFetching.message, + models.inferenceStatusFetching.reason, + ); } if (shown) { - this.props.resetErrors(); - } - } - - public componentDidMount() { - this.props.verifyAuthorized(); - } - - public componentDidUpdate() { - this.showErrors(); - this.showMessages(); - - if (!this.props.userInitialized || this.props.user == null) { - // not authorized user - return; - } - - if (!this.props.formatsInitialized && !this.props.formatsFetching) { - this.props.loadFormats(); - } - - if (!this.props.usersInitialized && !this.props.usersFetching) { - this.props.loadUsers(); - } - - if (!this.props.pluginsInitialized && !this.props.pluginsFetching) { - this.props.initPlugins(); + resetErrors(); } } // Where you go depends on your URL - public render() { - const readyForRender = - (this.props.userInitialized && this.props.user == null) || - (this.props.userInitialized && this.props.formatsInitialized && - this.props.pluginsInitialized && this.props.usersInitialized); + public render(): JSX.Element { + const { + userInitialized, + usersInitialized, + pluginsInitialized, + formatsInitialized, + installedAutoAnnotation, + installedTFSegmentation, + installedTFAnnotation, + user, + } = this.props; - const withModels = this.props.installedAutoAnnotation - || this.props.installedTFAnnotation || this.props.installedTFSegmentation; + const readyForRender = (userInitialized && user == null) + || (userInitialized && formatsInitialized + && pluginsInitialized && usersInitialized); + + const withModels = installedAutoAnnotation + || installedTFAnnotation || installedTFSegmentation; if (readyForRender) { - if (this.props.user) { + if (user) { return ( - - - - - { withModels && - } - { this.props.installedAutoAnnotation && - } - + + + + + { withModels + && } + { installedAutoAnnotation + && } + - - + + ); - } else { - return ( - - - - - - - - ); } - } else { + return ( - + + + + + + + ); } + + return ( + + ); } } diff --git a/cvat-ui/src/components/feedback.tsx b/cvat-ui/src/components/feedback.tsx index 475d3ed71884..ead88caee65c 100644 --- a/cvat-ui/src/components/feedback.tsx +++ b/cvat-ui/src/components/feedback.tsx @@ -31,88 +31,87 @@ interface State { active: boolean; } -export default class Feedback extends React.PureComponent<{}, State> { - public constructor(props: {}) { - super(props); - this.state = { - active: false, - } - } +function renderContent(): JSX.Element { + const githubURL = 'https://github.com/opencv/cvat'; + const githubImage = 'https://raw.githubusercontent.com/opencv/' + + 'cvat/develop/cvat/apps/documentation/static/documentation/images/cvat.jpg'; + const questionsURL = 'https://gitter.im/opencv-cvat/public'; + const feedbackURL = 'https://gitter.im/opencv-cvat/public'; - private renderContent() { - const githubURL = 'https://github.com/opencv/cvat'; - const githubImage = 'https://raw.githubusercontent.com/opencv/' - + 'cvat/develop/cvat/apps/documentation/static/documentation/images/cvat.jpg'; - const questionsURL = 'https://gitter.im/opencv-cvat/public'; - const feedbackURL = 'https://gitter.im/opencv-cvat/public'; + return ( + <> + + + Star us on + GitHub + +
+ + + Leave a + feedback + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + Do you need help? Contact us on + gitter + + + ); +} - return ( - <> - - - Star us on GitHub - -
- - - Leave a feedback - -
-
- - - - - - - - - - - - - - - - - - - - - - - - -
-
- - Do you need help? Contact us on gitter - - - ); - } +export default function Feedback(): JSX.Element { + const [visible, setVisible] = React.useState(false); - public render() { - return ( - <> - Help to make CVAT better - } - content={this.renderContent()} - visible={this.state.active} + return ( + <> + Help to make CVAT better + } + content={renderContent()} + visible={visible} + > + - - - ); - } -} \ No newline at end of file + { visible ? + : } + + + + ); +} diff --git a/cvat-ui/src/index.tsx b/cvat-ui/src/index.tsx index 621c4813a3da..3ef428246f11 100644 --- a/cvat-ui/src/index.tsx +++ b/cvat-ui/src/index.tsx @@ -19,7 +19,7 @@ import { import { CombinedState, NotificationsState, - } from './reducers/interfaces'; +} from './reducers/interfaces'; createCVATStore(createRootReducer); const cvatStore = getCVATStore(); @@ -65,7 +65,7 @@ function mapStateToProps(state: CombinedState): StateToProps { installedAutoAnnotation: plugins.plugins.AUTO_ANNOTATION, installedTFSegmentation: plugins.plugins.TF_SEGMENTATION, installedTFAnnotation: plugins.plugins.TF_ANNOTATION, - notifications: {...state.notifications}, + notifications: { ...state.notifications }, user: auth.user, }; } @@ -81,29 +81,10 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { }; } -function reduxAppWrapper(props: StateToProps & DispatchToProps) { +function reduxAppWrapper(props: StateToProps & DispatchToProps): JSX.Element { return ( - - ) + + ); } const ReduxAppWrapper = connect( @@ -114,8 +95,8 @@ const ReduxAppWrapper = connect( ReactDOM.render( ( - + ), - document.getElementById('root') -) + document.getElementById('root'), +); From c4bab65d85e956fcc079743853a0ddce0ebb1d67 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 6 Dec 2019 12:15:23 +0300 Subject: [PATCH 10/17] Tasks page --- .../src/components/tasks-page/empty-list.tsx | 19 +- .../src/components/tasks-page/task-item.tsx | 171 +++++++++-------- .../src/components/tasks-page/task-list.tsx | 21 +- .../src/components/tasks-page/tasks-page.tsx | 180 ++++++++++-------- cvat-ui/src/components/tasks-page/top-bar.tsx | 41 ++-- cvat-ui/src/stylesheet.css | 4 + 6 files changed, 248 insertions(+), 188 deletions(-) diff --git a/cvat-ui/src/components/tasks-page/empty-list.tsx b/cvat-ui/src/components/tasks-page/empty-list.tsx index 8b631631142a..bbc89e00fdd0 100644 --- a/cvat-ui/src/components/tasks-page/empty-list.tsx +++ b/cvat-ui/src/components/tasks-page/empty-list.tsx @@ -8,32 +8,31 @@ import { Icon, } from 'antd'; -export default function EmptyListComponent() { - const emptyTasksIcon = () => (); +export default function EmptyListComponent(): JSX.Element { + const emptyTasksIcon = (): JSX.Element => (); return (
- + - {'No tasks created yet ...'} + No tasks created yet ... - {'To get started with your annotation project'} + To get started with your annotation project - + - {'create a new task'} + create a new task
- - ) -} \ No newline at end of file + ); +} diff --git a/cvat-ui/src/components/tasks-page/task-item.tsx b/cvat-ui/src/components/tasks-page/task-item.tsx index 02dd8ba5a219..4c5461006146 100644 --- a/cvat-ui/src/components/tasks-page/task-item.tsx +++ b/cvat-ui/src/components/tasks-page/task-item.tsx @@ -25,71 +25,80 @@ export interface TaskItemProps { } class TaskItemComponent extends React.PureComponent { - constructor(props: TaskItemProps & RouteComponentProps) { - super(props); - } - - private renderPreview() { + private renderPreview(): JSX.Element { + const { previewImage } = this.props; return (
- Preview + Preview
- ) + ); } - private renderDescription() { + private renderDescription(): JSX.Element { // Task info - const task = this.props.taskInstance; - const { id } = task; - const owner = task.owner ? task.owner.username : null; - const updated = moment(task.updatedDate).fromNow(); - const created = moment(task.createdDate).format('MMMM Do YYYY'); + const { taskInstance } = this.props; + const { id } = taskInstance; + const owner = taskInstance.owner ? taskInstance.owner.username : null; + const updated = moment(taskInstance.updatedDate).fromNow(); + const created = moment(taskInstance.createdDate).format('MMMM Do YYYY'); // Get and truncate a task name - const name = `${task.name.substring(0, 70)}${task.name.length > 70 ? '...' : ''}`; + const name = `${taskInstance.name.substring(0, 70)}${taskInstance.name.length > 70 ? '...' : ''}`; return ( - {`${id} ${name}`}
- { owner ? - <> - - Created { owner ? 'by ' + owner : '' } on {created} -
- : null + {`${id} ${name}`} +
+ { owner + && ( + <> + + {`Created ${owner ? `by ${owner}` : ''} on ${created}`} + +
+ + ) } {`Last updated ${updated}`} - ) + ); } - private renderProgress() { - const task = this.props.taskInstance; + private renderProgress(): JSX.Element { + const { + taskInstance, + activeInference, + } = this.props; // Count number of jobs and performed jobs - const numOfJobs = task.jobs.length; - const numOfCompleted = task.jobs.filter( - (job: any) => job.status === 'completed' + const numOfJobs = taskInstance.jobs.length; + const numOfCompleted = taskInstance.jobs.filter( + (job: any) => job.status === 'completed', ).length; // Progress appearence depends on number of jobs - const progressColor = numOfCompleted === numOfJobs ? 'cvat-task-completed-progress': - numOfCompleted ? 'cvat-task-progress-progress' : 'cvat-task-pending-progress'; + let progressColor = null; + let progressText = null; + if (numOfCompleted === numOfJobs) { + progressColor = 'cvat-task-completed-progress'; + progressText = Completed; + } else if (numOfCompleted) { + progressColor = 'cvat-task-progress-progress'; + progressText = In Progress; + } else { + progressColor = 'cvat-task-pending-progress'; + progressText = Pending; + } return ( - + - { numOfCompleted === numOfJobs ? - {'Completed'} - : numOfCompleted ? - {'In Progress'} - : {'Pending'} - } + { progressText } {`${numOfCompleted} of ${numOfJobs} jobs`} @@ -107,78 +116,86 @@ class TaskItemComponent extends React.PureComponent - { this.props.activeInference ? - <> - - - Automatic annotation - - - - - - - - : null + { activeInference + && ( + <> + + + Automatic annotation + + + + + + + + + ) } - ) + ); } - private renderNavigation() { - const subMenuIcon = () => (); - const { id } = this.props.taskInstance; + private renderNavigation(): JSX.Element { + const subMenuIcon = (): JSX.Element => (); + const { + taskInstance, + history, + } = this.props; + const { id } = taskInstance; return ( - + Actions - - }> - + }> + - ) + ); } - public render() { + public render(): JSX.Element { + const { deleted } = this.props; const style = {}; - if (this.props.deleted) { + if (deleted) { (style as any).pointerEvents = 'none'; (style as any).opacity = 0.5; } return ( - + {this.renderPreview()} {this.renderDescription()} {this.renderProgress()} {this.renderNavigation()} - ) - }; + ); + } } export default withRouter(TaskItemComponent); diff --git a/cvat-ui/src/components/tasks-page/task-list.tsx b/cvat-ui/src/components/tasks-page/task-list.tsx index bfce6f0796c3..da7f79c13e13 100644 --- a/cvat-ui/src/components/tasks-page/task-list.tsx +++ b/cvat-ui/src/components/tasks-page/task-list.tsx @@ -15,10 +15,15 @@ export interface ContentListProps { numberOfTasks: number; } -export default function TaskListComponent(props: ContentListProps) { - const tasks = props.currentTasksIndexes; - const taskViews = tasks.map( - (tid, id) => +export default function TaskListComponent(props: ContentListProps): JSX.Element { + const { + currentTasksIndexes, + numberOfTasks, + currentPage, + onSwitchPage, + } = props; + const taskViews = currentTasksIndexes.map( + (tid, id) => , ); return ( @@ -32,14 +37,14 @@ export default function TaskListComponent(props: ContentListProps) { - ) + ); } diff --git a/cvat-ui/src/components/tasks-page/tasks-page.tsx b/cvat-ui/src/components/tasks-page/tasks-page.tsx index 2b8da555cc01..25b2db39d795 100644 --- a/cvat-ui/src/components/tasks-page/tasks-page.tsx +++ b/cvat-ui/src/components/tasks-page/tasks-page.tsx @@ -22,135 +22,155 @@ interface TasksPageProps { onGetTasks: (gettingQuery: TasksQuery) => void; } -class TasksPageComponent extends React.PureComponent { - constructor(props: any) { - super(props); - } +function getSearchField(gettingQuery: TasksQuery): string { + let searchString = ''; + for (const field of Object.keys(gettingQuery)) { + if (gettingQuery[field] !== null && field !== 'page') { + if (field === 'search') { + return (gettingQuery[field] as any) as string; + } - private updateURL(gettingQuery: TasksQuery) { - let queryString = '?'; - for (const field of Object.keys(gettingQuery)) { - if (gettingQuery[field] !== null) { - queryString += `${field}=${gettingQuery[field]}&`; + // not constant condition + // eslint-disable-next-line + if (typeof (gettingQuery[field] === 'number')) { + searchString += `${field}:${gettingQuery[field]} AND `; + } else { + searchString += `${field}:"${gettingQuery[field]}" AND `; } } - this.props.history.replace({ - search: queryString.slice(0, -1), - }); } - private getSearchField(gettingQuery: TasksQuery): string { - let searchString = ''; - for (const field of Object.keys(gettingQuery)) { - if (gettingQuery[field] !== null && field !== 'page') { - if (field === 'search') { - return (gettingQuery[field] as any) as string; - } else { - if (typeof (gettingQuery[field] === 'number')) { - searchString += `${field}:${gettingQuery[field]} AND `; + return searchString.slice(0, -5); +} + +class TasksPageComponent extends React.PureComponent { + public componentDidMount(): void { + const { + gettingQuery, + location, + onGetTasks, + } = this.props; + const params = new URLSearchParams(location.search); + + const query = { ...gettingQuery }; + for (const field of Object.keys(query)) { + if (params.has(field)) { + const value = params.get(field); + if (value) { + if (field === 'id' || field === 'page') { + if (Number.isInteger(+value)) { + query[field] = +value; + } } else { - searchString += `${field}:"${gettingQuery[field]}" AND `; + query[field] = value; } } + } else if (field === 'page') { + query[field] = 1; + } else { + query[field] = null; } } - return searchString.slice(0, -5); + this.updateURL(query); + onGetTasks(query); } private handleSearch = (value: string): void => { - const gettingQuery = { ...this.props.gettingQuery }; + const { + gettingQuery, + onGetTasks, + } = this.props; + + const query = { ...gettingQuery }; const search = value.replace(/\s+/g, ' ').replace(/\s*:+\s*/g, ':').trim(); const fields = ['name', 'mode', 'owner', 'assignee', 'status', 'id']; for (const field of fields) { - gettingQuery[field] = null; + query[field] = null; } - gettingQuery.search = null; + query.search = null; let specificRequest = false; for (const param of search.split(/[\s]+and[\s]+|[\s]+AND[\s]+/)) { if (param.includes(':')) { - const [name, value] = param.split(':'); - if (fields.includes(name) && !!value) { + const [field, fieldValue] = param.split(':'); + if (fields.includes(field) && !!fieldValue) { specificRequest = true; - if (name === 'id') { - if (Number.isInteger(+value)) { - gettingQuery[name] = +value; + if (field === 'id') { + if (Number.isInteger(+fieldValue)) { + query[field] = +fieldValue; } } else { - gettingQuery[name] = value; + query[field] = fieldValue; } } } } - gettingQuery.page = 1; + query.page = 1; if (!specificRequest && value) { // only id - gettingQuery.search = value; + query.search = value; } - this.updateURL(gettingQuery); - this.props.onGetTasks(gettingQuery); - } + this.updateURL(query); + onGetTasks(query); + }; private handlePagination = (page: number): void => { - const gettingQuery = { ...this.props.gettingQuery }; + const { + gettingQuery, + onGetTasks, + } = this.props; + const query = { ...gettingQuery }; gettingQuery.page = page; this.updateURL(gettingQuery); - this.props.onGetTasks(gettingQuery); - } - - public componentDidMount() { - const gettingQuery = { ...this.props.gettingQuery }; - const params = new URLSearchParams(this.props.location.search); + onGetTasks(query); + }; + private updateURL(gettingQuery: TasksQuery): void { + const { history } = this.props; + let queryString = '?'; for (const field of Object.keys(gettingQuery)) { - if (params.has(field)) { - const value = params.get(field); - if (value) { - if (field === 'id' || field === 'page') { - if (Number.isInteger(+value)) { - gettingQuery[field] = +value; - } - } else { - gettingQuery[field] = value; - } - } - } else { - if (field === 'page') { - gettingQuery[field] = 1; - } else { - gettingQuery[field] = null; - } + if (gettingQuery[field] !== null) { + queryString += `${field}=${gettingQuery[field]}&`; } } - - this.updateURL(gettingQuery); - this.props.onGetTasks(gettingQuery); + history.replace({ + search: queryString.slice(0, -1), + }); } - public render() { - if (this.props.tasksFetching) { + public render(): JSX.Element { + const { + tasksFetching, + gettingQuery, + numberOfVisibleTasks, + } = this.props; + + if (tasksFetching) { return ( - + ); - } else { - return ( -
- - {this.props.numberOfVisibleTasks ? + } + + return ( +
+ + {numberOfVisibleTasks + ? ( : } -
- ) - } + /> + ) : + } +
+ ); } } -export default withRouter(TasksPageComponent); \ No newline at end of file +export default withRouter(TasksPageComponent); diff --git a/cvat-ui/src/components/tasks-page/top-bar.tsx b/cvat-ui/src/components/tasks-page/top-bar.tsx index b8cfea73f738..486e1dbb1f33 100644 --- a/cvat-ui/src/components/tasks-page/top-bar.tsx +++ b/cvat-ui/src/components/tasks-page/top-bar.tsx @@ -16,35 +16,50 @@ interface VisibleTopBarProps { searchValue: string; } -function TopBarComponent(props: VisibleTopBarProps & RouteComponentProps) { +function TopBarComponent(props: VisibleTopBarProps & RouteComponentProps): JSX.Element { + const { + searchValue, + history, + onSearch, + } = props; + return ( <> - {'Default project'} + Default project Tasks - + md={{ span: 11 }} + lg={{ span: 9 }} + xl={{ span: 8 }} + xxl={{ span: 7 }} + > + - ) + ); } export default withRouter(TopBarComponent); diff --git a/cvat-ui/src/stylesheet.css b/cvat-ui/src/stylesheet.css index 1a2aef1828a4..4c3c2b34c05f 100644 --- a/cvat-ui/src/stylesheet.css +++ b/cvat-ui/src/stylesheet.css @@ -275,6 +275,10 @@ border: 1px solid #40a9ff; } +.cvat-tasks-list-item > div:nth-child(2) { + word-break: break-all; +} + .cvat-tasks-list-item > div:nth-child(4) > div { margin-right: 20px; } From fc234eb48a12538840d6ff86c4c5d4bafe538fa6 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 6 Dec 2019 12:32:49 +0300 Subject: [PATCH 11/17] Containers --- .../containers/actions-menu/actions-menu.tsx | 44 ++++++------------- .../annotation-page/annotation-page.tsx | 10 ++--- .../create-model-page/create-model-page.tsx | 12 ++--- .../create-task-page/create-task-page.tsx | 12 ++--- .../containers/file-manager/file-manager.tsx | 24 ++++++---- cvat-ui/src/containers/header/header.tsx | 18 +++----- .../src/containers/login-page/login-page.tsx | 11 ++--- .../model-runner-dialog.tsx | 33 +++++--------- .../containers/models-page/models-page.tsx | 31 ++++++------- .../register-page/register-page.tsx | 13 +++--- cvat-ui/src/containers/task-page/details.tsx | 28 +++++++----- cvat-ui/src/containers/task-page/job-list.tsx | 18 +++++--- .../src/containers/task-page/task-page.tsx | 14 ++---- .../src/containers/tasks-page/task-item.tsx | 17 +++---- .../src/containers/tasks-page/tasks-list.tsx | 21 ++++++--- .../src/containers/tasks-page/tasks-page.tsx | 20 ++++----- 16 files changed, 140 insertions(+), 186 deletions(-) diff --git a/cvat-ui/src/containers/actions-menu/actions-menu.tsx b/cvat-ui/src/containers/actions-menu/actions-menu.tsx index 1fab00afa0f3..dc8692c61454 100644 --- a/cvat-ui/src/containers/actions-menu/actions-menu.tsx +++ b/cvat-ui/src/containers/actions-menu/actions-menu.tsx @@ -4,7 +4,6 @@ import { connect } from 'react-redux'; import ActionsMenuComponent from '../../components/actions-menu/actions-menu'; import { CombinedState, - ActiveInference, } from '../../reducers/interfaces'; import { showRunModelDialog } from '../../actions/models-actions'; @@ -30,7 +29,7 @@ interface StateToProps { installedTFSegmentation: boolean; installedAutoAnnotation: boolean; inferenceIsActive: boolean; -}; +} interface DispatchToProps { onLoadAnnotation: (taskInstance: any, loader: any, file: File) => void; @@ -45,16 +44,16 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { const { activities } = state.tasks; const { dumps } = activities; const { loads } = activities; - const _exports = activities.exports; + const activeExports = activities.exports; const { plugins } = state.plugins; - const id = own.taskInstance.id; + const { id } = own.taskInstance; return { installedTFAnnotation: plugins.TF_ANNOTATION, installedTFSegmentation: plugins.TF_SEGMENTATION, installedAutoAnnotation: plugins.AUTO_ANNOTATION, dumpActivities: dumps.byTask[id] ? dumps.byTask[id] : null, - exportActivities: _exports.byTask[id] ? _exports.byTask[id] : null, + exportActivities: activeExports.byTask[id] ? activeExports.byTask[id] : null, loadActivity: loads.byTask[id] ? loads.byTask[id] : null, loaders: formats.annotationFormats .map((format: any): any[] => format.loaders).flat(), @@ -62,49 +61,32 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { .map((format: any): any[] => format.dumpers).flat(), exporters: formats.datasetFormats, inferenceIsActive: id in state.models.inferences, - }; + }; } function mapDispatchToProps(dispatch: any): DispatchToProps { return { - onLoadAnnotation: (taskInstance: any, loader: any, file: File) => { + onLoadAnnotation: (taskInstance: any, loader: any, file: File): void => { dispatch(loadAnnotationsAsync(taskInstance, loader, file)); }, - onDumpAnnotation: (taskInstance: any, dumper: any) => { + onDumpAnnotation: (taskInstance: any, dumper: any): void => { dispatch(dumpAnnotationsAsync(taskInstance, dumper)); }, - onExportDataset: (taskInstance: any, exporter: any) => { + onExportDataset: (taskInstance: any, exporter: any): void => { dispatch(exportDatasetAsync(taskInstance, exporter)); }, - onDeleteTask: (taskInstance: any) => { + onDeleteTask: (taskInstance: any): void => { dispatch(deleteTaskAsync(taskInstance)); }, - onOpenRunWindow: (taskInstance: any) => { + onOpenRunWindow: (taskInstance: any): void => { dispatch(showRunModelDialog(taskInstance)); - } + }, }; } -function ActionsMenuContainer(props: OwnProps & StateToProps & DispatchToProps) { +function ActionsMenuContainer(props: OwnProps & StateToProps & DispatchToProps): JSX.Element { return ( - + ); } diff --git a/cvat-ui/src/containers/annotation-page/annotation-page.tsx b/cvat-ui/src/containers/annotation-page/annotation-page.tsx index d0e482ace13d..07f5a7ca9665 100644 --- a/cvat-ui/src/containers/annotation-page/annotation-page.tsx +++ b/cvat-ui/src/containers/annotation-page/annotation-page.tsx @@ -1,15 +1,11 @@ import React from 'react'; export default class AnnotationPageContainer extends React.PureComponent { - constructor(props: any) { - super(props); - } - - public render() { + public render(): JSX.Element { return (
- "AnnotationPage" + AnnotationPage
); } -} \ No newline at end of file +} diff --git a/cvat-ui/src/containers/create-model-page/create-model-page.tsx b/cvat-ui/src/containers/create-model-page/create-model-page.tsx index 188589f47ea6..a20b6fb56283 100644 --- a/cvat-ui/src/containers/create-model-page/create-model-page.tsx +++ b/cvat-ui/src/containers/create-model-page/create-model-page.tsx @@ -18,7 +18,7 @@ interface DispatchToProps { } function mapStateToProps(state: CombinedState): StateToProps { - const { models} = state; + const { models } = state; return { isAdmin: state.auth.user.isAdmin, @@ -28,19 +28,15 @@ function mapStateToProps(state: CombinedState): StateToProps { function mapDispatchToProps(dispatch: any): DispatchToProps { return { - createModel(name: string, files: ModelFiles, global: boolean) { + createModel(name: string, files: ModelFiles, global: boolean): void { dispatch(createModelAsync(name, files, global)); }, }; } -function CreateModelPageContainer(props: StateToProps & DispatchToProps) { +function CreateModelPageContainer(props: StateToProps & DispatchToProps): JSX.Element { return ( - + ); } diff --git a/cvat-ui/src/containers/create-task-page/create-task-page.tsx b/cvat-ui/src/containers/create-task-page/create-task-page.tsx index a7655dce34ea..aac98fa42ed4 100644 --- a/cvat-ui/src/containers/create-task-page/create-task-page.tsx +++ b/cvat-ui/src/containers/create-task-page/create-task-page.tsx @@ -12,12 +12,12 @@ interface StateToProps { } interface DispatchToProps { - create: (data: CreateTaskData) => void; + onCreate: (data: CreateTaskData) => void; } function mapDispatchToProps(dispatch: any): DispatchToProps { return { - create: (data: CreateTaskData) => dispatch(createTaskAsync(data)), + onCreate: (data: CreateTaskData): void => dispatch(createTaskAsync(data)), }; } @@ -29,13 +29,9 @@ function mapStateToProps(state: CombinedState): StateToProps { }; } -function CreateTaskPageContainer(props: StateToProps & DispatchToProps) { +function CreateTaskPageContainer(props: StateToProps & DispatchToProps): JSX.Element { return ( - + ); } diff --git a/cvat-ui/src/containers/file-manager/file-manager.tsx b/cvat-ui/src/containers/file-manager/file-manager.tsx index 9cdbd9ed9fd5..40147d1111e2 100644 --- a/cvat-ui/src/containers/file-manager/file-manager.tsx +++ b/cvat-ui/src/containers/file-manager/file-manager.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { connect } from 'react-redux'; -import { TreeNodeNormal } from 'antd/lib/tree/Tree' +import { TreeNodeNormal } from 'antd/lib/tree/Tree'; import FileManagerComponent, { Files } from '../../components/file-manager/file-manager'; import { loadShareDataAsync } from '../../actions/share-actions'; @@ -44,9 +44,9 @@ function mapStateToProps(state: CombinedState): StateToProps { function mapDispatchToProps(dispatch: any): DispatchToProps { return { - getTreeData: (key: string, success: () => void, failure: () => void) => { + getTreeData: (key: string, success: () => void, failure: () => void): void => { dispatch(loadShareDataAsync(key, success, failure)); - } + }, }; } @@ -63,13 +63,21 @@ export class FileManagerContainer extends React.PureComponent { return this.managerComponentRef.reset(); } - public render() { + public render(): JSX.Element { + const { + treeData, + getTreeData, + withRemote, + } = this.props; + return ( this.managerComponentRef = component} + treeData={treeData} + onLoadData={getTreeData} + withRemote={withRemote} + ref={(component): void => { + this.managerComponentRef = component; + }} /> ); } diff --git a/cvat-ui/src/containers/header/header.tsx b/cvat-ui/src/containers/header/header.tsx index f3ec07fbe633..b434c18b6b12 100644 --- a/cvat-ui/src/containers/header/header.tsx +++ b/cvat-ui/src/containers/header/header.tsx @@ -19,7 +19,7 @@ interface StateToProps { } interface DispatchToProps { - logout(): void; + onLogout(): void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -37,21 +37,13 @@ function mapStateToProps(state: CombinedState): StateToProps { function mapDispatchToProps(dispatch: any): DispatchToProps { return { - logout: () => dispatch(logoutAsync()), - } + onLogout: (): void => dispatch(logoutAsync()), + }; } -function HeaderContainer(props: StateToProps & DispatchToProps) { +function HeaderContainer(props: StateToProps & DispatchToProps): JSX.Element { return ( - + ); } diff --git a/cvat-ui/src/containers/login-page/login-page.tsx b/cvat-ui/src/containers/login-page/login-page.tsx index f04e0e3c2392..b6b9b57dee84 100644 --- a/cvat-ui/src/containers/login-page/login-page.tsx +++ b/cvat-ui/src/containers/login-page/login-page.tsx @@ -9,7 +9,7 @@ interface StateToProps { } interface DispatchToProps { - login(username: string, password: string): void; + onLogin(username: string, password: string): void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -20,16 +20,13 @@ function mapStateToProps(state: CombinedState): StateToProps { function mapDispatchToProps(dispatch: any): DispatchToProps { return { - login: (...args) => dispatch(loginAsync(...args)), + onLogin: (...args): void => dispatch(loginAsync(...args)), }; } -function LoginPageContainer(props: DispatchToProps & StateToProps) { +function LoginPageContainer(props: DispatchToProps & StateToProps): JSX.Element { return ( - + ); } diff --git a/cvat-ui/src/containers/model-runner-dialog/model-runner-dialog.tsx b/cvat-ui/src/containers/model-runner-dialog/model-runner-dialog.tsx index 290c4df86e36..1da1386caef2 100644 --- a/cvat-ui/src/containers/model-runner-dialog/model-runner-dialog.tsx +++ b/cvat-ui/src/containers/model-runner-dialog/model-runner-dialog.tsx @@ -18,18 +18,18 @@ interface StateToProps { modelsInitialized: boolean; models: Model[]; activeProcesses: { - [index: string]: string + [index: string]: string; }; taskInstance: any; visible: boolean; } interface DispatchToProps { - inferModelAsync( + runInference( taskInstance: any, model: Model, mapping: { - [index: string]: string + [index: string]: string; }, cleanOut: boolean, ): void; @@ -52,42 +52,33 @@ function mapStateToProps(state: CombinedState): StateToProps { function mapDispatchToProps(dispatch: any): DispatchToProps { return ({ - inferModelAsync( + runInference( taskInstance: any, model: Model, mapping: { - [index: string]: string + [index: string]: string; }, - cleanOut: boolean): void { - dispatch(inferModelAsync(taskInstance, model, mapping, cleanOut)); + cleanOut: boolean, + ): void { + dispatch(inferModelAsync(taskInstance, model, mapping, cleanOut)); }, getModels(): void { dispatch(getModelsAsync()); }, closeDialog(): void { dispatch(closeRunModelDialog()); - } + }, }); } -function ModelRunnerModalContainer(props: StateToProps & DispatchToProps) { +function ModelRunnerModalContainer(props: StateToProps & DispatchToProps): JSX.Element { return ( - + ); } export default connect( mapStateToProps, mapDispatchToProps, -) (ModelRunnerModalContainer); +)(ModelRunnerModalContainer); diff --git a/cvat-ui/src/containers/models-page/models-page.tsx b/cvat-ui/src/containers/models-page/models-page.tsx index 76c2e7f81b8a..ba56bb97e255 100644 --- a/cvat-ui/src/containers/models-page/models-page.tsx +++ b/cvat-ui/src/containers/models-page/models-page.tsx @@ -43,33 +43,28 @@ function mapStateToProps(state: CombinedState): StateToProps { function mapDispatchToProps(dispatch: any): DispatchToProps { return { - getModels() { + getModels(): void { dispatch(getModelsAsync()); }, - deleteModel(id: number) { + deleteModel(id: number): void { dispatch(deleteModelAsync(id)); }, }; } -function ModelsPageContainer(props: DispatchToProps & StateToProps) { - const render = props.installedAutoAnnotation - || props.installedTFAnnotation - || props.installedTFSegmentation; +function ModelsPageContainer(props: DispatchToProps & StateToProps): JSX.Element | null { + const { + installedAutoAnnotation, + installedTFSegmentation, + installedTFAnnotation, + } = props; + + const render = installedAutoAnnotation + || installedTFAnnotation + || installedTFSegmentation; return ( - render ? - : null + render ? : null ); } diff --git a/cvat-ui/src/containers/register-page/register-page.tsx b/cvat-ui/src/containers/register-page/register-page.tsx index f989ece22bc2..c11310865444 100644 --- a/cvat-ui/src/containers/register-page/register-page.tsx +++ b/cvat-ui/src/containers/register-page/register-page.tsx @@ -9,7 +9,7 @@ interface StateToProps { } interface DispatchToProps { - register: (username: string, firstName: string, + onRegister: (username: string, firstName: string, lastName: string, email: string, password1: string, password2: string) => void; } @@ -22,16 +22,13 @@ function mapStateToProps(state: CombinedState): StateToProps { function mapDispatchToProps(dispatch: any): DispatchToProps { return { - register: (...args) => dispatch(registerAsync(...args)) - } + onRegister: (...args): void => dispatch(registerAsync(...args)), + }; } -function RegisterPageContainer(props: StateToProps & DispatchToProps) { +function RegisterPageContainer(props: StateToProps & DispatchToProps): JSX.Element { return ( - + ); } diff --git a/cvat-ui/src/containers/task-page/details.tsx b/cvat-ui/src/containers/task-page/details.tsx index 1f5155f34d65..913bbdf19ddf 100644 --- a/cvat-ui/src/containers/task-page/details.tsx +++ b/cvat-ui/src/containers/task-page/details.tsx @@ -21,7 +21,7 @@ interface DispatchToProps { onTaskUpdate: (taskInstance: any) => void; } -function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { +function mapStateToProps(state: CombinedState): StateToProps { const { plugins } = state.plugins; return { @@ -33,20 +33,26 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { function mapDispatchToProps(dispatch: any): DispatchToProps { return { - onTaskUpdate: (taskInstance: any) => - dispatch(updateTaskAsync(taskInstance)) - } + onTaskUpdate: (taskInstance: any): void => dispatch(updateTaskAsync(taskInstance)), + }; } -function TaskPageContainer(props: StateToProps & DispatchToProps & OwnProps) { +function TaskPageContainer(props: StateToProps & DispatchToProps & OwnProps): JSX.Element { + const { + task, + installedGit, + registeredUsers, + onTaskUpdate, + } = props; + return ( ); } @@ -54,4 +60,4 @@ function TaskPageContainer(props: StateToProps & DispatchToProps & OwnProps) { export default connect( mapStateToProps, mapDispatchToProps, -)(TaskPageContainer); \ No newline at end of file +)(TaskPageContainer); diff --git a/cvat-ui/src/containers/task-page/job-list.tsx b/cvat-ui/src/containers/task-page/job-list.tsx index 1714c585bda9..c7e32c589ccb 100644 --- a/cvat-ui/src/containers/task-page/job-list.tsx +++ b/cvat-ui/src/containers/task-page/job-list.tsx @@ -28,16 +28,22 @@ function mapStateToProps(state: CombinedState): StateToProps { function mapDispatchToProps(dispatch: any): DispatchToProps { return { - onJobUpdate: (jobInstance: any) => dispatch(updateJobAsync(jobInstance)), + onJobUpdate: (jobInstance: any): void => dispatch(updateJobAsync(jobInstance)), }; } -function TaskPageContainer(props: StateToProps & DispatchToProps & OwnProps) { +function TaskPageContainer(props: StateToProps & DispatchToProps & OwnProps): JSX.Element { + const { + task, + registeredUsers, + onJobUpdate, + } = props; + return ( ); } @@ -45,4 +51,4 @@ function TaskPageContainer(props: StateToProps & DispatchToProps & OwnProps) { export default connect( mapStateToProps, mapDispatchToProps, -)(TaskPageContainer); \ No newline at end of file +)(TaskPageContainer); diff --git a/cvat-ui/src/containers/task-page/task-page.tsx b/cvat-ui/src/containers/task-page/task-page.tsx index 844df0844ff8..1133e16620b3 100644 --- a/cvat-ui/src/containers/task-page/task-page.tsx +++ b/cvat-ui/src/containers/task-page/task-page.tsx @@ -21,7 +21,7 @@ interface StateToProps { } interface DispatchToProps { - fetchTask: (tid: number) => void; + onFetchTask: (tid: number) => void; } function mapStateToProps(state: CombinedState, own: Props): StateToProps { @@ -47,7 +47,7 @@ function mapStateToProps(state: CombinedState, own: Props): StateToProps { function mapDispatchToProps(dispatch: any): DispatchToProps { return { - fetchTask: (tid: number) => { + onFetchTask: (tid: number): void => { dispatch(getTasksAsync({ id: tid, page: 1, @@ -62,15 +62,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { }; } -function TaskPageContainer(props: StateToProps & DispatchToProps) { +function TaskPageContainer(props: StateToProps & DispatchToProps): JSX.Element { return ( - + ); } diff --git a/cvat-ui/src/containers/tasks-page/task-item.tsx b/cvat-ui/src/containers/tasks-page/task-item.tsx index e13390d7e3f5..38e5c1931167 100644 --- a/cvat-ui/src/containers/tasks-page/task-item.tsx +++ b/cvat-ui/src/containers/tasks-page/task-item.tsx @@ -7,14 +7,14 @@ import { ActiveInference, } from '../../reducers/interfaces'; -import TaskItemComponent from '../../components/tasks-page/task-item' +import TaskItemComponent from '../../components/tasks-page/task-item'; import { getTasksAsync, } from '../../actions/tasks-actions'; interface StateToProps { - deleteActivity: boolean | null; + deleted: boolean; previewImage: string; taskInstance: any; activeInference: ActiveInference | null; @@ -35,7 +35,7 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { const id = own.taskID; return { - deleteActivity: deletes.byTask[id] ? deletes.byTask[id] : null, + deleted: deletes.byTask[id] ? deletes.byTask[id] === true : false, previewImage: task.preview, taskInstance: task.instance, activeInference: state.models.inferences[id] || null, @@ -47,19 +47,14 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { getTasks: (query: TasksQuery): void => { dispatch(getTasksAsync(query)); }, - } + }; } type TasksItemContainerProps = StateToProps & DispatchToProps & OwnProps; -function TaskItemContainer(props: TasksItemContainerProps) { +function TaskItemContainer(props: TasksItemContainerProps): JSX.Element { return ( - + ); } diff --git a/cvat-ui/src/containers/tasks-page/tasks-list.tsx b/cvat-ui/src/containers/tasks-page/tasks-list.tsx index 8782e3f9fb15..31021c1a0221 100644 --- a/cvat-ui/src/containers/tasks-page/tasks-list.tsx +++ b/cvat-ui/src/containers/tasks-page/tasks-list.tsx @@ -33,19 +33,26 @@ function mapStateToProps(state: CombinedState): StateToProps { function mapDispatchToProps(dispatch: any): DispatchToProps { return { - getTasks: (query: TasksQuery) => {dispatch(getTasksAsync(query))} - } + getTasks: (query: TasksQuery): void => { + dispatch(getTasksAsync(query)); + }, + }; } type TasksListContainerProps = StateToProps & DispatchToProps & OwnProps; -function TasksListContainer(props: TasksListContainerProps) { +function TasksListContainer(props: TasksListContainerProps): JSX.Element { + const { + tasks, + onSwitchPage, + } = props; + return ( task.instance.id)} - currentPage={props.tasks.gettingQuery.page} - numberOfTasks={props.tasks.count} + onSwitchPage={onSwitchPage} + currentTasksIndexes={tasks.current.map((task) => task.instance.id)} + currentPage={tasks.gettingQuery.page} + numberOfTasks={tasks.count} /> ); } diff --git a/cvat-ui/src/containers/tasks-page/tasks-page.tsx b/cvat-ui/src/containers/tasks-page/tasks-page.tsx index 1420ca58927c..006e2c7d3e21 100644 --- a/cvat-ui/src/containers/tasks-page/tasks-page.tsx +++ b/cvat-ui/src/containers/tasks-page/tasks-page.tsx @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import { TasksQuery, - CombinedState + CombinedState, } from '../../reducers/interfaces'; import TasksPageComponent from '../../components/tasks-page/tasks-page'; @@ -18,7 +18,7 @@ interface StateToProps { } interface DispatchToProps { - getTasks: (gettingQuery: TasksQuery) => void; + onGetTasks: (gettingQuery: TasksQuery) => void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -34,21 +34,17 @@ function mapStateToProps(state: CombinedState): StateToProps { function mapDispatchToProps(dispatch: any): DispatchToProps { return { - getTasks: (query: TasksQuery) => {dispatch(getTasksAsync(query))} - } + onGetTasks: (query: TasksQuery): void => { + dispatch(getTasksAsync(query)); + }, + }; } type TasksPageContainerProps = StateToProps & DispatchToProps; -function TasksPageContainer(props: TasksPageContainerProps) { +function TasksPageContainer(props: TasksPageContainerProps): JSX.Element { return ( - + ); } From 8b5b48b6174d5380118e3d8e4104f12624d6295d Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 6 Dec 2019 12:34:36 +0300 Subject: [PATCH 12/17] Reducers --- cvat-ui/src/reducers/auth-reducer.ts | 15 +++++---------- cvat-ui/src/reducers/formats-reducer.ts | 2 +- cvat-ui/src/reducers/interfaces.ts | 4 ++-- cvat-ui/src/reducers/models-reducer.ts | 2 +- cvat-ui/src/reducers/notifications-reducer.ts | 14 +++++++------- cvat-ui/src/reducers/plugins-reducer.ts | 2 +- cvat-ui/src/reducers/share-reducer.ts | 2 +- cvat-ui/src/reducers/tasks-reducer.ts | 2 +- cvat-ui/src/reducers/users-reducer.ts | 2 +- 9 files changed, 20 insertions(+), 25 deletions(-) diff --git a/cvat-ui/src/reducers/auth-reducer.ts b/cvat-ui/src/reducers/auth-reducer.ts index 023aad82cf9d..63fecdf67ece 100644 --- a/cvat-ui/src/reducers/auth-reducer.ts +++ b/cvat-ui/src/reducers/auth-reducer.ts @@ -18,15 +18,15 @@ export default (state = defaultState, action: AnyAction): AuthState => { user: action.payload.user, }; case AuthActionTypes.AUTHORIZED_FAILED: - return { - ...state, - initialized: true, - }; + return { + ...state, + initialized: true, + }; case AuthActionTypes.LOGIN: return { ...state, fetching: true, - } + }; case AuthActionTypes.LOGIN_SUCCESS: return { ...state, @@ -49,11 +49,6 @@ export default (state = defaultState, action: AnyAction): AuthState => { fetching: false, user: null, }; - case AuthActionTypes.LOGIN_FAILED: - return { - ...state, - fetching: false, - }; case AuthActionTypes.REGISTER: return { ...state, diff --git a/cvat-ui/src/reducers/formats-reducer.ts b/cvat-ui/src/reducers/formats-reducer.ts index 01fa6e43d716..40fd2a80506f 100644 --- a/cvat-ui/src/reducers/formats-reducer.ts +++ b/cvat-ui/src/reducers/formats-reducer.ts @@ -37,7 +37,7 @@ export default (state = defaultState, action: AnyAction): FormatsState => { case AuthActionTypes.LOGOUT_SUCCESS: { return { ...defaultState, - } + }; } default: return state; diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index c08af738c529..6a18564671fc 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -147,8 +147,8 @@ export interface ModelFiles { } export interface ErrorState { - message: string, - reason: string, + message: string; + reason: string; } export interface NotificationsState { diff --git a/cvat-ui/src/reducers/models-reducer.ts b/cvat-ui/src/reducers/models-reducer.ts index 8636b224ab5f..664622fecffe 100644 --- a/cvat-ui/src/reducers/models-reducer.ts +++ b/cvat-ui/src/reducers/models-reducer.ts @@ -110,7 +110,7 @@ export default function (state = defaultState, action: AnyAction): ModelsState { case AuthActionTypes.LOGOUT_SUCCESS: { return { ...defaultState, - } + }; } default: { return { diff --git a/cvat-ui/src/reducers/notifications-reducer.ts b/cvat-ui/src/reducers/notifications-reducer.ts index 86074948396a..e6fa34873860 100644 --- a/cvat-ui/src/reducers/notifications-reducer.ts +++ b/cvat-ui/src/reducers/notifications-reducer.ts @@ -67,7 +67,7 @@ export default function (state = defaultState, action: AnyAction): Notifications authorized: { message: 'Could not check authorization on the server', reason: action.payload.error.toString(), - } + }, }, }, }; @@ -82,7 +82,7 @@ export default function (state = defaultState, action: AnyAction): Notifications login: { message: 'Could not login on the server', reason: action.payload.error.toString(), - } + }, }, }, }; @@ -323,7 +323,7 @@ export default function (state = defaultState, action: AnyAction): Notifications } case ModelsActionTypes.GET_INFERENCE_STATUS_SUCCESS: { if (action.payload.activeInference.status === 'finished') { - const taskID = action.payload.taskID; + const { taskID } = action.payload; return { ...state, messages: { @@ -351,7 +351,7 @@ export default function (state = defaultState, action: AnyAction): Notifications metaFetching: { message: 'Could not fetch models meta information', reason: action.payload.error.toString(), - } + }, }, }, }; @@ -368,7 +368,7 @@ export default function (state = defaultState, action: AnyAction): Notifications message: 'Could not fetch inference status for the ' + `task ${taskID}`, reason: action.payload.error.toString(), - } + }, }, }, }; @@ -400,7 +400,7 @@ export default function (state = defaultState, action: AnyAction): Notifications message: 'Could not infer model for the ' + `task ${taskID}`, reason: action.payload.error.toString(), - } + }, }, }, }; @@ -424,7 +424,7 @@ export default function (state = defaultState, action: AnyAction): Notifications case AuthActionTypes.LOGOUT_SUCCESS: { return { ...defaultState, - } + }; } default: { return { diff --git a/cvat-ui/src/reducers/plugins-reducer.ts b/cvat-ui/src/reducers/plugins-reducer.ts index 29290e18d456..1a7e1f160787 100644 --- a/cvat-ui/src/reducers/plugins-reducer.ts +++ b/cvat-ui/src/reducers/plugins-reducer.ts @@ -45,7 +45,7 @@ export default function (state = defaultState, action: AnyAction): PluginsState case AuthActionTypes.LOGOUT_SUCCESS: { return { ...defaultState, - } + }; } default: return { ...state }; diff --git a/cvat-ui/src/reducers/share-reducer.ts b/cvat-ui/src/reducers/share-reducer.ts index 66a989f1970c..185bf2565e90 100644 --- a/cvat-ui/src/reducers/share-reducer.ts +++ b/cvat-ui/src/reducers/share-reducer.ts @@ -46,7 +46,7 @@ export default function (state = defaultState, action: AnyAction): ShareState { case AuthActionTypes.LOGOUT_SUCCESS: { return { ...defaultState, - } + }; } default: return { diff --git a/cvat-ui/src/reducers/tasks-reducer.ts b/cvat-ui/src/reducers/tasks-reducer.ts index 200d4f4d0d10..cb6c975ff814 100644 --- a/cvat-ui/src/reducers/tasks-reducer.ts +++ b/cvat-ui/src/reducers/tasks-reducer.ts @@ -408,7 +408,7 @@ export default (state: TasksState = defaultState, action: AnyAction): TasksState case AuthActionTypes.LOGOUT_SUCCESS: { return { ...defaultState, - } + }; } default: return state; diff --git a/cvat-ui/src/reducers/users-reducer.ts b/cvat-ui/src/reducers/users-reducer.ts index b1f10d427427..bb37e6dfc470 100644 --- a/cvat-ui/src/reducers/users-reducer.ts +++ b/cvat-ui/src/reducers/users-reducer.ts @@ -35,7 +35,7 @@ export default function (state: UsersState = defaultState, action: AnyAction): U case AuthActionTypes.LOGOUT_SUCCESS: { return { ...defaultState, - } + }; } default: return { From afefad922bc91c8f74ca9f2ff83f42cc00ee7174 Mon Sep 17 00:00:00 2001 From: Nobody Date: Fri, 6 Dec 2019 10:38:19 +0000 Subject: [PATCH 13/17] Initial commit From b4052c45a6d307b3ff6477fd2adc975ef16913f8 Mon Sep 17 00:00:00 2001 From: Nobody Date: Fri, 6 Dec 2019 10:38:20 +0000 Subject: [PATCH 14/17] Initial commit From d5b2a633ba4779affdce074459081b95aa2f6605 Mon Sep 17 00:00:00 2001 From: Nobody Date: Fri, 6 Dec 2019 10:38:23 +0000 Subject: [PATCH 15/17] Initial commit From cf8cf3b1f45b8c0fe2915ef1894c5a63405fdc2b Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 6 Dec 2019 13:50:49 +0300 Subject: [PATCH 16/17] Fixed additional issues --- cvat-ui/.eslintrc.js | 1 + .../components/actions-menu/actions-menu.tsx | 6 +++--- .../create-model-page/create-model-form.tsx | 2 +- .../advanced-configuration-form.tsx | 2 +- .../basic-configuration-form.tsx | 2 +- .../create-task-page/create-task-content.tsx | 6 +++--- .../components/file-manager/file-manager.tsx | 2 +- .../components/labels-editor/label-form.tsx | 8 ++++---- .../components/labels-editor/labels-editor.tsx | 2 +- .../components/labels-editor/raw-viewer.tsx | 7 ++++--- .../src/components/login-page/login-form.tsx | 2 +- .../src/components/login-page/login-page.tsx | 2 +- .../model-runner-modal/model-runner-modal.tsx | 18 +++++++++--------- .../models-page/built-model-item.tsx | 2 +- .../models-page/built-models-list.tsx | 4 +++- .../src/components/models-page/models-page.tsx | 4 ++-- .../models-page/uploaded-model-item.tsx | 2 +- .../models-page/uploaded-models-list.tsx | 2 +- .../components/register-page/register-form.tsx | 2 +- cvat-ui/src/components/task-page/details.tsx | 12 ++++++------ cvat-ui/src/components/task-page/job-list.tsx | 7 +++---- .../src/components/task-page/user-selector.tsx | 2 +- .../src/components/tasks-page/task-item.tsx | 2 +- .../src/components/tasks-page/task-list.tsx | 2 +- .../src/containers/tasks-page/tasks-list.tsx | 2 +- 25 files changed, 53 insertions(+), 50 deletions(-) diff --git a/cvat-ui/.eslintrc.js b/cvat-ui/.eslintrc.js index 90bb054240ad..f40cac1879fe 100644 --- a/cvat-ui/.eslintrc.js +++ b/cvat-ui/.eslintrc.js @@ -28,6 +28,7 @@ module.exports = { '@typescript-eslint/indent': ['warn', 4], 'react/jsx-indent': ['warn', 4], 'react/jsx-indent-props': ['warn', 4], + 'react/jsx-props-no-spreading': 0, 'jsx-quotes': ['error', 'prefer-single'], 'arrow-parens': ['error', 'always'], '@typescript-eslint/no-explicit-any': [0], diff --git a/cvat-ui/src/components/actions-menu/actions-menu.tsx b/cvat-ui/src/components/actions-menu/actions-menu.tsx index 49a32a1c99ef..a9be862bba44 100644 --- a/cvat-ui/src/components/actions-menu/actions-menu.tsx +++ b/cvat-ui/src/components/actions-menu/actions-menu.tsx @@ -92,7 +92,7 @@ export default function ActionsMenuComponent(props: ActionsMenuComponentProps): > { - dumpers.map((dumper) => DumperItemComponent({ + dumpers.map((dumper): JSX.Element => DumperItemComponent({ dumper, taskInstance: props.taskInstance, dumpActivity: (props.dumpActivities || []) @@ -103,7 +103,7 @@ export default function ActionsMenuComponent(props: ActionsMenuComponentProps): { - loaders.map((loader) => LoaderItemComponent({ + loaders.map((loader): JSX.Element => LoaderItemComponent({ loader, taskInstance: props.taskInstance, loadActivity: props.loadActivity, @@ -113,7 +113,7 @@ export default function ActionsMenuComponent(props: ActionsMenuComponentProps): { - exporters.map((exporter) => ExportItemComponent({ + exporters.map((exporter): JSX.Element => ExportItemComponent({ exporter, taskInstance: props.taskInstance, exportActivity: (props.exportActivities || []) diff --git a/cvat-ui/src/components/create-model-page/create-model-form.tsx b/cvat-ui/src/components/create-model-page/create-model-form.tsx index df42cdda16fe..b33d83b0e756 100644 --- a/cvat-ui/src/components/create-model-page/create-model-form.tsx +++ b/cvat-ui/src/components/create-model-page/create-model-form.tsx @@ -18,7 +18,7 @@ export class CreateModelForm extends React.PureComponent { public submit(): Promise<{name: string; global: boolean}> { const { form } = this.props; return new Promise((resolve, reject) => { - form.validateFields((errors, values) => { + form.validateFields((errors, values): void => { if (!errors) { resolve({ name: values.name, 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 29fcadcc4c39..c12cf88236d2 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 @@ -40,7 +40,7 @@ class AdvancedConfigurationForm extends React.PureComponent { onSubmit, } = this.props; - form.validateFields((error, values) => { + form.validateFields((error, values): void => { if (!error) { const filteredValues = { ...values }; delete filteredValues.frameStep; diff --git a/cvat-ui/src/components/create-task-page/basic-configuration-form.tsx b/cvat-ui/src/components/create-task-page/basic-configuration-form.tsx index 805000f4b842..7bb56b65c5d2 100644 --- a/cvat-ui/src/components/create-task-page/basic-configuration-form.tsx +++ b/cvat-ui/src/components/create-task-page/basic-configuration-form.tsx @@ -22,7 +22,7 @@ class BasicConfigurationForm extends React.PureComponent { onSubmit, } = this.props; - form.validateFields((error, values) => { + form.validateFields((error, values): void => { if (!error) { onSubmit({ name: values.name, 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 07f3fb798ac9..f7d3347d1512 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 @@ -131,13 +131,13 @@ export default class CreateTaskContent extends React.PureComponent return this.advancedConfigurationComponent.submit(); } - return new Promise((resolve) => { + return new Promise((resolve): void => { resolve(); }); - }).then(() => { + }).then((): void => { const { onCreate } = this.props; onCreate(this.state); - }).catch(() => { + }).catch((): void => { notification.error({ message: 'Could not create a task', description: 'Please, check configuration you specified', diff --git a/cvat-ui/src/components/file-manager/file-manager.tsx b/cvat-ui/src/components/file-manager/file-manager.tsx index 3c3eca0d5bbb..1f12d563e6c2 100644 --- a/cvat-ui/src/components/file-manager/file-manager.tsx +++ b/cvat-ui/src/components/file-manager/file-manager.tsx @@ -59,7 +59,7 @@ export default class FileManager extends React.PureComponent { } private loadData = (key: string): Promise => new Promise( - (resolve, reject) => { + (resolve, reject): void => { const { onLoadData } = this.props; const success = (): void => resolve(); diff --git a/cvat-ui/src/components/labels-editor/label-form.tsx b/cvat-ui/src/components/labels-editor/label-form.tsx index 8f463c6b2e0c..ff76073f4136 100644 --- a/cvat-ui/src/components/labels-editor/label-form.tsx +++ b/cvat-ui/src/components/labels-editor/label-form.tsx @@ -52,12 +52,12 @@ class LabelForm extends React.PureComponent { onSubmit, } = this.props; - form.validateFields((error, values) => { + form.validateFields((error, values): void => { if (!error) { onSubmit({ name: values.labelName, id: label ? label.id : idGenerator(), - attributes: values.keys.map((key: number, index: number) => ( + attributes: values.keys.map((key: number, index: number): Attribute => ( { name: values.attrName[key], type: values.type[key], @@ -223,7 +223,7 @@ class LabelForm extends React.PureComponent { const validator = (_: any, strNumbers: string, callback: any): void => { const numbers = strNumbers .split(';') - .map((number) => Number.parseFloat(number)); + .map((number): number => Number.parseFloat(number)); if (numbers.length !== 3) { callback('Invalid input'); } @@ -470,7 +470,7 @@ class LabelForm extends React.PureComponent { form.getFieldDecorator('keys', { initialValue: label - ? label.attributes.map((attr: Attribute) => attr.id) + ? label.attributes.map((attr: Attribute): number => attr.id) : [], }); diff --git a/cvat-ui/src/components/labels-editor/labels-editor.tsx b/cvat-ui/src/components/labels-editor/labels-editor.tsx index 545dd5bbb0f2..912caf378c6c 100644 --- a/cvat-ui/src/components/labels-editor/labels-editor.tsx +++ b/cvat-ui/src/components/labels-editor/labels-editor.tsx @@ -179,7 +179,7 @@ export default class LabelsEditor } = this.state; const filteredUnsavedLabels = unsavedLabels.filter( - (_label: Label) => _label.id !== label.id, + (_label: Label): boolean => _label.id !== label.id, ); this.setState({ diff --git a/cvat-ui/src/components/labels-editor/raw-viewer.tsx b/cvat-ui/src/components/labels-editor/raw-viewer.tsx index 0c7f5cd1dd32..f6af19673f2f 100644 --- a/cvat-ui/src/components/labels-editor/raw-viewer.tsx +++ b/cvat-ui/src/components/labels-editor/raw-viewer.tsx @@ -13,6 +13,7 @@ import { FormComponentProps } from 'antd/lib/form/Form'; import { Label, + Attribute, } from './common'; type Props = FormComponentProps & { @@ -38,7 +39,7 @@ class RawViewer extends React.PureComponent { } = this.props; e.preventDefault(); - form.validateFields((error, values) => { + form.validateFields((error, values): void => { if (!error) { onSubmit(JSON.parse(values.labels)); } @@ -47,11 +48,11 @@ class RawViewer extends React.PureComponent { public render(): JSX.Element { const { labels } = this.props; - const convertedLabels = labels.map((label: any) => ( + const convertedLabels = labels.map((label: any): Label => ( { ...label, id: label.id < 0 ? undefined : label.id, - attributes: label.attributes.map((attribute: any) => ( + attributes: label.attributes.map((attribute: any): Attribute => ( { ...attribute, id: attribute.id < 0 ? undefined : attribute.id, diff --git a/cvat-ui/src/components/login-page/login-form.tsx b/cvat-ui/src/components/login-page/login-form.tsx index 510747184368..e2afeef32164 100644 --- a/cvat-ui/src/components/login-page/login-form.tsx +++ b/cvat-ui/src/components/login-page/login-form.tsx @@ -25,7 +25,7 @@ class LoginFormComponent extends React.PureComponent { onSubmit, } = this.props; - form.validateFields((error, values) => { + form.validateFields((error, values): void => { if (!error) { onSubmit(values); } diff --git a/cvat-ui/src/components/login-page/login-page.tsx b/cvat-ui/src/components/login-page/login-page.tsx index d36dec17cfb6..56e6dd7249ee 100644 --- a/cvat-ui/src/components/login-page/login-page.tsx +++ b/cvat-ui/src/components/login-page/login-page.tsx @@ -45,7 +45,7 @@ function LoginPageComponent(props: LoginPageComponentProps & RouteComponentProps New to CVAT? Create - an account + an account diff --git a/cvat-ui/src/components/model-runner-modal/model-runner-modal.tsx b/cvat-ui/src/components/model-runner-modal/model-runner-modal.tsx index bc5d5cd3edb9..dec8bb2dbbcb 100644 --- a/cvat-ui/src/components/model-runner-modal/model-runner-modal.tsx +++ b/cvat-ui/src/components/model-runner-modal/model-runner-modal.tsx @@ -118,13 +118,13 @@ export default class ModelRunnerModalComponent extends React.PureComponent label.name); + .map((label: any): string => label.name); const [defaultMapping, defaultColors]: StringObject[] = selectedModelInstance.labels - .reduce((acc: StringObject[], label) => { + .reduce((acc: StringObject[], label): StringObject[] => { if (taskLabels.includes(label)) { acc[0][label] = label; acc[1][label] = nextColor(); - taskLabels = taskLabels.filter((_label) => _label !== label); + taskLabels = taskLabels.filter((_label): boolean => _label !== label); } return acc; @@ -153,7 +153,7 @@ export default class ModelRunnerModalComponent extends React.PureComponent - {models.map((model) => ( + {models.map((model): JSX.Element => ( {model.name} @@ -250,7 +250,7 @@ export default class ModelRunnerModalComponent extends React.PureComponent - {options.map((label: string) => ( + {options.map((label: string): JSX.Element => ( {label} @@ -301,7 +301,7 @@ export default class ModelRunnerModalComponent extends React.PureComponent _model.name === selectedModel)[0]; + .filter((_model): boolean => _model.name === selectedModel)[0]; const excludedLabels: { model: string[]; @@ -330,7 +330,7 @@ export default class ModelRunnerModalComponent extends React.PureComponent label.name, - ).filter((label: string) => !excludedLabels.task.includes(label)); + ).filter((label: string): boolean => !excludedLabels.task.includes(label)); const mappingISAvailable = !!availableModelLabels.length && !!availableTaskLabels.length; @@ -378,7 +378,7 @@ export default class ModelRunnerModalComponent extends React.PureComponent model.name === selectedModel, + (model): boolean => model.name === selectedModel, )[0]; const enabledSubmit = (!!activeModel @@ -394,7 +394,7 @@ export default class ModelRunnerModalComponent extends React.PureComponent model.name === selectedModel)[0], + .filter((model): boolean => model.name === selectedModel)[0], mapping, cleanOut, ); diff --git a/cvat-ui/src/components/models-page/built-model-item.tsx b/cvat-ui/src/components/models-page/built-model-item.tsx index d03021546753..2e0adfa1c385 100644 --- a/cvat-ui/src/components/models-page/built-model-item.tsx +++ b/cvat-ui/src/components/models-page/built-model-item.tsx @@ -36,7 +36,7 @@ export default function BuiltModelItemComponent(props: Props): JSX.Element { value='Supported labels' > {model.labels.map( - (label) => ( + (label): JSX.Element => ( {label} diff --git a/cvat-ui/src/components/models-page/built-models-list.tsx b/cvat-ui/src/components/models-page/built-models-list.tsx index c0ca093c8c00..5ce892e64c8b 100644 --- a/cvat-ui/src/components/models-page/built-models-list.tsx +++ b/cvat-ui/src/components/models-page/built-models-list.tsx @@ -16,7 +16,9 @@ interface Props { export default function IntegratedModelsListComponent(props: Props): JSX.Element { const { models } = props; - const items = models.map((model) => ); + const items = models.map((model): JSX.Element => ( + + )); return ( <> diff --git a/cvat-ui/src/components/models-page/models-page.tsx b/cvat-ui/src/components/models-page/models-page.tsx index 3aca57e49bc9..bd26271dc8de 100644 --- a/cvat-ui/src/components/models-page/models-page.tsx +++ b/cvat-ui/src/components/models-page/models-page.tsx @@ -42,8 +42,8 @@ export default function ModelsPageComponent(props: Props): JSX.Element { ); } - const uploadedModels = models.filter((model) => model.id !== null); - const integratedModels = models.filter((model) => model.id === null); + const uploadedModels = models.filter((model): boolean => model.id !== null); + const integratedModels = models.filter((model): boolean => model.id === null); return (
diff --git a/cvat-ui/src/components/models-page/uploaded-model-item.tsx b/cvat-ui/src/components/models-page/uploaded-model-item.tsx index bbd6c565f3dd..9f93246b4746 100644 --- a/cvat-ui/src/components/models-page/uploaded-model-item.tsx +++ b/cvat-ui/src/components/models-page/uploaded-model-item.tsx @@ -57,7 +57,7 @@ export default function UploadedModelItem(props: Props): JSX.Element { value='Supported labels' > {model.labels.map( - (label) => ( + (label): JSX.Element => ( {label} diff --git a/cvat-ui/src/components/models-page/uploaded-models-list.tsx b/cvat-ui/src/components/models-page/uploaded-models-list.tsx index f5eca0df1887..233359293abc 100644 --- a/cvat-ui/src/components/models-page/uploaded-models-list.tsx +++ b/cvat-ui/src/components/models-page/uploaded-models-list.tsx @@ -23,7 +23,7 @@ export default function UploadedModelsListComponent(props: Props): JSX.Element { deleteModel, } = props; - const items = models.map((model) => { + const items = models.map((model): JSX.Element => { const owner = registeredUsers.filter((user) => user.id === model.ownerID)[0]; return ( { onSubmit, } = this.props; - form.validateFields((error, values) => { + form.validateFields((error, values): void => { if (!error) { onSubmit(values); } diff --git a/cvat-ui/src/components/task-page/details.tsx b/cvat-ui/src/components/task-page/details.tsx index 71e59bc392dc..52425be8c7ce 100644 --- a/cvat-ui/src/components/task-page/details.tsx +++ b/cvat-ui/src/components/task-page/details.tsx @@ -60,7 +60,7 @@ export default class DetailsComponent extends React.PureComponent this.mounted = true; getReposData(taskInstance.id) - .then((data) => { + .then((data): void => { if (data !== null && this.mounted) { if (data.status.error) { notification.error({ @@ -77,7 +77,7 @@ export default class DetailsComponent extends React.PureComponent repository: data.url, }); } - }).catch((error) => { + }).catch((error): void => { if (this.mounted) { notification.error({ message: 'Could not receive repository status', @@ -272,13 +272,13 @@ export default class DetailsComponent extends React.PureComponent repositoryStatus: 'syncing', }); - syncRepos(taskInstance.id).then(() => { + syncRepos(taskInstance.id).then((): void => { if (this.mounted) { this.setState({ repositoryStatus: 'sync', }); } - }).catch(() => { + }).catch((): void => { if (this.mounted) { this.setState({ repositoryStatus: '!sync', @@ -374,11 +374,11 @@ export default class DetailsComponent extends React.PureComponent label.toJSON(), + (label: any): string => label.toJSON(), )} onSubmit={(labels: any[]): void => { taskInstance.labels = labels - .map((labelData) => new core.classes.Label(labelData)); + .map((labelData): any => new core.classes.Label(labelData)); onTaskUpdate(taskInstance); }} /> diff --git a/cvat-ui/src/components/task-page/job-list.tsx b/cvat-ui/src/components/task-page/job-list.tsx index 01086e28d371..ad13707d2462 100644 --- a/cvat-ui/src/components/task-page/job-list.tsx +++ b/cvat-ui/src/components/task-page/job-list.tsx @@ -89,10 +89,9 @@ export default function JobListComponent(props: Props): JSX.Element { userInstance = null; } - onJobUpdate({ - ...jobInstance, - assignee: userInstance, - }); + // eslint-disable-next-line + jobInstance.assignee = userInstance; + onJobUpdate(jobInstance); }} /> ); diff --git a/cvat-ui/src/components/task-page/user-selector.tsx b/cvat-ui/src/components/task-page/user-selector.tsx index 07338f524968..b58b85a90c41 100644 --- a/cvat-ui/src/components/task-page/user-selector.tsx +++ b/cvat-ui/src/components/task-page/user-selector.tsx @@ -26,7 +26,7 @@ export default function UserSelector(props: Props): JSX.Element { onChange={onChange} > - { users.map((user) => ( + { users.map((user): JSX.Element => ( {user.username} diff --git a/cvat-ui/src/components/tasks-page/task-item.tsx b/cvat-ui/src/components/tasks-page/task-item.tsx index 4c5461006146..c7322cf59058 100644 --- a/cvat-ui/src/components/tasks-page/task-item.tsx +++ b/cvat-ui/src/components/tasks-page/task-item.tsx @@ -74,7 +74,7 @@ class TaskItemComponent extends React.PureComponent job.status === 'completed', + (job: any): boolean => job.status === 'completed', ).length; // Progress appearence depends on number of jobs diff --git a/cvat-ui/src/components/tasks-page/task-list.tsx b/cvat-ui/src/components/tasks-page/task-list.tsx index da7f79c13e13..ce8381096004 100644 --- a/cvat-ui/src/components/tasks-page/task-list.tsx +++ b/cvat-ui/src/components/tasks-page/task-list.tsx @@ -23,7 +23,7 @@ export default function TaskListComponent(props: ContentListProps): JSX.Element onSwitchPage, } = props; const taskViews = currentTasksIndexes.map( - (tid, id) => , + (tid, id): JSX.Element => , ); return ( diff --git a/cvat-ui/src/containers/tasks-page/tasks-list.tsx b/cvat-ui/src/containers/tasks-page/tasks-list.tsx index 31021c1a0221..7afd17b8de23 100644 --- a/cvat-ui/src/containers/tasks-page/tasks-list.tsx +++ b/cvat-ui/src/containers/tasks-page/tasks-list.tsx @@ -50,7 +50,7 @@ function TasksListContainer(props: TasksListContainerProps): JSX.Element { return ( task.instance.id)} + currentTasksIndexes={tasks.current.map((task): number => task.instance.id)} currentPage={tasks.gettingQuery.page} numberOfTasks={tasks.count} /> From 0b5ab47fe6f7c523cbbe9ebb3ab2f1ef75b007c9 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 6 Dec 2019 14:02:29 +0300 Subject: [PATCH 17/17] Small pagination fix --- cvat-ui/src/components/tasks-page/tasks-page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cvat-ui/src/components/tasks-page/tasks-page.tsx b/cvat-ui/src/components/tasks-page/tasks-page.tsx index 25b2db39d795..a0ebf786abbd 100644 --- a/cvat-ui/src/components/tasks-page/tasks-page.tsx +++ b/cvat-ui/src/components/tasks-page/tasks-page.tsx @@ -124,8 +124,8 @@ class TasksPageComponent extends React.PureComponent