├── .eslintrc.yml ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ ├── codeql-analysis.yml │ ├── coverage.yml │ ├── docs.yml │ └── issue-greetings.yml ├── .gitignore ├── .nvmrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docker └── Dockerfile ├── docs ├── export.md ├── import.md ├── index.md ├── local-setup.md └── shortcuts.md ├── examples ├── ai-demo.gif ├── alfa-demo.gif ├── bbox-demo.gif ├── demo-base.gif ├── demo-base.mp4 ├── demo-posenet.gif ├── demo-posenet.mp4 ├── demo-ssd.gif ├── demo-ssd.mp4 ├── object_detection_basketball.gif ├── points-demo.gif └── polygon-demo.gif ├── index.html ├── jest.config.ts ├── mkdocs.yml ├── package-lock.json ├── package.json ├── public ├── favicon.png ├── ico │ ├── accept-all.png │ ├── actions.png │ ├── add.png │ ├── ai.png │ ├── api.png │ ├── box-closed.png │ ├── box-opened.png │ ├── bug.png │ ├── camera.png │ ├── cancel.png │ ├── checkbox-checked.png │ ├── checkbox-unchecked.png │ ├── close.png │ ├── colors-off.png │ ├── colors-on.png │ ├── cross-hair.png │ ├── delete.png │ ├── documentation.png │ ├── down.png │ ├── download.png │ ├── export-labels.png │ ├── eye.png │ ├── file.png │ ├── files.png │ ├── github-logo.png │ ├── hand-fill-grab.png │ ├── hand-fill.png │ ├── hand.png │ ├── heart.png │ ├── hide.png │ ├── import-labels.png │ ├── labels.png │ ├── labels_list.png │ ├── labels_list_empty.png │ ├── left.png │ ├── line.png │ ├── main-image-color.png │ ├── main-image-dark.png │ ├── main-image-dark_alter.png │ ├── make-sense-ico-transparent.png │ ├── medium-logo.png │ ├── more.png │ ├── move.png │ ├── new_labels.png │ ├── object.png │ ├── ok.png │ ├── online.png │ ├── open-source.png │ ├── patreon-logo.png │ ├── pictures.png │ ├── plant.png │ ├── plus.png │ ├── point.png │ ├── polygon.png │ ├── polyline.png │ ├── private.png │ ├── rectangle.png │ ├── refresh.png │ ├── reject-all.png │ ├── remove.png │ ├── resize.png │ ├── right.png │ ├── roboflow-logo.png │ ├── robot.png │ ├── rocket.png │ ├── small_window.png │ ├── tags.png │ ├── take-off.png │ ├── trash.png │ ├── twitch-logo.png │ ├── type-writer.png │ ├── upload.png │ ├── youtube-logo.png │ ├── zoom-fit.png │ ├── zoom-in.png │ ├── zoom-max.png │ └── zoom-out.png ├── make-sense-ico-transparent.png ├── make-sense-ico.png └── manifest.json ├── src ├── App.scss ├── App.tsx ├── __test__ │ └── custom-test-env.js ├── ai │ ├── PoseDetector.ts │ ├── RoboflowAPIObjectDetector.ts │ ├── SSDObjectDetector.ts │ └── YOLOV5ObjectDetector.ts ├── configureStore.ts ├── configureTest.ts ├── data │ ├── EditorData.ts │ ├── ExportFormatData.ts │ ├── HotKeyAction.ts │ ├── ImportFormatData.ts │ ├── ImporterSpecData.ts │ ├── MobileDeviceData.ts │ ├── RectAnchor.ts │ ├── enums │ │ ├── AIModel.ts │ │ ├── AcceptedFileType.ts │ │ ├── AnnotationFormatType.ts │ │ ├── ContextType.ts │ │ ├── CustomCursorStyle.ts │ │ ├── Direction.ts │ │ ├── EventType.ts │ │ ├── InferenceServerType.ts │ │ ├── LabelStatus.ts │ │ ├── LabelType.ts │ │ ├── LineAnchorType.ts │ │ ├── Notification.ts │ │ ├── NotificationType.ts │ │ ├── PopupWindowType.ts │ │ └── ProjectType.ts │ ├── info │ │ ├── DropDownMenuData.ts │ │ ├── EditorFeatureData.ts │ │ ├── InferenceServerData.ts │ │ ├── LabelToolkitData.ts │ │ ├── NotificationsData.ts │ │ └── SocialMediaData.ts │ └── labels │ │ ├── COCO.ts │ │ └── VGG.ts ├── index.scss ├── index.tsx ├── interfaces │ ├── ILabelFormatData.ts │ ├── ILine.ts │ ├── IPoint.ts │ ├── IRect.ts │ └── ISize.ts ├── logic │ ├── __tests__ │ │ ├── actions │ │ │ ├── AIActions.test.ts │ │ │ └── AIObjectDetectionActions.test.ts │ │ ├── export │ │ │ ├── RectLabelExport.test.ts │ │ │ └── polygon │ │ │ │ ├── COCOExporter.test.ts │ │ │ │ └── VGGExporter.test.ts │ │ └── import │ │ │ ├── coco │ │ │ └── COCOUtils.tests.ts │ │ │ ├── voc │ │ │ └── VOCImporter.tests.ts │ │ │ └── yolo │ │ │ ├── YOLOImporter.test.ts │ │ │ └── YOLOUtils.test.ts │ ├── actions │ │ ├── AIActions.ts │ │ ├── AIPoseDetectionActions.ts │ │ ├── AIRoboflowAPIObjectDetectionActions.ts │ │ ├── AISSDObjectDetectionActions.ts │ │ ├── AIYOLOObjectDetectionActions.ts │ │ ├── EditorActions.ts │ │ ├── ImageActions.ts │ │ ├── LabelActions.ts │ │ ├── PopupActions.ts │ │ └── ViewPortActions.ts │ ├── context │ │ ├── BaseContext.ts │ │ ├── ContextManager.ts │ │ ├── EditorContext.ts │ │ └── PopupContext.ts │ ├── export │ │ ├── LineLabelExport.ts │ │ ├── PointLabelsExport.ts │ │ ├── RectLabelsExporter.ts │ │ ├── TagLabelsExport.ts │ │ └── polygon │ │ │ ├── COCOExporter.ts │ │ │ ├── PolygonLabelsExporter.ts │ │ │ └── VGGExporter.ts │ ├── helpers │ │ ├── CSSHelper.ts │ │ └── ViewPortHelper.ts │ ├── imageRepository │ │ ├── ImageLoadManager.ts │ │ └── ImageRepository.ts │ ├── import │ │ ├── AnnotationImporter.ts │ │ ├── coco │ │ │ ├── COCOErrors.ts │ │ │ ├── COCOImporter.ts │ │ │ └── COCOUtils.ts │ │ ├── voc │ │ │ └── VOCImporter.ts │ │ └── yolo │ │ │ ├── YOLOErrors.ts │ │ │ ├── YOLOImporter.ts │ │ │ └── YOLOUtils.ts │ ├── initializer │ │ └── AppInitializer.ts │ └── render │ │ ├── BaseRenderEngine.ts │ │ ├── LineRenderEngine.ts │ │ ├── PointRenderEngine.ts │ │ ├── PolygonRenderEngine.ts │ │ ├── PrimaryEditorRenderEngine.ts │ │ └── RectRenderEngine.ts ├── settings │ ├── RenderEngineSettings.ts │ ├── Settings.ts │ ├── ViewPointSettings.ts │ └── _Settings.scss ├── staticModels │ ├── EditorModel.ts │ └── PlatformModel.ts ├── store │ ├── Actions.ts │ ├── ai │ │ ├── actionCreators.ts │ │ ├── reducer.ts │ │ └── types.ts │ ├── general │ │ ├── actionCreators.ts │ │ ├── reducer.ts │ │ └── types.ts │ ├── index.ts │ ├── labels │ │ ├── actionCreators.ts │ │ ├── reducer.ts │ │ └── types.ts │ ├── notifications │ │ ├── actionCreators.ts │ │ ├── reducer.ts │ │ └── types.ts │ └── selectors │ │ ├── AISelector.ts │ │ ├── GeneralSelector.ts │ │ └── LabelsSelector.ts ├── utils │ ├── ArrayUtil.ts │ ├── CanvasUtil.ts │ ├── DirectionUtil.ts │ ├── DrawUtil.ts │ ├── EditorUtil.ts │ ├── EnvironmentUtil.ts │ ├── ExporterUtil.ts │ ├── FileUtil.ts │ ├── ImageDataUtil.ts │ ├── ImageUtil.ts │ ├── LabelUtil.ts │ ├── LineUtil.ts │ ├── MouseEventUtil.ts │ ├── NotificationUtil.ts │ ├── NumberUtil.ts │ ├── PlatformUtil.ts │ ├── PointUtil.ts │ ├── PolygonUtil.ts │ ├── RectUtil.ts │ ├── RenderEngineUtil.ts │ ├── SizeUtil.ts │ ├── UnitUtil.ts │ ├── VirtualListUtil.ts │ ├── XMLSanitizerUtil.ts │ └── __tests__ │ │ ├── ArrayUtil.test.ts │ │ ├── DrawUtil.test.ts │ │ ├── FileUtil.test.ts │ │ ├── ImageDataUtil.test.ts │ │ ├── LabelUtil.test.ts │ │ ├── LineUtil.test.ts │ │ ├── NumberUtil.test.ts │ │ ├── RectUtil.test.ts │ │ └── RenderEngineUtil.test.ts └── views │ ├── Common │ ├── ImageButton │ │ ├── ImageButton.scss │ │ └── ImageButton.tsx │ ├── StyledTextField │ │ └── StyledTextField.tsx │ ├── TextButton │ │ ├── TextButton.scss │ │ └── TextButton.tsx │ ├── TextInput │ │ ├── TextInput.scss │ │ └── TextInput.tsx │ ├── UnderlineTextButton │ │ ├── UnderlineTextButton.scss │ │ └── UnderlineTextButton.tsx │ └── VirtualList │ │ └── VirtualList.tsx │ ├── EditorView │ ├── Editor │ │ ├── Editor.scss │ │ └── Editor.tsx │ ├── EditorBottomNavigationBar │ │ ├── EditorBottomNavigationBar.scss │ │ └── EditorBottomNavigationBar.tsx │ ├── EditorContainer │ │ ├── EditorContainer.scss │ │ └── EditorContainer.tsx │ ├── EditorTopNavigationBar │ │ ├── EditorTopNavigationBar.scss │ │ └── EditorTopNavigationBar.tsx │ ├── EditorView.scss │ ├── EditorView.tsx │ ├── FeatureInProgress │ │ ├── FeatureInProgress.scss │ │ └── FeatureInProgress.tsx │ ├── LabelControlPanel │ │ ├── LabelControlPanel.scss │ │ └── LabelControlPanel.tsx │ ├── SideNavigationBar │ │ ├── EmptyLabelList │ │ │ ├── EmptyLabelList.scss │ │ │ └── EmptyLabelList.tsx │ │ ├── ImagePreview │ │ │ ├── ImagePreview.scss │ │ │ └── ImagePreview.tsx │ │ ├── ImagesList │ │ │ ├── ImagesList.scss │ │ │ └── ImagesList.tsx │ │ ├── LabelInputField │ │ │ ├── LabelInputField.scss │ │ │ └── LabelInputField.tsx │ │ ├── LabelsToolkit │ │ │ ├── LabelsToolkit.scss │ │ │ └── LabelsToolkit.tsx │ │ ├── LineLabelsList │ │ │ ├── LineLabelsList.scss │ │ │ └── LineLabelsList.tsx │ │ ├── PointLabelsList │ │ │ ├── PointLabelsList.scss │ │ │ └── PointLabelsList.tsx │ │ ├── PolygonLabelsList │ │ │ ├── PolygonLabelsList.scss │ │ │ └── PolygonLabelsList.tsx │ │ ├── RectLabelsList │ │ │ ├── RectLabelsList.scss │ │ │ └── RectLabelsList.tsx │ │ ├── SideNavigationBar.scss │ │ ├── SideNavigationBar.tsx │ │ └── TagLabelsList │ │ │ ├── TagLabelsList.scss │ │ │ └── TagLabelsList.tsx │ ├── StateBar │ │ ├── StateBar.scss │ │ └── StateBar.tsx │ ├── TopNavigationBar │ │ ├── DropDownMenu │ │ │ ├── DropDownMenu.scss │ │ │ └── DropDownMenu.tsx │ │ ├── TopNavigationBar.scss │ │ └── TopNavigationBar.tsx │ └── VerticalEditorButton │ │ ├── VerticalEditorButton.scss │ │ └── VerticalEditorButton.tsx │ ├── MainView │ ├── ImagesDropZone │ │ ├── ImagesDropZone.scss │ │ └── ImagesDropZone.tsx │ ├── MainView.scss │ └── MainView.tsx │ ├── MobileMainView │ ├── MobileMainView.scss │ └── MobileMainView.tsx │ ├── NotificationsView │ ├── NotificationsView.scss │ └── NotificationsView.tsx │ ├── PopupView │ ├── ConnectInferenceServerPopup │ │ ├── ConnectInferenceServerPopup.scss │ │ └── ConnectInferenceServerPopup.tsx │ ├── ExitProjectPopup │ │ ├── ExitProjectPopup.scss │ │ └── ExitProjectPopup.tsx │ ├── ExportLabelsPopup │ │ ├── ExportLabelPopup.scss │ │ └── ExportLabelPopup.tsx │ ├── GenericLabelTypePopup │ │ ├── GenericLabelTypePopup.scss │ │ └── GenericLabelTypePopup.tsx │ ├── GenericSideMenuPopup │ │ ├── GenericSideMenuPopup.scss │ │ └── GenericSideMenuPopup.tsx │ ├── GenericYesNoPopup │ │ ├── GenericYesNoPopup.scss │ │ └── GenericYesNoPopup.tsx │ ├── ImportLabelPopup │ │ ├── ImportLabelPopup.scss │ │ └── ImportLabelPopup.tsx │ ├── InsertLabelNamesPopup │ │ ├── ColorSelectorView │ │ │ ├── ColorSelectorView.scss │ │ │ └── ColorSelectorView.tsx │ │ ├── InsertLabelNamesPopup.scss │ │ └── InsertLabelNamesPopup.tsx │ ├── LoadLabelNamesPopup │ │ ├── LoadLabelNamesPopup.scss │ │ └── LoadLabelNamesPopup.tsx │ ├── LoadModelPopup │ │ ├── LoadModelPopup.scss │ │ └── LoadModelPopup.tsx │ ├── LoadMoreImagesPopup │ │ ├── LoadMoreImagesPopup.scss │ │ └── LoadMoreImagesPopup.tsx │ ├── LoadYOLOv5ModelPopup │ │ ├── LoadYOLOv5ModelPopup.scss │ │ └── LoadYOLOv5ModelPopup.tsx │ ├── PopupView.scss │ ├── PopupView.tsx │ └── SuggestLabelNamesPopup │ │ ├── SuggestLabelNamesPopup.scss │ │ └── SuggestLabelNamesPopup.tsx │ └── SizeItUpView │ ├── SizeItUpView.scss │ └── SizeItUpView.tsx ├── tsconfig.json ├── tslint.json └── vite.config.ts /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | extends: 2 | - eslint:recommended 3 | - plugin:@typescript-eslint/recommended 4 | - plugin:react/recommended 5 | parser: "@typescript-eslint/parser" 6 | plugins: 7 | - "@typescript-eslint" 8 | rules: 9 | "@typescript-eslint/no-inferrable-types": off 10 | no-shadow: off 11 | "@typescript-eslint/no-shadow": 12 | - error 13 | no-use-before-define: off 14 | "@typescript-eslint/no-use-before-define": 15 | - error 16 | react/jsx-filename-extension: 17 | - warn 18 | - extensions: 19 | - .tsx 20 | complexity: 21 | - error 22 | - 15 23 | no-await-in-loop: warn 24 | no-eval: error 25 | no-implied-eval: error 26 | prefer-promise-reject-errors: warn 27 | env: 28 | browser: true 29 | settings: 30 | react: 31 | pragma: React 32 | version: detect 33 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: SkalskiP 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Pre-flight checklist 2 | 3 | - [ ] Unit tests for all non-trivial changes 4 | - [ ] Tested locally 5 | - [ ] Updated wiki 6 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Code coverage 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [14, 16] 13 | 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v3 17 | 18 | - name: Set up Node.js ${{ matrix.node-version }} 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | 23 | - name: Update NPM 24 | run: npm i -g npm 25 | 26 | - name: Install dependencies 27 | run: npm install 28 | 29 | - name: Run the tests 30 | run: npm run test:coverage 31 | 32 | - name: Upload coverage to Codecov 33 | if: ${{ matrix.node-version == '14'}} 34 | uses: codecov/codecov-action@v1 35 | with: 36 | token: ${{ secrets.CODECOV_TOKEN }} 37 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy documantation 2 | 3 | on: [push] 4 | 5 | jobs: 6 | deploy: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions/setup-python@v2 11 | with: 12 | python-version: 3.x 13 | - run: pip install mkdocs-material 14 | - run: mkdir -p docs/img 15 | - run: cp public/make-sense-ico-transparent.png docs/img/logo.png 16 | - run: cp public/make-sense-ico.png docs/img/favicon.png 17 | - run: mkdocs gh-deploy --force 18 | -------------------------------------------------------------------------------- /.github/workflows/issue-greetings.yml: -------------------------------------------------------------------------------- 1 | name: Issue greetings 2 | 3 | on: [issues] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/first-interaction@v1 10 | with: 11 | repo-token: ${{ secrets.GITHUB_TOKEN }} 12 | issue-message: | 13 | 👋 Hello @${{ github.actor }}, thank you for your interest in make-sense - free to use online tool for labelling photos! 🏷️ 14 | 15 | ## 🐞 Bug reports 16 | 17 | If you noticed that make-sense is not working properly, please provide us with as much information as possible. To make your life easier, we have prepared a **bug report template** containing all the relevant details. We know, we ask for a lot... However, please believe that knowing all that extra information - like the type of browser you use or the version of node you have installed - really helps us to solve your problems faster and more efficiently. 😉 18 | 19 | ## 💬 Get in touch 20 | 21 | If you've been trying to contact us but for some reason we haven't responded to your issue yet, don't hesitate to get back to us on [Gitter](https://gitter.im/make-sense-ai/community) or [Twitter](https://twitter.com/PiotrSkalski92). 22 | 23 | ## 💻 Local setup 24 | 25 | ```bash 26 | # clone repository 27 | git clone https://github.com/SkalskiP/make-sense.git 28 | 29 | # navigate to main dir 30 | cd make-sense 31 | 32 | # install dependencies 33 | npm install 34 | 35 | # serve with hot reload at localhost:3000 36 | npm start 37 | ``` 38 | To ensure proper functionality of the application locally, an npm `8.x.x` and node.js `v16.x.x` versions are required. More information about this problem is available in the [#16](https://github.com/SkalskiP/make-sense/issues/16) issue. 39 | 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ide 2 | .idea/ 3 | .vscode/ 4 | 5 | # dependencies 6 | /node_modules/ 7 | /dist/ 8 | /.pnp 9 | .pnp.js 10 | 11 | # testing 12 | /coverage 13 | 14 | # production 15 | /build 16 | 17 | # misc 18 | .DS_Store 19 | .env.local 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16.16.0 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guide 2 | 3 | ### 💬 Submit Feedback 4 | The feedback should be submitted by creating an issue on GitHub [issues][1]. 5 | Select the related template (bug report, feature request, or custom) and add the corresponding labels. 6 | 7 | ### 🐞 Fix Bugs 8 | You may look through the GitHub [issues][1] for bugs. 9 | 10 | ### 💡 Implement Features 11 | You may look through the GitHub [issues][1] for feature requests. 12 | 13 | ### 🚀 Pull Requests (PR) 14 | - Fork the repository and create a new branch from the `develop` branch. 15 | - For bug fixes, add new tests. We use [jest.js][2] to test our code. 16 | - Do a PR from your new branch to our `develop` branch of the original make-sense repo. 17 | 18 | [1]: https://github.com/SkalskiP/make-sense/issues/ 19 | [2]: https://jestjs.io/ 20 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.16.0 2 | 3 | RUN apt-get update && apt-get -y install git && rm -rf /var/lib/apt/lists/* 4 | 5 | COPY ./ /make-sense 6 | 7 | RUN cd /make-sense && \ 8 | npm install 9 | 10 | WORKDIR /make-sense 11 | 12 | ENTRYPOINT ["npm", "run", "dev"] 13 | -------------------------------------------------------------------------------- /docs/export.md: -------------------------------------------------------------------------------- 1 | # Export Formats 2 | 3 | | | CSV | YOLO | VOC XML | VGG JSON | COCO JSON | PIXEL MASK | 4 | |:-------------:|:---:|:----:|:-------:|:--------:|:---------:|:----------:| 5 | | **Point** | ✓ | ✗ | ☐ | ☐ | ☐ | ✗ | 6 | | **Line** | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | 7 | | **Rect** | ✓ | ✓ | ✓ | ☐ | ☐ | ✗ | 8 | | **Polygon** | ☐ | ✗ | ☐ | ✓ | ✓ | ☐ | 9 | | **Label** | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | 10 | 11 | **Table 1.** The matrix of supported labels export formats, where: 12 | 13 | - ✓ - supported format 14 | - ☐ - not yet supported format 15 | - ✗ - format does not make sense for a given label type 16 | -------------------------------------------------------------------------------- /docs/import.md: -------------------------------------------------------------------------------- 1 | # Import Formats 2 | 3 | | | CSV | YOLO | VOC XML | VGG JSON | COCO JSON | PIXEL MASK | 4 | |:-------------:|:---:|:----:|:-------:|:--------:|:---------:|:----------:| 5 | | **Point** | ☐ | ✗ | ☐ | ☐ | ☐ | ✗ | 6 | | **Line** | ☐ | ✗ | ✗ | ✗ | ✗ | ✗ | 7 | | **Rect** | ☐ | ✓ | ☐ | ☐ | ✓ | ✗ | 8 | | **Polygon** | ☐ | ✗ | ☐ | ☐ | ✓ | ☐ | 9 | | **Label** | ☐ | ✗ | ✗ | ✗ | ✗ | ✗ | 10 | 11 | **Table 1.** The matrix of supported labels import formats, where: 12 | 13 | - ✓ - supported format 14 | - ☐ - not yet supported format 15 | - ✗ - format does not make sense for a given label type 16 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 |

makesense.ai

2 | 3 |

4 | make-sense 5 |

