├── .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 |
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 |
65 | }
66 | {!href &&

}
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 |

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 |
19 | {labelBefore}
20 | >;
21 |
22 | const after = <>
23 |
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 &&

}
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 |

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 |

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 |
--------------------------------------------------------------------------------