6 | 7 | 8 | [makesense.ai][1] is a free to use online tool for labelling photos. Thanks to the use of a browser it does not require any complicated installation - just visit the website and you are ready to go. It also doesn't matter which operating system you're running on - we do our best to be truly cross-platform. It is perfect for small computer vision deeplearning projects, making the process of preparing a dataset much easier and faster. Prepared labels can be downloaded in one of multiple supported formats. The application was written in TypeScript and is based on React/Redux duo. 9 | 10 | ## Advanced AI functionalities 11 | 12 | [makesense.ai][1] strives to significantly reduce the time we have to spend on labeling photos. To achieve this, we are going to use many different AI models that will be able to give you recommendations as well as automate repetitive and tedious activities. 13 | 14 | * [SSD model][8] pretrained on the [COCO dataset][9], which will do some of the work for you in drawing bboxes on photos and also (in some cases) suggest a label. 15 | * [PoseNet model][11] is a vision model that can be used to estimate the pose of a person in an image or video by estimating where key body joints are. 16 | 17 | In the future, we also plan to add, among other things, models that classify photos, detect characteristic features of faces as well as whole faces. The engine that drives our AI functionalities is [TensorFlow.js][10] - JS version of the most popular framework for training neural networks. This choice allows us not only to speed up your work but also to care about the privacy of your data, because unlike with other commercial and open source tools, your photos do not have to be transferred to the server. This time AI comes to your device! 18 | 19 | 20 | [1]: http://makesense.ai 21 | [8]: https://arxiv.org/abs/1512.02325 22 | [9]: http://cocodataset.org 23 | [10]: https://www.tensorflow.org/js 24 | [11]: https://www.tensorflow.org/lite/models/pose_estimation/overview 25 | -------------------------------------------------------------------------------- /docs/local-setup.md: -------------------------------------------------------------------------------- 1 | # Set Up the Project Locally 2 | 3 | ```bash 4 | # clone repository 5 | git clone https://github.com/SkalskiP/make-sense.git 6 | 7 | # navigate to main dir 8 | cd make-sense 9 | 10 | # install dependencies 11 | npm install 12 | 13 | # serve with hot reload at localhost:3000 14 | npm start 15 | ``` 16 | To ensure proper functionality of the application locally, npm `6.x.x` and node.js `v11.x.x` versions are required. More information about this problem is available in the [#16][1]. 17 | 18 | [1]: https://github.com/SkalskiP/make-sense/issues/16 19 | -------------------------------------------------------------------------------- /docs/shortcuts.md: -------------------------------------------------------------------------------- 1 | # Keyboard Shortcuts 2 | 3 | | Functionality | Context | Mac | Windows / Linux | 4 | |:-----------------------------------|:--------:|:---:|:----------------:| 5 | | Polygon autocomplete | Editor | Enter | Enter | 6 | | Cancel polygon drawing | Editor | Escape | Escape | 7 | | Delete currently selected label | Editor | Backspace | Delete | 8 | | Load previous image | Editor | + Left | Ctrl + Left | 9 | | Load next image | Editor | + Right | Ctrl + Right | 10 | | Zoom in | Editor | + + | Ctrl + + | 11 | | Zoom out | Editor | + - | Ctrl + - | 12 | | Move image | Editor | Up / Down / Left / Right | Up / Down / Left / Right | 13 | | Select Label | Editor | + 0-9 | Ctrl + 0-9 | 14 | | Exit popup | Popup | Escape | Escape | 15 | -------------------------------------------------------------------------------- /examples/ai-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/examples/ai-demo.gif -------------------------------------------------------------------------------- /examples/alfa-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/examples/alfa-demo.gif -------------------------------------------------------------------------------- /examples/bbox-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/examples/bbox-demo.gif -------------------------------------------------------------------------------- /examples/demo-base.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/examples/demo-base.gif -------------------------------------------------------------------------------- /examples/demo-base.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/examples/demo-base.mp4 -------------------------------------------------------------------------------- /examples/demo-posenet.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/examples/demo-posenet.gif -------------------------------------------------------------------------------- /examples/demo-posenet.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/examples/demo-posenet.mp4 -------------------------------------------------------------------------------- /examples/demo-ssd.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/examples/demo-ssd.gif -------------------------------------------------------------------------------- /examples/demo-ssd.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/examples/demo-ssd.mp4 -------------------------------------------------------------------------------- /examples/object_detection_basketball.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/examples/object_detection_basketball.gif -------------------------------------------------------------------------------- /examples/points-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/examples/points-demo.gif -------------------------------------------------------------------------------- /examples/polygon-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/examples/polygon-demo.gif -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | 31 | Make Sense 32 | 33 | 34 | 39 | 40 | 43 |
44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from '@jest/types'; 2 | 3 | // Sync object 4 | const config: Config.InitialOptions = { 5 | rootDir: process.cwd(), 6 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 7 | transformIgnorePatterns: [], 8 | testEnvironment: 'jsdom', 9 | roots: ["/src"], 10 | setupFilesAfterEnv: ["/src/configureTest.ts"], 11 | transform: { 12 | "^.+(t|j)sx?$": [ 13 | "@swc/jest", 14 | { 15 | jsc: { 16 | transform: { 17 | react: { 18 | runtime: 'automatic', 19 | }, 20 | }, 21 | }, 22 | }, 23 | ], 24 | }, 25 | "moduleNameMapper": { 26 | "\\.(css|scss|less)$": "identity-obj-proxy" 27 | }, 28 | extensionsToTreatAsEsm: [".ts", ".tsx"], 29 | collectCoverageFrom: [ 30 | "**/*.{ts,tsx}", 31 | "!**/node_modules/**", 32 | "!**/dist/**", 33 | "!**/coverage/**", 34 | ], 35 | }; 36 | export default config; 37 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | # Project information 2 | site_name: Make Sense 3 | site_url: https://skalskip.github.io/make-sense 4 | site_author: Piotr Skalski 5 | site_description: >- 6 | Free to use online tool for labelling photos 7 | 8 | # Repository 9 | repo_name: 'SkalskiP/make-sense' 10 | repo_url: 'https://github.com/SkalskiP/make-sense' 11 | edit_uri: "" 12 | 13 | # Copyright 14 | copyright: Copyright © 2019 Piotr Skalski 15 | 16 | # Customization 17 | extra: 18 | version: 1.11.0-alpha 19 | social: 20 | - icon: fontawesome/brands/github 21 | link: https://github.com/SkalskiP 22 | - icon: fontawesome/brands/twitter 23 | link: https://twitter.com/PiotrSkalski92 24 | - icon: fontawesome/brands/linkedin 25 | link: https://www.linkedin.com/in/piotr-skalski-36b5b4122/ 26 | 27 | # Page tree 28 | nav: 29 | - Home: index.md 30 | - Import: import.md 31 | - Export: export.md 32 | - Shortcuts: shortcuts.md 33 | - Local setup: local-setup.md 34 | 35 | # Configuration 36 | theme: 37 | name: 'material' 38 | palette: 39 | primary: 'black' 40 | accent: 'indigo' 41 | font: 42 | text: Roboto 43 | code: Roboto Mono 44 | logo: 'img/logo.png' 45 | favicon: 'img/favicon.png' 46 | features: 47 | - navigation.expand 48 | icon: 49 | repo: fontawesome/brands/github 50 | 51 | # Google Analytics 52 | google_analytics: 53 | - 'UA-155837750-2' 54 | - 'auto' 55 | 56 | # Extensions 57 | markdown_extensions: 58 | - attr_list 59 | - def_list 60 | - footnotes 61 | - pymdownx.highlight 62 | - pymdownx.superfences 63 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/favicon.png -------------------------------------------------------------------------------- /public/ico/accept-all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/accept-all.png -------------------------------------------------------------------------------- /public/ico/actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/actions.png -------------------------------------------------------------------------------- /public/ico/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/add.png -------------------------------------------------------------------------------- /public/ico/ai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/ai.png -------------------------------------------------------------------------------- /public/ico/api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/api.png -------------------------------------------------------------------------------- /public/ico/box-closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/box-closed.png -------------------------------------------------------------------------------- /public/ico/box-opened.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/box-opened.png -------------------------------------------------------------------------------- /public/ico/bug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/bug.png -------------------------------------------------------------------------------- /public/ico/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/camera.png -------------------------------------------------------------------------------- /public/ico/cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/cancel.png -------------------------------------------------------------------------------- /public/ico/checkbox-checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/checkbox-checked.png -------------------------------------------------------------------------------- /public/ico/checkbox-unchecked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/checkbox-unchecked.png -------------------------------------------------------------------------------- /public/ico/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/close.png -------------------------------------------------------------------------------- /public/ico/colors-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/colors-off.png -------------------------------------------------------------------------------- /public/ico/colors-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/colors-on.png -------------------------------------------------------------------------------- /public/ico/cross-hair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/cross-hair.png -------------------------------------------------------------------------------- /public/ico/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/delete.png -------------------------------------------------------------------------------- /public/ico/documentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/documentation.png -------------------------------------------------------------------------------- /public/ico/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/down.png -------------------------------------------------------------------------------- /public/ico/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/download.png -------------------------------------------------------------------------------- /public/ico/export-labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/export-labels.png -------------------------------------------------------------------------------- /public/ico/eye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/eye.png -------------------------------------------------------------------------------- /public/ico/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/file.png -------------------------------------------------------------------------------- /public/ico/files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/files.png -------------------------------------------------------------------------------- /public/ico/github-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/github-logo.png -------------------------------------------------------------------------------- /public/ico/hand-fill-grab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/hand-fill-grab.png -------------------------------------------------------------------------------- /public/ico/hand-fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/hand-fill.png -------------------------------------------------------------------------------- /public/ico/hand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/hand.png -------------------------------------------------------------------------------- /public/ico/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/heart.png -------------------------------------------------------------------------------- /public/ico/hide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/hide.png -------------------------------------------------------------------------------- /public/ico/import-labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/import-labels.png -------------------------------------------------------------------------------- /public/ico/labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/labels.png -------------------------------------------------------------------------------- /public/ico/labels_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/labels_list.png -------------------------------------------------------------------------------- /public/ico/labels_list_empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/labels_list_empty.png -------------------------------------------------------------------------------- /public/ico/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/left.png -------------------------------------------------------------------------------- /public/ico/line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/line.png -------------------------------------------------------------------------------- /public/ico/main-image-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/main-image-color.png -------------------------------------------------------------------------------- /public/ico/main-image-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/main-image-dark.png -------------------------------------------------------------------------------- /public/ico/main-image-dark_alter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/main-image-dark_alter.png -------------------------------------------------------------------------------- /public/ico/make-sense-ico-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/make-sense-ico-transparent.png -------------------------------------------------------------------------------- /public/ico/medium-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/medium-logo.png -------------------------------------------------------------------------------- /public/ico/more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/more.png -------------------------------------------------------------------------------- /public/ico/move.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/move.png -------------------------------------------------------------------------------- /public/ico/new_labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/new_labels.png -------------------------------------------------------------------------------- /public/ico/object.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/object.png -------------------------------------------------------------------------------- /public/ico/ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/ok.png -------------------------------------------------------------------------------- /public/ico/online.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/online.png -------------------------------------------------------------------------------- /public/ico/open-source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/open-source.png -------------------------------------------------------------------------------- /public/ico/patreon-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/patreon-logo.png -------------------------------------------------------------------------------- /public/ico/pictures.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/pictures.png -------------------------------------------------------------------------------- /public/ico/plant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/plant.png -------------------------------------------------------------------------------- /public/ico/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/plus.png -------------------------------------------------------------------------------- /public/ico/point.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/point.png -------------------------------------------------------------------------------- /public/ico/polygon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/polygon.png -------------------------------------------------------------------------------- /public/ico/polyline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/polyline.png -------------------------------------------------------------------------------- /public/ico/private.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/private.png -------------------------------------------------------------------------------- /public/ico/rectangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/rectangle.png -------------------------------------------------------------------------------- /public/ico/refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/refresh.png -------------------------------------------------------------------------------- /public/ico/reject-all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/reject-all.png -------------------------------------------------------------------------------- /public/ico/remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/remove.png -------------------------------------------------------------------------------- /public/ico/resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/resize.png -------------------------------------------------------------------------------- /public/ico/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/right.png -------------------------------------------------------------------------------- /public/ico/roboflow-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/roboflow-logo.png -------------------------------------------------------------------------------- /public/ico/robot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/robot.png -------------------------------------------------------------------------------- /public/ico/rocket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/rocket.png -------------------------------------------------------------------------------- /public/ico/small_window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/small_window.png -------------------------------------------------------------------------------- /public/ico/tags.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/tags.png -------------------------------------------------------------------------------- /public/ico/take-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/take-off.png -------------------------------------------------------------------------------- /public/ico/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/trash.png -------------------------------------------------------------------------------- /public/ico/twitch-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/twitch-logo.png -------------------------------------------------------------------------------- /public/ico/type-writer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/type-writer.png -------------------------------------------------------------------------------- /public/ico/upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/upload.png -------------------------------------------------------------------------------- /public/ico/youtube-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/youtube-logo.png -------------------------------------------------------------------------------- /public/ico/zoom-fit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/zoom-fit.png -------------------------------------------------------------------------------- /public/ico/zoom-in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/zoom-in.png -------------------------------------------------------------------------------- /public/ico/zoom-max.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/zoom-max.png -------------------------------------------------------------------------------- /public/ico/zoom-out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/ico/zoom-out.png -------------------------------------------------------------------------------- /public/make-sense-ico-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/make-sense-ico-transparent.png -------------------------------------------------------------------------------- /public/make-sense-ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkalskiP/make-sense/ee5ee68b439703dc5820f18f614d773580f7c464/public/make-sense-ico.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "makesense.ai", 3 | "name": "makesense.ai - free to use online tool for labelling photos", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/App.scss: -------------------------------------------------------------------------------- 1 | @import './settings/_Settings'; 2 | 3 | .App { 4 | --leading-color: #{$secondaryColor}; 5 | --button-text-color: #{white}; 6 | --hue-value: 172deg; 7 | } 8 | 9 | .App.AI { 10 | --leading-color: #{$primaryColor}; 11 | --button-text-color: #{$darkThemeSecondColor}; 12 | --hue-value: 120deg; 13 | } -------------------------------------------------------------------------------- /src/__test__/custom-test-env.js: -------------------------------------------------------------------------------- 1 | const Environment = require('jest-environment-jsdom'); 2 | 3 | /** 4 | * A custom environment to set the TextEncoder that is required by TensorFlow.js. 5 | */ 6 | module.exports = class CustomTestEnvironment extends Environment { 7 | async setup() { 8 | await super.setup(); 9 | const { TextEncoder } = require('util'); 10 | this.global.TextEncoder = TextEncoder; 11 | } 12 | } -------------------------------------------------------------------------------- /src/configureStore.ts: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux'; 2 | import { rootReducer } from './store'; 3 | 4 | export default function configureStore() { 5 | return createStore( 6 | rootReducer, 7 | // @ts-ignore 8 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() 9 | ); 10 | } -------------------------------------------------------------------------------- /src/configureTest.ts: -------------------------------------------------------------------------------- 1 | import "@testing-library/jest-dom"; 2 | import crypto from 'crypto'; 3 | 4 | jest.mock("./App.tsx", () => "App"); 5 | 6 | 7 | Object.defineProperty(global.self, 'crypto', { 8 | value: { 9 | getRandomValues: arr => crypto.randomBytes(arr.length) 10 | } 11 | }); 12 | 13 | import { unmountComponentAtNode } from "react-dom"; 14 | 15 | let container = null; 16 | beforeEach(() => { 17 | // setup a DOM element as a render target 18 | container = document.createElement("div"); 19 | container.setAttribute("id", "root"); 20 | document.body.appendChild(container); 21 | }); 22 | 23 | afterEach(() => { 24 | // cleanup on exiting 25 | unmountComponentAtNode(container); 26 | container.remove(); 27 | container = null; 28 | }); -------------------------------------------------------------------------------- /src/data/EditorData.ts: -------------------------------------------------------------------------------- 1 | import {IPoint} from '../interfaces/IPoint'; 2 | import {IRect} from '../interfaces/IRect'; 3 | import {ISize} from '../interfaces/ISize'; 4 | 5 | export interface EditorData { 6 | viewPortContentSize: ISize, 7 | mousePositionOnViewPortContent: IPoint, 8 | activeKeyCombo: string[], 9 | event?: Event 10 | zoom: number, 11 | viewPortSize: ISize, 12 | defaultRenderImageRect: IRect, 13 | realImageSize: ISize, 14 | viewPortContentImageRect: IRect, 15 | absoluteViewPortContentScrollPosition: IPoint 16 | } 17 | -------------------------------------------------------------------------------- /src/data/ExportFormatData.ts: -------------------------------------------------------------------------------- 1 | import {ILabelFormatData} from '../interfaces/ILabelFormatData'; 2 | import {LabelType} from './enums/LabelType'; 3 | import {AnnotationFormatType} from './enums/AnnotationFormatType'; 4 | 5 | export type ExportFormatDataMap = Record; 6 | 7 | export const ExportFormatData: ExportFormatDataMap = { 8 | [LabelType.RECT]: [ 9 | { 10 | type: AnnotationFormatType.YOLO, 11 | label: 'A .zip package containing files in YOLO format.' 12 | }, 13 | { 14 | type: AnnotationFormatType.VOC, 15 | label: 'A .zip package containing files in VOC XML format.' 16 | }, 17 | { 18 | type: AnnotationFormatType.CSV, 19 | label: 'Single CSV file.' 20 | } 21 | ], 22 | [LabelType.POINT]: [ 23 | { 24 | type: AnnotationFormatType.CSV, 25 | label: 'Single CSV file.' 26 | } 27 | ], 28 | [LabelType.LINE]: [ 29 | { 30 | type: AnnotationFormatType.CSV, 31 | label: 'Single CSV file.' 32 | } 33 | ], 34 | [LabelType.POLYGON]: [ 35 | { 36 | type: AnnotationFormatType.VGG, 37 | label: 'Single file in VGG JSON format.' 38 | }, 39 | { 40 | type: AnnotationFormatType.COCO, 41 | label: 'Single file in COCO JSON format.' 42 | } 43 | ], 44 | [LabelType.IMAGE_RECOGNITION]: [ 45 | { 46 | type: AnnotationFormatType.CSV, 47 | label: 'Single CSV file.' 48 | }, 49 | { 50 | type: AnnotationFormatType.JSON, 51 | label: 'Single JSON file.' 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /src/data/HotKeyAction.ts: -------------------------------------------------------------------------------- 1 | export type HotKeyAction = { 2 | keyCombo: string[]; 3 | action: (event: KeyboardEvent) => unknown; 4 | } -------------------------------------------------------------------------------- /src/data/ImportFormatData.ts: -------------------------------------------------------------------------------- 1 | import {LabelType} from './enums/LabelType'; 2 | import {ILabelFormatData} from '../interfaces/ILabelFormatData'; 3 | import {AnnotationFormatType} from './enums/AnnotationFormatType'; 4 | 5 | export type ImportFormatDataMap = Record 6 | 7 | export const ImportFormatData: ImportFormatDataMap = { 8 | [LabelType.RECT]: [ 9 | { 10 | type: AnnotationFormatType.COCO, 11 | label: 'Single file in COCO JSON format.' 12 | }, 13 | { 14 | type: AnnotationFormatType.YOLO, 15 | label: 'Multiple files in YOLO format along with labels names definition - labels.txt file.' 16 | }, 17 | { 18 | type: AnnotationFormatType.VOC, 19 | label: 'Multiple files in VOC XML format.' 20 | } 21 | ], 22 | [LabelType.POINT]: [], 23 | [LabelType.LINE]: [], 24 | [LabelType.POLYGON]: [ 25 | { 26 | type: AnnotationFormatType.COCO, 27 | label: 'Single file in COCO JSON format.' 28 | } 29 | ], 30 | [LabelType.IMAGE_RECOGNITION]: [] 31 | } 32 | -------------------------------------------------------------------------------- /src/data/ImporterSpecData.ts: -------------------------------------------------------------------------------- 1 | import {AnnotationFormatType} from './enums/AnnotationFormatType'; 2 | import {AnnotationImporter} from '../logic/import/AnnotationImporter'; 3 | import {COCOImporter} from '../logic/import/coco/COCOImporter'; 4 | import {YOLOImporter} from '../logic/import/yolo/YOLOImporter'; 5 | import {VOCImporter} from '../logic/import/voc/VOCImporter'; 6 | 7 | export type ImporterSpecDataMap = Record; 8 | 9 | 10 | export const ImporterSpecData: ImporterSpecDataMap = { 11 | [AnnotationFormatType.COCO]: COCOImporter, 12 | [AnnotationFormatType.CSV]: undefined, 13 | [AnnotationFormatType.JSON]: undefined, 14 | [AnnotationFormatType.VGG]: undefined, 15 | [AnnotationFormatType.VOC]: VOCImporter, 16 | [AnnotationFormatType.YOLO]: YOLOImporter 17 | } 18 | -------------------------------------------------------------------------------- /src/data/MobileDeviceData.ts: -------------------------------------------------------------------------------- 1 | export interface MobileDeviceData { 2 | manufacturer: string, 3 | browser: string, 4 | os: string 5 | } -------------------------------------------------------------------------------- /src/data/RectAnchor.ts: -------------------------------------------------------------------------------- 1 | import {IPoint} from "../interfaces/IPoint"; 2 | import {Direction} from "./enums/Direction"; 3 | 4 | export interface RectAnchor { 5 | type: Direction, 6 | position: IPoint 7 | } -------------------------------------------------------------------------------- /src/data/enums/AIModel.ts: -------------------------------------------------------------------------------- 1 | export enum AIModel { 2 | YOLO_V5_OBJECT_DETECTION = 'YOLO_V5_OBJECT_DETECTION', 3 | SSD_OBJECT_DETECTION = 'SSD_OBJECT_DETECTION', 4 | POSE_DETECTION = 'POSE_DETECTION' 5 | } 6 | -------------------------------------------------------------------------------- /src/data/enums/AcceptedFileType.ts: -------------------------------------------------------------------------------- 1 | export enum AcceptedFileType { 2 | IMAGE = 'image/jpeg, image/png', 3 | TEXT = 'text/plain', 4 | JSON = 'application/json', 5 | XML = 'application/xml', 6 | } -------------------------------------------------------------------------------- /src/data/enums/AnnotationFormatType.ts: -------------------------------------------------------------------------------- 1 | export enum AnnotationFormatType { 2 | YOLO = 'YOLO', 3 | COCO = 'COCO', 4 | CSV = 'CSV', 5 | JSON = 'JSON', 6 | VOC = 'VOC', 7 | VGG = 'VGG' 8 | } -------------------------------------------------------------------------------- /src/data/enums/ContextType.ts: -------------------------------------------------------------------------------- 1 | export enum ContextType { 2 | EDITOR = "EDITOR", 3 | LEFT_NAVBAR = "LEFT_NAVBAR", 4 | RIGHT_NAVBAR = "RIGHT_NAVBAR", 5 | POPUP = "POPUP", 6 | DROPDOWN = "DROPDOWN" 7 | } -------------------------------------------------------------------------------- /src/data/enums/CustomCursorStyle.ts: -------------------------------------------------------------------------------- 1 | export enum CustomCursorStyle { 2 | DEFAULT = "DEFAULT", 3 | MOVE = "MOVE", 4 | RESIZE = "RESIZE", 5 | ADD = "ADD", 6 | CANCEL = "CANCEL", 7 | CLOSE = "CLOSE", 8 | GRAB = "GRAB", 9 | GRABBING = "GRABBING" 10 | } -------------------------------------------------------------------------------- /src/data/enums/Direction.ts: -------------------------------------------------------------------------------- 1 | export enum Direction { 2 | TOP = "TOP", 3 | BOTTOM = "BOTTOM", 4 | LEFT = "LEFT", 5 | RIGHT = "RIGHT", 6 | TOP_RIGHT = "TOP_RIGHT", 7 | TOP_LEFT = "TOP_LEFT", 8 | BOTTOM_RIGHT = "BOTTOM_RIGHT", 9 | BOTTOM_LEFT = "BOTTOM_LEFT", 10 | CENTER = "CENTER" 11 | } -------------------------------------------------------------------------------- /src/data/enums/EventType.ts: -------------------------------------------------------------------------------- 1 | export enum EventType { 2 | RESIZE = "resize", 3 | MOUSE_UP = "mouseup", 4 | MOUSE_DOWN = "mousedown", 5 | MOUSE_MOVE = "mousemove", 6 | MOUSE_WHEEL = "wheel", 7 | KEY_DOWN = "keydown", 8 | KEY_PRESS = "keypress", 9 | KEY_UP = "keyup", 10 | FOCUS = "focus" 11 | } -------------------------------------------------------------------------------- /src/data/enums/InferenceServerType.ts: -------------------------------------------------------------------------------- 1 | export enum InferenceServerType { 2 | ROBOFLOW = 'ROBOFLOW', 3 | MAKESENSE = 'MAKESENSE' 4 | } -------------------------------------------------------------------------------- /src/data/enums/LabelStatus.ts: -------------------------------------------------------------------------------- 1 | export enum LabelStatus { 2 | ACCEPTED = "ACCEPTED", 3 | REJECTED = "REJECTED", 4 | UNDECIDED = "UNDECIDED" 5 | } -------------------------------------------------------------------------------- /src/data/enums/LabelType.ts: -------------------------------------------------------------------------------- 1 | export enum LabelType { 2 | IMAGE_RECOGNITION = 'IMAGE RECOGNITION', 3 | POINT = 'POINT', 4 | RECT = 'RECT', 5 | POLYGON = 'POLYGON', 6 | LINE = 'LINE' 7 | } -------------------------------------------------------------------------------- /src/data/enums/LineAnchorType.ts: -------------------------------------------------------------------------------- 1 | export enum LineAnchorType { 2 | START = "START", 3 | END = "END" 4 | } -------------------------------------------------------------------------------- /src/data/enums/Notification.ts: -------------------------------------------------------------------------------- 1 | export enum Notification { 2 | EMPTY_LABEL_NAME_ERROR = 0, 3 | NON_UNIQUE_LABEL_NAMES_ERROR = 1, 4 | MODEL_DOWNLOAD_ERROR = 2, 5 | MODEL_INFERENCE_ERROR = 3, 6 | MODEL_LOAD_ERROR = 4, 7 | LABELS_FILE_UPLOAD_ERROR = 5, 8 | ANNOTATION_FILE_PARSE_ERROR = 6, 9 | ANNOTATION_IMPORT_ASSERTION_ERROR = 7, 10 | UNSUPPORTED_INFERENCE_SERVER_MESSAGE = 8, 11 | ROBOFLOW_INFERENCE_SERVER_ERROR = 9, 12 | } 13 | -------------------------------------------------------------------------------- /src/data/enums/NotificationType.ts: -------------------------------------------------------------------------------- 1 | export enum NotificationType { 2 | ERROR = 'ERROR', 3 | SUCCESS = 'SUCCESS', 4 | MESSAGE = 'MESSAGE', 5 | WARNING = 'WARNING' 6 | } 7 | -------------------------------------------------------------------------------- /src/data/enums/PopupWindowType.ts: -------------------------------------------------------------------------------- 1 | export enum PopupWindowType { 2 | LOAD_LABEL_NAMES = 'LOAD_LABEL_NAMES', 3 | UPDATE_LABEL = 'UPDATE_LABEL', 4 | SUGGEST_LABEL_NAMES = 'SUGGEST_LABEL_NAMES', 5 | IMPORT_IMAGES = 'IMPORT_IMAGES', 6 | LOAD_AI_MODEL = 'LOAD_AI_MODEL', 7 | CONNECT_AI_MODEL_VIA_API = 'CONNECT_AI_MODEL_VIA_API', 8 | LOAD_YOLO_V5_MODEL = 'LOAD_YOLO_V5_MODEL', 9 | EXPORT_ANNOTATIONS = 'EXPORT_ANNOTATIONS', 10 | IMPORT_ANNOTATIONS = 'IMPORT_ANNOTATIONS', 11 | INSERT_LABEL_NAMES = 'INSERT_LABEL_NAMES', 12 | EXIT_PROJECT = 'EXIT_PROJECT', 13 | LOADER = 'LOADER', 14 | } 15 | -------------------------------------------------------------------------------- /src/data/enums/ProjectType.ts: -------------------------------------------------------------------------------- 1 | export enum ProjectType { 2 | IMAGE_RECOGNITION = 'IMAGE_RECOGNITION', 3 | OBJECT_DETECTION = 'OBJECT_DETECTION' 4 | } 5 | -------------------------------------------------------------------------------- /src/data/info/EditorFeatureData.ts: -------------------------------------------------------------------------------- 1 | export interface IEditorFeature { 2 | displayText:string; 3 | imageSrc:string; 4 | imageAlt:string; 5 | } 6 | 7 | export const EditorFeatureData: IEditorFeature[] = [ 8 | { 9 | displayText: 'Open source and free to use under GPLv3 license', 10 | imageSrc: 'ico/open-source.png', 11 | imageAlt: 'open-source', 12 | }, 13 | { 14 | displayText: 'No advanced installation required, just open up your browser', 15 | imageSrc: 'ico/online.png', 16 | imageAlt: 'online', 17 | }, 18 | { 19 | displayText: "We don't store your images, because we don't send them anywhere", 20 | imageSrc: 'ico/private.png', 21 | imageAlt: 'private', 22 | }, 23 | { 24 | displayText: 'Support multiple label types - rects, lines, points and polygons', 25 | imageSrc: 'ico/labels.png', 26 | imageAlt: 'labels', 27 | }, 28 | { 29 | displayText: 'Support output file formats like YOLO, VOC XML, VGG JSON, CSV', 30 | imageSrc: 'ico/file.png', 31 | imageAlt: 'file', 32 | }, 33 | { 34 | displayText: 'Use AI to make your work more productive', 35 | imageSrc: 'ico/robot.png', 36 | imageAlt: 'robot', 37 | }, 38 | ]; -------------------------------------------------------------------------------- /src/data/info/InferenceServerData.ts: -------------------------------------------------------------------------------- 1 | import { InferenceServerType } from '../enums/InferenceServerType'; 2 | 3 | export interface IInferenceServer { 4 | name: string 5 | imageSrc: string 6 | imageAlt: string 7 | isDisabled: boolean 8 | } 9 | 10 | export const InferenceServerDataMap: Record = { 11 | [InferenceServerType.ROBOFLOW]: { 12 | name: 'Roboflow Inference Server', 13 | imageSrc: 'ico/roboflow-logo.png', 14 | imageAlt: 'roboflow-inference-server', 15 | isDisabled: false 16 | }, 17 | [InferenceServerType.MAKESENSE]: { 18 | name: 'Make Sense Inference Server', 19 | imageSrc: 'ico/make-sense-ico-transparent.png', 20 | imageAlt: 'make-sense-inference-server', 21 | isDisabled: true 22 | } 23 | } -------------------------------------------------------------------------------- /src/data/info/LabelToolkitData.ts: -------------------------------------------------------------------------------- 1 | import {LabelType} from '../enums/LabelType'; 2 | import {ProjectType} from '../enums/ProjectType'; 3 | 4 | export interface ILabelToolkit { 5 | labelType: LabelType; 6 | headerText: string; 7 | imageSrc: string; 8 | imageAlt: string; 9 | projectType: ProjectType; 10 | } 11 | 12 | export const LabelToolkitData: ILabelToolkit[] = [ 13 | { 14 | labelType: LabelType.IMAGE_RECOGNITION, 15 | headerText: 'Image recognition', 16 | imageSrc: 'ico/object.png', 17 | imageAlt: 'object', 18 | projectType: ProjectType.IMAGE_RECOGNITION, 19 | }, 20 | { 21 | labelType: LabelType.RECT, 22 | headerText: 'Rect', 23 | imageSrc: 'ico/rectangle.png', 24 | imageAlt: 'rectangle', 25 | projectType: ProjectType.OBJECT_DETECTION, 26 | }, 27 | { 28 | labelType: LabelType.POINT, 29 | headerText: 'Point', 30 | imageSrc: 'ico/point.png', 31 | imageAlt: 'point', 32 | projectType: ProjectType.OBJECT_DETECTION, 33 | }, 34 | { 35 | labelType: LabelType.LINE, 36 | headerText: 'Line', 37 | imageSrc: 'ico/line.png', 38 | imageAlt: 'line', 39 | projectType: ProjectType.OBJECT_DETECTION, 40 | }, 41 | { 42 | labelType: LabelType.POLYGON, 43 | headerText: 'Polygon', 44 | imageSrc: 'ico/polygon.png', 45 | imageAlt: 'polygon', 46 | projectType: ProjectType.OBJECT_DETECTION, 47 | }, 48 | ]; -------------------------------------------------------------------------------- /src/data/info/SocialMediaData.ts: -------------------------------------------------------------------------------- 1 | import {Settings} from '../../settings/Settings'; 2 | 3 | export interface ISocialMedia { 4 | displayName:string; 5 | imageSrc:string; 6 | imageAlt:string; 7 | href:string; 8 | tooltipMessage:string; 9 | } 10 | 11 | export const SocialMediaData: ISocialMedia[] = [ 12 | { 13 | displayName: 'Github', 14 | imageSrc: '/ico/github-logo.png', 15 | imageAlt: 'GitHub', 16 | href: Settings.GITHUB_URL, 17 | tooltipMessage: 'Show us some love ⭐ on GitHub', 18 | }, 19 | { 20 | displayName: 'Medium', 21 | imageSrc: '/ico/medium-logo.png', 22 | imageAlt: 'Medium', 23 | href: Settings.MEDIUM_URL, 24 | tooltipMessage: 'Read our AI content on Medium', 25 | }, 26 | { 27 | displayName: 'YouTube', 28 | imageSrc: '/ico/youtube-logo.png', 29 | imageAlt: 'YouTube', 30 | href: Settings.YOUTUBE_URL, 31 | tooltipMessage: 'Watch our AI tutorials on YouTube' 32 | }, 33 | { 34 | displayName: 'Twitch', 35 | imageSrc: '/ico/twitch-logo.png', 36 | imageAlt: 'Twitch', 37 | href: Settings.TWITCH_URL, 38 | tooltipMessage: 'Fight along with us in Kaggle competitions on Twitch' 39 | }, 40 | ]; 41 | -------------------------------------------------------------------------------- /src/data/labels/COCO.ts: -------------------------------------------------------------------------------- 1 | export type COCOSegmentation = number[][] 2 | export type COCOBBox = [number, number, number, number] 3 | 4 | export type COCOInfo = { 5 | description: string; 6 | } 7 | 8 | export type COCOImage = { 9 | id: number; 10 | width: number; 11 | height: number; 12 | file_name: string; 13 | } 14 | 15 | export type COCOCategory = { 16 | id: number; 17 | name: string; 18 | } 19 | 20 | export type COCOAnnotation = { 21 | id: number; 22 | category_id: number; 23 | iscrowd: number; 24 | segmentation: COCOSegmentation; 25 | image_id: number; 26 | area: number; 27 | bbox: COCOBBox; 28 | } 29 | 30 | export type COCOObject = { 31 | info: COCOInfo, 32 | images: COCOImage[], 33 | annotations: COCOAnnotation[], 34 | categories: COCOCategory[] 35 | } -------------------------------------------------------------------------------- /src/data/labels/VGG.ts: -------------------------------------------------------------------------------- 1 | export interface VGGShape {} 2 | 3 | export interface VGGRect extends VGGShape { 4 | name: string, 5 | x: number, 6 | y: number, 7 | width: number, 8 | height: number 9 | } 10 | 11 | export interface VGGPolygon extends VGGShape { 12 | name: string, 13 | all_points_x: number[], 14 | all_points_y: number[] 15 | } 16 | 17 | export interface VGGRegion { 18 | shape_attributes: VGGShape, 19 | region_attributes: { [key:string]:string; } 20 | } 21 | 22 | export type VGGRegionsData = { [key: string]: VGGRegion; } 23 | 24 | export type VGGFileData = { 25 | fileref: string; 26 | size: number; 27 | filename: string; 28 | base64_img_data: string; 29 | file_attributes: object; 30 | regions: VGGRegionsData; 31 | } 32 | 33 | export type VGGObject = { [key: string]: VGGFileData; } -------------------------------------------------------------------------------- /src/index.scss: -------------------------------------------------------------------------------- 1 | body { 2 | height: 100vh; 3 | width: 100vw; 4 | margin: 0; 5 | padding: 0; 6 | 7 | font-family: 'Saira Semi Condensed', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 8 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 9 | sans-serif; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | 13 | overflow-x: hidden; 14 | overflow-y: hidden; 15 | } 16 | 17 | #root { 18 | width: 100%; 19 | height: 100%; 20 | } 21 | 22 | code { 23 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 24 | monospace; 25 | } -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.scss'; 4 | import App from './App'; 5 | import configureStore from './configureStore'; 6 | import { Provider } from 'react-redux'; 7 | import { AppInitializer } from './logic/initializer/AppInitializer'; 8 | 9 | export const store = configureStore(); 10 | AppInitializer.inti(); 11 | 12 | const root = ReactDOM.createRoot(document.getElementById('root') || document.createElement('div')); 13 | root.render( 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | 21 | -------------------------------------------------------------------------------- /src/interfaces/ILabelFormatData.ts: -------------------------------------------------------------------------------- 1 | import {AnnotationFormatType} from "../data/enums/AnnotationFormatType"; 2 | 3 | export interface ILabelFormatData { 4 | type: AnnotationFormatType, 5 | label: string 6 | } -------------------------------------------------------------------------------- /src/interfaces/ILine.ts: -------------------------------------------------------------------------------- 1 | import {IPoint} from "./IPoint"; 2 | 3 | export interface ILine { 4 | start: IPoint, 5 | end: IPoint 6 | } -------------------------------------------------------------------------------- /src/interfaces/IPoint.ts: -------------------------------------------------------------------------------- 1 | export interface IPoint { 2 | x:number, 3 | y:number 4 | } -------------------------------------------------------------------------------- /src/interfaces/IRect.ts: -------------------------------------------------------------------------------- 1 | export interface IRect { 2 | x: number, 3 | y: number, 4 | height: number, 5 | width: number 6 | } 7 | -------------------------------------------------------------------------------- /src/interfaces/ISize.ts: -------------------------------------------------------------------------------- 1 | export interface ISize { 2 | width:number, 3 | height:number 4 | } -------------------------------------------------------------------------------- /src/logic/__tests__/actions/AIActions.test.ts: -------------------------------------------------------------------------------- 1 | import {AIActions} from "../../actions/AIActions"; 2 | 3 | describe('AIActions excludeRejectedLabelNames method', () => { 4 | it('should return list with correct values', () => { 5 | // GIVEN 6 | const suggestedLabels: string[] = [ 7 | "label_1", 8 | "label_2", 9 | "label_3", 10 | "label_4", 11 | ]; 12 | 13 | const rejectedLabels: string[] = [ 14 | "label_3", 15 | "label_4", 16 | "label_5", 17 | ]; 18 | 19 | // WHEN 20 | const excludedLabels: string[] = AIActions.excludeRejectedLabelNames(suggestedLabels, rejectedLabels); 21 | 22 | // THEN 23 | const expectedLabels: string[] = [ 24 | "label_1", 25 | "label_2", 26 | ]; 27 | expect(excludedLabels.toString()).toBe(expectedLabels.toString()); 28 | }); 29 | }); -------------------------------------------------------------------------------- /src/logic/__tests__/actions/AIObjectDetectionActions.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import {LabelName} from '../../../store/labels/types'; 3 | import {DetectedObject} from '@tensorflow-models/coco-ssd'; 4 | import {AISSDObjectDetectionActions} from '../../actions/AISSDObjectDetectionActions'; 5 | 6 | describe('AIObjectDetectionActions extractNewSuggestedLabelNames method', () => { 7 | const mockLabelNames: LabelName[] = [ 8 | { 9 | id: 'id_1', 10 | name: 'label_1', 11 | color: '#000000' 12 | }, 13 | { 14 | id: 'id_2', 15 | name: 'label_2', 16 | color: '#000000' 17 | }, 18 | { 19 | id: 'id_3', 20 | name: 'label_3', 21 | color: '#000000' 22 | } 23 | ]; 24 | 25 | it('should return list with correct values', () => { 26 | // GIVEN 27 | const labelNames: LabelName[] = mockLabelNames; 28 | const predictions: DetectedObject[] = [ 29 | { 30 | bbox: [1, 2, 3 , 4], 31 | class: 'label_3', 32 | score: 0 33 | }, 34 | { 35 | bbox: [1, 2, 3 , 4], 36 | class: 'label_4', 37 | score: 0 38 | }, 39 | { 40 | bbox: [1, 2, 3 , 4], 41 | class: 'label_5', 42 | score: 0 43 | } 44 | ]; 45 | 46 | // WHEN 47 | const suggestedLabels: string[] = AISSDObjectDetectionActions 48 | .extractNewSuggestedLabelNames(labelNames, predictions); 49 | 50 | // THEN 51 | expect(suggestedLabels.toString()).toBe(['label_4', 'label_5'].toString()); 52 | }); 53 | 54 | it('should return empty list', () => { 55 | // GIVEN 56 | const labelNames: LabelName[] = mockLabelNames; 57 | const predictions: DetectedObject[] = [ 58 | { 59 | bbox: [1, 2, 3 , 4], 60 | class: 'label_3', 61 | score: 0 62 | }, 63 | { 64 | bbox: [1, 2, 3 , 4], 65 | class: 'label_1', 66 | score: 0 67 | } 68 | ]; 69 | 70 | // WHEN 71 | const suggestedLabels: string[] = AISSDObjectDetectionActions 72 | .extractNewSuggestedLabelNames(labelNames, predictions); 73 | 74 | // THEN 75 | expect(suggestedLabels.toString()).toBe([].toString()); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /src/logic/__tests__/export/polygon/COCOExporter.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import { COCOCategory, COCOInfo } from "../../../../data/labels/COCO"; 3 | import { COCOExporter } from "../../../export/polygon/COCOExporter"; 4 | import { LabelName } from "../../../../store/labels/types"; 5 | 6 | describe('COCOExporter produces correct COCO label', () => { 7 | it('should produce correct info component', () => { 8 | const givenDescription = "lorem ipsum"; 9 | const expectedCOCOInfo: COCOInfo = { 10 | "description": "lorem ipsum" 11 | }; 12 | expect(COCOExporter.getInfoComponent(givenDescription)).toEqual(expectedCOCOInfo); 13 | }); 14 | 15 | it('should produce correct categories component', () => { 16 | const givenLabelNames: LabelName[] = [ 17 | { 18 | "id": "id_1", 19 | "name": "label_1" 20 | }, 21 | { 22 | "id": "id_2", 23 | "name": "label_2" 24 | }, 25 | { 26 | "id": "id_3", 27 | "name": "label_3" 28 | } 29 | ]; 30 | const expectedCOCOCategories: COCOCategory[] = [ 31 | { 32 | "id": 1, 33 | "name": "label_1" 34 | }, 35 | { 36 | "id": 2, 37 | "name": "label_2" 38 | }, 39 | { 40 | "id": 3, 41 | "name": "label_3" 42 | } 43 | ]; 44 | expect(COCOExporter.getCategoriesComponent(givenLabelNames)).toEqual(expectedCOCOCategories); 45 | }); 46 | }); -------------------------------------------------------------------------------- /src/logic/__tests__/import/coco/COCOUtils.tests.ts: -------------------------------------------------------------------------------- 1 | import {COCOUtils} from "../../../import/coco/COCOUtils"; 2 | import {isEqual} from "lodash"; 3 | 4 | describe('COCOUtils bbox2rect method', () => { 5 | it('should return valid IRect', () => { 6 | // given 7 | const x = 10, y = 20, width= 30, height = 40; 8 | const bbox: [number, number, number, number] = [x, y, width, height] 9 | 10 | // when 11 | const result = COCOUtils.bbox2rect(bbox); 12 | 13 | // then 14 | const expectedResult = { 15 | x: x, 16 | y: y, 17 | width: width, 18 | height: height 19 | } 20 | expect(result).toEqual(expectedResult); 21 | }); 22 | }); 23 | 24 | describe('COCOUtils segmentation2vertices method', () => { 25 | it('should return valid array of polygon vertices', () => { 26 | // given 27 | const p1x = 10, p1y = 20, p2x = 30, p2y = 40, p3x = 50, p3y = 60; 28 | const segmentation: number[][] = [[p1x, p1y, p2x, p2y, p3x, p3y]]; 29 | 30 | // when 31 | const result = COCOUtils.segmentation2vertices(segmentation); 32 | 33 | // then 34 | const expectedResult = [[ 35 | {x: p1x, y: p1y}, 36 | {x: p2x, y: p2y}, 37 | {x: p3x, y: p3y} 38 | ]] 39 | expect(isEqual(result, expectedResult)).toBe(true); 40 | }); 41 | }); -------------------------------------------------------------------------------- /src/logic/__tests__/import/yolo/YOLOImporter.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import { ImageData } from '../../../../store/labels/types'; 3 | import { AcceptedFileType } from '../../../../data/enums/AcceptedFileType'; 4 | import { v4 as uuidv4 } from 'uuid'; 5 | import { YOLOImporter } from '../../../import/yolo/YOLOImporter'; 6 | import { isEqual } from 'lodash'; 7 | 8 | const getDummyImageData = (fileName: string): ImageData => { 9 | return { 10 | id: uuidv4(), 11 | fileData: new File([''], fileName, { type: AcceptedFileType.IMAGE }), 12 | loadStatus: true, 13 | labelRects: [], 14 | labelPoints: [], 15 | labelLines: [], 16 | labelPolygons: [], 17 | labelNameIds: [], 18 | isVisitedByYOLOObjectDetector: false, 19 | isVisitedBySSDObjectDetector: false, 20 | isVisitedByPoseDetector: false, 21 | isVisitedByRoboflowAPI: false 22 | }; 23 | }; 24 | 25 | const getDummyFileData = (fileName: string): File => { 26 | return new File([''], fileName, { type: AcceptedFileType.TEXT }); 27 | }; 28 | 29 | describe('YOLOImporter filterFilesData method', () => { 30 | it('should return correct fileData partition', () => { 31 | // given 32 | const imageFileNames = [ 33 | '00000.png', 34 | '00001.png', 35 | '00002.png', 36 | '00003.png', 37 | '00004.png' 38 | ]; 39 | const imagesData: ImageData[] = imageFileNames.map((fileName: string) => getDummyImageData(fileName)); 40 | const annotationFileNames = [ 41 | '00002.txt', 42 | '00003.txt', 43 | '00004.txt', 44 | '00005.txt', 45 | '00006.txt' 46 | ]; 47 | const annotationFiles: File[] = annotationFileNames.map((fileName: string) => getDummyFileData(fileName)); 48 | const labelFileNames = [ 49 | 'labels.txt' 50 | ]; 51 | const labelFiles: File[] = labelFileNames.map((fileName: string) => getDummyFileData(fileName)); 52 | const filesData: File[] = [...annotationFiles, ...labelFiles]; 53 | const expectedAnnotationFileNames = [ 54 | '00002.txt', 55 | '00003.txt', 56 | '00004.txt' 57 | ]; 58 | 59 | // when 60 | const result = YOLOImporter.filterFilesData(filesData, imagesData); 61 | 62 | // then 63 | const resultNames = result.annotationFiles.map((item: File) => item.name); 64 | expect(result.labelNameFile.name).toEqual('labels.txt'); 65 | expect(result.annotationFiles.length).toEqual(3); 66 | expect(isEqual(resultNames, expectedAnnotationFileNames)).toBe(true); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /src/logic/actions/PopupActions.ts: -------------------------------------------------------------------------------- 1 | import {ContextManager} from '../context/ContextManager'; 2 | import {store} from '../../index'; 3 | import {updateActivePopupType} from '../../store/general/actionCreators'; 4 | 5 | export class PopupActions { 6 | public static close() { 7 | store.dispatch(updateActivePopupType(null)); 8 | ContextManager.restoreCtx(); 9 | } 10 | } -------------------------------------------------------------------------------- /src/logic/context/BaseContext.ts: -------------------------------------------------------------------------------- 1 | import {HotKeyAction} from "../../data/HotKeyAction"; 2 | 3 | export class BaseContext { 4 | public static actions: HotKeyAction[] = []; 5 | 6 | public static getActions(): HotKeyAction[] { 7 | return this.actions; 8 | } 9 | } -------------------------------------------------------------------------------- /src/logic/context/PopupContext.ts: -------------------------------------------------------------------------------- 1 | import {HotKeyAction} from '../../data/HotKeyAction'; 2 | import {PopupWindowType} from '../../data/enums/PopupWindowType'; 3 | import {GeneralSelector} from '../../store/selectors/GeneralSelector'; 4 | import {BaseContext} from './BaseContext'; 5 | import {PopupActions} from '../actions/PopupActions'; 6 | import {Settings} from '../../settings/Settings'; 7 | 8 | export class PopupContext extends BaseContext { 9 | public static actions: HotKeyAction[] = [ 10 | { 11 | keyCombo: ['Escape'], 12 | action: (event: KeyboardEvent) => { 13 | const popupType: PopupWindowType = GeneralSelector.getActivePopupType(); 14 | const canBeClosed: boolean = Settings.CLOSEABLE_POPUPS.includes(popupType); 15 | if (canBeClosed) { 16 | PopupActions.close(); 17 | } 18 | } 19 | } 20 | ]; 21 | } 22 | -------------------------------------------------------------------------------- /src/logic/export/LineLabelExport.ts: -------------------------------------------------------------------------------- 1 | import {AnnotationFormatType} from "../../data/enums/AnnotationFormatType"; 2 | import {LabelsSelector} from "../../store/selectors/LabelsSelector"; 3 | import {ImageData, LabelLine, LabelName} from "../../store/labels/types"; 4 | import {ExporterUtil} from "../../utils/ExporterUtil"; 5 | import {ImageRepository} from "../imageRepository/ImageRepository"; 6 | import {findLast} from "lodash"; 7 | 8 | export class LineLabelsExporter { 9 | public static export(exportFormatType: AnnotationFormatType): void { 10 | switch (exportFormatType) { 11 | case AnnotationFormatType.CSV: 12 | LineLabelsExporter.exportAsCSV(); 13 | break; 14 | default: 15 | return; 16 | } 17 | } 18 | 19 | private static exportAsCSV(): void { 20 | const content: string = LabelsSelector.getImagesData() 21 | .map((imageData: ImageData) => { 22 | return LineLabelsExporter.wrapLineLabelsIntoCSV(imageData)}) 23 | .filter((imageLabelData: string) => { 24 | return !!imageLabelData}) 25 | .join("\n"); 26 | const fileName: string = `${ExporterUtil.getExportFileName()}.csv`; 27 | ExporterUtil.saveAs(content, fileName); 28 | } 29 | 30 | private static wrapLineLabelsIntoCSV(imageData: ImageData): string { 31 | if (imageData.labelLines.length === 0 || !imageData.loadStatus) 32 | return null; 33 | 34 | const image: HTMLImageElement = ImageRepository.getById(imageData.id); 35 | const labelNames: LabelName[] = LabelsSelector.getLabelNames(); 36 | const labelLinesString: string[] = imageData.labelLines.map((labelLine: LabelLine) => { 37 | const labelName: LabelName = findLast(labelNames, {id: labelLine.labelId}); 38 | const labelFields = !!labelName ? [ 39 | labelName.name, 40 | Math.round(labelLine.line.start.x).toString(), 41 | Math.round(labelLine.line.start.y).toString(), 42 | Math.round(labelLine.line.end.x).toString(), 43 | Math.round(labelLine.line.end.y).toString(), 44 | imageData.fileData.name, 45 | image.width.toString(), 46 | image.height.toString() 47 | ] : []; 48 | return labelFields.join(",") 49 | }); 50 | return labelLinesString.join("\n"); 51 | } 52 | } -------------------------------------------------------------------------------- /src/logic/export/PointLabelsExport.ts: -------------------------------------------------------------------------------- 1 | import {AnnotationFormatType} from "../../data/enums/AnnotationFormatType"; 2 | import {ImageData, LabelName, LabelPoint} from "../../store/labels/types"; 3 | import {ImageRepository} from "../imageRepository/ImageRepository"; 4 | import {LabelsSelector} from "../../store/selectors/LabelsSelector"; 5 | import {ExporterUtil} from "../../utils/ExporterUtil"; 6 | import {findLast} from "lodash"; 7 | 8 | export class PointLabelsExporter { 9 | public static export(exportFormatType: AnnotationFormatType): void { 10 | switch (exportFormatType) { 11 | case AnnotationFormatType.CSV: 12 | PointLabelsExporter.exportAsCSV(); 13 | break; 14 | default: 15 | return; 16 | } 17 | } 18 | 19 | private static exportAsCSV(): void { 20 | const content: string = LabelsSelector.getImagesData() 21 | .map((imageData: ImageData) => { 22 | return PointLabelsExporter.wrapRectLabelsIntoCSV(imageData)}) 23 | .filter((imageLabelData: string) => { 24 | return !!imageLabelData}) 25 | .join("\n"); 26 | const fileName: string = `${ExporterUtil.getExportFileName()}.csv`; 27 | ExporterUtil.saveAs(content, fileName); 28 | } 29 | 30 | private static wrapRectLabelsIntoCSV(imageData: ImageData): string { 31 | if (imageData.labelPoints.length === 0 || !imageData.loadStatus) 32 | return null; 33 | 34 | const image: HTMLImageElement = ImageRepository.getById(imageData.id); 35 | const labelNames: LabelName[] = LabelsSelector.getLabelNames(); 36 | const labelRectsString: string[] = imageData.labelPoints.map((labelPoint: LabelPoint) => { 37 | const labelName: LabelName = findLast(labelNames, {id: labelPoint.labelId}); 38 | const labelFields = !!labelName ? [ 39 | labelName.name, 40 | Math.round(labelPoint.point.x).toString(), 41 | Math.round(labelPoint.point.y).toString(), 42 | imageData.fileData.name, 43 | image.width.toString(), 44 | image.height.toString() 45 | ] : []; 46 | return labelFields.join(",") 47 | }); 48 | return labelRectsString.join("\n"); 49 | } 50 | } -------------------------------------------------------------------------------- /src/logic/export/polygon/PolygonLabelsExporter.ts: -------------------------------------------------------------------------------- 1 | import {AnnotationFormatType} from "../../../data/enums/AnnotationFormatType"; 2 | import {VGGExporter} from "./VGGExporter"; 3 | import {COCOExporter} from "./COCOExporter"; 4 | 5 | export class PolygonLabelsExporter { 6 | public static export(exportFormatType: AnnotationFormatType): void { 7 | switch (exportFormatType) { 8 | case AnnotationFormatType.VGG: 9 | VGGExporter.export(); 10 | break; 11 | case AnnotationFormatType.COCO: 12 | COCOExporter.export(); 13 | break; 14 | default: 15 | return; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/logic/helpers/CSSHelper.ts: -------------------------------------------------------------------------------- 1 | import {Settings} from '../../settings/Settings'; 2 | import {AISelector} from '../../store/selectors/AISelector'; 3 | 4 | export class CSSHelper { 5 | public static getLeadingColor(): string { 6 | return AISelector.isAISSDObjectDetectorModelLoaded() || AISelector.isAIYOLOObjectDetectorModelLoaded() || 7 | AISelector.isAIPoseDetectorModelLoaded() ? Settings.PRIMARY_COLOR : Settings.SECONDARY_COLOR; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/logic/imageRepository/ImageLoadManager.ts: -------------------------------------------------------------------------------- 1 | export class ImageLoadManager { 2 | 3 | private static queue: (() => Promise)[] = []; 4 | private static isRunning: boolean = false; 5 | 6 | public static add(fx: Promise) { 7 | ImageLoadManager.queue.push(async () => await fx); 8 | } 9 | 10 | public static run() { 11 | setTimeout(() => ImageLoadManager.runQueue(), 10); 12 | } 13 | 14 | public static addAndRun(fx: Promise) { 15 | ImageLoadManager.add(fx); 16 | ImageLoadManager.run(); 17 | } 18 | 19 | public static async runQueue() { 20 | if (!ImageLoadManager.isRunning) { 21 | ImageLoadManager.isRunning = true; 22 | await ImageLoadManager.runTasks(); 23 | ImageLoadManager.isRunning = false; 24 | } 25 | } 26 | 27 | private static async runTasks() { 28 | while (ImageLoadManager.queue.length > 0) { 29 | const fx = ImageLoadManager.queue.shift(); 30 | await fx(); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/logic/imageRepository/ImageRepository.ts: -------------------------------------------------------------------------------- 1 | import {zip} from "lodash"; 2 | 3 | export type ImageMap = { [s: string]: HTMLImageElement; }; 4 | 5 | export class ImageRepository { 6 | private static repository: ImageMap = {}; 7 | 8 | public static storeImage(id: string, image: HTMLImageElement) { 9 | ImageRepository.repository[id] = image; 10 | } 11 | 12 | public static storeImages(ids: string[], images: HTMLImageElement[]) { 13 | zip(ids, images).forEach((pair: [string, HTMLImageElement]) => { 14 | ImageRepository.storeImage(...pair); 15 | }) 16 | } 17 | 18 | public static getById(uuid: string): HTMLImageElement { 19 | return ImageRepository.repository[uuid]; 20 | } 21 | } -------------------------------------------------------------------------------- /src/logic/import/AnnotationImporter.ts: -------------------------------------------------------------------------------- 1 | import {ImageData, LabelName} from '../../store/labels/types'; 2 | import {LabelType} from '../../data/enums/LabelType'; 3 | 4 | export type ImportResult = { 5 | imagesData: ImageData[] 6 | labelNames: LabelName[] 7 | } 8 | 9 | export class AnnotationImporter { 10 | public labelType: LabelType[] 11 | 12 | constructor(labelType: LabelType[]) { 13 | this.labelType = labelType; 14 | } 15 | 16 | public import( 17 | filesData: File[], 18 | onSuccess: (imagesData: ImageData[], labelNames: LabelName[]) => any, 19 | onFailure: (error?:Error) => any 20 | ): void { 21 | throw new Error('Method not implemented.'); 22 | } 23 | } -------------------------------------------------------------------------------- /src/logic/import/coco/COCOErrors.ts: -------------------------------------------------------------------------------- 1 | export class COCOAnnotationsLoadingError extends Error { 2 | constructor(message) { 3 | super(message); 4 | this.name = "COCOAnnotationsLoadingError"; 5 | } 6 | } 7 | 8 | export class COCOFormatValidationError extends COCOAnnotationsLoadingError { 9 | constructor(message) { 10 | super(message); 11 | this.name = "COCOFormatValidationError"; 12 | } 13 | } 14 | 15 | export class COCOAnnotationReadingError extends COCOAnnotationsLoadingError { 16 | constructor() { 17 | super("Unexpected error occurred during reading annotations from file"); 18 | this.name = "COCOAnnotationReadingError"; 19 | } 20 | } 21 | 22 | export class COCOAnnotationDeserializationError extends COCOAnnotationsLoadingError { 23 | constructor() { 24 | super("COCO annotation file need to be in JSON format"); 25 | this.name = "COCOAnnotationDeserializationError"; 26 | } 27 | } 28 | 29 | export class COCOAnnotationFileCountError extends COCOAnnotationsLoadingError { 30 | constructor() { 31 | super("COCO annotation requires single file but multiple were given"); 32 | this.name = "COCOAnnotationFileCountError"; 33 | } 34 | } -------------------------------------------------------------------------------- /src/logic/import/coco/COCOUtils.ts: -------------------------------------------------------------------------------- 1 | import {COCOBBox, COCOSegmentation} from "../../../data/labels/COCO"; 2 | import {IRect} from "../../../interfaces/IRect"; 3 | import {IPoint} from "../../../interfaces/IPoint"; 4 | import {chunk} from "lodash"; 5 | 6 | export class COCOUtils { 7 | public static bbox2rect(bbox: COCOBBox): IRect { 8 | return { 9 | x: bbox[0], 10 | y: bbox[1], 11 | width: bbox[2], 12 | height: bbox[3] 13 | } 14 | } 15 | 16 | public static segmentation2vertices(segmentation: COCOSegmentation): IPoint[][] { 17 | return segmentation.map((segment: number[]) => { 18 | return chunk(segment, 2).map((pair: number[]) => { 19 | return {x: pair[0], y: pair[1]} 20 | }) 21 | }) 22 | } 23 | } -------------------------------------------------------------------------------- /src/logic/import/yolo/YOLOErrors.ts: -------------------------------------------------------------------------------- 1 | export class YOLOAnnotationsLoadingError extends Error { 2 | constructor(message) { 3 | super(message); 4 | this.name = 'YOLOAnnotationsLoadingError'; 5 | } 6 | } 7 | 8 | export class YOLOLabelsReadingError extends YOLOAnnotationsLoadingError { 9 | constructor() { 10 | super('Unexpected error occurred during reading label names from labels.txt file'); 11 | this.name = 'YOLOLabelsLoadingError'; 12 | } 13 | } 14 | 15 | export class NoLabelNamesFileProvidedError extends YOLOAnnotationsLoadingError { 16 | constructor() { 17 | super('For YOLO labels to be loaded correctly, labels.txt file is required'); 18 | this.name = 'NoLabelNamesFileProvidedError'; 19 | } 20 | } 21 | 22 | export class LabelNamesNotUniqueError extends YOLOAnnotationsLoadingError { 23 | constructor() { 24 | super('Label names listed in labels.txt file should be unique'); 25 | this.name = 'LabelNamesNotUniqueError'; 26 | } 27 | } 28 | 29 | export class AnnotationsParsingError extends YOLOAnnotationsLoadingError { 30 | constructor(imageName: string) { 31 | super(`Unexpected error occurred during parsing of ${imageName} annotations file`); 32 | this.name = 'AnnotationsParsingError'; 33 | } 34 | } -------------------------------------------------------------------------------- /src/settings/RenderEngineSettings.ts: -------------------------------------------------------------------------------- 1 | import {ISize} from '../interfaces/ISize'; 2 | import {Settings} from './Settings'; 3 | 4 | export class RenderEngineSettings { 5 | public static readonly LINE_THICKNESS: number = 2; 6 | public static readonly lineActiveColor: string = Settings.PRIMARY_COLOR; 7 | public static readonly defaultLineColor: string = '#ffffff'; 8 | public static readonly CROSS_HAIR_LINE_COLOR: string = '#ffffff'; 9 | public static readonly crossHairPadding: number = 25; 10 | public static readonly anchorSize: ISize = { 11 | width: Settings.RESIZE_HANDLE_DIMENSION_PX, 12 | height: Settings.RESIZE_HANDLE_DIMENSION_PX 13 | }; 14 | public static readonly anchorHoverSize: ISize = { 15 | width: Settings.RESIZE_HANDLE_HOVER_DIMENSION_PX, 16 | height: Settings.RESIZE_HANDLE_HOVER_DIMENSION_PX 17 | }; 18 | public static readonly suggestedAnchorDetectionSize: ISize = { 19 | width: 100, 20 | height: 100 21 | }; 22 | public static readonly defaultAnchorColor: string = '#ffffff'; 23 | public static readonly inactiveAnchorColor: string = Settings.DARK_THEME_SECOND_COLOR; 24 | 25 | public static readonly DEFAULT_ANCHOR_COLOR: string = '#ffffff'; 26 | public static readonly ACTIVE_ANCHOR_COLOR: string = Settings.SECONDARY_COLOR; 27 | public static readonly INACTIVE_ANCHOR_COLOR: string = Settings.DARK_THEME_SECOND_COLOR; 28 | 29 | public static readonly DEFAULT_LINE_COLOR: string = '#ffffff'; 30 | public static readonly ACTIVE_LINE_COLOR: string = Settings.PRIMARY_COLOR; 31 | public static readonly INACTIVE_LINE_COLOR: string = '#ffffff'; 32 | } 33 | -------------------------------------------------------------------------------- /src/settings/ViewPointSettings.ts: -------------------------------------------------------------------------------- 1 | export class ViewPointSettings { 2 | public static readonly CANVAS_MIN_MARGIN_PX: number = 20; 3 | public static readonly MIN_ZOOM: number = 1; 4 | public static readonly MAX_ZOOM: number = 4; 5 | public static readonly ZOOM_STEP: number = 0.1; 6 | public static readonly TRANSLATION_STEP_PX: number = 20; 7 | } -------------------------------------------------------------------------------- /src/settings/_Settings.scss: -------------------------------------------------------------------------------- 1 | $darkThemeFirstColor: #171717; 2 | $darkThemeSecondColor: #282828; 3 | $darkThemeThirdColor: #4c4c4c; 4 | $darkThemeForthColor: #262c2f; 5 | 6 | $primaryColor: #2af598; 7 | $secondaryColor: #009efd; 8 | 9 | $topNavigationBarHeight: 35px; 10 | $stateBarHeight: 2px; 11 | $sideNavigationBarCompanionWidth: 23px; 12 | $sideNavigationBarContentWidth: 300px; 13 | $editorBottomNavigationBarHeight: 40px; 14 | $editorTopNavigationBarHeight: 40px; 15 | $toolboxWidth: 50px; -------------------------------------------------------------------------------- /src/staticModels/EditorModel.ts: -------------------------------------------------------------------------------- 1 | import { PrimaryEditorRenderEngine } from "../logic/render/PrimaryEditorRenderEngine"; 2 | import { BaseRenderEngine } from "../logic/render/BaseRenderEngine"; 3 | import { IRect } from "../interfaces/IRect"; 4 | import { IPoint } from "../interfaces/IPoint"; 5 | import { ISize } from "../interfaces/ISize"; 6 | import Scrollbars from "react-custom-scrollbars-2"; 7 | import { ViewPortHelper } from "../logic/helpers/ViewPortHelper"; 8 | 9 | export class EditorModel { 10 | public static editor: HTMLDivElement; 11 | public static canvas: HTMLCanvasElement; 12 | public static mousePositionIndicator: HTMLDivElement; 13 | public static cursor: HTMLDivElement; 14 | public static viewPortScrollbars: Scrollbars; 15 | public static image: HTMLImageElement; 16 | 17 | public static primaryRenderingEngine: PrimaryEditorRenderEngine; 18 | public static supportRenderingEngine: BaseRenderEngine; 19 | 20 | public static viewPortHelper: ViewPortHelper; 21 | 22 | public static isLoading: boolean = false; 23 | public static viewPortActionsDisabled: boolean = false; 24 | public static mousePositionOnViewPortContent: IPoint; 25 | public static viewPortSize: ISize; 26 | 27 | // x and y describe the dimension of the margin that remains constant regardless of the scale of the image 28 | // width and height describes the render image size for 100% scale 29 | public static defaultRenderImageRect: IRect; 30 | } -------------------------------------------------------------------------------- /src/staticModels/PlatformModel.ts: -------------------------------------------------------------------------------- 1 | import {MobileDeviceData} from "../data/MobileDeviceData"; 2 | 3 | export class PlatformModel { 4 | public static mobileDeviceData: MobileDeviceData; 5 | public static isMac: boolean; 6 | public static isSafari: boolean; 7 | public static isFirefox: boolean; 8 | } -------------------------------------------------------------------------------- /src/store/Actions.ts: -------------------------------------------------------------------------------- 1 | export enum Action { 2 | // AI 3 | UPDATE_SUGGESTED_LABEL_LIST = '@@UPDATE_SUGGESTED_LABEL_LIST', 4 | UPDATE_REJECTED_SUGGESTED_LABEL_LIST = '@@UPDATE_REJECTED_SUGGESTED_LABEL_LIST', 5 | UPDATE_SSD_OBJECT_DETECTOR_STATUS = '@@UPDATE_SSD_OBJECT_DETECTOR_STATUS', 6 | UPDATE_YOLO_V5_OBJECT_DETECTOR_STATUS = '@@UPDATE_YOLO_V5_OBJECT_DETECTOR_STATUS', 7 | UPDATE_POSE_DETECTOR_STATUS = '@@UPDATE_POSE_DETECTOR_STATUS', 8 | UPDATE_DISABLED_AI_FLAG = '@@UPDATE_DISABLED_AI_FLAG', 9 | UPDATE_ROBOFLOW_API_DETAILS = '@@UPDATE_ROBOFLOW_API_DETAILS', 10 | 11 | // GENERAL 12 | UPDATE_PROJECT_DATA = '@@UPDATE_PROJECT_DATA', 13 | UPDATE_WINDOW_SIZE = '@@UPDATE_WINDOW_SIZE', 14 | UPDATE_ACTIVE_POPUP_TYPE = '@@UPDATE_ACTIVE_POPUP_TYPE', 15 | UPDATE_CUSTOM_CURSOR_STYLE = '@@UPDATE_CUSTOM_CURSOR_STYLE', 16 | UPDATE_CONTEXT = '@@UPDATE_CONTEXT', 17 | UPDATE_PREVENT_CUSTOM_CURSOR_STATUS = '@@UPDATE_PREVENT_CUSTOM_CURSOR_STATUS', 18 | UPDATE_IMAGE_DRAG_MODE_STATUS = '@@UPDATE_IMAGE_DRAG_MODE_STATUS', 19 | UPDATE_CROSS_HAIR_VISIBLE_STATUS = '@@UPDATE_CROSS_HAIR_VISIBLE_STATUS', 20 | UPDATE_ENABLE_PER_CLASS_COLORATION_STATUS = '@@UPDATE_ENABLE_PER_CLASS_COLORATION_STATUS', 21 | UPDATE_ZOOM = '@@UPDATE_ZOOM', 22 | 23 | // LABELS 24 | UPDATE_ACTIVE_IMAGE_INDEX = '@@UPDATE_ACTIVE_IMAGE_INDEX', 25 | UPDATE_IMAGE_DATA_BY_ID = '@@UPDATE_IMAGE_DATA_BY_ID', 26 | ADD_IMAGES_DATA = '@@ADD_IMAGES_DATA', 27 | UPDATE_IMAGES_DATA = '@@UPDATE_IMAGES_DATA', 28 | UPDATE_ACTIVE_LABEL_NAME_ID = '@@UPDATE_ACTIVE_LABEL_NAME_ID', 29 | UPDATE_ACTIVE_LABEL_TYPE = '@@UPDATE_ACTIVE_LABEL_TYPE', 30 | UPDATE_ACTIVE_LABEL_ID = '@@UPDATE_ACTIVE_LABEL_ID', 31 | UPDATE_HIGHLIGHTED_LABEL_ID = '@@UPDATE_HIGHLIGHTED_LABEL_ID', 32 | UPDATE_LABEL_NAMES = '@@UPDATE_LABEL_NAMES', 33 | UPDATE_FIRST_LABEL_CREATED_FLAG = '@@UPDATE_FIRST_LABEL_CREATED_FLAG', 34 | 35 | // NOTIFICATIONS 36 | SUBMIT_NEW_NOTIFICATION = '@@SUBMIT_NEW_NOTIFICATION', 37 | DELETE_NOTIFICATION_BY_ID = '@@DELETE_NOTIFICATION_BY_ID' 38 | } 39 | -------------------------------------------------------------------------------- /src/store/ai/actionCreators.ts: -------------------------------------------------------------------------------- 1 | import { Action } from '../Actions'; 2 | import { AIActionTypes, RoboflowAPIDetails } from './types'; 3 | 4 | export function updateSuggestedLabelList(labelList: string[]): AIActionTypes { 5 | return { 6 | type: Action.UPDATE_SUGGESTED_LABEL_LIST, 7 | payload: { 8 | labelList, 9 | } 10 | } 11 | } 12 | 13 | export function updateRejectedSuggestedLabelList(labelList: string[]): AIActionTypes { 14 | return { 15 | type: Action.UPDATE_REJECTED_SUGGESTED_LABEL_LIST, 16 | payload: { 17 | labelList, 18 | } 19 | } 20 | } 21 | 22 | export function updateSSDObjectDetectorStatus(isSSDObjectDetectorLoaded: boolean): AIActionTypes { 23 | return { 24 | type: Action.UPDATE_SSD_OBJECT_DETECTOR_STATUS, 25 | payload: { 26 | isSSDObjectDetectorLoaded, 27 | } 28 | } 29 | } 30 | 31 | export function updateYOLOV5ObjectDetectorStatus(isYOLOV5ObjectDetectorLoaded: boolean): AIActionTypes { 32 | return { 33 | type: Action.UPDATE_YOLO_V5_OBJECT_DETECTOR_STATUS, 34 | payload: { 35 | isYOLOV5ObjectDetectorLoaded, 36 | } 37 | } 38 | } 39 | 40 | export function updatePoseDetectorStatus(isPoseDetectorLoaded: boolean): AIActionTypes { 41 | return { 42 | type: Action.UPDATE_POSE_DETECTOR_STATUS, 43 | payload: { 44 | isPoseDetectorLoaded, 45 | } 46 | } 47 | } 48 | 49 | export function updateDisabledAIFlag(isAIDisabled: boolean): AIActionTypes { 50 | return { 51 | type: Action.UPDATE_DISABLED_AI_FLAG, 52 | payload: { 53 | isAIDisabled, 54 | } 55 | } 56 | } 57 | 58 | export function updateRoboflowAPIDetails(roboflowAPIDetails: RoboflowAPIDetails): AIActionTypes { 59 | return { 60 | type: Action.UPDATE_ROBOFLOW_API_DETAILS, 61 | payload: { 62 | roboflowAPIDetails 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/store/ai/reducer.ts: -------------------------------------------------------------------------------- 1 | import { AIActionTypes, AIState } from './types'; 2 | import { Action } from '../Actions'; 3 | 4 | const initialState: AIState = { 5 | suggestedLabelList: [], 6 | rejectedSuggestedLabelList: [], 7 | isSSDObjectDetectorLoaded: false, 8 | isYOLOV5ObjectDetectorLoaded: false, 9 | isPoseDetectorLoaded: false, 10 | roboflowAPIDetails: { 11 | status: false, 12 | model: '', 13 | key: '' 14 | }, 15 | isAIDisabled: false 16 | }; 17 | 18 | export function aiReducer( 19 | state = initialState, 20 | action: AIActionTypes 21 | ): AIState { 22 | switch (action.type) { 23 | case Action.UPDATE_SUGGESTED_LABEL_LIST: { 24 | return { 25 | ...state, 26 | suggestedLabelList: action.payload.labelList 27 | } 28 | } 29 | case Action.UPDATE_REJECTED_SUGGESTED_LABEL_LIST: { 30 | return { 31 | ...state, 32 | rejectedSuggestedLabelList: action.payload.labelList 33 | } 34 | } 35 | case Action.UPDATE_SSD_OBJECT_DETECTOR_STATUS: { 36 | return { 37 | ...state, 38 | isSSDObjectDetectorLoaded: action.payload.isSSDObjectDetectorLoaded 39 | } 40 | } 41 | case Action.UPDATE_YOLO_V5_OBJECT_DETECTOR_STATUS: { 42 | return { 43 | ...state, 44 | isYOLOV5ObjectDetectorLoaded: action.payload.isYOLOV5ObjectDetectorLoaded 45 | } 46 | } 47 | case Action.UPDATE_POSE_DETECTOR_STATUS: { 48 | return { 49 | ...state, 50 | isPoseDetectorLoaded: action.payload.isPoseDetectorLoaded 51 | } 52 | } 53 | case Action.UPDATE_DISABLED_AI_FLAG: { 54 | return { 55 | ...state, 56 | isAIDisabled: action.payload.isAIDisabled 57 | } 58 | } 59 | case Action.UPDATE_ROBOFLOW_API_DETAILS: { 60 | return { 61 | ...state, 62 | roboflowAPIDetails: action.payload.roboflowAPIDetails 63 | } 64 | } 65 | default: 66 | return state; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/store/ai/types.ts: -------------------------------------------------------------------------------- 1 | import {Action} from '../Actions'; 2 | 3 | export type RoboflowAPIDetails = { 4 | status: boolean, 5 | model: string, 6 | key: string 7 | } 8 | 9 | export type AIState = { 10 | // SSD LOCAL 11 | isSSDObjectDetectorLoaded: boolean; 12 | 13 | // YOLO V5 LOCAL 14 | isYOLOV5ObjectDetectorLoaded: boolean; 15 | 16 | // POSE NET LOCAL 17 | isPoseDetectorLoaded: boolean; 18 | 19 | // ROBOFLOW API 20 | roboflowAPIDetails: RoboflowAPIDetails; 21 | 22 | // GENERAL 23 | suggestedLabelList: string[]; 24 | rejectedSuggestedLabelList: string[]; 25 | isAIDisabled: boolean; 26 | } 27 | 28 | interface UpdateSuggestedLabelList { 29 | type: typeof Action.UPDATE_SUGGESTED_LABEL_LIST; 30 | payload: { 31 | labelList: string[]; 32 | } 33 | } 34 | 35 | interface UpdateRejectedSuggestedLabelList { 36 | type: typeof Action.UPDATE_REJECTED_SUGGESTED_LABEL_LIST; 37 | payload: { 38 | labelList: string[]; 39 | } 40 | } 41 | 42 | interface UpdateSSDObjectDetectorStatus { 43 | type: typeof Action.UPDATE_SSD_OBJECT_DETECTOR_STATUS; 44 | payload: { 45 | isSSDObjectDetectorLoaded: boolean; 46 | } 47 | } 48 | 49 | interface UpdateYOLOV5ObjectDetectorStatus { 50 | type: typeof Action.UPDATE_YOLO_V5_OBJECT_DETECTOR_STATUS; 51 | payload: { 52 | isYOLOV5ObjectDetectorLoaded: boolean; 53 | } 54 | } 55 | 56 | interface UpdatePoseDetectorStatus { 57 | type: typeof Action.UPDATE_POSE_DETECTOR_STATUS; 58 | payload: { 59 | isPoseDetectorLoaded: boolean; 60 | } 61 | } 62 | 63 | interface UpdateDisabledAIFlag { 64 | type: typeof Action.UPDATE_DISABLED_AI_FLAG; 65 | payload: { 66 | isAIDisabled: boolean; 67 | } 68 | } 69 | 70 | interface UpdateRoboflowAPIDetails { 71 | type: typeof Action.UPDATE_ROBOFLOW_API_DETAILS; 72 | payload: { 73 | roboflowAPIDetails: RoboflowAPIDetails 74 | } 75 | } 76 | 77 | export type AIActionTypes = UpdateSuggestedLabelList 78 | | UpdateRejectedSuggestedLabelList 79 | | UpdateSSDObjectDetectorStatus 80 | | UpdateYOLOV5ObjectDetectorStatus 81 | | UpdatePoseDetectorStatus 82 | | UpdateDisabledAIFlag 83 | | UpdateRoboflowAPIDetails 84 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import {labelsReducer} from './labels/reducer'; 3 | import {generalReducer} from './general/reducer'; 4 | import {aiReducer} from './ai/reducer'; 5 | import {notificationsReducer} from './notifications/reducer'; 6 | 7 | export const rootReducer = combineReducers({ 8 | general: generalReducer, 9 | labels: labelsReducer, 10 | ai: aiReducer, 11 | notifications: notificationsReducer 12 | }); 13 | 14 | export type AppState = ReturnType; 15 | -------------------------------------------------------------------------------- /src/store/labels/actionCreators.ts: -------------------------------------------------------------------------------- 1 | import {LabelsActionTypes, ImageData, LabelName} from './types'; 2 | import {Action} from '../Actions'; 3 | import {LabelType} from '../../data/enums/LabelType'; 4 | 5 | export function updateActiveImageIndex(activeImageIndex: number): LabelsActionTypes { 6 | return { 7 | type: Action.UPDATE_ACTIVE_IMAGE_INDEX, 8 | payload: { 9 | activeImageIndex, 10 | }, 11 | }; 12 | } 13 | 14 | export function updateActiveLabelNameId(activeLabelNameId: string): LabelsActionTypes { 15 | return { 16 | type: Action.UPDATE_ACTIVE_LABEL_NAME_ID, 17 | payload: { 18 | activeLabelNameId, 19 | }, 20 | }; 21 | } 22 | 23 | export function updateActiveLabelId(activeLabelId: string): LabelsActionTypes { 24 | return { 25 | type: Action.UPDATE_ACTIVE_LABEL_ID, 26 | payload: { 27 | activeLabelId, 28 | }, 29 | }; 30 | } 31 | 32 | export function updateHighlightedLabelId(highlightedLabelId: string): LabelsActionTypes { 33 | return { 34 | type: Action.UPDATE_HIGHLIGHTED_LABEL_ID, 35 | payload: { 36 | highlightedLabelId, 37 | }, 38 | }; 39 | } 40 | 41 | export function updateActiveLabelType(activeLabelType: LabelType): LabelsActionTypes { 42 | return { 43 | type: Action.UPDATE_ACTIVE_LABEL_TYPE, 44 | payload: { 45 | activeLabelType, 46 | }, 47 | }; 48 | } 49 | 50 | export function updateImageDataById(id: string, newImageData: ImageData): LabelsActionTypes { 51 | return { 52 | type: Action.UPDATE_IMAGE_DATA_BY_ID, 53 | payload: { 54 | id, 55 | newImageData 56 | }, 57 | }; 58 | } 59 | 60 | export function addImageData(imageData: ImageData[]): LabelsActionTypes { 61 | return { 62 | type: Action.ADD_IMAGES_DATA, 63 | payload: { 64 | imageData, 65 | }, 66 | }; 67 | } 68 | 69 | export function updateImageData(imageData: ImageData[]): LabelsActionTypes { 70 | return { 71 | type: Action.UPDATE_IMAGES_DATA, 72 | payload: { 73 | imageData, 74 | }, 75 | }; 76 | } 77 | 78 | export function updateLabelNames(labels: LabelName[]): LabelsActionTypes { 79 | return { 80 | type: Action.UPDATE_LABEL_NAMES, 81 | payload: { 82 | labels 83 | } 84 | } 85 | } 86 | 87 | export function updateFirstLabelCreatedFlag(firstLabelCreatedFlag: boolean): LabelsActionTypes { 88 | return { 89 | type: Action.UPDATE_FIRST_LABEL_CREATED_FLAG, 90 | payload: { 91 | firstLabelCreatedFlag 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/store/notifications/actionCreators.ts: -------------------------------------------------------------------------------- 1 | import {INotification, NotificationsActionType} from './types'; 2 | import {Action} from '../Actions'; 3 | 4 | export function submitNewNotification(notification: INotification): NotificationsActionType { 5 | return { 6 | type: Action.SUBMIT_NEW_NOTIFICATION, 7 | payload: { 8 | notification, 9 | }, 10 | }; 11 | } 12 | 13 | 14 | export function deleteNotificationById(id: string): NotificationsActionType { 15 | return { 16 | type: Action.DELETE_NOTIFICATION_BY_ID, 17 | payload: { 18 | id, 19 | }, 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/store/notifications/reducer.ts: -------------------------------------------------------------------------------- 1 | import {INotification, NotificationsActionType, NotificationsState} from './types'; 2 | import {Action} from '../Actions'; 3 | 4 | const initialState: NotificationsState = { 5 | queue: [] 6 | } 7 | 8 | export function notificationsReducer( 9 | state = initialState, 10 | action: NotificationsActionType 11 | ): NotificationsState { 12 | switch (action.type) { 13 | case Action.SUBMIT_NEW_NOTIFICATION: { 14 | if (state.queue.length > 0 && state.queue.at(-1).type === action.payload.notification.type) { 15 | return state; 16 | } else { 17 | return { 18 | ...state, 19 | queue: [...state.queue, action.payload.notification] 20 | } 21 | } 22 | } 23 | case Action.DELETE_NOTIFICATION_BY_ID: { 24 | return { 25 | ...state, 26 | queue: state.queue 27 | .filter((message: INotification) => message.id !== action.payload.id) 28 | } 29 | } 30 | default: 31 | return state; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/store/notifications/types.ts: -------------------------------------------------------------------------------- 1 | import {NotificationType} from '../../data/enums/NotificationType'; 2 | import {Action} from '../Actions'; 3 | 4 | export interface INotification { 5 | id: string, 6 | type: NotificationType, 7 | header: string, 8 | description: string 9 | } 10 | 11 | export type NotificationsState = { 12 | queue: INotification[] 13 | } 14 | 15 | interface SubmitNewNotification { 16 | type: typeof Action.SUBMIT_NEW_NOTIFICATION; 17 | payload: { 18 | notification: INotification; 19 | } 20 | } 21 | 22 | interface DeleteNotificationById { 23 | type: typeof Action.DELETE_NOTIFICATION_BY_ID; 24 | payload: { 25 | id: string; 26 | } 27 | } 28 | 29 | export type NotificationsActionType = SubmitNewNotification | DeleteNotificationById 30 | -------------------------------------------------------------------------------- /src/store/selectors/AISelector.ts: -------------------------------------------------------------------------------- 1 | import {store} from '../..'; 2 | import { RoboflowAPIDetails } from '../ai/types'; 3 | 4 | export class AISelector { 5 | public static getSuggestedLabelList(): string[] { 6 | return store.getState().ai.suggestedLabelList; 7 | } 8 | 9 | public static getRejectedSuggestedLabelList(): string[] { 10 | return store.getState().ai.rejectedSuggestedLabelList; 11 | } 12 | 13 | public static isAISSDObjectDetectorModelLoaded(): boolean { 14 | return store.getState().ai.isSSDObjectDetectorLoaded; 15 | } 16 | 17 | public static isAIYOLOObjectDetectorModelLoaded(): boolean { 18 | return store.getState().ai.isYOLOV5ObjectDetectorLoaded; 19 | } 20 | 21 | public static isAIPoseDetectorModelLoaded(): boolean { 22 | return store.getState().ai.isPoseDetectorLoaded; 23 | } 24 | 25 | public static isRoboflowAPIModelLoaded(): boolean { 26 | const roboflowAPIDetails = store.getState().ai.roboflowAPIDetails; 27 | return ( 28 | roboflowAPIDetails.model !== '' && roboflowAPIDetails.key !== '' && roboflowAPIDetails.status 29 | ); 30 | } 31 | 32 | public static isAIDisabled(): boolean { 33 | return store.getState().ai.isAIDisabled; 34 | } 35 | 36 | public static getRoboflowAPIDetails(): RoboflowAPIDetails { 37 | return store.getState().ai.roboflowAPIDetails 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/store/selectors/GeneralSelector.ts: -------------------------------------------------------------------------------- 1 | import {store} from '../..'; 2 | import {PopupWindowType} from '../../data/enums/PopupWindowType'; 3 | import {ContextType} from '../../data/enums/ContextType'; 4 | import {CustomCursorStyle} from '../../data/enums/CustomCursorStyle'; 5 | import {ProjectType} from '../../data/enums/ProjectType'; 6 | 7 | export class GeneralSelector { 8 | public static getActivePopupType(): PopupWindowType { 9 | return store.getState().general.activePopupType; 10 | } 11 | 12 | public static getActiveContext(): ContextType { 13 | return store.getState().general.activeContext; 14 | } 15 | 16 | public static getPreventCustomCursorStatus(): boolean { 17 | return store.getState().general.preventCustomCursor; 18 | } 19 | 20 | public static getImageDragModeStatus(): boolean { 21 | return store.getState().general.imageDragMode; 22 | } 23 | 24 | public static getCrossHairVisibleStatus(): boolean { 25 | return store.getState().general.crossHairVisible; 26 | } 27 | 28 | public static getCustomCursorStyle(): CustomCursorStyle { 29 | return store.getState().general.customCursorStyle; 30 | } 31 | 32 | public static getProjectName(): string { 33 | return store.getState().general.projectData.name; 34 | } 35 | 36 | public static getProjectType(): ProjectType { 37 | return store.getState().general.projectData.type; 38 | } 39 | 40 | public static getZoom(): number { 41 | return store.getState().general.zoom; 42 | } 43 | 44 | public static getEnablePerClassColorationStatus(): boolean { 45 | return store.getState().general.enablePerClassColoration 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/utils/ArrayUtil.ts: -------------------------------------------------------------------------------- 1 | export type PartitionResult = { 2 | pass: T[] 3 | fail: T[] 4 | } 5 | 6 | export class ArrayUtilAmbiguousMatchError extends Error { 7 | constructor() { 8 | super('Given predicate results in more than one value being matched.'); 9 | this.name = 'ArrayUtilAmbiguousMatchError'; 10 | } 11 | } 12 | 13 | export class EmptyArrayError extends Error { 14 | constructor() { 15 | super('Given array is empty.'); 16 | this.name = 'EmptyArrayError'; 17 | } 18 | } 19 | 20 | export class NegativeIndexError extends Error { 21 | constructor() { 22 | super('Index can not be negative.'); 23 | this.name = 'NegativeIndexError'; 24 | } 25 | } 26 | 27 | export class ArrayUtil { 28 | public static partition(array: T[], predicate: (T) => boolean): PartitionResult { 29 | return array.reduce((acc: PartitionResult, item: T) => { 30 | if (predicate(item)) 31 | acc.pass.push(item) 32 | else 33 | acc.fail.push(item) 34 | return acc 35 | }, {pass: [], fail: []}) 36 | } 37 | 38 | public static match(array1: T[], array2: P[], predicate: (key: T, value: P) => boolean): [T, P][] { 39 | return array1.reduce((acc: [T, P][], key: T) => { 40 | const match = array2.filter((value: P) => predicate(key, value)) 41 | if (match.length === 1) { 42 | acc.push([key, match[0]]) 43 | } else if (match.length > 1) { 44 | throw new ArrayUtilAmbiguousMatchError() 45 | } 46 | return acc 47 | }, []) 48 | } 49 | 50 | public static unzip(array: [T, P][]): [T[], P[]] { 51 | return array.reduce((acc: [T[], P[]], i: [T, P]) => { 52 | acc[0].push(i[0]); 53 | acc[1].push(i[1]); 54 | return acc; 55 | }, [[], []]) 56 | } 57 | 58 | public static getByInfiniteIndex(array: T[], index: number): T { 59 | if (array.length === 0) { 60 | throw new EmptyArrayError() 61 | } 62 | if (index < 0) { 63 | throw new NegativeIndexError() 64 | } 65 | const boundedIndex: number = index % array.length 66 | return array[boundedIndex] 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/utils/CanvasUtil.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {IPoint} from "../interfaces/IPoint"; 3 | import {IRect} from "../interfaces/IRect"; 4 | import {ISize} from "../interfaces/ISize"; 5 | 6 | export class CanvasUtil { 7 | public static getMousePositionOnCanvasFromEvent(event: React.MouseEvent | MouseEvent, canvas: HTMLCanvasElement): IPoint { 8 | if (!!canvas && !!event) { 9 | const canvasRect: DOMRect = canvas.getBoundingClientRect(); 10 | return { 11 | x: event.clientX - canvasRect.left, 12 | y: event.clientY - canvasRect.top 13 | } 14 | } 15 | return null; 16 | } 17 | 18 | public static getClientRect(canvas: HTMLCanvasElement): IRect { 19 | if (canvas) { 20 | const canvasRect: DOMRect = canvas.getBoundingClientRect(); 21 | return { 22 | x: canvasRect.left, 23 | y: canvasRect.top, 24 | width: canvasRect.width, 25 | height: canvasRect.height 26 | } 27 | } 28 | return null; 29 | } 30 | 31 | public static getSize(canvas: HTMLCanvasElement): ISize { 32 | if (canvas) { 33 | const canvasRect: DOMRect = canvas.getBoundingClientRect(); 34 | return { 35 | width: canvasRect.width, 36 | height: canvasRect.height 37 | } 38 | } 39 | return null; 40 | } 41 | } -------------------------------------------------------------------------------- /src/utils/DirectionUtil.ts: -------------------------------------------------------------------------------- 1 | import {Direction} from "../data/enums/Direction"; 2 | import {IPoint} from "../interfaces/IPoint"; 3 | 4 | export class DirectionUtil { 5 | public static convertDirectionToVector(direction: Direction): IPoint { 6 | switch (direction) { 7 | case Direction.RIGHT: 8 | return {x: 1, y: 0}; 9 | case Direction.LEFT: 10 | return {x: -1, y: 0}; 11 | case Direction.TOP: 12 | return {x: 0, y: 1}; 13 | case Direction.BOTTOM: 14 | return {x: 0, y: -1}; 15 | case Direction.TOP_RIGHT: 16 | return {x: 1, y: 1}; 17 | case Direction.TOP_LEFT: 18 | return {x: -1, y: 1}; 19 | case Direction.BOTTOM_RIGHT: 20 | return {x: 1, y: -1}; 21 | case Direction.BOTTOM_LEFT: 22 | return {x: -1, y: -1}; 23 | case Direction.CENTER: 24 | return {x: 0, y: 0}; 25 | default: 26 | return null; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/utils/EditorUtil.ts: -------------------------------------------------------------------------------- 1 | import {CustomCursorStyle} from "../data/enums/CustomCursorStyle"; 2 | import classNames from "classnames"; 3 | 4 | export class EditorUtil { 5 | public static getIndicator = (cursorStyle: CustomCursorStyle): string => { 6 | switch (cursorStyle) { 7 | case CustomCursorStyle.ADD: 8 | return "ico/plus.png"; 9 | case CustomCursorStyle.RESIZE: 10 | return "ico/resize.png"; 11 | case CustomCursorStyle.CLOSE: 12 | return "ico/close.png"; 13 | case CustomCursorStyle.MOVE: 14 | return "ico/move.png"; 15 | case CustomCursorStyle.CANCEL: 16 | return "ico/cancel.png"; 17 | case CustomCursorStyle.GRAB: 18 | return "ico/hand-fill.png"; 19 | case CustomCursorStyle.GRABBING: 20 | return "ico/hand-fill-grab.png"; 21 | default: 22 | return null; 23 | } 24 | }; 25 | 26 | public static getCursorStyle = (cursorStyle: CustomCursorStyle) => { 27 | return classNames( 28 | "Cursor", { 29 | "move": cursorStyle === CustomCursorStyle.MOVE, 30 | "add": cursorStyle === CustomCursorStyle.ADD, 31 | "resize": cursorStyle === CustomCursorStyle.RESIZE, 32 | "close": cursorStyle === CustomCursorStyle.CLOSE, 33 | "cancel": cursorStyle === CustomCursorStyle.CANCEL, 34 | "grab": cursorStyle === CustomCursorStyle.GRAB, 35 | "grabbing": cursorStyle === CustomCursorStyle.GRABBING 36 | } 37 | ); 38 | }; 39 | } -------------------------------------------------------------------------------- /src/utils/EnvironmentUtil.ts: -------------------------------------------------------------------------------- 1 | export class EnvironmentUtil { 2 | public static isDev(): boolean { 3 | return process.env.NODE_ENV === 'development'; 4 | } 5 | 6 | public static isProd(): boolean { 7 | return process.env.NODE_ENV === 'production'; 8 | } 9 | } -------------------------------------------------------------------------------- /src/utils/ExporterUtil.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import {GeneralSelector} from '../store/selectors/GeneralSelector'; 3 | import {saveAs} from 'file-saver'; 4 | 5 | export class ExporterUtil { 6 | public static getExportFileName(): string { 7 | const projectName: string = GeneralSelector.getProjectName(); 8 | const date: string = moment().format('YYYY-MM-DD-hh-mm-ss'); 9 | return `labels_${projectName}_${date}` 10 | } 11 | 12 | public static saveAs(content: string, fileName: string): void { 13 | const blob = new Blob([content], {type: 'text/plain;charset=utf-8'}); 14 | try { 15 | saveAs(blob, fileName); 16 | } catch (error) { 17 | // TODO: Implement file save error handling 18 | throw new Error(error as string); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/FileUtil.ts: -------------------------------------------------------------------------------- 1 | 2 | export class FileUtil { 3 | public static loadImageBase64(fileData: File): Promise { 4 | return new Promise((resolve, reject) => { 5 | const reader = new FileReader(); 6 | reader.readAsDataURL(fileData); 7 | reader.onload = () => resolve(reader.result); 8 | reader.onerror = (error) => reject(error); 9 | }); 10 | } 11 | 12 | public static loadImage(fileData: File): Promise { 13 | return new Promise((resolve, reject) => { 14 | const url = URL.createObjectURL(fileData); 15 | const image = new Image(); 16 | image.src = url; 17 | image.onload = () => resolve(image); 18 | image.onerror = reject; 19 | }); 20 | } 21 | 22 | public static loadImages(fileData: File[]): Promise { 23 | return new Promise((resolve, reject) => { 24 | const promises: Promise[] = fileData.map((data: File) => FileUtil.loadImage(data)); 25 | Promise 26 | .all(promises) 27 | .then((values: HTMLImageElement[]) => resolve(values)) 28 | .catch((error) => reject(error)); 29 | }); 30 | } 31 | 32 | public static readFile(fileData: File): Promise { 33 | return new Promise((resolve, reject) => { 34 | const reader = new FileReader(); 35 | reader.onloadend = (event: any) => { 36 | resolve(event?.target?.result); 37 | }; 38 | reader.onerror = reject; 39 | reader.readAsText(fileData); 40 | }); 41 | } 42 | 43 | public static readFiles(fileData: File[]): Promise { 44 | return new Promise((resolve, reject) => { 45 | const promises: Promise[] = fileData.map((data: File) => FileUtil.readFile(data)); 46 | Promise 47 | .all(promises) 48 | .then((values: string[]) => resolve(values)) 49 | .catch((error) => reject(error)); 50 | }); 51 | } 52 | 53 | public static extractFileExtension(name: string): string | null { 54 | const parts = name.split('.'); 55 | return parts.length > 1 ? parts[parts.length - 1] : null; 56 | } 57 | 58 | public static extractFileName(name: string): string | null { 59 | const splitPath = name.split('.'); 60 | let fName = ''; 61 | for (const idx of Array(splitPath.length - 1).keys()) { 62 | if (fName === '') fName += splitPath[idx]; 63 | else fName += '.' + splitPath[idx]; 64 | } 65 | return fName; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/utils/ImageDataUtil.ts: -------------------------------------------------------------------------------- 1 | import {ImageData} from '../store/labels/types'; 2 | import { v4 as uuidv4 } from 'uuid'; 3 | import {FileUtil} from './FileUtil'; 4 | import {ImageRepository} from '../logic/imageRepository/ImageRepository'; 5 | 6 | export class ImageDataUtil { 7 | public static createImageDataFromFileData(fileData: File): ImageData { 8 | return { 9 | id: uuidv4(), 10 | fileData, 11 | loadStatus: false, 12 | labelRects: [], 13 | labelPoints: [], 14 | labelLines: [], 15 | labelPolygons: [], 16 | labelNameIds: [], 17 | isVisitedByYOLOObjectDetector: false, 18 | isVisitedBySSDObjectDetector: false, 19 | isVisitedByPoseDetector: false, 20 | isVisitedByRoboflowAPI: false 21 | } 22 | } 23 | 24 | public static cleanAnnotations(item: ImageData): ImageData { 25 | return { 26 | ...item, 27 | labelRects: [], 28 | labelPoints: [], 29 | labelLines: [], 30 | labelPolygons: [], 31 | labelNameIds: [] 32 | } 33 | } 34 | 35 | public static arrange(items: ImageData[], idArrangement: string[]): ImageData[] { 36 | return items.sort((a: ImageData, b: ImageData) => { 37 | return idArrangement.indexOf(a.id) - idArrangement.indexOf(b.id) 38 | }) 39 | } 40 | 41 | public static loadMissingImages(images: ImageData[]): Promise { 42 | return new Promise((resolve, reject) => { 43 | const missingImages = images.filter((i: ImageData) => !i.loadStatus); 44 | const missingImagesFiles = missingImages.map((i: ImageData) => i.fileData); 45 | FileUtil.loadImages(missingImagesFiles) 46 | .then((htmlImageElements:HTMLImageElement[]) => { 47 | ImageRepository.storeImages(missingImages.map((i: ImageData) => i.id), htmlImageElements); 48 | resolve() 49 | }) 50 | .catch((error: Error) => reject(error)); 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/utils/ImageUtil.ts: -------------------------------------------------------------------------------- 1 | import {ISize} from "../interfaces/ISize"; 2 | 3 | export class ImageUtil { 4 | public static getSize(image: HTMLImageElement): ISize { 5 | if (!image) return null; 6 | return { 7 | width: image.width, 8 | height: image.height 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/utils/LabelUtil.ts: -------------------------------------------------------------------------------- 1 | import {Annotation, LabelName, LabelPoint, LabelPolygon, LabelRect} from '../store/labels/types'; 2 | import { v4 as uuidv4 } from 'uuid'; 3 | import {find} from 'lodash'; 4 | import {IRect} from '../interfaces/IRect'; 5 | import {LabelStatus} from '../data/enums/LabelStatus'; 6 | import {IPoint} from '../interfaces/IPoint'; 7 | import { sample } from 'lodash'; 8 | import {Settings} from '../settings/Settings'; 9 | 10 | export class LabelUtil { 11 | public static createLabelName(name: string): LabelName { 12 | return { 13 | id: uuidv4(), 14 | name, 15 | color: sample(Settings.LABEL_COLORS_PALETTE) 16 | } 17 | } 18 | 19 | public static createLabelRect(labelId: string, rect: IRect): LabelRect { 20 | return { 21 | id: uuidv4(), 22 | labelId, 23 | rect, 24 | isVisible: true, 25 | isCreatedByAI: false, 26 | status: LabelStatus.ACCEPTED, 27 | suggestedLabel: null 28 | } 29 | } 30 | 31 | public static createLabelPolygon(labelId: string, vertices: IPoint[]): LabelPolygon { 32 | return { 33 | id: uuidv4(), 34 | labelId, 35 | vertices, 36 | isVisible: true 37 | } 38 | } 39 | 40 | public static createLabelPoint(labelId: string, point: IPoint): LabelPoint { 41 | return { 42 | id: uuidv4(), 43 | labelId, 44 | point, 45 | isVisible: true, 46 | isCreatedByAI: false, 47 | status: LabelStatus.ACCEPTED, 48 | suggestedLabel: null 49 | } 50 | } 51 | 52 | public static toggleAnnotationVisibility(annotation: AnnotationType): AnnotationType { 53 | return { 54 | ...annotation, 55 | isVisible: !annotation.isVisible 56 | } 57 | } 58 | 59 | public static labelNamesIdsDiff(oldLabelNames: LabelName[], newLabelNames: LabelName[]): string[] { 60 | return oldLabelNames.reduce((missingIds: string[], labelName: LabelName) => { 61 | if (!find(newLabelNames, { 'id': labelName.id })) { 62 | missingIds.push(labelName.id); 63 | } 64 | return missingIds 65 | }, []) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/utils/LineUtil.ts: -------------------------------------------------------------------------------- 1 | import {ILine} from "../interfaces/ILine"; 2 | import {IPoint} from "../interfaces/IPoint"; 3 | 4 | export class LineUtil { 5 | public static getDistanceFromLine(l: ILine, p: IPoint): number { 6 | if (l.start.x !== l.end.x || l.start.y !== l.end.y) { 7 | const nom: number = Math.abs((l.end.y - l.start.y) * p.x - (l.end.x - l.start.x) * p.y + l.end.x * l.start.y - l.end.y * l.start.x); 8 | const denom: number = Math.sqrt(Math.pow(l.end.y - l.start.y, 2) + Math.pow(l.end.x - l.start.x, 2)); 9 | return nom / denom; 10 | } 11 | return null; 12 | } 13 | 14 | public static getCenter(l: ILine): IPoint { 15 | return { 16 | x: (l.start.x + l.end.x) / 2, 17 | y: (l.start.y + l.end.y) / 2 18 | } 19 | } 20 | 21 | public static getPoints(l: ILine): IPoint[] { 22 | return [l.start, l.end] 23 | } 24 | } -------------------------------------------------------------------------------- /src/utils/MouseEventUtil.ts: -------------------------------------------------------------------------------- 1 | import {EventType} from "../data/enums/EventType"; 2 | 3 | export class MouseEventUtil { 4 | public static getEventType(event: Event): EventType | null { 5 | if (!event) return null; 6 | 7 | switch (event.type) { 8 | case EventType.MOUSE_DOWN: 9 | return EventType.MOUSE_DOWN; 10 | case EventType.MOUSE_UP: 11 | return EventType.MOUSE_UP; 12 | case EventType.MOUSE_MOVE: 13 | return EventType.MOUSE_MOVE; 14 | default: 15 | return null; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/utils/NotificationUtil.ts: -------------------------------------------------------------------------------- 1 | import {INotification} from '../store/notifications/types'; 2 | import {v4 as uuidv4} from 'uuid'; 3 | import {NotificationType} from '../data/enums/NotificationType'; 4 | import {NotificationContent} from "../data/info/NotificationsData"; 5 | 6 | export class NotificationUtil { 7 | public static createErrorNotification(content: NotificationContent): INotification { 8 | return { 9 | id: uuidv4(), 10 | type: NotificationType.ERROR, 11 | header: content.header, 12 | description: content.description 13 | } 14 | } 15 | 16 | public static createMessageNotification(content: NotificationContent): INotification { 17 | return { 18 | id: uuidv4(), 19 | type: NotificationType.MESSAGE, 20 | header: content.header, 21 | description: content.description 22 | } 23 | } 24 | 25 | public static createWarningNotification(content: NotificationContent): INotification { 26 | return { 27 | id: uuidv4(), 28 | type: NotificationType.WARNING, 29 | header: content.header, 30 | description: content.description 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/utils/NumberUtil.ts: -------------------------------------------------------------------------------- 1 | export class NumberUtil { 2 | public static snapValueToRange(value: number, min: number, max: number): number { 3 | if (value < min) 4 | return min; 5 | if (value > max) 6 | return max; 7 | return value; 8 | } 9 | 10 | public static isValueInRange(value: number, min: number, max: number): boolean { 11 | return value >= min && value <= max; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/PlatformUtil.ts: -------------------------------------------------------------------------------- 1 | import {MobileDeviceData} from "../data/MobileDeviceData"; 2 | import MobileDetect from 'mobile-detect' 3 | 4 | export class PlatformUtil { 5 | public static getMobileDeviceData(userAgent: string): MobileDeviceData { 6 | const mobileDetect = new MobileDetect(userAgent); 7 | return { 8 | manufacturer: mobileDetect.mobile(), 9 | browser: mobileDetect.userAgent(), 10 | os: mobileDetect.os() 11 | } 12 | } 13 | 14 | public static isMac(userAgent: string): boolean { 15 | return !!userAgent.toLowerCase().match("mac"); 16 | } 17 | 18 | public static isSafari(userAgent: string): boolean { 19 | return !!userAgent.toLowerCase().match("safari"); 20 | } 21 | 22 | public static isFirefox(userAgent: string): boolean { 23 | return !!userAgent.toLowerCase().match("firefox"); 24 | } 25 | } -------------------------------------------------------------------------------- /src/utils/PointUtil.ts: -------------------------------------------------------------------------------- 1 | import {IPoint} from '../interfaces/IPoint'; 2 | 3 | export class PointUtil { 4 | public static equals(p1: IPoint, p2: IPoint): boolean { 5 | return p1.x === p2.x && p1.y === p2.y; 6 | } 7 | 8 | public static add(p1: IPoint, p2: IPoint): IPoint { 9 | return { 10 | x: p1.x + p2.x, 11 | y: p1.y + p2.y 12 | } 13 | } 14 | 15 | public static subtract(p1: IPoint, p2: IPoint): IPoint { 16 | return { 17 | x: p1.x - p2.x, 18 | y: p1.y - p2.y 19 | } 20 | } 21 | 22 | public static multiply(p1: IPoint, factor: number) { 23 | return { 24 | x: p1.x * factor, 25 | y: p1.y * factor 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/utils/PolygonUtil.ts: -------------------------------------------------------------------------------- 1 | import {IPoint} from '../interfaces/IPoint'; 2 | import {ILine} from '../interfaces/ILine'; 3 | 4 | export class PolygonUtil { 5 | public static getEdges(vertices: IPoint[], closed: boolean = true): ILine[] { 6 | const points: IPoint[] = closed ? vertices.concat(vertices[0]) : vertices; 7 | const lines: ILine[] = []; 8 | for (let i = 0; i < points.length - 1; i++) { 9 | lines.push({start: points[i], end: points[i + 1]}) 10 | } 11 | return lines; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/SizeUtil.ts: -------------------------------------------------------------------------------- 1 | import {ISize} from "../interfaces/ISize"; 2 | 3 | export class SizeUtil { 4 | public static scale(size: ISize, scale: number): ISize { 5 | return { 6 | width: size.width * scale, 7 | height: size.height * scale 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/utils/UnitUtil.ts: -------------------------------------------------------------------------------- 1 | export class UnitUtil { 2 | 3 | public static deg2rad(angleDeg:number) { 4 | return(angleDeg * Math.PI/180); 5 | } 6 | 7 | public static rad2deg(angleRad:number) { 8 | return(angleRad * 180/Math.PI); 9 | } 10 | } -------------------------------------------------------------------------------- /src/utils/VirtualListUtil.ts: -------------------------------------------------------------------------------- 1 | import {ISize} from "../interfaces/ISize"; 2 | import {IPoint} from "../interfaces/IPoint"; 3 | 4 | export class VirtualListUtil { 5 | public static calculateGridSize(listSize: ISize, childSize: ISize, childCount: number): ISize { 6 | const columnCount: number = Math.floor(listSize.width / childSize.width); 7 | const rowCount: number = Math.ceil(childCount / columnCount); 8 | return {width: columnCount, height: rowCount}; 9 | } 10 | 11 | public static calculateContentSize(listSize: ISize, childSize: ISize, gridSize: ISize): ISize { 12 | const sizeFromGrid:ISize = { 13 | width: childSize.width * gridSize.width, 14 | height: childSize.height * gridSize.height 15 | }; 16 | 17 | return { 18 | width: Math.max(listSize.width, sizeFromGrid.width), 19 | height: sizeFromGrid.height 20 | } 21 | } 22 | 23 | public static calculateAnchorPoints(listSize: ISize, childSize: ISize, childCount: number): IPoint[] { 24 | const gridSize: ISize = VirtualListUtil.calculateGridSize(listSize, childSize, childCount); 25 | const contentWrapperSize: ISize = VirtualListUtil.calculateContentSize(listSize, childSize, gridSize); 26 | const horizontalMargin = (contentWrapperSize.width - gridSize.width * childSize.width) / (gridSize.width + 1); 27 | 28 | const anchors = []; 29 | for (let i = 0; i < childCount; i++) { 30 | const rowCount: number = Math.floor(i / gridSize.width); 31 | const columnCount: number = i % gridSize.width; 32 | 33 | const anchor: IPoint = { 34 | x: rowCount * horizontalMargin + columnCount * childSize.width, 35 | y: rowCount * childSize.height 36 | }; 37 | anchors.push(anchor); 38 | } 39 | return anchors; 40 | } 41 | } -------------------------------------------------------------------------------- /src/utils/XMLSanitizerUtil.ts: -------------------------------------------------------------------------------- 1 | export class XMLSanitizerUtil { 2 | public static sanitize(input: string): string { 3 | return input 4 | .replace('<', '<') 5 | .replace('>', '>') 6 | .replace('&', '&') 7 | .replace("'", ''') 8 | .replace("/", '/') 9 | } 10 | } -------------------------------------------------------------------------------- /src/utils/__tests__/DrawUtil.test.ts: -------------------------------------------------------------------------------- 1 | import {DrawUtil} from '../DrawUtil'; 2 | 3 | describe('DrawUtil hexToRGB method', () => { 4 | it('should return correct white rgb value when alpha is null', () => { 5 | // given 6 | const hex = '#ffffff' 7 | // when 8 | const result = DrawUtil.hexToRGB(hex) 9 | // then 10 | expect(result).toEqual('rgb(255, 255, 255)'); 11 | }) 12 | 13 | it('should return correct black rgb value when alpha is null', () => { 14 | // given 15 | const hex = '#000000' 16 | // when 17 | const result = DrawUtil.hexToRGB(hex) 18 | // then 19 | expect(result).toEqual('rgb(0, 0, 0)'); 20 | }) 21 | 22 | it('should return correct rgba value when alpha is null', () => { 23 | // given 24 | const hex = '#000000' 25 | const alpha = 0.5 26 | // when 27 | const result = DrawUtil.hexToRGB(hex, alpha) 28 | // then 29 | expect(result).toEqual('rgba(0, 0, 0, 0.5)'); 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /src/utils/__tests__/FileUtil.test.ts: -------------------------------------------------------------------------------- 1 | import {FileUtil} from "../FileUtil"; 2 | 3 | describe('FileUtil extractFileExtension method', () => { 4 | it('should return file extension', () => { 5 | // given 6 | const name: string = "labels.txt"; 7 | 8 | // when 9 | const result = FileUtil.extractFileExtension(name); 10 | 11 | // then 12 | const expectedResult = "txt"; 13 | expect(result).toEqual(expectedResult); 14 | }); 15 | 16 | it('should return file extension even with multiple dots', () => { 17 | // given 18 | const name: string = "custom.file-name.12.labels.txt"; 19 | 20 | // when 21 | const result = FileUtil.extractFileExtension(name); 22 | 23 | // then 24 | const expectedResult = "txt"; 25 | expect(result).toEqual(expectedResult); 26 | }); 27 | 28 | it('should return null', () => { 29 | // given 30 | const name: string = "labels"; 31 | 32 | // when 33 | const result = FileUtil.extractFileExtension(name); 34 | 35 | // then 36 | const expectedResult = null; 37 | expect(result).toEqual(expectedResult); 38 | }); 39 | }); 40 | 41 | describe('FileUtil extractFileName method', () => { 42 | it('should return file name', () => { 43 | // given 44 | const name: string = "labels.txt"; 45 | 46 | // when 47 | const result = FileUtil.extractFileName(name); 48 | 49 | // then 50 | const expectedResult = "labels"; 51 | expect(result).toEqual(expectedResult); 52 | }); 53 | 54 | it('should return file name even with multiple dots', () => { 55 | // given 56 | const name: string = "custom.file-name.12.labels.txt"; 57 | 58 | // when 59 | const result = FileUtil.extractFileName(name); 60 | 61 | // then 62 | const expectedResult = "custom.file-name.12.labels"; 63 | expect(result).toEqual(expectedResult); 64 | }); 65 | }); -------------------------------------------------------------------------------- /src/utils/__tests__/ImageDataUtil.test.ts: -------------------------------------------------------------------------------- 1 | import { ImageData } from '../../store/labels/types'; 2 | import { v4 as uuidv4 } from 'uuid'; 3 | import { LabelUtil } from '../LabelUtil'; 4 | import { ImageDataUtil } from '../ImageDataUtil'; 5 | import { AcceptedFileType } from '../../data/enums/AcceptedFileType'; 6 | 7 | 8 | const getDummyImageData = (id: string): ImageData => { 9 | return { 10 | id, 11 | fileData: new File([''], 'filename.txt', { type: AcceptedFileType.TEXT }), 12 | loadStatus: true, 13 | labelRects: [], 14 | labelPoints: [], 15 | labelLines: [], 16 | labelPolygons: [], 17 | labelNameIds: [], 18 | isVisitedByYOLOObjectDetector: false, 19 | isVisitedBySSDObjectDetector: false, 20 | isVisitedByPoseDetector: false, 21 | isVisitedByRoboflowAPI: false 22 | }; 23 | }; 24 | 25 | 26 | describe('ImageDataUtil cleanAnnotation method', () => { 27 | it('should return new ImageData object without annotations', () => { 28 | // given 29 | const item: ImageData = getDummyImageData(uuidv4()); 30 | item.labelRects = [ 31 | LabelUtil.createLabelRect('label-id', { x: 1, y: 1, width: 1, height: 1 }), 32 | LabelUtil.createLabelRect('label-id', { x: 1, y: 1, width: 1, height: 1 }) 33 | ]; 34 | 35 | // when 36 | const result = ImageDataUtil.cleanAnnotations(item); 37 | 38 | // then 39 | expect(result.labelRects.length).toEqual(0); 40 | expect(item.labelRects.length).toEqual(2); 41 | }); 42 | }); 43 | 44 | describe('ImageDataUtil arrange method', () => { 45 | it('should return new array with correctly arranged ImageData objects', () => { 46 | // given 47 | const idA = uuidv4(), idB = uuidv4(), idC = uuidv4(), idD = uuidv4(); 48 | const givenIdArrangement = [idD, idA, idB, idC]; 49 | const expectedIdArrangement = [idA, idB, idC, idD]; 50 | const items = givenIdArrangement.map((id: string) => getDummyImageData(id)); 51 | 52 | // when 53 | const result = ImageDataUtil.arrange(items, expectedIdArrangement); 54 | const resultIdArrangement = result.map((item: ImageData) => item.id); 55 | 56 | // then 57 | expect(JSON.stringify(expectedIdArrangement)).toBe(JSON.stringify(resultIdArrangement)); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /src/utils/__tests__/NumberUtil.test.ts: -------------------------------------------------------------------------------- 1 | import {NumberUtil} from "../NumberUtil"; 2 | 3 | describe('NumberUtil snapValueToRange method', () => { 4 | it('should return value rounded to the upper bound', () => { 5 | const result: number = NumberUtil.snapValueToRange(1.0000005, 0, 1) 6 | expect(result).toBe(1); 7 | }); 8 | it('should return value rounded to the lower bound', () => { 9 | const result: number = NumberUtil.snapValueToRange(-0.0000005, 0, 1) 10 | expect(result).toBe(0); 11 | }); 12 | it('should return unmodified value', () => { 13 | const result: number = NumberUtil.snapValueToRange(0.5, 0, 1) 14 | expect(result).toBe(0.5); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/utils/__tests__/RectUtil.test.ts: -------------------------------------------------------------------------------- 1 | import {RectUtil} from '../RectUtil'; 2 | import {IRect} from '../../interfaces/IRect'; 3 | import {IPoint} from '../../interfaces/IPoint'; 4 | import {ISize} from '../../interfaces/ISize'; 5 | 6 | describe('RectUtil getRatio method', () => { 7 | it('should return correct value of rect ratio', () => { 8 | // given 9 | const rect: IRect = {x: 0, y: 0, width: 10, height: 5}; 10 | // when 11 | const result = RectUtil.getRatio(rect) 12 | // then 13 | expect(result).toBe(2); 14 | }); 15 | 16 | it('should return null', () => { 17 | expect(RectUtil.getRatio(null)).toBe(null); 18 | }); 19 | }); 20 | 21 | describe('RectUtil getCenter method', () => { 22 | it('should return correct center value', () => { 23 | // given 24 | const rect: IRect = {x: 0, y: 0, width: 10, height: 20}; 25 | const expectedResult: IPoint = {x: 5, y: 10}; 26 | // when 27 | const result = RectUtil.getCenter(rect) 28 | // then 29 | expect(result).toMatchObject(expectedResult); 30 | }) 31 | }) 32 | 33 | describe('RectUtil getSize method', () => { 34 | it('should return correct size value', () => { 35 | // given 36 | const rect: IRect = {x: 0, y: 0, width: 10, height: 20}; 37 | const expectedSize: ISize = {width: 10, height: 20}; 38 | // when 39 | const result = RectUtil.getSize(rect) 40 | // then 41 | expect(result).toMatchObject(expectedSize); 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /src/views/Common/ImageButton/ImageButton.scss: -------------------------------------------------------------------------------- 1 | .ImageButton { 2 | display: flex; 3 | flex-direction: row; 4 | flex-wrap: nowrap; 5 | justify-content: center; 6 | align-content: center; 7 | align-items: center; 8 | border-radius: 50%; 9 | box-sizing: border-box; 10 | transition: background-color 0.7s ease; 11 | margin: 5px 2px; 12 | 13 | > img { 14 | user-select: none; 15 | } 16 | 17 | &:hover ~ .Cursor { 18 | width: 20px; 19 | height: 20px; 20 | border-color: transparent; 21 | background-color: rgba(0, 0, 0, 0.2); 22 | } 23 | } 24 | 25 | .ImageButton:not(.disabled):hover { 26 | cursor: pointer; 27 | } 28 | 29 | .ImageButton:hover { 30 | background-color: rgba(255, 255, 255, 0.3); 31 | } -------------------------------------------------------------------------------- /src/views/Common/ImageButton/ImageButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {ISize} from "../../../interfaces/ISize"; 3 | import './ImageButton.scss'; 4 | import classNames from "classnames"; 5 | import {LegacyRef} from "react"; 6 | 7 | export interface ImageButtonProps extends React.HTMLProps { 8 | buttonSize:ISize, 9 | padding?:number; 10 | image:string, 11 | imageAlt:string, 12 | href?:string 13 | onClick?:() => any; 14 | style?:React.CSSProperties 15 | isActive?:boolean; 16 | isDisabled?:boolean; 17 | externalClassName?:string; 18 | } 19 | 20 | export const ImageButton = React.forwardRef((props: ImageButtonProps, ref: LegacyRef) => { 21 | const {buttonSize, padding, image, imageAlt, href, onClick, style, isActive, isDisabled, externalClassName} = props; 22 | const imagePadding:number = !!padding ? padding : 10; 23 | 24 | const onClickHandler = (event: React.MouseEvent) => { 25 | event.stopPropagation(); 26 | !!onClick && onClick(); 27 | }; 28 | 29 | const buttonStyle:React.CSSProperties = { 30 | ...style, 31 | width: buttonSize.width, 32 | height: buttonSize.height 33 | }; 34 | 35 | const imageStyle:React.CSSProperties = { 36 | maxWidth: buttonSize.width - imagePadding, 37 | maxHeight: buttonSize.height - imagePadding 38 | }; 39 | 40 | const getClassName = () => { 41 | return classNames( 42 | "ImageButton", 43 | externalClassName, 44 | { 45 | "active": isActive, 46 | "disabled": isDisabled, 47 | } 48 | ); 49 | }; 50 | 51 | return( 52 |
58 | {!!href && 59 | {imageAlt} 65 | } 66 | {!href && {imageAlt}} 72 |
73 | ); 74 | }); 75 | -------------------------------------------------------------------------------- /src/views/Common/StyledTextField/StyledTextField.tsx: -------------------------------------------------------------------------------- 1 | import { TextField } from '@mui/material'; 2 | import { styled } from '@mui/system'; 3 | import { Settings } from '../../../settings/Settings'; 4 | 5 | 6 | export const StyledTextField = styled(TextField)({ 7 | '& .MuiInputBase-root': { 8 | color: 'white', 9 | }, 10 | '& label': { 11 | color: 'white', 12 | }, 13 | '& .MuiInput-underline:before': { 14 | borderBottomColor: 'white', 15 | }, 16 | '& .MuiInput-underline:hover:before': { 17 | borderBottomColor: 'white', 18 | }, 19 | '& label.Mui-focused': { 20 | color: Settings.SECONDARY_COLOR, 21 | }, 22 | '& .MuiInput-underline:after': { 23 | borderBottomColor: Settings.SECONDARY_COLOR, 24 | } 25 | }); -------------------------------------------------------------------------------- /src/views/Common/TextButton/TextButton.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .TextButton { 4 | display: block; 5 | cursor: pointer; 6 | user-select: none; 7 | line-height: 0; 8 | word-wrap: break-word; 9 | padding: 20px 30px; 10 | text-decoration: none; 11 | box-shadow: black 0 0 0 2px inset; 12 | color: black; 13 | margin: initial; 14 | border-radius: 2px; 15 | transition: 0.3s ease-in-out; 16 | background: transparent; 17 | font-weight: 700; 18 | 19 | > a { 20 | text-decoration: none; 21 | color: black; 22 | } 23 | 24 | &:hover { 25 | box-shadow: inherit; 26 | color: white; 27 | background-color: black; 28 | 29 | > a { 30 | text-decoration: none; 31 | color: white; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/views/Common/TextButton/TextButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import './TextButton.scss'; 3 | import classNames from 'classnames'; 4 | 5 | interface IProps { 6 | key?:string; 7 | label:string; 8 | onClick?:() => any; 9 | style?:React.CSSProperties; 10 | isActive?:boolean; 11 | isDisabled?:boolean; 12 | externalClassName?:string; 13 | } 14 | 15 | export const TextButton = (props:IProps) => { 16 | const { key, label, onClick, style, isActive, isDisabled, externalClassName} = props; 17 | 18 | const getClassName = () => { 19 | return classNames( 20 | 'TextButton', 21 | externalClassName, 22 | { 23 | 'active': isActive, 24 | 'disabled': isDisabled 25 | } 26 | ); 27 | }; 28 | 29 | const onClickHandler = (event: React.MouseEvent) => { 30 | event.stopPropagation(); 31 | if (onClick) { 32 | onClick(); 33 | } 34 | }; 35 | 36 | return( 37 |
43 | {label} 44 |
45 | ) 46 | }; 47 | -------------------------------------------------------------------------------- /src/views/Common/TextInput/TextInput.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | $secondary-color: white; 4 | $main-color: transparent; 5 | $width: 300px; 6 | 7 | * { 8 | box-sizing: border-box; 9 | } 10 | 11 | .TextInput { 12 | height: calc($width/6); 13 | overflow: hidden; 14 | position: relative; 15 | } 16 | 17 | label { 18 | position: absolute; 19 | top: calc($width/15); 20 | color: white; 21 | font: 400 calc($width/15); 22 | cursor: text; 23 | transition: .25s ease; 24 | } 25 | 26 | input { 27 | display: block; 28 | width: 100%; 29 | padding-top: calc($width/15); 30 | border: none; 31 | border-radius: 0; // For iOS 32 | color: white; 33 | background: $main-color; 34 | font-size: calc($width/20); 35 | transition: .3s ease; 36 | &:valid { 37 | ~label { 38 | top: 0; 39 | font: 700 calc($width/25); 40 | color: white; 41 | } 42 | } 43 | &:focus { 44 | outline: none; 45 | ~label { 46 | top: 0; 47 | font: 700 calc($width/25); 48 | color: $secondaryColor; // fallback if new css variables are not supported by browser 49 | color: var(--leading-color); 50 | } 51 | 52 | ~ .Bar { 53 | height: 2px; 54 | } 55 | } 56 | 57 | // Stop Chrome's hideous pale yellow background on auto-fill 58 | 59 | &:-webkit-autofill { 60 | -webkit-box-shadow: 0 0 0px 1000px $main-color inset; 61 | -webkit-text-fill-color: white !important; 62 | } 63 | } 64 | 65 | .Bar { 66 | background: white; 67 | content: ''; 68 | width: $width; 69 | height: 1px; 70 | transition: .3s ease; 71 | position: relative; 72 | } 73 | 74 | ::selection { 75 | background: rgba($secondaryColor, .3); 76 | background: rgba(var(--leading-color), .3); 77 | } 78 | -------------------------------------------------------------------------------- /src/views/Common/TextInput/TextInput.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './TextInput.scss'; 3 | 4 | interface IProps { 5 | label?: string; 6 | isPassword: boolean; 7 | onChange?: (event: React.ChangeEvent) => any; 8 | onFocus?: (event: React.FocusEvent) => any; 9 | inputStyle?: React.CSSProperties; 10 | labelStyle?: React.CSSProperties; 11 | barStyle?: React.CSSProperties; 12 | value?: string; 13 | onKeyUp?: (event: React.KeyboardEvent) => void; 14 | } 15 | 16 | const TextInput = (props: IProps) => { 17 | 18 | const { 19 | label, 20 | isPassword, 21 | onChange, 22 | onFocus, 23 | inputStyle, 24 | labelStyle, 25 | barStyle, 26 | value, 27 | onKeyUp 28 | } = props; 29 | 30 | const getInputType = () => { 31 | return isPassword ? 'password' : 'text'; 32 | }; 33 | 34 | return ( 35 |
36 | 44 | {!!label && } 49 |
53 |
54 | ); 55 | }; 56 | 57 | export default TextInput; 58 | -------------------------------------------------------------------------------- /src/views/Common/UnderlineTextButton/UnderlineTextButton.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .UnderlineTextButton { 4 | position: relative; 5 | font-size: 14px; 6 | font-weight: 700; 7 | cursor: pointer; 8 | user-select: none; 9 | width: fit-content; 10 | line-height: 0; 11 | word-wrap: break-word; 12 | height: 3px; // <- adjust 13 | padding: 15px 0; // <- adjust 14 | margin: 0 10px; 15 | text-decoration: none; 16 | color: white; 17 | 18 | &:hover, 19 | &.active { 20 | color: $secondaryColor; // fallback if new css variables are not supported by browser 21 | color: var(--leading-color); 22 | } 23 | 24 | &.over::before { 25 | background-color: $secondaryColor; // fallback if new css variables are not supported by browser 26 | background-color: var(--leading-color); 27 | width: 100%; 28 | position: absolute; 29 | top: 0; 30 | height: 2px; // <- adjust 31 | left: 0; 32 | z-index: 100; 33 | display: block; 34 | } 35 | 36 | &.under::after { 37 | background-color: $secondaryColor; // fallback if new css variables are not supported by browser 38 | background-color: var(--leading-color); 39 | width: 100%; 40 | position: absolute; 41 | bottom: 0; 42 | height: 2px; // <- adjust 43 | left: 0; 44 | z-index: 100; 45 | display: block; 46 | } 47 | 48 | &.over:hover::before, 49 | &.over.active::before { 50 | content: ''; 51 | } 52 | 53 | &.under:hover::after, 54 | &.under.active::after { 55 | content: ''; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/views/Common/UnderlineTextButton/UnderlineTextButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classNames from 'classnames' 3 | import './UnderlineTextButton.scss' 4 | 5 | interface IProps { 6 | under?: boolean 7 | over?: boolean 8 | active?: boolean 9 | key?: string 10 | label: string 11 | onClick?: () => any 12 | style?: React.CSSProperties 13 | } 14 | 15 | export const UnderlineTextButton = (props: IProps) => { 16 | const { under, over, active, key, label, onClick, style } = props; 17 | 18 | const getClassName = () => { 19 | return classNames('UnderlineTextButton', { 20 | under: under, 21 | over: over, 22 | active: active, 23 | }) 24 | }; 25 | 26 | return ( 27 |
33 | {label} 34 |
35 | ) 36 | }; 37 | -------------------------------------------------------------------------------- /src/views/EditorView/Editor/Editor.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .Editor { 4 | align-self: stretch; 5 | flex: 1; 6 | position: relative; 7 | 8 | .ViewPortContent { 9 | position: relative; 10 | 11 | .track-horizontal { 12 | cursor: none; 13 | } 14 | 15 | .track-vertical { 16 | cursor: none; 17 | } 18 | 19 | .ImageCanvas { 20 | position: absolute; 21 | top: 0; 22 | left: 0; 23 | cursor: none; 24 | 25 | &:hover { 26 | cursor: none; 27 | } 28 | } 29 | } 30 | 31 | .MousePositionIndicator { 32 | position: absolute; 33 | color: white; 34 | font-size: 12px; 35 | background-color: $darkThemeThirdColor; 36 | opacity: 0.6; 37 | padding: 5px; 38 | user-select: none; 39 | pointer-events: none; 40 | z-index: 100; 41 | width: fit-content; 42 | } 43 | 44 | .Cursor { 45 | position: absolute; 46 | width: 6px; 47 | height: 6px; 48 | transition: width 0.05s ease-out, height 0.05s ease-out, background-color 0.05s ease-in; 49 | transform: translate(-50%, -50%); 50 | border-radius: 50%; 51 | pointer-events: none; 52 | border: 2px solid white; 53 | background-color: white; 54 | z-index: 100000; 55 | 56 | > img { 57 | position: absolute; 58 | max-width: 20px; 59 | max-height: 20px; 60 | filter: brightness(0) invert(1); 61 | bottom: calc(50% + 10px); 62 | left: calc(50% + 10px); 63 | display: none; 64 | user-select: none; 65 | } 66 | 67 | &.move, &.add, &.resize, &.close { 68 | width: 24px; 69 | height: 24px; 70 | background-color: transparent; 71 | } 72 | 73 | &.grabbing { 74 | width: 18px; 75 | height: 18px; 76 | background-color: rgba(255, 255, 255, 0.5); 77 | border: 2px solid transparent; 78 | } 79 | 80 | &.move, &.add, &.resize, &.close, &.cancel, &.grab, &.grabbing { 81 | > img { 82 | display: block; 83 | } 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /src/views/EditorView/EditorBottomNavigationBar/EditorBottomNavigationBar.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .EditorBottomNavigationBar { 4 | align-self: stretch; 5 | height: $editorBottomNavigationBarHeight; 6 | border-top: solid 1px $darkThemeFirstColor; 7 | background-color: $darkThemeSecondColor; 8 | user-select: none; 9 | 10 | display: flex; 11 | flex-direction: row; 12 | flex-wrap: nowrap; 13 | justify-content: center; 14 | align-items: center; 15 | align-content: center; 16 | 17 | &.with-context { 18 | background-color: $darkThemeForthColor; 19 | } 20 | 21 | .ImageButton { 22 | transition: transform 0.3s; 23 | 24 | img { 25 | filter: brightness(0) invert(1); 26 | user-select: none; 27 | } 28 | 29 | &:hover { 30 | background-color: transparent; 31 | } 32 | 33 | &:not(.disabled):hover { 34 | filter: brightness(35%) sepia(100%) hue-rotate(172deg) saturate(2000%); // fallback if new css variables are not supported by browser 35 | filter: brightness(35%) sepia(100%) hue-rotate(var(--hue-value)) saturate(2000%); 36 | &.right { 37 | transform: translate(2px); 38 | } 39 | &.left { 40 | transform: translate(-2px); 41 | } 42 | } 43 | 44 | &.disabled { 45 | img { 46 | filter: invert(1) opacity(25%); 47 | user-select: none; 48 | } 49 | } 50 | } 51 | 52 | .CurrentImageName { 53 | min-width: 200px; 54 | padding: 0 20px; 55 | color: white; 56 | font-size: 14px; 57 | 58 | display: flex; 59 | flex-direction: row; 60 | flex-wrap: nowrap; 61 | justify-content: center; 62 | align-items: center; 63 | align-content: center; 64 | } 65 | 66 | .CurrentImageCount { 67 | min-width: 80px; 68 | padding: 0 20px; 69 | color: white; 70 | 71 | display: flex; 72 | flex-direction: row; 73 | flex-wrap: nowrap; 74 | justify-content: center; 75 | align-items: center; 76 | align-content: center; 77 | } 78 | } -------------------------------------------------------------------------------- /src/views/EditorView/EditorContainer/EditorContainer.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .EditorContainer { 4 | align-self: stretch; 5 | flex: 0 0 calc(100% - #{$topNavigationBarHeight}); 6 | height: calc(100% - #{$topNavigationBarHeight}); 7 | 8 | display: flex; 9 | flex-direction: row; 10 | flex-wrap: nowrap; 11 | justify-content: flex-start; 12 | align-items: center; 13 | align-content: flex-start; 14 | 15 | .EditorWrapper { 16 | align-self: stretch; 17 | flex: 1; 18 | min-width: 0; 19 | display: flex; 20 | flex-direction: column; 21 | flex-wrap: nowrap; 22 | justify-content: center; 23 | align-items: center; 24 | align-content: center; 25 | } 26 | } -------------------------------------------------------------------------------- /src/views/EditorView/EditorTopNavigationBar/EditorTopNavigationBar.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .EditorTopNavigationBar { 4 | align-self: stretch; 5 | height: $editorTopNavigationBarHeight; 6 | border-bottom: solid 1px $darkThemeFirstColor; 7 | background-color: $darkThemeSecondColor; 8 | user-select: none; 9 | 10 | display: flex; 11 | flex-direction: row; 12 | flex-wrap: nowrap; 13 | justify-content: flex-start; 14 | align-items: center; 15 | align-content: flex-start; 16 | 17 | padding: 0 5px; 18 | 19 | &.with-context { 20 | background-color: $darkThemeForthColor; 21 | } 22 | 23 | .ButtonWrapper { 24 | display: flex; 25 | flex-direction: row; 26 | flex-wrap: nowrap; 27 | justify-content: flex-start; 28 | align-items: center; 29 | align-content: flex-start; 30 | 31 | &:not(:last-child) { 32 | padding-right: 5px; 33 | margin-right: 5px; 34 | border-right: solid 1px $darkThemeFirstColor; 35 | } 36 | } 37 | 38 | .ImageButton { 39 | transition: transform 0.3s; 40 | margin: 0 2px; 41 | 42 | img { 43 | filter: brightness(0) invert(1); 44 | } 45 | 46 | &:hover { 47 | border-radius: 3px; 48 | background-color: rgba(0, 0, 0, 0.2); 49 | } 50 | 51 | &.active { 52 | border-radius: 3px; 53 | background-color: rgba(0, 0, 0, 0.4); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/views/EditorView/EditorView.scss: -------------------------------------------------------------------------------- 1 | @import '../../settings/_Settings'; 2 | 3 | .EditorView { 4 | position: absolute; 5 | height: 100vh; 6 | width: 100vw; 7 | margin: 0; 8 | padding: 0; 9 | 10 | background-color: $darkThemeThirdColor; 11 | 12 | display: flex; 13 | flex-direction: column; 14 | flex-wrap: nowrap; 15 | justify-content: flex-start; 16 | align-items: center; 17 | align-content: flex-start; 18 | 19 | &.withPopup { 20 | filter: blur(2px) brightness(0.3); 21 | pointer-events: none; 22 | } 23 | } -------------------------------------------------------------------------------- /src/views/EditorView/EditorView.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './EditorView.scss'; 3 | import EditorContainer from './EditorContainer/EditorContainer'; 4 | import {PopupWindowType} from '../../data/enums/PopupWindowType'; 5 | import {AppState} from '../../store'; 6 | import {connect} from 'react-redux'; 7 | import classNames from 'classnames'; 8 | import TopNavigationBar from './TopNavigationBar/TopNavigationBar'; 9 | 10 | interface IProps { 11 | activePopupType: PopupWindowType; 12 | } 13 | 14 | const EditorView: React.FC = ({activePopupType}) => { 15 | 16 | const getClassName = () => { 17 | return classNames( 18 | 'EditorView', 19 | { 20 | 'withPopup': !!activePopupType 21 | } 22 | ); 23 | }; 24 | 25 | return ( 26 |
30 | 31 | 32 |
33 | ); 34 | }; 35 | 36 | const mapStateToProps = (state: AppState) => ({ 37 | activePopupType: state.general.activePopupType 38 | }); 39 | 40 | export default connect( 41 | mapStateToProps 42 | )(EditorView); 43 | -------------------------------------------------------------------------------- /src/views/EditorView/FeatureInProgress/FeatureInProgress.scss: -------------------------------------------------------------------------------- 1 | .FeatureInProgress { 2 | display: flex; 3 | flex-direction: column; 4 | flex-wrap: nowrap; 5 | justify-content: center; 6 | align-items: center; 7 | align-content: center; 8 | text-align: center; 9 | width: 150px; 10 | color: white; 11 | font-size: 16px; 12 | user-select: none; 13 | 14 | > img { 15 | filter: brightness(0) invert(1); 16 | max-width: 100px; 17 | max-height: 100px; 18 | user-select: none; 19 | margin: 20px; 20 | } 21 | 22 | > p { 23 | &.extraBold { 24 | font-size: 18px; 25 | font-weight: 600; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/views/EditorView/FeatureInProgress/FeatureInProgress.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './FeatureInProgress.scss'; 3 | 4 | export const FeatureInProgress: React.FC = () => { 5 | return( 6 |
9 | {"take_off"} 14 |

new feature
coming soon...

15 |
16 | ) 17 | }; -------------------------------------------------------------------------------- /src/views/EditorView/LabelControlPanel/LabelControlPanel.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .LabelControlPanel { 4 | position: absolute; 5 | cursor: pointer; 6 | 7 | display: flex; 8 | flex-direction: row; 9 | flex-wrap: nowrap; 10 | justify-content: center; 11 | align-items: center; 12 | align-content: center; 13 | 14 | .ImageButton { 15 | transition: transform 0.3s; 16 | 17 | img { 18 | filter: brightness(0) invert(1); 19 | user-select: none; 20 | } 21 | 22 | &:hover { 23 | background-color: transparent; 24 | } 25 | 26 | &:not(.disabled):hover { 27 | filter: brightness(35%) sepia(100%) hue-rotate(172deg) saturate(2000%); // fallback if new css variables are not supported by browser 28 | filter: brightness(35%) sepia(100%) hue-rotate(var(--hue-value)) saturate(2000%); 29 | 30 | &.right { 31 | transform: translate(2px); 32 | } 33 | 34 | &.left { 35 | transform: translate(-2px); 36 | } 37 | } 38 | 39 | &.disabled { 40 | img { 41 | filter: invert(1) opacity(25%); 42 | user-select: none; 43 | } 44 | } 45 | } 46 | 47 | .SuggestedLabel { 48 | align-self: stretch; 49 | 50 | justify-content: center; 51 | align-items: center; 52 | align-content: center; 53 | 54 | padding: 0 10px 0 15px; 55 | color: white; 56 | } 57 | 58 | $randomNumber: random(5); 59 | 60 | &.is-active { 61 | background-color: $darkThemeSecondColor; 62 | border: solid 1px $primaryColor; 63 | animation: brColor 5s infinite linear; 64 | animation-delay: $randomNumber + s; 65 | transform: translate(-15px, -15px); 66 | border-radius: 3px; 67 | min-width: 30px; 68 | height: 30px; 69 | z-index: 1000; 70 | } 71 | 72 | &:not(.is-active) { 73 | background-color: $primaryColor; 74 | animation: bgColor 5s infinite linear; 75 | animation-delay: $randomNumber + s; 76 | transform: translate(-6px, -6px); 77 | border-radius: 6px; 78 | width: 12px; 79 | height: 12px; 80 | z-index: 1; 81 | } 82 | } 83 | 84 | @keyframes bgColor { 85 | 50% { 86 | background-color: $secondaryColor; 87 | } 88 | 100% { 89 | background-color: $primaryColor; 90 | } 91 | } 92 | 93 | @keyframes brColor { 94 | 50% { 95 | border-color: $secondaryColor; 96 | } 97 | 100% { 98 | border-color: $primaryColor; 99 | } 100 | } -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/EmptyLabelList/EmptyLabelList.scss: -------------------------------------------------------------------------------- 1 | .EmptyLabelList { 2 | display: flex; 3 | flex-direction: column; 4 | flex-wrap: nowrap; 5 | justify-content: center; 6 | align-items: center; 7 | align-content: center; 8 | text-align: center; 9 | width: 150px; 10 | color: white; 11 | font-size: 16px; 12 | user-select: none; 13 | 14 | > img { 15 | filter: brightness(0) invert(1); 16 | max-width: 80px; 17 | max-height: 80px; 18 | margin-bottom: 20px; 19 | user-select: none; 20 | } 21 | 22 | > p { 23 | &.extraBold { 24 | font-size: 16px; 25 | font-weight: 600; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/EmptyLabelList/EmptyLabelList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './EmptyLabelList.scss'; 3 | import {AppState} from "../../../../store"; 4 | import {connect} from "react-redux"; 5 | 6 | interface IProps { 7 | firstLabelCreatedFlag: boolean; 8 | labelBefore: string; 9 | labelAfter: string; 10 | } 11 | 12 | const EmptyLabelList: React.FC = ({firstLabelCreatedFlag, labelBefore, labelAfter}) => { 13 | const before = <> 14 | {"lets_start"} 19 |

{labelBefore}

20 | ; 21 | 22 | const after = <> 23 | {"no_labels"} 28 |

{labelAfter}

29 | ; 30 | 31 | return(
32 | {!firstLabelCreatedFlag ? before : after} 33 |
) 34 | }; 35 | 36 | const mapDispatchToProps = {}; 37 | 38 | const mapStateToProps = (state: AppState) => ({ 39 | firstLabelCreatedFlag: state.labels.firstLabelCreatedFlag 40 | }); 41 | 42 | export default connect( 43 | mapStateToProps, 44 | mapDispatchToProps 45 | )(EmptyLabelList); -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/ImagePreview/ImagePreview.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../settings/Settings'; 2 | 3 | .ImagePreview { 4 | position: absolute; 5 | cursor: pointer; 6 | 7 | display: flex; 8 | flex-direction: row; 9 | flex-wrap: nowrap; 10 | justify-content: center; 11 | align-items: center; 12 | align-content: center; 13 | 14 | border-bottom: solid 1px $darkThemeFirstColor; 15 | &:nth-child(odd) { 16 | border-right: solid 1px $darkThemeFirstColor; 17 | } 18 | 19 | .Foreground { 20 | position: absolute; 21 | z-index: 100; 22 | transition: transform 0.3s; 23 | 24 | .Image { 25 | position: absolute; 26 | left: 0; 27 | top: 0; 28 | border: solid 1px $darkThemeSecondColor; 29 | user-select: none; 30 | } 31 | 32 | .CheckBox { 33 | position: absolute; 34 | z-index: 1000; 35 | max-width: 20px; 36 | max-height: 20px; 37 | bottom: -10px; 38 | left: -10px; 39 | filter: invert(1) brightness(35%) sepia(100%) hue-rotate(172deg) saturate(2000%); // fallback if new css variables are not supported by browser 40 | filter: invert(1) brightness(35%) sepia(100%) hue-rotate(var(--hue-value)) saturate(2000%); 41 | } 42 | } 43 | 44 | .Background { 45 | position: absolute; 46 | z-index: 0; 47 | background-color: $darkThemeThirdColor; 48 | transition: background-color 0.3s ease-in-out, transform 0.3s; 49 | } 50 | 51 | &.selected { 52 | .Foreground { 53 | transform: translate(-2px, 2px); 54 | } 55 | .Background { 56 | transform: translate(2px, -2px); 57 | background-color: $secondaryColor; // fallback if new css variables are not supported by browser 58 | background-color: var(--leading-color); 59 | } 60 | } 61 | 62 | &:hover { 63 | .Foreground { 64 | transform: translate(-2px, 2px); 65 | } 66 | .Background { 67 | transform: translate(2px, -2px); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/ImagesList/ImagesList.scss: -------------------------------------------------------------------------------- 1 | .ImagesList { 2 | align-self: stretch; 3 | flex: 1; 4 | overflow: hidden; 5 | } -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/LabelsToolkit/LabelsToolkit.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../settings/Settings'; 2 | 3 | .LabelsToolkit { 4 | align-self: stretch; 5 | flex: 1; 6 | overflow: hidden; 7 | 8 | display: flex; 9 | flex-direction: column; 10 | flex-wrap: nowrap; 11 | justify-content: flex-start; 12 | align-items: center; 13 | align-content: flex-start; 14 | 15 | .Header { 16 | position: relative; 17 | align-self: stretch; 18 | box-sizing: border-box; 19 | padding: 0 25px; 20 | color: white; 21 | font-size: 14px; 22 | user-select: none; 23 | cursor: pointer; 24 | 25 | display: flex; 26 | flex-direction: row; 27 | flex-wrap: nowrap; 28 | justify-content: space-between; 29 | align-items: center; 30 | align-content: space-between; 31 | 32 | .HeaderGroupWrapper { 33 | align-self: stretch; 34 | 35 | display: flex; 36 | flex-direction: row; 37 | flex-wrap: nowrap; 38 | justify-content: center; 39 | align-items: center; 40 | align-content: center; 41 | } 42 | 43 | .Marker { 44 | position: absolute; 45 | height: calc(100% - 4px); 46 | width: 5px; 47 | top: 2px; 48 | left: -5px; 49 | background-color: $darkThemeThirdColor; 50 | transition: background-color 0.3s ease-in-out, transform 0.3s; 51 | } 52 | 53 | .Ico { 54 | max-width: 20px; 55 | max-height: 20px; 56 | margin-right: 20px; 57 | filter: brightness(0) invert(1); 58 | } 59 | 60 | .Arrow { 61 | max-width: 12px; 62 | max-height: 12px; 63 | filter: brightness(0) invert(1); 64 | } 65 | 66 | &:hover { 67 | .Marker { 68 | transform: translate(5px); 69 | } 70 | } 71 | 72 | &.active { 73 | background-color: rgba(0, 0, 0, 0.1); 74 | .Marker { 75 | transform: translate(5px); 76 | background-color: $secondaryColor; // fallback if new css variables are not supported by browser 77 | background-color: var(--leading-color); 78 | } 79 | 80 | .Arrow { 81 | transform: rotate(180deg); 82 | } 83 | } 84 | } 85 | 86 | .Content { 87 | align-self: stretch; 88 | box-sizing: border-box; 89 | height: 0; 90 | transition: height 0.3s ease-in-out; 91 | overflow: hidden; 92 | 93 | display: flex; 94 | flex-direction: column; 95 | flex-wrap: nowrap; 96 | justify-content: center; 97 | align-items: center; 98 | align-content: center; 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/LineLabelsList/LineLabelsList.scss: -------------------------------------------------------------------------------- 1 | .LineLabelsList { 2 | display: flex; 3 | flex-direction: column; 4 | flex-wrap: nowrap; 5 | justify-content: center; 6 | align-items: center; 7 | align-content: center; 8 | 9 | .LineLabelsListContent { 10 | display: flex; 11 | flex-direction: column; 12 | flex-wrap: nowrap; 13 | justify-content: flex-start; 14 | align-items: center; 15 | align-content: flex-start; 16 | } 17 | } -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/PointLabelsList/PointLabelsList.scss: -------------------------------------------------------------------------------- 1 | .PointLabelsList { 2 | display: flex; 3 | flex-direction: column; 4 | flex-wrap: nowrap; 5 | justify-content: center; 6 | align-items: center; 7 | align-content: center; 8 | 9 | .PointLabelsListContent { 10 | display: flex; 11 | flex-direction: column; 12 | flex-wrap: nowrap; 13 | justify-content: flex-start; 14 | align-items: center; 15 | align-content: flex-start; 16 | } 17 | } -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/PolygonLabelsList/PolygonLabelsList.scss: -------------------------------------------------------------------------------- 1 | .PolygonLabelsList { 2 | display: flex; 3 | flex-direction: column; 4 | flex-wrap: nowrap; 5 | justify-content: center; 6 | align-items: center; 7 | align-content: center; 8 | 9 | .PolygonLabelsListContent { 10 | display: flex; 11 | flex-direction: column; 12 | flex-wrap: nowrap; 13 | justify-content: flex-start; 14 | align-items: center; 15 | align-content: flex-start; 16 | } 17 | } -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/RectLabelsList/RectLabelsList.scss: -------------------------------------------------------------------------------- 1 | .RectLabelsList { 2 | display: flex; 3 | flex-direction: column; 4 | flex-wrap: nowrap; 5 | justify-content: center; 6 | align-items: center; 7 | align-content: center; 8 | 9 | .RectLabelsListContent { 10 | display: flex; 11 | flex-direction: column; 12 | flex-wrap: nowrap; 13 | justify-content: flex-start; 14 | align-items: center; 15 | align-content: flex-start; 16 | } 17 | } -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/SideNavigationBar.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .SideNavigationBar { 4 | align-self: stretch; 5 | background-color: $darkThemeSecondColor; 6 | 7 | display: flex; 8 | flex-wrap: nowrap; 9 | justify-content: flex-start; 10 | align-items: center; 11 | align-content: flex-start; 12 | 13 | &.with-context { 14 | .NavigationBarContentWrapper { 15 | background-color: $darkThemeForthColor; 16 | } 17 | } 18 | 19 | .CompanionBar { 20 | min-width: $sideNavigationBarCompanionWidth; 21 | width: $sideNavigationBarCompanionWidth; 22 | display: block; 23 | position: relative; 24 | height: 100%; 25 | } 26 | 27 | .NavigationBarContentWrapper { 28 | align-self: stretch; 29 | min-width: $sideNavigationBarContentWidth; 30 | width: $sideNavigationBarContentWidth; 31 | flex-direction: column; 32 | display: flex; 33 | flex-wrap: nowrap; 34 | justify-content: flex-end; 35 | align-items: center; 36 | align-content: flex-end; 37 | } 38 | 39 | &.left { 40 | flex-direction: row; 41 | border-right: solid 1px $darkThemeFirstColor; 42 | 43 | .CompanionBar { 44 | border-right: solid 1px $darkThemeFirstColor; 45 | 46 | .VerticalEditorButton { 47 | transform: rotate(-90deg) translateY(-2px); 48 | } 49 | } 50 | } 51 | 52 | &.right { 53 | flex-direction: row-reverse; 54 | border-left: solid 1px $darkThemeFirstColor; 55 | 56 | .CompanionBar { 57 | border-left: solid 1px $darkThemeFirstColor; 58 | 59 | .VerticalEditorButton { 60 | transform: rotate(-90deg) translateY(-1px); 61 | } 62 | } 63 | } 64 | 65 | &.closed { 66 | border: none; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/SideNavigationBar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import './SideNavigationBar.scss'; 4 | import {Direction} from "../../../data/enums/Direction"; 5 | 6 | interface IProps { 7 | direction: Direction 8 | isOpen: boolean; 9 | isWithContext?: boolean; 10 | renderCompanion?: () => any; 11 | renderContent?: () => any; 12 | } 13 | 14 | export const SideNavigationBar: React.FC = (props) => { 15 | const {direction, isOpen, isWithContext, renderContent, renderCompanion} = props; 16 | 17 | const getClassName = () => { 18 | return classNames( 19 | "SideNavigationBar", 20 | { 21 | "left": direction === Direction.LEFT, 22 | "right": direction === Direction.RIGHT, 23 | "with-context": isWithContext, 24 | "closed": !isOpen 25 | } 26 | ); 27 | }; 28 | 29 | return ( 30 |
31 |
32 | {renderCompanion && renderCompanion()} 33 |
34 | {isOpen &&
35 | {renderContent && renderContent()} 36 |
} 37 |
38 | ); 39 | }; -------------------------------------------------------------------------------- /src/views/EditorView/SideNavigationBar/TagLabelsList/TagLabelsList.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../settings/Settings'; 2 | 3 | .TagLabelsList { 4 | display: flex; 5 | flex-direction: column; 6 | flex-wrap: nowrap; 7 | justify-content: center; 8 | align-items: center; 9 | align-content: center; 10 | 11 | .EmptyLabelList { 12 | display: flex; 13 | flex-direction: column; 14 | flex-wrap: nowrap; 15 | justify-content: center; 16 | align-items: center; 17 | align-content: center; 18 | text-align: center; 19 | width: 150px; 20 | color: white; 21 | font-size: 16px; 22 | user-select: none; 23 | 24 | > img { 25 | filter: brightness(0) invert(1); 26 | max-width: 80px; 27 | max-height: 80px; 28 | margin-bottom: 20px; 29 | user-select: none; 30 | } 31 | 32 | > p { 33 | &.extraBold { 34 | font-size: 16px; 35 | font-weight: 600; 36 | } 37 | } 38 | } 39 | 40 | .TagLabelsListContent { 41 | display: flex; 42 | flex-direction: row; 43 | flex-wrap: wrap; 44 | justify-content: flex-start; 45 | align-items: center; 46 | align-content: flex-start; 47 | flex: 1 1 auto; 48 | } 49 | 50 | .TagItem { 51 | margin-right: 10px; 52 | margin-bottom: 10px; 53 | padding: 5px 20px; 54 | border-radius: 3px; 55 | background-color: rgba(0, 0, 0, 0.2); 56 | font-size: 14px; 57 | font-weight: 700; 58 | cursor: pointer; 59 | user-select: none; 60 | text-decoration: none; 61 | color: white; 62 | 63 | &:hover { 64 | background-color: rgba(0, 0, 0, 0.4); 65 | } 66 | 67 | &.active { 68 | background-color: $secondaryColor; // fallback if new css variables are not supported by browser 69 | background-color: var(--leading-color); 70 | } 71 | } 72 | 73 | .ImageButton { 74 | margin: 0 0 10px 0; 75 | border-radius: 3px; 76 | background-color: rgba(0, 0, 0, 0.2); 77 | 78 | img { 79 | filter: brightness(0) invert(1); 80 | user-select: none; 81 | width: 16px; 82 | height: 16px; 83 | } 84 | 85 | &:hover { 86 | background-color: rgba(0, 0, 0, 0.4); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /src/views/EditorView/StateBar/StateBar.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .StateBar { 4 | align-self: stretch; 5 | height: $stateBarHeight; 6 | background-color: $darkThemeFirstColor; 7 | 8 | display: flex; 9 | flex-direction: row; 10 | flex-wrap: nowrap; 11 | justify-content: flex-start; 12 | align-items: center; 13 | align-content: flex-start; 14 | 15 | .done { 16 | align-self: stretch; 17 | background-color: $secondaryColor; // fallback if new css variables are not supported by browser 18 | background-color: var(--leading-color); 19 | transition: width 0.4s ease-out; 20 | } 21 | } -------------------------------------------------------------------------------- /src/views/EditorView/VerticalEditorButton/VerticalEditorButton.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .VerticalEditorButton { 4 | height: $sideNavigationBarCompanionWidth; 5 | width: 80px; 6 | background-color: transparent; 7 | transition: 0.3s ease-in-out; 8 | transform: rotate(-90deg); 9 | transform-origin: 0 0; 10 | margin-bottom: 60px; 11 | user-select: none; 12 | cursor: pointer; 13 | position: absolute; 14 | top: 81px; 15 | left: 0; 16 | 17 | display: flex; 18 | flex-direction: row; 19 | flex-wrap: nowrap; 20 | justify-content: center; 21 | align-items: center; 22 | align-content: center; 23 | 24 | color: white; 25 | font-size: 13px; 26 | border-color: $darkThemeSecondColor; 27 | border-left: 1px; 28 | 29 | img { 30 | width: 15px; 31 | height: 15px; 32 | filter: brightness(0) invert(1); 33 | margin-right: 5px; 34 | user-select: none; 35 | } 36 | 37 | &:hover { 38 | background-color: $darkThemeFirstColor; 39 | } 40 | 41 | &.active { 42 | background-color: $darkThemeFirstColor; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/views/EditorView/VerticalEditorButton/VerticalEditorButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import classNames from "classnames"; 3 | import './VerticalEditorButton.scss'; 4 | 5 | interface IProps { 6 | key?:string; 7 | label:string; 8 | onClick?:() => any; 9 | style?:React.CSSProperties; 10 | isActive?:boolean; 11 | isDisabled?:boolean; 12 | image?:string, 13 | imageAlt?:string, 14 | } 15 | 16 | export const VerticalEditorButton = (props:IProps) => { 17 | 18 | const { key, label, onClick, style, isActive, isDisabled, image, imageAlt} = props; 19 | 20 | const getClassName = () => { 21 | return classNames( 22 | "VerticalEditorButton", 23 | { 24 | "active": isActive, 25 | "disabled": isDisabled 26 | } 27 | ); 28 | }; 29 | 30 | return( 31 |
37 | {image && {imageAlt}} 42 | {label} 43 |
44 | ) 45 | }; -------------------------------------------------------------------------------- /src/views/MainView/ImagesDropZone/ImagesDropZone.scss: -------------------------------------------------------------------------------- 1 | .ImagesDropZone { 2 | opacity: 0; 3 | display: flex; 4 | flex-direction: column; 5 | flex-wrap: nowrap; 6 | justify-content: center; 7 | align-items: center; 8 | 9 | .DropZone { 10 | user-select: none; 11 | width: 400px; 12 | height: 250px; 13 | box-shadow: black 0 0 0 2px inset; 14 | border-radius: 4px; 15 | margin-bottom: 6px; 16 | 17 | display: flex; 18 | flex-direction: column; 19 | justify-items: center; 20 | justify-content: center; 21 | align-items: center; 22 | cursor: pointer; 23 | outline: none; 24 | 25 | > img { 26 | max-width: 60px; 27 | max-height: 60px; 28 | margin-bottom: 10px; 29 | user-select: none; 30 | } 31 | 32 | > input { 33 | outline: none; 34 | } 35 | 36 | > p { 37 | margin-top: 2px; 38 | margin-bottom: 0; 39 | 40 | &.extraBold { 41 | font-size: 18px; 42 | font-weight: 600; 43 | } 44 | } 45 | } 46 | 47 | .DropZoneButtons { 48 | align-self: stretch; 49 | display: flex; 50 | flex-direction: row; 51 | flex-wrap: nowrap; 52 | justify-content: space-between; 53 | align-items: center; 54 | 55 | .TextButton { 56 | width: calc(50% - 3px); 57 | display: flex; 58 | flex-direction: row; 59 | flex-wrap: nowrap; 60 | justify-content: center; 61 | align-items: center; 62 | 63 | &.disabled { 64 | box-shadow: rgba(0, 0, 0, 0.2) 0 0 0 2px inset; 65 | color: rgba(0, 0, 0, 0.2); 66 | cursor: default; 67 | 68 | &:hover { 69 | box-shadow: rgba(0, 0, 0, 0.2) 0 0 0 2px inset; 70 | color: rgba(0, 0, 0, 0.2); 71 | background-color: transparent; 72 | } 73 | } 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /src/views/PopupView/ConnectInferenceServerPopup/ConnectInferenceServerPopup.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .right-container { 4 | display: flex; 5 | flex-direction: column; 6 | flex-wrap: nowrap; 7 | align-items: center; 8 | align-content: flex-start; 9 | 10 | align-self: stretch; 11 | flex: 1; 12 | 13 | .loader { 14 | display: flex; 15 | align-items: center; 16 | align-content: center; 17 | justify-content: center; 18 | 19 | align-self: stretch; 20 | flex: 1; 21 | } 22 | 23 | .message { 24 | align-self: stretch; 25 | color: white; 26 | font-size: 15px; 27 | padding: 30px 40px; 28 | border-bottom: solid 1px $darkThemeFirstColor; 29 | } 30 | 31 | .details { 32 | align-self: stretch; 33 | padding: 30px 40px; 34 | 35 | display: flex; 36 | flex-direction: column; 37 | flex-wrap: nowrap; 38 | justify-content: flex-start; 39 | align-items: flex-start; 40 | align-content: flex-start; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/views/PopupView/ExitProjectPopup/ExitProjectPopup.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .ExitProjectPopupContent { 4 | align-self: stretch; 5 | flex: 1; 6 | display: flex; 7 | flex-direction: column; 8 | flex-wrap: nowrap; 9 | justify-content: flex-start; 10 | align-items: center; 11 | align-content: flex-start; 12 | padding-top: 30px; 13 | 14 | .Message { 15 | align-self: stretch; 16 | color: white; 17 | font-size: 15px; 18 | padding: 0 40px 30px 40px; 19 | } 20 | } -------------------------------------------------------------------------------- /src/views/PopupView/ExportLabelsPopup/ExportLabelPopup.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .GenericLabelTypePopupContent { 4 | 5 | .RightContainer { 6 | display: flex; 7 | flex-direction: column; 8 | flex-wrap: nowrap; 9 | justify-content: flex-start; 10 | align-items: center; 11 | align-content: flex-start; 12 | 13 | .Message { 14 | align-self: stretch; 15 | color: white; 16 | font-size: 15px; 17 | padding: 30px 40px; 18 | border-bottom: solid 1px $darkThemeFirstColor; 19 | } 20 | 21 | .Options { 22 | align-self: stretch; 23 | padding: 30px 40px; 24 | 25 | display: flex; 26 | flex-direction: column; 27 | flex-wrap: nowrap; 28 | justify-content: flex-start; 29 | align-items: flex-start; 30 | align-content: flex-start; 31 | 32 | .OptionsItem { 33 | display: flex; 34 | flex-direction: row; 35 | flex-wrap: nowrap; 36 | justify-content: center; 37 | align-items: center; 38 | align-content: center; 39 | color: white; 40 | 41 | cursor: pointer; 42 | user-select: none; 43 | font-size: 15px; 44 | padding: 5px 0; 45 | 46 | > img { 47 | max-width: 20px; 48 | max-height: 20px; 49 | margin-right: 10px; 50 | filter: invert(1) brightness(35%) sepia(100%) hue-rotate(172deg) saturate(2000%); // fallback if new css variables are not supported by browser 51 | filter: invert(1) brightness(35%) sepia(100%) hue-rotate(var(--hue-value)) saturate(2000%); 52 | user-select: none; 53 | } 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/views/PopupView/GenericLabelTypePopup/GenericLabelTypePopup.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .GenericLabelTypePopupContent { 4 | align-self: stretch; 5 | flex: 1; 6 | display: flex; 7 | flex-direction: row; 8 | flex-wrap: nowrap; 9 | justify-content: flex-start; 10 | align-items: center; 11 | align-content: flex-start; 12 | min-height: 300px; 13 | 14 | .LeftContainer { 15 | width: 50px; 16 | align-self: stretch; 17 | border-right: solid 1px $darkThemeFirstColor; 18 | 19 | display: flex; 20 | flex-direction: column; 21 | flex-wrap: nowrap; 22 | justify-content: flex-start; 23 | align-items: center; 24 | align-content: flex-start; 25 | padding: 3px 0; 26 | 27 | .ImageButton { 28 | transition: transform 0.3s; 29 | 30 | img { 31 | filter: brightness(0) invert(1); 32 | } 33 | 34 | &:hover { 35 | border-radius: 3px; 36 | background-color: rgba(0, 0, 0, 0.2); 37 | } 38 | 39 | &.active { 40 | border-radius: 3px; 41 | background-color: rgba(0, 0, 0, 0.4); 42 | } 43 | } 44 | } 45 | 46 | .RightContainer { 47 | align-self: stretch; 48 | flex: 1; 49 | } 50 | } -------------------------------------------------------------------------------- /src/views/PopupView/GenericSideMenuPopup/GenericSideMenuPopup.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .generic-side-menu-popup { 4 | align-self: stretch; 5 | flex: 1; 6 | display: flex; 7 | flex-direction: row; 8 | flex-wrap: nowrap; 9 | justify-content: flex-start; 10 | align-items: center; 11 | align-content: flex-start; 12 | min-height: 300px; 13 | 14 | .left-container { 15 | width: 50px; 16 | align-self: stretch; 17 | border-right: solid 1px $darkThemeFirstColor; 18 | 19 | display: flex; 20 | flex-direction: column; 21 | flex-wrap: nowrap; 22 | justify-content: flex-start; 23 | align-items: center; 24 | align-content: flex-start; 25 | padding: 3px 0; 26 | 27 | .ImageButton { 28 | transition: transform 0.3s; 29 | 30 | img { 31 | filter: brightness(0) invert(1); 32 | } 33 | 34 | &:hover { 35 | border-radius: 3px; 36 | background-color: rgba(0, 0, 0, 0.2); 37 | } 38 | 39 | &.active { 40 | border-radius: 3px; 41 | background-color: rgba(0, 0, 0, 0.4); 42 | } 43 | } 44 | } 45 | 46 | .right-container { 47 | align-self: stretch; 48 | flex: 1; 49 | } 50 | } -------------------------------------------------------------------------------- /src/views/PopupView/GenericSideMenuPopup/GenericSideMenuPopup.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './GenericSideMenuPopup.scss' 3 | import { GenericYesNoPopup } from '../GenericYesNoPopup/GenericYesNoPopup'; 4 | 5 | interface IProps { 6 | title: string; 7 | acceptLabel: string; 8 | onAccept: () => void; 9 | disableAcceptButton?: boolean; 10 | rejectLabel: string; 11 | onReject: () => void; 12 | renderContent: () => JSX.Element; 13 | renderSideMenuContent: () => JSX.Element[]; 14 | } 15 | 16 | export const GenericSideMenuPopup: React.FC = ( 17 | { 18 | title, 19 | acceptLabel, 20 | onAccept, 21 | disableAcceptButton, 22 | rejectLabel, 23 | onReject, 24 | renderContent, 25 | renderSideMenuContent 26 | } 27 | ) => { 28 | 29 | const renderPopupContent = () => { 30 | return (
31 |
32 | {renderSideMenuContent()} 33 |
34 |
35 | {renderContent()} 36 |
37 |
); 38 | } 39 | 40 | return( 41 | 50 | ); 51 | } -------------------------------------------------------------------------------- /src/views/PopupView/GenericYesNoPopup/GenericYesNoPopup.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .GenericYesNoPopup { 4 | max-height: 600px; 5 | max-width: 500px; 6 | width: 50%; 7 | border-radius: 5px; 8 | user-select: none; 9 | box-shadow: 0px 0px 10px 3px rgba(255, 255, 255, 0.02); 10 | 11 | display: flex; 12 | flex-direction: column; 13 | flex-wrap: nowrap; 14 | overflow: hidden; 15 | 16 | .Header { 17 | align-self: stretch; 18 | height: 40px; 19 | box-shadow: 0px 2px 15px 0px rgba(0,0,0,0.4); 20 | color: white; 21 | font-weight: 900; 22 | font-size: 18px; 23 | background-color: $darkThemeFirstColor; 24 | 25 | display: flex; 26 | flex-direction: column; 27 | flex-wrap: nowrap; 28 | justify-content: center; 29 | align-items: center; 30 | align-content: center; 31 | } 32 | 33 | .Content { 34 | align-self: stretch; 35 | font-size: 14px; 36 | background-color: $darkThemeSecondColor; 37 | flex: 1; 38 | 39 | display: flex; 40 | flex-direction: column; 41 | flex-wrap: nowrap; 42 | justify-content: center; 43 | align-items: center; 44 | align-content: center; 45 | } 46 | 47 | .Footer { 48 | padding: 0 40px; 49 | height: 80px; 50 | align-self: stretch; 51 | background-color: $darkThemeSecondColor; 52 | border-top: solid 1px $darkThemeFirstColor; 53 | 54 | display: flex; 55 | flex-direction: row; 56 | flex-wrap: nowrap; 57 | justify-content: flex-end; 58 | align-items: center; 59 | align-content: flex-end; 60 | 61 | .TextButton { 62 | margin-left: 20px; 63 | box-shadow: $secondaryColor 0 0 0 2px inset; // fallback if new css variables are not supported by browser 64 | box-shadow: var(--leading-color) 0 0 0 2px inset; 65 | background-color: $secondaryColor; // fallback if new css variables are not supported by browser 66 | background-color: var(--leading-color); 67 | color: white; // fallback if new css variables are not supported by browser 68 | color: var(--button-text-color); 69 | 70 | &:hover { 71 | background-color: transparent; 72 | color: $secondaryColor; // fallback if new css variables are not supported by browser 73 | color: var(--leading-color); 74 | } 75 | 76 | &.disabled { 77 | box-shadow: $darkThemeThirdColor 0 0 0 2px inset; 78 | background-color: $darkThemeThirdColor; 79 | cursor: default; 80 | opacity: 0.6; 81 | 82 | &:hover { 83 | box-shadow: $darkThemeThirdColor 0 0 0 2px inset; 84 | background-color: $darkThemeThirdColor; 85 | color: white; 86 | } 87 | } 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /src/views/PopupView/GenericYesNoPopup/GenericYesNoPopup.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState} from 'react' 2 | import './GenericYesNoPopup.scss' 3 | import {TextButton} from '../../Common/TextButton/TextButton'; 4 | import {ContextManager} from '../../../logic/context/ContextManager'; 5 | import {ContextType} from '../../../data/enums/ContextType'; 6 | 7 | interface IProps { 8 | title: string; 9 | renderContent: () => any; 10 | acceptLabel?: string; 11 | onAccept?: () => any; 12 | skipAcceptButton?: boolean; 13 | disableAcceptButton?: boolean; 14 | rejectLabel?: string; 15 | onReject?: () => any; 16 | skipRejectButton?: boolean; 17 | disableRejectButton?: boolean; 18 | } 19 | 20 | export const GenericYesNoPopup: React.FC = ( 21 | { 22 | title, 23 | renderContent, 24 | acceptLabel, 25 | onAccept, 26 | skipAcceptButton, 27 | disableAcceptButton, 28 | rejectLabel, 29 | onReject, 30 | skipRejectButton, 31 | disableRejectButton 32 | }) => { 33 | 34 | const [status, setMountStatus] = useState(false); 35 | useEffect(() => { 36 | if (!status) { 37 | ContextManager.switchCtx(ContextType.POPUP); 38 | setMountStatus(true); 39 | } 40 | }, [status]); 41 | 42 | return ( 43 |
44 |
45 | {title} 46 |
47 |
48 | {renderContent()} 49 |
50 |
51 | {!skipRejectButton && } 57 | {!skipAcceptButton && } 63 |
64 |
65 | ) 66 | }; 67 | -------------------------------------------------------------------------------- /src/views/PopupView/ImportLabelPopup/ImportLabelPopup.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .GenericLabelTypePopupContent { 4 | 5 | .RightContainer { 6 | display: flex; 7 | flex-direction: column; 8 | flex-wrap: nowrap; 9 | justify-content: flex-start; 10 | align-items: center; 11 | align-content: flex-start; 12 | 13 | .FeatureInProgress { 14 | flex: 1; 15 | } 16 | 17 | .DropZone { 18 | user-select: none; 19 | align-self: stretch; 20 | border-radius: 4px; 21 | flex: 1; 22 | color: white; 23 | 24 | display: flex; 25 | flex-direction: column; 26 | justify-items: center; 27 | justify-content: center; 28 | align-items: center; 29 | cursor: pointer; 30 | outline: none; 31 | 32 | &:hover { 33 | background-color: rgba(0, 0, 0, 0.03); 34 | } 35 | 36 | > img { 37 | max-width: 60px; 38 | max-height: 60px; 39 | margin-bottom: 30px; 40 | filter: brightness(0) invert(1); 41 | user-select: none; 42 | } 43 | 44 | > input { 45 | outline: none; 46 | } 47 | 48 | > p { 49 | margin-top: 2px; 50 | margin-bottom: 0; 51 | 52 | &.extraBold { 53 | font-size: 18px; 54 | font-weight: 600; 55 | } 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/views/PopupView/InsertLabelNamesPopup/ColorSelectorView/ColorSelectorView.scss: -------------------------------------------------------------------------------- 1 | .ColorSelectorView { 2 | width: 30px; 3 | height: 30px; 4 | border-radius: 2px; 5 | 6 | display: flex; 7 | flex-direction: column; 8 | flex-wrap: nowrap; 9 | justify-content: center; 10 | align-items: center; 11 | align-content: center; 12 | 13 | > img { 14 | filter: brightness(0) invert(1); 15 | width: 20px; 16 | height: 20px; 17 | } 18 | 19 | &:hover { 20 | cursor: pointer; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/views/PopupView/InsertLabelNamesPopup/ColorSelectorView/ColorSelectorView.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './ColorSelectorView.scss' 3 | 4 | interface IProps { 5 | color: string; 6 | onClick: () => any; 7 | } 8 | 9 | export const ColorSelectorView: React.FC = ({color, onClick}) => { 10 | return
17 | {'refresh'} 22 |
23 | } 24 | -------------------------------------------------------------------------------- /src/views/PopupView/LoadLabelNamesPopup/LoadLabelNamesPopup.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .LoadLabelsPopupContent { 4 | display: flex; 5 | flex-direction: column; 6 | flex-wrap: nowrap; 7 | justify-content: center; 8 | align-items: center; 9 | align-content: center; 10 | padding-top: 30px; 11 | flex: 1; 12 | min-height: 450px; 13 | 14 | .Message { 15 | align-self: stretch; 16 | color: white; 17 | font-size: 15px; 18 | padding: 0 40px 30px 40px; 19 | border-bottom: solid 1px $darkThemeFirstColor; 20 | } 21 | 22 | .DropZone { 23 | user-select: none; 24 | align-self: stretch; 25 | border-radius: 4px; 26 | flex: 1; 27 | color: white; 28 | 29 | display: flex; 30 | flex-direction: column; 31 | justify-items: center; 32 | justify-content: center; 33 | align-items: center; 34 | cursor: pointer; 35 | outline: none; 36 | 37 | &:hover { 38 | background-color: rgba(0, 0, 0, 0.03); 39 | } 40 | 41 | > img { 42 | max-width: 60px; 43 | max-height: 60px; 44 | margin-bottom: 30px; 45 | filter: brightness(0) invert(1); 46 | user-select: none; 47 | } 48 | 49 | > input { 50 | outline: none; 51 | } 52 | 53 | > p { 54 | margin-top: 2px; 55 | margin-bottom: 0; 56 | 57 | &.extraBold { 58 | font-size: 18px; 59 | font-weight: 600; 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/views/PopupView/LoadModelPopup/LoadModelPopup.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .LoadModelPopupContent { 4 | display: flex; 5 | flex-direction: column; 6 | flex-wrap: nowrap; 7 | justify-content: center; 8 | align-items: center; 9 | align-content: center; 10 | padding-top: 30px; 11 | flex: 1; 12 | 13 | .Message { 14 | align-self: stretch; 15 | color: white; 16 | font-size: 15px; 17 | padding: 0 40px 30px 40px; 18 | } 19 | 20 | .Companion { 21 | align-self: stretch; 22 | padding-bottom: 30px; 23 | display: flex; 24 | flex-direction: column; 25 | flex-wrap: nowrap; 26 | justify-content: center; 27 | align-items: center; 28 | align-content: center; 29 | height: 150px; 30 | 31 | .Options { 32 | align-self: stretch; 33 | flex: 1; 34 | padding: 0 40px; 35 | 36 | .OptionsItem { 37 | display: flex; 38 | flex-direction: row; 39 | flex-wrap: nowrap; 40 | justify-content: flex-start; 41 | align-items: center; 42 | align-content: flex-start; 43 | color: white; 44 | 45 | cursor: pointer; 46 | user-select: none; 47 | font-size: 15px; 48 | padding: 5px 0; 49 | 50 | > img { 51 | max-width: 20px; 52 | max-height: 20px; 53 | margin-right: 10px; 54 | filter: invert(1) brightness(35%) sepia(100%) hue-rotate(172deg) saturate(2000%); // fallback if new css variables are not supported by browser 55 | filter: invert(1) brightness(35%) sepia(100%) hue-rotate(var(--hue-value)) saturate(2000%); 56 | user-select: none; 57 | } 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/views/PopupView/LoadMoreImagesPopup/LoadMoreImagesPopup.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .LoadMoreImagesPopupContent { 4 | display: flex; 5 | flex-direction: column; 6 | flex-wrap: nowrap; 7 | justify-content: center; 8 | align-items: center; 9 | align-content: center; 10 | flex: 1; 11 | min-height: 300px; 12 | align-self: stretch; 13 | 14 | .DropZone { 15 | user-select: none; 16 | align-self: stretch; 17 | border-radius: 4px; 18 | flex: 1; 19 | color: white; 20 | 21 | display: flex; 22 | flex-direction: column; 23 | justify-items: center; 24 | justify-content: center; 25 | align-items: center; 26 | cursor: pointer; 27 | outline: none; 28 | 29 | &:hover { 30 | background-color: rgba(0, 0, 0, 0.03); 31 | } 32 | 33 | > img { 34 | max-width: 60px; 35 | max-height: 60px; 36 | margin-bottom: 30px; 37 | filter: brightness(0) invert(1); 38 | user-select: none; 39 | } 40 | 41 | > input { 42 | outline: none; 43 | } 44 | 45 | > p { 46 | margin-top: 2px; 47 | margin-bottom: 0; 48 | 49 | &.extraBold { 50 | font-size: 18px; 51 | font-weight: 600; 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/views/PopupView/PopupView.scss: -------------------------------------------------------------------------------- 1 | @import '../../settings/_Settings'; 2 | 3 | .PopupView { 4 | position: absolute; 5 | height: 100vh; 6 | width: 100vw; 7 | margin: 0; 8 | padding: 0; 9 | z-index: 1000; 10 | 11 | display: flex; 12 | flex-direction: column; 13 | flex-wrap: nowrap; 14 | justify-content: center; 15 | align-items: center; 16 | align-content: center; 17 | } -------------------------------------------------------------------------------- /src/views/PopupView/SuggestLabelNamesPopup/SuggestLabelNamesPopup.scss: -------------------------------------------------------------------------------- 1 | @import '../../../settings/Settings'; 2 | 3 | .SuggestLabelNamesPopupContent { 4 | display: flex; 5 | flex-direction: column; 6 | flex-wrap: nowrap; 7 | justify-content: flex-start; 8 | align-items: center; 9 | align-content: flex-start; 10 | padding-top: 30px; 11 | flex: 1; 12 | min-height: 350px; 13 | 14 | .Message { 15 | align-self: stretch; 16 | color: white; 17 | font-size: 15px; 18 | padding: 0 40px 0 40px; 19 | } 20 | 21 | .OptionsItem { 22 | display: flex; 23 | flex-direction: row; 24 | flex-wrap: nowrap; 25 | justify-content: flex-start; 26 | align-items: center; 27 | align-content: flex-start; 28 | color: white; 29 | 30 | cursor: pointer; 31 | user-select: none; 32 | font-size: 15px; 33 | padding: 5px 0; 34 | 35 | > img { 36 | max-width: 20px; 37 | max-height: 20px; 38 | margin-right: 10px; 39 | filter: invert(1) brightness(35%) sepia(100%) hue-rotate(172deg) saturate(2000%); // fallback if new css variables are not supported by browser 40 | filter: invert(1) brightness(35%) sepia(100%) hue-rotate(var(--hue-value)) saturate(2000%); 41 | user-select: none; 42 | } 43 | } 44 | 45 | .AllToggle { 46 | align-self: stretch; 47 | color: white; 48 | font-size: 15px; 49 | padding: 10px 40px 20px 40px; 50 | border-bottom: solid 1px $darkThemeFirstColor; 51 | } 52 | 53 | .LabelNamesContainer { 54 | align-self: stretch; 55 | flex: 1; 56 | 57 | display: flex; 58 | flex-direction: column; 59 | flex-wrap: nowrap; 60 | justify-content: flex-start; 61 | align-items: center; 62 | align-content: flex-start; 63 | 64 | color: white; 65 | 66 | .LabelNamesContent { 67 | margin-left: 40px; 68 | margin-right: 10px; 69 | margin-top: 20px; 70 | 71 | display: flex; 72 | flex-direction: column; 73 | flex-wrap: nowrap; 74 | justify-content: flex-start; 75 | align-items: flex-start; 76 | align-content: flex-start; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/views/SizeItUpView/SizeItUpView.scss: -------------------------------------------------------------------------------- 1 | @import '../../settings/_Settings'; 2 | 3 | .SizeItUpView { 4 | position: absolute; 5 | height: 100vh; 6 | width: 100vw; 7 | margin: 0; 8 | padding: 0; 9 | 10 | background-color: $darkThemeSecondColor; 11 | 12 | display: flex; 13 | flex-direction: column; 14 | flex-wrap: nowrap; 15 | justify-content: center; 16 | align-items: center; 17 | align-content: center; 18 | 19 | text-align: center; 20 | color: white; 21 | font-size: 18px; 22 | user-select: none; 23 | 24 | > img { 25 | filter: brightness(0) invert(1); 26 | max-width: 100px; 27 | max-height: 100px; 28 | user-select: none; 29 | margin: 25px; 30 | } 31 | 32 | > p { 33 | &.extraBold { 34 | font-size: 18px; 35 | font-weight: 600; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/views/SizeItUpView/SizeItUpView.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './SizeItUpView.scss'; 3 | import {Settings} from "../../settings/Settings"; 4 | 5 | export const SizeItUpView: React.FC = () => { 6 | return(
7 |

Ops... This window is to tight for me!

8 | {"small_window"} 13 |

Please... make it at least {Settings.EDITOR_MIN_WIDTH} x {Settings.EDITOR_MIN_HEIGHT} px.

14 |
) 15 | }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "ESNext", 5 | "downlevelIteration": true, 6 | "lib": [ 7 | "dom", 8 | "dom.iterable", 9 | "esnext" 10 | ], 11 | "allowJs": true, 12 | "skipLibCheck": true, 13 | "esModuleInterop": true, 14 | "allowSyntheticDefaultImports": true, 15 | "strict": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "module": "esnext", 18 | "moduleResolution": "node", 19 | "resolveJsonModule": true, 20 | "isolatedModules": true, 21 | "outDir": "dist", 22 | "noEmit": true, 23 | "jsx": "react-jsxdev", 24 | "strictNullChecks": false, 25 | "noImplicitReturns": true, 26 | "noImplicitThis": true, 27 | "suppressImplicitAnyIndexErrors": true, 28 | "noImplicitAny": false, 29 | "strictPropertyInitialization": false, 30 | "noFallthroughCasesInSwitch": true, 31 | "types": [ 32 | "vite/client", 33 | ] 34 | }, 35 | "include": [ 36 | "src" 37 | ], 38 | "exclude": ["./node_modules", "./public", "./dist", "./.vscode"] 39 | } 40 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:recommended", 4 | "tslint-react" 5 | ], 6 | "rules": { 7 | "max-line-length": { 8 | "options": [120] 9 | }, 10 | "quotemark": [ 11 | true, 12 | "single", 13 | "avoid-escape" 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineConfig, 3 | loadEnv, 4 | UserConfig, 5 | UserConfigExport, 6 | } from 'vite'; 7 | 8 | import react from '@vitejs/plugin-react'; 9 | 10 | export default ({ mode }: UserConfig): UserConfigExport => { 11 | process.env = { ...process.env, ...loadEnv(mode || 'development', process.cwd()) }; 12 | return defineConfig({ 13 | plugins: [react()], 14 | build: { 15 | minify: 'terser', 16 | sourcemap: mode === 'development', 17 | chunkSizeWarningLimit: 1024 * 1024, 18 | rollupOptions: { 19 | treeshake: true, 20 | maxParallelFileReads: 4, 21 | output: { 22 | manualChunks: { 23 | lodash: ['lodash'], 24 | classnames: ['classnames'], 25 | runtime: ['react', 'react-is'], 26 | 'runtime-dom': ['react-dom'], 27 | 28 | ai: ['@tensorflow/tfjs', 29 | '@tensorflow/tfjs-backend-cpu', 30 | '@tensorflow/tfjs-backend-webgl', 31 | '@tensorflow/tfjs-core', 32 | '@tensorflow/tfjs-node'], 33 | models: [ 34 | '@tensorflow-models/coco-ssd', 35 | '@tensorflow-models/posenet', 36 | ], 37 | ui: ['@mui/material', '@mui/system'], 38 | moment: ['moment'] 39 | 40 | }, 41 | }, 42 | }, 43 | }, 44 | esbuild: { 45 | logOverride: { 'this-is-undefined-in-esm': 'silent' } 46 | }, 47 | css: { 48 | modules: { 49 | generateScopedName: mode === 'development' ? '[name]__[local]___[hash:base64:5]' : '[hash:base64:8]', 50 | scopeBehaviour: 'local', 51 | localsConvention: 'camelCase', 52 | }, 53 | postcss: { 54 | plugins: [ 55 | { 56 | postcssPlugin: 'internal:charset-removal', 57 | AtRule: { 58 | charset: (atRule) => { 59 | if (atRule.name === 'charset') { 60 | atRule.remove(); 61 | } 62 | }, 63 | }, 64 | }, 65 | ], 66 | }, 67 | }, 68 | }); 69 | }; 70 | --------------------------------------------------------------------------------