├── .env.defaults ├── .env.example ├── .eslintignore ├── .eslintrc.js ├── .github ├── CODEOWNERS ├── autolabeler.yml ├── dependabot.yml ├── pr-title-checker-config.json ├── pull_request_template.md └── workflows │ ├── build.yml │ ├── eslint.yml │ ├── git-command.yml │ ├── help-command.yml │ ├── jira-command.yml │ ├── pr-labeler.yml │ ├── slash-command-dispatch.yml │ ├── sync-pr-ls.yml │ └── sync-pr-lse.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── bin └── publish.sh ├── dev-server.js ├── docs ├── api_reference.md ├── dm_architecture_diagram.pdf └── image.png ├── get-build.js ├── package.json ├── public └── index.html ├── src ├── assets │ ├── Running-24.gif │ ├── Running-24@2x.gif │ ├── Running-48.gif │ ├── Running-48@2x.gif │ ├── Running-64.gif │ ├── Running-64@2x.gif │ ├── Running.gif │ ├── icons │ │ ├── annotation.svg │ │ ├── ban.svg │ │ ├── check_alt.svg │ │ ├── comment_check.svg │ │ ├── comment_red.svg │ │ ├── cross_alt.svg │ │ ├── gear.svg │ │ ├── gear_new_menu.svg │ │ ├── gear_new_ui.svg │ │ ├── grid.svg │ │ ├── index.js │ │ ├── list.svg │ │ ├── plus.svg │ │ ├── refresh.svg │ │ ├── refresh2.svg │ │ ├── spark.svg │ │ ├── star_square.svg │ │ ├── thumbs_down.svg │ │ └── thumbs_up.svg │ └── running.js ├── components │ ├── App │ │ ├── AntOverrides.styl │ │ ├── App.js │ │ └── App.styl │ ├── CellViews │ │ ├── Agreement │ │ │ ├── Agreement.js │ │ │ └── Agreement.styl │ │ ├── Annotators │ │ │ ├── Annotators.js │ │ │ └── Annotators.styl │ │ ├── AudioCell.js │ │ ├── BooleanCell.js │ │ ├── DateTimeCell.js │ │ ├── ImageCell.js │ │ ├── NumberCell.js │ │ ├── ProjectCell.js │ │ ├── StringCell.js │ │ ├── VideoCell.js │ │ └── index.js │ ├── Common │ │ ├── AnnotationPreview │ │ │ ├── AnnotationPreview.js │ │ │ └── AnnotationPreview.styl │ │ ├── Badge │ │ │ ├── Badge.js │ │ │ └── Badge.styl │ │ ├── Button │ │ │ ├── Button.js │ │ │ └── Button.styl │ │ ├── Checkbox │ │ │ ├── Checkbox.js │ │ │ └── Checkbox.styl │ │ ├── DatePicker │ │ │ ├── DatePicker.global.styl │ │ │ ├── DatePicker.js │ │ │ └── DatePicker.styl │ │ ├── Dropdown │ │ │ ├── Dropdown.js │ │ │ ├── Dropdown.styl │ │ │ ├── DropdownComponent.js │ │ │ ├── DropdownContext.js │ │ │ └── DropdownTrigger.js │ │ ├── ErrorBox.js │ │ ├── FieldsButton.js │ │ ├── FiltersPane.js │ │ ├── Form │ │ │ ├── Elements │ │ │ │ ├── Counter │ │ │ │ │ ├── Counter.js │ │ │ │ │ └── Counter.styl │ │ │ │ ├── Input │ │ │ │ │ ├── Input.js │ │ │ │ │ └── Input.styl │ │ │ │ ├── Label │ │ │ │ │ ├── Label.js │ │ │ │ │ └── Label.styl │ │ │ │ ├── RadioGroup │ │ │ │ │ ├── RadioGroup.js │ │ │ │ │ └── RadioGroup.styl │ │ │ │ ├── Range │ │ │ │ │ └── Range.js │ │ │ │ ├── Select │ │ │ │ │ ├── Select.js │ │ │ │ │ └── Select.styl │ │ │ │ ├── TextArea │ │ │ │ │ └── TextArea.js │ │ │ │ ├── Toggle │ │ │ │ │ ├── Toggle.js │ │ │ │ │ └── Toggle.styl │ │ │ │ └── index.ts │ │ │ ├── Form.js │ │ │ ├── Form.styl │ │ │ ├── FormContext.js │ │ │ ├── FormField.js │ │ │ ├── Utils.ts │ │ │ ├── Validation │ │ │ │ ├── Validation.styl │ │ │ │ └── Validators.js │ │ │ └── index.js │ │ ├── Icon │ │ │ ├── Icon.js │ │ │ └── Icon.styl │ │ ├── Input │ │ │ ├── Input.js │ │ │ └── Input.styl │ │ ├── Interface.js │ │ ├── MediaPlayer │ │ │ ├── Duration.js │ │ │ ├── MediaPlayer.js │ │ │ ├── MediaPlayer.styl │ │ │ ├── MediaSeeker.js │ │ │ └── MediaSeeker.styl │ │ ├── Menu │ │ │ ├── Menu.js │ │ │ ├── Menu.styl │ │ │ ├── MenuContext.js │ │ │ └── MenuItem.js │ │ ├── Modal │ │ │ ├── Modal.js │ │ │ ├── Modal.styl │ │ │ └── ModalPopup.js │ │ ├── Oneof │ │ │ └── Oneof.js │ │ ├── Pagination │ │ │ ├── Pagination.styl │ │ │ └── Pagination.tsx │ │ ├── RadioGroup │ │ │ ├── RadioGroup.js │ │ │ └── RadioGroup.styl │ │ ├── Range │ │ │ ├── Range.js │ │ │ └── Range.styl │ │ ├── Resizer │ │ │ ├── Resizer.js │ │ │ └── Resizer.styl │ │ ├── SDKButtons.js │ │ ├── Select │ │ │ ├── Select.js │ │ │ └── Select.styl │ │ ├── SharedAudio.js │ │ ├── SkeletonLoader │ │ │ ├── SkeletonGap.tsx │ │ │ ├── SkeletonLine.tsx │ │ │ ├── SkeletonLoader.styl │ │ │ ├── SkeletonLoader.tsx │ │ │ └── index.tsx │ │ ├── Space │ │ │ ├── Space.js │ │ │ └── Space.styl │ │ ├── Spinner.js │ │ ├── Table │ │ │ ├── Table.js │ │ │ ├── Table.styl │ │ │ ├── TableCell │ │ │ │ └── TableCell.js │ │ │ ├── TableCheckbox.js │ │ │ ├── TableContext.js │ │ │ ├── TableHead │ │ │ │ ├── TableHead.js │ │ │ │ └── TableHead.styl │ │ │ ├── TableRow │ │ │ │ ├── TableRow.js │ │ │ │ └── TableRow.styl │ │ │ └── utils.js │ │ ├── TableOld │ │ │ ├── Table.js │ │ │ ├── Table.styl │ │ │ ├── TableCell │ │ │ │ └── TableCell.js │ │ │ ├── TableCheckbox.js │ │ │ ├── TableContext.js │ │ │ ├── TableHead │ │ │ │ ├── TableHead.js │ │ │ │ └── TableHead.styl │ │ │ ├── TableRow │ │ │ │ ├── TableRow.js │ │ │ │ └── TableRow.styl │ │ │ └── utils.js │ │ ├── Tabs │ │ │ ├── Tabs.js │ │ │ ├── Tabs.styl │ │ │ └── TabsMenu.js │ │ ├── Tag │ │ │ ├── Tag.js │ │ │ └── Tag.styl │ │ ├── Tooltip │ │ │ ├── Tooltip.js │ │ │ └── Tooltip.styl │ │ └── Userpic │ │ │ ├── Userpic.js │ │ │ └── Userpic.styl │ ├── DataGroups │ │ ├── AudioDataGroup.js │ │ ├── ImageDataGroup.js │ │ ├── TextDataGroup.js │ │ └── index.js │ ├── DataManager │ │ ├── DataManager.js │ │ ├── DataManager.styl │ │ └── Toolbar │ │ │ ├── ActionsButton.js │ │ │ ├── ActionsButton.styl │ │ │ ├── GridWidthButton.js │ │ │ ├── LabelButton.js │ │ │ ├── LoadingPossum.js │ │ │ ├── OrderButton.js │ │ │ ├── RefreshButton.js │ │ │ ├── TabPanel.styl │ │ │ ├── Toolbar.js │ │ │ ├── ViewToggle.js │ │ │ └── instruments.js │ ├── Filters │ │ ├── FilterDropdown.js │ │ ├── FilterInput.js │ │ ├── FilterLine │ │ │ ├── FilterLine.js │ │ │ ├── FilterLine.styl │ │ │ └── FilterOperation.js │ │ ├── Filters.js │ │ ├── Filters.styl │ │ ├── FiltersSidebar │ │ │ ├── FilterSidebar.js │ │ │ └── FilterSidebar.styl │ │ └── types │ │ │ ├── Boolean.js │ │ │ ├── Common.js │ │ │ ├── Date.js │ │ │ ├── Datetime.js │ │ │ ├── List.js │ │ │ ├── Number.js │ │ │ ├── String.js │ │ │ ├── Utility.js │ │ │ └── index.js │ ├── Label │ │ ├── Label.js │ │ ├── Label.styl │ │ ├── LabelButtons.js │ │ └── Toolbar │ │ │ ├── Toolbar.js │ │ │ └── Toolbar.styl │ └── MainView │ │ ├── DataView │ │ ├── Table.js │ │ └── Table.styl │ │ ├── GridView │ │ ├── GridView.js │ │ └── GridView.styl │ │ └── index.js ├── dev.js ├── hooks │ ├── useFirstMountState.ts │ ├── useSet.ts │ └── useUpdateEffect.ts ├── index.js ├── mixins │ └── DataStore │ │ ├── DataStore.js │ │ ├── DataStoreItem.js │ │ └── index.js ├── providers │ ├── MultiProvider.js │ └── SDKProvider.js ├── sdk │ ├── api-config.js │ ├── app-create.js │ ├── comments-sdk.js │ ├── dm-sdk.js │ ├── hotkeys.ts │ ├── index.js │ ├── keymap.ts │ ├── lsf-history.ts │ ├── lsf-sdk.js │ └── lsf-utils.ts ├── stores │ ├── Action.js │ ├── AppStore.js │ ├── Assignee.js │ ├── DataStores │ │ ├── annotations.js │ │ ├── index.js │ │ └── tasks.js │ ├── DynamicModel.js │ ├── Tabs │ │ ├── filter_utils.ts │ │ ├── index.js │ │ ├── store.js │ │ ├── tab.js │ │ ├── tab_column.js │ │ ├── tab_filter.js │ │ ├── tab_filter_type.js │ │ ├── tab_hidden_columns.js │ │ └── tab_selected_items.js │ ├── Users.js │ └── types.js ├── styles │ └── waiting.styl ├── themes │ └── default │ │ └── colors.styl ├── types │ ├── Global.d.ts │ └── Task.ts └── utils │ ├── ActivityObserver.ts │ ├── api-proxy │ ├── index.js │ └── status-codes.json │ ├── bem.tsx │ ├── colors.js │ ├── debounce.js │ ├── dom.js │ ├── feature-flags.js │ ├── helpers.ts │ ├── history.js │ ├── hooks.js │ ├── packJSON.js │ ├── random.js │ ├── transition.js │ ├── types.js │ └── utils.ts ├── tsconfig.json ├── webpack.config-builder.js ├── webpack.config.js └── yarn.lock /.env.defaults: -------------------------------------------------------------------------------- 1 | API_GATEWAY=http://localhost:8000/api/dm 2 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # API_GATEWAY=http://[ls-hostname]/api/dm 2 | # LS_ACCESS_TOKEN=[your LS token here] 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .eslintrc.js 2 | webpack.config.js 3 | webpack.config-builder.js 4 | dev-server.js 5 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | .github/ @farioas 2 | -------------------------------------------------------------------------------- /.github/autolabeler.yml: -------------------------------------------------------------------------------- 1 | template: "Mandatory field" #https://github.com/release-drafter/release-drafter/blob/master/bin/generate-schema.js#L15 2 | autolabeler: 3 | # - label: 'breaking' 4 | # body: 5 | # - '/BREAKING CHANGE/i' 6 | - label: 'fix' 7 | title: 8 | - '/^fix:/' 9 | - label: 'feat' 10 | title: 11 | - '/^feat:/' 12 | - label: 'docs' 13 | title: 14 | - '/^docs:/' 15 | - label: 'chore' 16 | title: 17 | - '/^chore:/' 18 | - label: 'ci' 19 | title: 20 | - '/^ci:/' 21 | - label: 'perf' 22 | title: 23 | - '/^perf:/' 24 | - label: 'refactor' 25 | title: 26 | - '/^refactor:/' 27 | - label: 'style' 28 | title: 29 | - '/^style:/' 30 | - label: 'test' 31 | title: 32 | - '/^test:/' 33 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | timezone: "Europe/London" 8 | time: "09:00" 9 | day: "monday" 10 | commit-message: 11 | prefix: "ci:" 12 | -------------------------------------------------------------------------------- /.github/pr-title-checker-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "LABEL": { 3 | "name": "title needs formatting", 4 | "color": "EEEEEE" 5 | }, 6 | "CHECKS": { 7 | "prefixes": [ 8 | "fix: ", 9 | "feat: ", 10 | "docs: ", 11 | "chore: ", 12 | "ci: ", 13 | "perf: ", 14 | "refactor: ", 15 | "style: ", 16 | "test: " 17 | ], 18 | "ignoreLabels": [ 19 | "skip-changelog", 20 | "skip-ci" 21 | ] 22 | }, 23 | "MESSAGES": { 24 | "success": "PR title is valid", 25 | "failure": "PR title is invalid", 26 | "notice": "Please read the doc: [Release versioning strategy](https://heartex.atlassian.net/l/c/brYSL9qf)" 27 | } 28 | } -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### PR fulfills these requirements 2 | - [ ] Commit message(s) and PR title follows the format `[fix|feat|ci|chore|doc]: TICKET-ID: Short description of change made` ex. `fix: DEV-XXXX: Removed inconsistent code usage causing intermittent errors` 3 | - [ ] Tests for the changes have been added/updated (for bug fixes/features) 4 | - [ ] Docs have been added/updated (for bug fixes/features) 5 | - [ ] Best efforts were made to ensure docs/code are concise and coherent (checked for spelling/grammatical errors, commented out code, debug logs etc.) 6 | - [ ] Self-reviewed and ran all changes on a local instance (for bug fixes/features) 7 | 8 | 9 | 10 | #### Change has impacts in these area(s) 11 | _(check all that apply)_ 12 | - [ ] Product design 13 | - [ ] Frontend 14 | 15 | 16 | 17 | ### Describe the reason for change 18 | _(link to issue, supportive screenshots etc.)_ 19 | 20 | 21 | 22 | #### What does this fix? 23 | _(if this is a bug fix)_ 24 | 25 | 26 | 27 | #### What is the new behavior? 28 | _(if this is a breaking or feature change)_ 29 | 30 | 31 | 32 | #### What is the current behavior? 33 | _(if this is a breaking or feature change)_ 34 | 35 | 36 | 37 | #### What libraries were added/updated? 38 | _(list all with version changes)_ 39 | 40 | 41 | 42 | #### Does this change affect performance? 43 | _(if so describe the impacts positive or negative)_ 44 | 45 | 46 | 47 | #### Does this change affect security? 48 | _(if so describe the impacts positive or negative)_ 49 | 50 | 51 | 52 | #### What alternative approaches were there? 53 | _(briefly list any if applicable)_ 54 | 55 | 56 | 57 | #### What feature flags were used to cover this change? 58 | _(briefly list any if applicable)_ 59 | 60 | 61 | 62 | ### Does this PR introduce a breaking change? 63 | _(check only one)_ 64 | - [ ] Yes, and covered entirely by feature flag(s) 65 | - [ ] Yes, and covered partially by feature flag(s) 66 | - [ ] No 67 | - [ ] Not sure (briefly explain the situation below) 68 | 69 | 70 | 71 | ### What level of testing was included in the change? 72 | _(check all that apply)_ 73 | - [ ] e2e 74 | - [ ] integration 75 | - [ ] unit 76 | 77 | 78 | 79 | ### Which logical domain(s) does this change affect? 80 | _(for bug fixes/features, be as precise as possible. ex. Authentication, Annotation History, Review Stream etc.)_ 81 | 82 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | push: 5 | branches: [master, "*/*", "*/**", "fb-**"] 6 | 7 | env: 8 | NODE: 18 9 | CACHE_NAME_PREFIX: v1 10 | 11 | jobs: 12 | build: 13 | # ci can be skipped with `[skip ci]` prefix in message 14 | if: "!contains(github.event.head_commit.message, 'skip ci')" 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: actions/setup-node@v4 20 | with: 21 | node-version: "${{ env.NODE }}" 22 | 23 | - name: Upgrade Yarn 24 | run: npm install -g yarn@1.22 25 | 26 | - name: Get yarn cache directory path 27 | id: yarn-cache-dir-path 28 | run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT 29 | 30 | - name: Configure yarn cache 31 | uses: actions/cache@v4 32 | with: 33 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 34 | key: ${{ env.CACHE_NAME_PREFIX }}-${{ runner.os }}-node-${{ env.NODE }}-${{ hashFiles('**/package.json') }}-${{ hashFiles('**/yarn.lock') }} 35 | 36 | - name: Print Yarn cache size 37 | run: du -d 0 -h ${{ steps.yarn-cache-dir-path.outputs.dir }} 38 | 39 | - name: Install dependencies 40 | run: yarn install 41 | 42 | - run: yarn install 43 | - run: yarn run build:module 44 | env: 45 | CI: false # on true webpack breaks on warnings, and we have them a lot 46 | NODE_ENV: 'production' 47 | 48 | # run http-server with build in background (will be killed after job ends) 49 | # do this only for master branch (so only for push event) 50 | # because pr can contain unfinished job 51 | - run: npx serve -l tcp://localhost:3000 build & 52 | if: github.event_name == 'push' 53 | 54 | - uses: actions/upload-artifact@master 55 | if: ${{ failure() }} 56 | with: 57 | name: e2e output 58 | path: e2e/output/ 59 | 60 | # upload this build as artifact to current Action 61 | - uses: actions/upload-artifact@master 62 | with: 63 | name: build ${{ github.event.pull_request.head.sha || github.sha }} 64 | path: build/ 65 | -------------------------------------------------------------------------------- /.github/workflows/eslint.yml: -------------------------------------------------------------------------------- 1 | name: Lint Code Base 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - 'lse-release/**' 8 | - 'ls-release/**' 9 | pull_request: 10 | types: 11 | - opened 12 | - synchronize 13 | - reopened 14 | - ready_for_review 15 | branches: 16 | - master 17 | - 'lse-release/**' 18 | - 'ls-release/**' 19 | 20 | env: 21 | NODE: 18 22 | CACHE_NAME_PREFIX: v1 23 | 24 | jobs: 25 | run-lint: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Checkout code 29 | uses: actions/checkout@v4 30 | with: 31 | fetch-depth: 0 32 | 33 | - uses: actions/setup-node@v4 34 | with: 35 | node-version: "${{ env.NODE }}" 36 | 37 | - name: Upgrade Yarn 38 | run: npm install -g yarn@1.22 39 | 40 | - name: Get yarn cache directory path 41 | id: yarn-cache-dir-path 42 | run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT 43 | 44 | - name: Configure yarn cache 45 | uses: actions/cache@v4 46 | with: 47 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 48 | key: ${{ env.CACHE_NAME_PREFIX }}-${{ runner.os }}-node-${{ env.NODE }}-${{ hashFiles('**/package.json') }}-${{ hashFiles('**/yarn.lock') }} 49 | 50 | - name: Print Yarn cache size 51 | run: du -d 0 -h ${{ steps.yarn-cache-dir-path.outputs.dir }} 52 | 53 | - name: Lint 54 | run: yarn install --frozen-lockfile && yarn lint 55 | -------------------------------------------------------------------------------- /.github/workflows/help-command.yml: -------------------------------------------------------------------------------- 1 | name: "/help command" 2 | 3 | on: 4 | repository_dispatch: 5 | types: [ help-command ] 6 | jobs: 7 | help: 8 | runs-on: ubuntu-latest 9 | timeout-minutes: 1 10 | steps: 11 | - name: Update comment if empty 12 | if: ${{ github.event.client_payload.slash_command.args.all == '' }} 13 | uses: peter-evans/create-or-update-comment@v4 14 | with: 15 | token: ${{ secrets.GIT_PAT }} 16 | repository: ${{ github.event.client_payload.github.payload.repository.full_name }} 17 | comment-id: ${{ github.event.client_payload.github.payload.comment.id }} 18 | body: | 19 | > Command | Description 20 | > --- | --- 21 | > /git [\ ...] | Actions with git. Type `/git help` for an additional help. 22 | reaction-type: hooray 23 | -------------------------------------------------------------------------------- /.github/workflows/pr-labeler.yml: -------------------------------------------------------------------------------- 1 | name: "PR labeler" 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - edited 8 | - reopened 9 | - synchronize 10 | - ready_for_review 11 | branches: 12 | - master 13 | - develop 14 | - 'lse-release/**' 15 | 16 | jobs: 17 | autolabel: 18 | name: "PR label validator" 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: hmarr/debug-action@v3.0.0 22 | - uses: thehanimo/pr-title-checker@v1.4.2 23 | name: "Validate PR's title" 24 | with: 25 | GITHUB_TOKEN: ${{ secrets.GIT_PAT }} 26 | pass_on_octokit_error: false 27 | configuration_path: ".github/pr-title-checker-config.json" 28 | - uses: release-drafter/release-drafter@v6.0.0 29 | name: "Set PR's label based on title" 30 | with: 31 | disable-releaser: true 32 | config-name: autolabeler.yml 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GIT_PAT }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | __pycache__/ 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # Dependency directory 12 | node_modules/ 13 | 14 | # Backend 15 | backend/env3/ 16 | backend/__pycache__ 17 | 18 | # IDEA 19 | .idea 20 | *.iml 21 | 22 | # VSCODE 23 | .vscode/ 24 | 25 | # Window 26 | Thumbs.db 27 | Desktop.ini 28 | 29 | # MAC 30 | .DS_Store 31 | 32 | # Env 33 | .env 34 | .env.local 35 | .env.development.local 36 | .env.test.local 37 | .env.production.local 38 | 39 | report.*.json 40 | 41 | # Testing 42 | coverage/ 43 | public/static/ 44 | 45 | # Examples of local env 46 | src/examples 47 | 48 | # XML configs 49 | build/static/media/config.*.xml 50 | 51 | # codecept screenshots 52 | e2e/output 53 | 54 | .cache 55 | dist 56 | build 57 | build-tmp 58 | .npmrc 59 | 60 | # actions-hub 61 | .github/actions-hub 62 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. 6 | 7 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. 8 | 9 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. 10 | 11 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 12 | 13 | This Code of Conduct is adapted from the Contributor Covenant, version 1.0.0, available at https://www.contributor-covenant.org/version/1/0/0/code-of-conduct.html 14 | -------------------------------------------------------------------------------- /bin/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # TODO all steps will be executed even if some steps will fail 4 | 5 | if [ -z $1 ]; then 6 | echo "Provide a new version as a first argument" 7 | exit 1 8 | fi 9 | 10 | VERSION=$1 11 | COMPANY="heartexlabs" 12 | REPO="datamanager" 13 | 14 | # Colors for colored output 15 | GREEN='\033[0;32m' 16 | NC='\033[0m' # No Color 17 | 18 | # Just to be sure 19 | git checkout master 20 | git pull 21 | 22 | # Create new build 23 | rm -rf build 24 | yarn build:module 25 | rm build/.gitignore 26 | 27 | # Replace links to published files in README to the actual one 28 | # `ls -tU` sorts files by creation date (recent is first) 29 | # `head -1` gets the first one (the recent) 30 | sed -E -e "s/main\..*js/$(cd build/static/js && ls -tU *.js | head -1)/"\ 31 | -e "s/main\..*css/$(cd build/static/css && ls -tU *.css | head -1)/"\ 32 | -e "s/[0-9]\.[0-9]+\.[0-9]+/$VERSION/"\ 33 | -i '' README.md 34 | git add README.md 35 | 36 | # Patch version 37 | sed -E -e "s/^ \"version\".*$/ \"version\": \"$VERSION\",/" -i '' package.json package-lock.json 38 | git add package.json package-lock.json 39 | 40 | echo && echo -e "${GREEN}### README and package.json modified successfully${NC}" && echo 41 | 42 | # Create release commit and tag and push them 43 | git commit -m "$VERSION" 44 | git tag v$VERSION 45 | 46 | git push origin master 47 | git push origin v$VERSION 48 | 49 | echo && echo -e "${GREEN}### Release commit and tag pushed to github${NC}" && echo 50 | 51 | # Remove prepublish step because we are using custom script 52 | sed -E -e "s/^ *\"prepublishOnly\".*$//" -i '' package.json 53 | 54 | # Authenticate within npmjs.com using Access Token from NPMJS_TOKEN 55 | echo "//registry.npmjs.org/:_authToken=${NPMJS_TOKEN}" > ".npmrc" 56 | 57 | # Publish the package 58 | npm publish 59 | 60 | echo && echo -e "${GREEN}### NPM package published${NC}" && echo 61 | 62 | # GitHub Packages requires scoped @company/repo name 63 | sed -E -e "s/^ \"name\".*$/ \"name\": \"@$COMPANY\/$REPO\",/" -i '' package.json 64 | 65 | # Authenticate within Github Packages using Personal Access Token 66 | echo "//npm.pkg.github.com/:_authToken=${GITHUB_PACKAGES_TOKEN}" > ".npmrc" 67 | 68 | # Publish the package 69 | npm publish --registry=https://npm.pkg.github.com/ 70 | 71 | echo && echo -e "${GREEN}### GitHub package published${NC}" && echo 72 | 73 | # Restore modified files 74 | git checkout -- package.json package-lock.json 75 | 76 | # clean up 77 | rm -rf build 78 | -------------------------------------------------------------------------------- /dev-server.js: -------------------------------------------------------------------------------- 1 | const WebpackDevServer = require('webpack-dev-server'); 2 | const webpack = require('webpack'); 3 | 4 | const config = require('./webpack.config-builder')({ 5 | withDevServer: true 6 | }); 7 | 8 | const port = 5000; 9 | 10 | config.entry.main.unshift( 11 | `webpack-dev-server/client?http://localhost:${port}/`, 12 | `webpack/hot/dev-server` 13 | ); 14 | 15 | const compiler = webpack(config); 16 | const server = new WebpackDevServer(compiler, config.devServer); 17 | 18 | server.listen(port, 'localhost', () => { 19 | console.log(`dev server listening on port ${port}`); 20 | }); 21 | -------------------------------------------------------------------------------- /docs/dm_architecture_diagram.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/dm2/28adbc7a7be2ff7378c5e4c50e71517b06c8a667/docs/dm_architecture_diagram.pdf -------------------------------------------------------------------------------- /docs/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/dm2/28adbc7a7be2ff7378c5e4c50e71517b06c8a667/docs/image.png -------------------------------------------------------------------------------- /src/assets/Running-24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/dm2/28adbc7a7be2ff7378c5e4c50e71517b06c8a667/src/assets/Running-24.gif -------------------------------------------------------------------------------- /src/assets/Running-24@2x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/dm2/28adbc7a7be2ff7378c5e4c50e71517b06c8a667/src/assets/Running-24@2x.gif -------------------------------------------------------------------------------- /src/assets/Running-48.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/dm2/28adbc7a7be2ff7378c5e4c50e71517b06c8a667/src/assets/Running-48.gif -------------------------------------------------------------------------------- /src/assets/Running-48@2x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/dm2/28adbc7a7be2ff7378c5e4c50e71517b06c8a667/src/assets/Running-48@2x.gif -------------------------------------------------------------------------------- /src/assets/Running-64.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/dm2/28adbc7a7be2ff7378c5e4c50e71517b06c8a667/src/assets/Running-64.gif -------------------------------------------------------------------------------- /src/assets/Running-64@2x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/dm2/28adbc7a7be2ff7378c5e4c50e71517b06c8a667/src/assets/Running-64@2x.gif -------------------------------------------------------------------------------- /src/assets/Running.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/dm2/28adbc7a7be2ff7378c5e4c50e71517b06c8a667/src/assets/Running.gif -------------------------------------------------------------------------------- /src/assets/icons/annotation.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/assets/icons/ban.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/icons/check_alt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/comment_check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/comment_red.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/cross_alt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/gear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/assets/icons/gear_new_menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/gear_new_ui.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/grid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/index.js: -------------------------------------------------------------------------------- 1 | export { ReactComponent as LsAnnotation } from './annotation.svg'; 2 | export { ReactComponent as LsBanSquare } from './ban.svg'; 3 | export { ReactComponent as LsCheckAlt } from "./check_alt.svg"; 4 | export { ReactComponent as LsCrossAlt } from "./cross_alt.svg"; 5 | export { ReactComponent as LsSparkSquare } from './spark.svg'; 6 | export { ReactComponent as LsThumbsDown } from './thumbs_down.svg'; 7 | export { ReactComponent as LsThumbsUp } from './thumbs_up.svg'; 8 | export { ReactComponent as LsStarSquare } from './star_square.svg'; 9 | export { ReactComponent as LSPlus } from './plus.svg'; 10 | export { ReactComponent as LsRefresh } from "./refresh.svg"; 11 | export { ReactComponent as LsGear } from "./gear.svg"; 12 | export { ReactComponent as LsGearNewUI } from "./gear_new_menu.svg"; 13 | export { ReactComponent as CommentCheck } from "./comment_check.svg"; 14 | export { ReactComponent as CommentRed } from "./comment_red.svg"; 15 | export { ReactComponent as LsGrid } from "./grid.svg"; 16 | export { ReactComponent as LsList } from "./list.svg"; 17 | export { ReactComponent as LsRefresh2 } from "./refresh2.svg"; -------------------------------------------------------------------------------- /src/assets/icons/list.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/refresh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/icons/refresh2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/spark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/star_square.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/thumbs_down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/thumbs_up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/running.js: -------------------------------------------------------------------------------- 1 | import Running24 from "!!url-loader!./Running-24.gif"; 2 | import Running24x2 from "!!url-loader!./Running-24@2x.gif"; 3 | import Running48 from "!!url-loader!./Running-48.gif"; 4 | import Running48x2 from "!!url-loader!./Running-48@2x.gif"; 5 | import Running64 from "!!url-loader!./Running-64.gif"; 6 | import Running64x2 from "!!url-loader!./Running-64@2x.gif"; 7 | import Running from "!!url-loader!./Running.gif"; 8 | 9 | const sizes = { 10 | full: { 11 | x1: Running, 12 | x2: Running, 13 | }, 14 | 24: { 15 | x1: Running24, 16 | x2: Running24x2, 17 | }, 18 | 48: { 19 | x1: Running48, 20 | x2: Running48x2, 21 | }, 22 | 64: { 23 | x1: Running64, 24 | x2: Running64x2, 25 | }, 26 | }; 27 | 28 | export default sizes; 29 | -------------------------------------------------------------------------------- /src/components/App/AntOverrides.styl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/dm2/28adbc7a7be2ff7378c5e4c50e71517b06c8a667/src/components/App/AntOverrides.styl -------------------------------------------------------------------------------- /src/components/App/App.js: -------------------------------------------------------------------------------- 1 | import { observer, Provider } from "mobx-react"; 2 | import React from "react"; 3 | import { SDKProvider } from "../../providers/SDKProvider"; 4 | import { Block, Elem } from "../../utils/bem"; 5 | import { Spinner } from "../Common/Spinner"; 6 | import { DataManager } from "../DataManager/DataManager"; 7 | import { Labeling } from "../Label/Label"; 8 | import "./App.styl"; 9 | 10 | class ErrorBoundary extends React.Component { 11 | state = { 12 | error: null, 13 | }; 14 | 15 | componentDidCatch(error) { 16 | this.setState({ error }); 17 | } 18 | 19 | render() { 20 | return this.state.error ? ( 21 |
{this.state.error}
22 | ) : ( 23 | this.props.children 24 | ); 25 | } 26 | } 27 | 28 | /** 29 | * Main Component 30 | * @param {{app: import("../../stores/AppStore").AppStore} param0 31 | */ 32 | const AppComponent = ({ app }) => { 33 | return ( 34 | 35 | 36 | 37 | 38 | {app.crashed ? ( 39 | 40 | Oops... 41 | 42 | Project has been deleted or not yet created. 43 | 44 | 45 | ) : app.loading ? ( 46 | 47 | 48 | 49 | ) : app.isLabeling ? ( 50 | 51 | ) : ( 52 | 53 | )} 54 | 55 | 56 | 57 | 58 | 59 | ); 60 | }; 61 | 62 | export const App = observer(AppComponent); 63 | -------------------------------------------------------------------------------- /src/components/App/App.styl: -------------------------------------------------------------------------------- 1 | .root 2 | height 100% 3 | box-sizing border-box 4 | background-color #f0f0f0 5 | font-family Roboto 6 | 7 | .tab-panel 8 | display flex 9 | padding 12px 16px 10 | background-color #fff 11 | justify-content space-between 12 | &_newUI 13 | padding 8px 14 | 15 | .grid 16 | flex 1 17 | 18 | .grid__item 19 | padding 10px 20 | justify-content space-between 21 | box-sizing border-box 22 | box-shadow -0.5px -0.5px 0 0.5px #ccc 23 | 24 | .container 25 | padding 2em 26 | width 90% 27 | height 600px 28 | margin-left auto 29 | margin-right auto 30 | 31 | .app-loader 32 | width 100% 33 | height 100% 34 | display flex 35 | align-items center 36 | justify-content center 37 | 38 | .ant-picker-cell 39 | padding 3px 0 !important 40 | border none !important 41 | 42 | .offscreen-lsf 43 | bottom 100% 44 | right 100% 45 | opacity 0 46 | position absolute 47 | width 100vw 48 | height 100vh 49 | background-color #fff 50 | z-index 100000 51 | 52 | .offscreen-lsf.visible 53 | bottom auto 54 | right auto 55 | top 0 56 | left 0 57 | opacity 1 58 | 59 | .fill-container 60 | flex 1 61 | display flex 62 | align-items center 63 | justify-content center 64 | 65 | .horizontal-shadow 66 | &::after 67 | top 100% 68 | width 100% 69 | left 0 70 | content "" 71 | position absolute 72 | height 4px 73 | background linear-gradient(to bottom, rgba(0, 0, 0, 0.15), transparent) 74 | 75 | .crash 76 | width 100vw 77 | height 100vh 78 | display flex 79 | align-items center 80 | justify-content center 81 | flex-direction column 82 | 83 | &__header 84 | font-size 22px 85 | font-weight 500 86 | 87 | &__description 88 | margin-top 32px 89 | font-size 16px 90 | -------------------------------------------------------------------------------- /src/components/CellViews/Agreement/Agreement.js: -------------------------------------------------------------------------------- 1 | import { Block, Elem } from '../../../utils/bem'; 2 | import { isDefined } from "../../../utils/utils"; 3 | import "./Agreement.styl"; 4 | 5 | const agreement = (p) => { 6 | if (!isDefined(p)) return "zero"; 7 | if (p < 33) return "low"; 8 | if (p < 66) return "medium"; 9 | return "high"; 10 | }; 11 | 12 | const formatNumber = (num) => { 13 | const number = Number(num); 14 | 15 | if (num % 1 === 0) { 16 | return number; 17 | } else { 18 | return number.toFixed(2); 19 | } 20 | }; 21 | 22 | export const Agreement = (column) => { 23 | return ( 24 | 25 | 26 | {isDefined(column.value) ? `${formatNumber(column.value)}%` : ""} 27 | 28 | 29 | ); 30 | }; 31 | 32 | Agreement.userSelectable = false; 33 | -------------------------------------------------------------------------------- /src/components/CellViews/Agreement/Agreement.styl: -------------------------------------------------------------------------------- 1 | .agreement 2 | &__score 3 | &_zero 4 | color: #000 5 | 6 | &_low 7 | color: #DD0000 8 | 9 | &_medium 10 | color: #D98F00 11 | 12 | &_high 13 | color: #419400 14 | -------------------------------------------------------------------------------- /src/components/CellViews/Annotators/Annotators.styl: -------------------------------------------------------------------------------- 1 | .annotators 2 | display grid 3 | grid-auto-flow column 4 | grid-column-gap 4px 5 | 6 | &__item 7 | z-index 1 8 | width 28px 9 | height 28px 10 | border-radius 100% 11 | position relative 12 | 13 | &:hover 14 | z-index 10 15 | 16 | .userpic-badge 17 | width 10px 18 | height 10px 19 | color #fff 20 | display flex 21 | align-items center 22 | justify-content center 23 | border-radius 2px 24 | box-shadow 0 0 0 2px #fff 25 | 26 | &_accepted 27 | background-color #64BF00 28 | 29 | &_rejected 30 | background-color #DD0000 31 | 32 | &_fixed 33 | background-color #FA8C16 34 | -------------------------------------------------------------------------------- /src/components/CellViews/AudioCell.js: -------------------------------------------------------------------------------- 1 | import { MediaPlayer } from "../Common/MediaPlayer/MediaPlayer"; 2 | 3 | export const AudioCell = (column) => { 4 | return ; 5 | }; 6 | 7 | AudioCell.style = { 8 | width: 50, 9 | minWidth: 240, 10 | }; 11 | 12 | /* Audio Plus */ 13 | 14 | export const AudioPlusCell = (column) => { 15 | return ; 16 | }; 17 | 18 | AudioPlusCell.style = { 19 | width: 240, 20 | minWidth: 240, 21 | }; 22 | 23 | AudioPlusCell.userSelectable = false; 24 | -------------------------------------------------------------------------------- /src/components/CellViews/BooleanCell.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Tag } from "../Common/Tag/Tag"; 3 | 4 | const parseBoolean = (value) => { 5 | if ([true, 1, "true", "1", "yes"].includes(value) || !!value === true) { 6 | return true; 7 | } else { 8 | return false; 9 | } 10 | }; 11 | 12 | export const BooleanCell = (column) => { 13 | const boolValue = parseBoolean(column.value); 14 | 15 | if (boolValue === true) { 16 | return true; 17 | } else if (boolValue === false) { 18 | return false; 19 | } 20 | 21 | return null; 22 | }; 23 | 24 | BooleanCell.userSelectable = false; 25 | -------------------------------------------------------------------------------- /src/components/CellViews/DateTimeCell.js: -------------------------------------------------------------------------------- 1 | import { format, isValid } from "date-fns"; 2 | export const dateTimeFormat = "MMM dd yyyy, HH:mm:ss"; 3 | 4 | export const DateTimeCell = (column) => { 5 | const date = new Date(column.value); 6 | 7 | return column.value ? ( 8 |
9 | {isValid(date) ? format(date, dateTimeFormat) : ""} 10 |
11 | ) : ( 12 | "" 13 | ); 14 | }; 15 | 16 | DateTimeCell.displayType = false; 17 | -------------------------------------------------------------------------------- /src/components/CellViews/ImageCell.js: -------------------------------------------------------------------------------- 1 | import { getRoot } from "mobx-state-tree"; 2 | import { FF_LSDV_4711, isFF } from "../../utils/feature-flags"; 3 | import { AnnotationPreview } from "../Common/AnnotationPreview/AnnotationPreview"; 4 | 5 | const imgDefaultProps = {}; 6 | 7 | if (isFF(FF_LSDV_4711)) imgDefaultProps.crossOrigin = 'anonymous'; 8 | 9 | export const ImageCell = (column) => { 10 | const { 11 | original, 12 | value, 13 | column: { alias }, 14 | } = column; 15 | const root = getRoot(original); 16 | 17 | const renderImagePreview = original.total_annotations === 0 || !root.showPreviews; 18 | const imgSrc = Array.isArray(value) ? value[0] : value; 19 | 20 | if (!imgSrc) return null; 21 | 22 | return renderImagePreview ? ( 23 | Data 35 | ) : ( 36 | 50 | ); 51 | }; 52 | -------------------------------------------------------------------------------- /src/components/CellViews/NumberCell.js: -------------------------------------------------------------------------------- 1 | import { isDefined } from "../../utils/utils"; 2 | 3 | const formatNumber = (num) => { 4 | const number = Number(num); 5 | 6 | if (num % 1 === 0) { 7 | return number; 8 | } else { 9 | return number.toFixed(3); 10 | } 11 | }; 12 | 13 | export const NumberCell = (column) => 14 | isDefined(column.value) ? formatNumber(column.value) : ""; 15 | 16 | // NumberCell.userSelectable = false; 17 | -------------------------------------------------------------------------------- /src/components/CellViews/ProjectCell.js: -------------------------------------------------------------------------------- 1 | import { getRoot } from "mobx-state-tree"; 2 | import { Fragment } from "react"; 3 | 4 | const ProjectLink = ({ project }) => { 5 | const projectID = project.id; 6 | const onClick = (e) => { 7 | e.stopPropagation(); 8 | 9 | }; 10 | 11 | return ( 12 | 13 | {project.title} 14 | 15 | ); 16 | }; 17 | 18 | export const ProjectCell = (cell) => { 19 | const { original, value } = cell; 20 | const root = getRoot(original); 21 | const projectList = value 22 | .map(projectRef => root.taskStore.associatedList.find(proj => proj.id === projectRef.project_id)) 23 | .filter(Boolean); 24 | 25 | return ( 26 |
34 | {projectList && ( 35 | projectList 36 | .map((projectRef, index) => ( 37 | 38 | {index > 0 && ", "} 39 | 40 | 41 | )) 42 | )} 43 |
44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /src/components/CellViews/StringCell.js: -------------------------------------------------------------------------------- 1 | import { format, isValid } from "date-fns"; 2 | import { dateTimeFormat } from "./DateTimeCell"; 3 | 4 | export const valueToString = (value) => { 5 | if (typeof value === "string") return value; 6 | /* if undefined or null we'll treat it as empty string */ 7 | if (value === undefined || value === null) return ""; 8 | if (value instanceof Date && isValid(value)) return format(value, dateTimeFormat); 9 | 10 | try { 11 | /* JSON.stringify will handle JSON and non-strings, non-null, non-undefined */ 12 | return JSON.stringify(value); 13 | } catch { 14 | return 'Error: Invalid JSON'; 15 | } 16 | }; 17 | 18 | export const StringCell = ({ value }) => { 19 | const style = { 20 | maxHeight: "100%", 21 | overflow: "hidden", 22 | fontSize: 12, 23 | lineHeight: "16px", 24 | }; 25 | 26 | return ( 27 |
28 | {valueToString(value)} 29 |
30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /src/components/CellViews/VideoCell.js: -------------------------------------------------------------------------------- 1 | import { MediaPlayer } from "../Common/MediaPlayer/MediaPlayer"; 2 | 3 | export const VideoCell = (column) => { 4 | return ; 5 | }; 6 | 7 | VideoCell.style = { 8 | width: 240, 9 | minWidth: 240, 10 | }; 11 | 12 | -------------------------------------------------------------------------------- /src/components/CellViews/index.js: -------------------------------------------------------------------------------- 1 | export { Agreement } from './Agreement/Agreement'; 2 | export { 3 | Annotators, 4 | Annotators as Reviewers, 5 | Annotators as UpdatedBy, 6 | Annotators as CommentAuthors 7 | } from './Annotators/Annotators'; 8 | export { AudioCell as Audio, AudioPlusCell as AudioPlus } from "./AudioCell"; 9 | export { BooleanCell as Boolean } from "./BooleanCell"; 10 | export { DateTimeCell as Date, DateTimeCell as Datetime } from "./DateTimeCell"; 11 | export { ImageCell as Image } from "./ImageCell"; 12 | export { NumberCell as Number } from "./NumberCell"; 13 | export { StringCell as String } from "./StringCell"; 14 | export { StringCell as Text } from "./StringCell"; 15 | export { VideoCell as Video } from "./VideoCell"; 16 | export { ProjectCell as Project } from './ProjectCell'; 17 | 18 | -------------------------------------------------------------------------------- /src/components/Common/AnnotationPreview/AnnotationPreview.styl: -------------------------------------------------------------------------------- 1 | .annotation-preview 2 | width 100% 3 | height 100% 4 | display flex 5 | align-items center 6 | justify-content center 7 | position relative 8 | overflow hidden 9 | -------------------------------------------------------------------------------- /src/components/Common/Badge/Badge.js: -------------------------------------------------------------------------------- 1 | import { Block } from "../../../utils/bem"; 2 | import "./Badge.styl"; 3 | 4 | export const Badge = ({ children, size, className, color, style }) => { 5 | return ( 6 | 12 | {children} 13 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/Common/Badge/Badge.styl: -------------------------------------------------------------------------------- 1 | .badge 2 | height 24px 3 | font-size 16px 4 | border-radius 30px 5 | display inline-flex 6 | align-items center 7 | justify-content center 8 | padding 0 7px 9 | color #fff 10 | background-color $accent_color 11 | 12 | &_size_small 13 | height 16px 14 | font-size 12px 15 | padding 0 5px 16 | 17 | &_size_medium 18 | height 24px 19 | font-size 16px 20 | padding 0 7px 21 | 22 | &_size_large 23 | height 32px 24 | font-size 18px 25 | padding 0 10px 26 | -------------------------------------------------------------------------------- /src/components/Common/Button/Button.js: -------------------------------------------------------------------------------- 1 | import React, { cloneElement, forwardRef, useMemo } from "react"; 2 | import { Block, Elem } from "../../../utils/bem"; 3 | import { FF_LOPS_E_10, isFF } from "../../../utils/feature-flags"; 4 | import { isDefined } from "../../../utils/utils"; 5 | import "./Button.styl"; 6 | 7 | export const Button = forwardRef( 8 | ( 9 | { 10 | children, 11 | type, 12 | extra, 13 | className, 14 | href, 15 | size, 16 | waiting, 17 | icon, 18 | tag, 19 | look, 20 | ...rest 21 | }, 22 | ref, 23 | ) => { 24 | const finalTag = tag ?? href ? "a" : "button"; 25 | 26 | const mods = { 27 | size, 28 | waiting, 29 | type, 30 | look, 31 | withIcon: !!icon, 32 | withExtra: !!extra, 33 | disabled: !!rest.disabled, 34 | newUI: isFF(FF_LOPS_E_10), 35 | }; 36 | 37 | const iconElem = useMemo(() => { 38 | if (!icon) return null; 39 | 40 | switch (size) { 41 | case "small": 42 | return cloneElement(icon, { ...icon.props, size: 12 }); 43 | case "compact": 44 | return cloneElement(icon, { ...icon.props, size: 14 }); 45 | default: 46 | return icon; 47 | } 48 | }, [icon, size]); 49 | 50 | return ( 51 | 60 | <> 61 | {isDefined(iconElem) && ( 62 | 63 | {iconElem ?? null} 64 | 65 | )} 66 | {isDefined(iconElem) && isDefined(children) ? ( 67 | {children} 68 | ) : (children ?? null)} 69 | {isDefined(extra) ? {extra} : null} 70 | 71 | 72 | ); 73 | }, 74 | ); 75 | Button.displayName = "Button"; 76 | 77 | Button.Group = ({ className, children, collapsed, ...rest }) => { 78 | return ( 79 | 80 | {children} 81 | 82 | ); 83 | }; 84 | -------------------------------------------------------------------------------- /src/components/Common/Checkbox/Checkbox.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { cn } from "../../../utils/bem"; 3 | import "./Checkbox.styl"; 4 | 5 | export const Checkbox = ({ 6 | checked, 7 | indeterminate, 8 | style, 9 | onChange, 10 | children, 11 | ...props 12 | }) => { 13 | const rootClass = cn("checkbox"); 14 | const checkboxRef = React.createRef(); 15 | const withLabel = !!children; 16 | 17 | React.useEffect(() => { 18 | checkboxRef.current.indeterminate = indeterminate; 19 | }, [checkboxRef, indeterminate]); 20 | 21 | const checkboxContent = ( 22 | 23 | { 30 | onChange?.(e); 31 | }} 32 | /> 33 | 36 | 37 | ); 38 | 39 | return ( 40 |
44 | {children ? ( 45 | 48 | ) : ( 49 | checkboxContent 50 | )} 51 |
52 | ); 53 | }; 54 | -------------------------------------------------------------------------------- /src/components/Common/Checkbox/Checkbox.styl: -------------------------------------------------------------------------------- 1 | $check-icon = "data:image/svg+xml;charset=UTF-8,%3csvg width='13' height='10' viewBox='0 0 13 10' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M1.5 4.5L5 8L11 2' stroke='black' stroke-width='2' stroke-linecap='square'/%3e%3c/svg%3e" 2 | 3 | .checkbox 4 | display inline-flex 5 | align-items center 6 | justify-content center 7 | 8 | &__box 9 | overflow hidden 10 | position relative 11 | white-space nowrap 12 | display inline-block 13 | max-width 18px 14 | max-height 18px 15 | cursor pointer 16 | margin 2px 17 | 18 | &__input 19 | top 0 20 | left 0 21 | margin 0 22 | padding 0 23 | opacity 0 24 | width 100% 25 | z-index 10 26 | height 100% 27 | border none 28 | position absolute 29 | 30 | &__check 31 | width 18px 32 | height 18px 33 | background #fff 34 | will-change all 35 | border-radius 4px 36 | position relative 37 | display inline-block 38 | transition all 80ms ease 39 | box-shadow inset 0px -1px 0px rgba(0, 0, 0, 0.1), inset 0px 0px 0px 1px rgba(0, 0, 0, 0.15) 40 | 41 | &__check::before, 42 | &__check::after 43 | top 0 44 | left 0 45 | bottom 0 46 | right 0 47 | display block 48 | content '' 49 | opacity 0 50 | position absolute 51 | 52 | &__check::before 53 | background-image url($check-icon) 54 | background-repeat no-repeat 55 | background-position center 56 | transition all 120ms ease 57 | 58 | &__check::after 59 | top 4px 60 | left 4px 61 | bottom 4px 62 | right 4px 63 | border-radius 2px 64 | background-color #009afe 65 | 66 | &__check 67 | &_checked::before 68 | opacity 1 69 | 70 | &_indeterminate::after 71 | opacity 1 72 | 73 | &__label 74 | width 100% 75 | display flex 76 | align-items center 77 | 78 | &:hover &__check 79 | background-color #efefef 80 | 81 | &__input:checked + &__check::before 82 | opacity 1 83 | 84 | &_withLabel &__box 85 | margin-right 5px 86 | 87 | -------------------------------------------------------------------------------- /src/components/Common/DatePicker/DatePicker.global.styl: -------------------------------------------------------------------------------- 1 | .react-datepicker 2 | border none 3 | display flex 4 | width 100% 5 | height 100% 6 | max-height 290px 7 | 8 | &__time 9 | flex 1 10 | height calc(100% - 84px) 11 | 12 | &, 13 | &-container, 14 | &-box 15 | display flex 16 | 17 | &-container, 18 | &-box 19 | flex-direction column 20 | 21 | &-list 22 | flex 1 23 | 24 | &__day 25 | &--outside-month 26 | opacity 0.5 27 | -------------------------------------------------------------------------------- /src/components/Common/DatePicker/DatePicker.styl: -------------------------------------------------------------------------------- 1 | .datepicker 2 | &__output 3 | width 100% 4 | display grid 5 | grid-auto-columns 1fr 6 | grid-auto-flow column 7 | 8 | .input 9 | min-width 110px 10 | text-align center 11 | 12 | &_range 13 | grid-auto-columns 1fr 34px 1fr 14 | 15 | &__separator 16 | margin 0 10px 17 | display flex 18 | align-items center 19 | 20 | -------------------------------------------------------------------------------- /src/components/Common/Dropdown/Dropdown.js: -------------------------------------------------------------------------------- 1 | import { Dropdown } from "./DropdownComponent"; 2 | import { DropdownTrigger } from "./DropdownTrigger"; 3 | 4 | Dropdown.Trigger = DropdownTrigger; 5 | 6 | export { Dropdown }; 7 | -------------------------------------------------------------------------------- /src/components/Common/Dropdown/Dropdown.styl: -------------------------------------------------------------------------------- 1 | .dropdown 2 | --menu-animation-duration: 0.15s; 3 | --menu-animation-curve: cubic-bezier(0.21, 1.04, 0.68, 1); 4 | --menu-animation-start: -10px; 5 | 6 | top calc(100% + 1px) 7 | z-index 500 8 | display none 9 | position absolute 10 | box-sizing border-box 11 | background-color #fff 12 | box-shadow 0 5px 20px rgba(0,0,0,0.2) 13 | will-change transform, opacity 14 | 15 | &_align 16 | &_left 17 | left -20px 18 | 19 | &_right 20 | right -20px 21 | 22 | &__trigger 23 | position relative 24 | 25 | &.before-appear, 26 | &.before-disappear 27 | transition-property opacity, transform 28 | transition-duration var(--menu-animation-duration) 29 | transition-iming-function var(--menu-animation-curve) 30 | 31 | // Animation transition handling 32 | &.before-appear 33 | opacity 0 34 | display flex 35 | transform translate3d(0, var(--menu-animation-start), 0) 36 | 37 | &.appear 38 | opacity 1 39 | transform translate3d(0, 0, 0) 40 | 41 | &.visible 42 | opacity 1 43 | display flex 44 | 45 | &.before-disappear 46 | opacity 1 47 | display flex 48 | transform translate3d(0, 0, 0) 49 | 50 | &.disappear 51 | opacity 0 52 | transform translate3d(0, var(--menu-animation-start), 0) 53 | z-index -1 !important 54 | -------------------------------------------------------------------------------- /src/components/Common/Dropdown/DropdownContext.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const DropdownContext = React.createContext(); 4 | -------------------------------------------------------------------------------- /src/components/Common/ErrorBox.js: -------------------------------------------------------------------------------- 1 | import { inject } from "mobx-react"; 2 | import React from "react"; 3 | import { RiErrorWarningFill } from "react-icons/ri"; 4 | import { Button } from "./Button/Button"; 5 | import { Dropdown } from "./Dropdown/Dropdown"; 6 | import { Menu } from "./Menu/Menu"; 7 | 8 | const ErrorRenderer = (error, i) => { 9 | return ( 10 | 11 | {error.response?.detail} 12 | 13 | ); 14 | }; 15 | 16 | const injector = inject(({ store }) => { 17 | return { 18 | errors: store.serverErrors, 19 | }; 20 | }); 21 | 22 | export const ErrorBox = injector(({ errors }) => { 23 | return errors?.size > 0 ? ( 24 | {Array.from(errors.values()).map(ErrorRenderer)}} 26 | > 27 | 45 | 46 | ) : null; 47 | }); 48 | -------------------------------------------------------------------------------- /src/components/Common/FiltersPane.js: -------------------------------------------------------------------------------- 1 | import { inject, observer } from "mobx-react"; 2 | import React, { useEffect, useRef } from "react"; 3 | import { FaAngleDown, FaChevronDown } from "react-icons/fa"; 4 | import { FF_LOPS_E_10, isFF } from "../../utils/feature-flags"; 5 | import { Filters } from "../Filters/Filters"; 6 | import { Badge } from "./Badge/Badge"; 7 | import { Button } from "./Button/Button"; 8 | import { Dropdown } from "./Dropdown/Dropdown"; 9 | 10 | const buttonInjector = inject(({ store }) => { 11 | const { viewsStore, currentView } = store; 12 | 13 | return { 14 | viewsStore, 15 | sidebarEnabled: viewsStore?.sidebarEnabled ?? false, 16 | activeFiltersNumber: currentView?.filtersApplied ?? false, 17 | }; 18 | }); 19 | 20 | export const FiltersButton = buttonInjector(observer( 21 | React.forwardRef(({ activeFiltersNumber, size, sidebarEnabled, viewsStore, ...rest }, ref) => { 22 | const hasFilters = activeFiltersNumber > 0; 23 | 24 | return ( 25 | 40 | ); 41 | }), 42 | )); 43 | 44 | const injector = inject(({ store }) => { 45 | return { 46 | sidebarEnabled: store?.viewsStore?.sidebarEnabled ?? false, 47 | }; 48 | }); 49 | 50 | export const FiltersPane = injector( 51 | observer(({ sidebarEnabled, size, ...rest }) => { 52 | const dropdown = useRef(); 53 | 54 | useEffect(() => { 55 | if (sidebarEnabled === true) { 56 | dropdown?.current?.close(); 57 | } 58 | }, [sidebarEnabled]); 59 | 60 | return ( 61 | } 65 | > 66 | 67 | 68 | ); 69 | }), 70 | ); 71 | -------------------------------------------------------------------------------- /src/components/Common/Form/Elements/Counter/Counter.styl: -------------------------------------------------------------------------------- 1 | .counter 2 | width 114px 3 | height 40px 4 | display flex 5 | position relative 6 | min-width 114px 7 | border-radius 8px 8 | background #FAFAFA 9 | box-sizing border-box 10 | box-shadow 0 0 0 1px $black_15 inset 11 | transition box-shadow 80ms ease 12 | 13 | &_disabled 14 | opacity 0.6 15 | background-color #efefef 16 | 17 | &__btn 18 | min-width 32px 19 | min-height 32px 20 | margin 4px 21 | border-radius 5px 22 | border-radius 4px 23 | background #FFFFFF 24 | display flex 25 | color $accent_color 26 | border none 27 | outline none 28 | align-items center 29 | justify-content center 30 | transition all 80ms ease 31 | box-shadow 0px 1px 0px $black_10, 0px 0px 0px 1px $black_2, 0px 5px 10px $black_15 32 | 33 | &_disabled 34 | box-shadow none 35 | background none 36 | pointer-events none 37 | color rgba(#000, 0.2) 38 | 39 | &:active 40 | background #fcfcfc 41 | box-shadow 0px 1px 0px $black_10, 0px 0px 0px 1px $black_2, 0px 2px 2px $black_15 42 | 43 | &:active, &:hover 44 | color $accent_color 45 | 46 | &__input 47 | flex 1 48 | width 100% 49 | border none 50 | padding 0 51 | z-index 2 52 | background none 53 | text-align center 54 | position relative 55 | outline none 56 | font-size 16px 57 | line-height 22px 58 | 59 | &_under 60 | left 40px 61 | right 40px 62 | height 100% 63 | width auto 64 | display flex 65 | align-items center 66 | z-index 1 67 | opacity 0.6 68 | position absolute 69 | 70 | &_withPostfix 71 | text-align left 72 | padding 0 16px 73 | 74 | -------------------------------------------------------------------------------- /src/components/Common/Form/Elements/Input/Input.js: -------------------------------------------------------------------------------- 1 | import { cn } from '../../../../../utils/bem'; 2 | import { FF_LOPS_E_10, isFF } from '../../../../../utils/feature-flags'; 3 | import { FormField } from '../../FormField'; 4 | import { default as Label } from '../Label/Label'; 5 | import './Input.styl'; 6 | 7 | const Input = ({ label, className, validate, required, skip, labelProps, ghost, ...props }) => { 8 | const mods = { 9 | ghost, 10 | newUI: isFF(FF_LOPS_E_10), 11 | }; 12 | const classList = [ 13 | cn('form-input').mod(mods), 14 | className, 15 | ].join(" ").trim(); 16 | 17 | const input = ( 18 | 26 | {({ ref }) => ( 27 | 28 | )} 29 | 30 | ); 31 | 32 | return label ? : input; 33 | }; 34 | 35 | export default Input; 36 | -------------------------------------------------------------------------------- /src/components/Common/Form/Elements/Input/Input.styl: -------------------------------------------------------------------------------- 1 | .form-input, 2 | .form-select, 3 | .textarea 4 | height 40px 5 | min-height 40px 6 | background #FAFAFA 7 | font-size 16px 8 | line-height 22px 9 | border 1px solid $black_15 10 | box-sizing border-box 11 | border-radius 5px 12 | padding 0 16px 13 | transition box-shadow 80ms ease 14 | 15 | &_newUI 16 | border-color rgba(137, 128, 152, 0.16) 17 | 18 | &_ghost 19 | border none 20 | padding 0 21 | background-color transparent 22 | outline none 23 | 24 | &_size 25 | &_small 26 | height 24px 27 | font-size 12px 28 | line-height 18px 29 | min-height 24px 30 | padding 0 8px 31 | 32 | input.form-input[type=radio] 33 | width: 16px 34 | height: 16px 35 | min-height: 0 36 | 37 | input.form-input[type=range] 38 | padding 0 39 | 40 | .textarea 41 | padding 12px 16px 42 | min-height 50px 43 | -------------------------------------------------------------------------------- /src/components/Common/Form/Elements/Label/Label.js: -------------------------------------------------------------------------------- 1 | import { forwardRef } from 'react'; 2 | import { Block, Elem } from '../../../../../utils/bem'; 3 | import './Label.styl'; 4 | 5 | const Label = forwardRef(({ 6 | text, 7 | children, 8 | required, 9 | placement, 10 | description, 11 | size, 12 | large, 13 | style, 14 | simple, 15 | flat, 16 | }, ref) => { 17 | const tagName = simple ? 'div' : 'label'; 18 | const mods = { 19 | size, 20 | large, 21 | flat, 22 | placement, 23 | withDescription: !!description, 24 | empty: !children, 25 | }; 26 | 27 | return ( 28 | 29 | 30 | 31 | {text} 32 | {description && {description}} 33 | 34 | 35 | {children} 36 | 37 | ); 38 | }); 39 | 40 | export default Label; 41 | -------------------------------------------------------------------------------- /src/components/Common/Form/Elements/Label/Label.styl: -------------------------------------------------------------------------------- 1 | .label 2 | margin-bottom 0 3 | 4 | &__text 5 | padding 0 16px 6 | height 22px 7 | display flex 8 | margin-bottom 4px 9 | font-size 14px 10 | line-height 22px 11 | justify-content space-between 12 | 13 | &__description 14 | margin-top 5px 15 | font-size 14px 16 | line-height 22px 17 | color $black_40 18 | 19 | &__field 20 | line-height 0 21 | 22 | &_size_large &__text 23 | font-weight 500 24 | font-size 16px 25 | line-height 22px 26 | margin-bottom 16px 27 | 28 | &_flat &__text 29 | padding 0 30 | 31 | .input, .select, .textarea 32 | width 100% 33 | 34 | &[data-required] &__text::after 35 | content "Required" 36 | color $black_20 37 | 38 | &_large &__text 39 | font-size 16px 40 | font-weight 500 41 | margin-bottom 16px 42 | 43 | &_placement_right 44 | display inline-flex 45 | flex-direction row-reverse 46 | &_placement_left 47 | display inline-flex 48 | 49 | &_empty &__text, 50 | &_placement_right &__text 51 | &_placement_left &__text 52 | margin-bottom 0 53 | font-size 16px 54 | line-height 22px 55 | height auto 56 | align-items center 57 | 58 | &_placement_right:not(&_withDescription) &__field 59 | &_placement_left:not(&_withDescription) &__field 60 | display flex 61 | align-items center 62 | 63 | &_placement_right&_withDescription &__field 64 | &_placement_left&_withDescription &__field 65 | margin-top 5px 66 | -------------------------------------------------------------------------------- /src/components/Common/Form/Elements/RadioGroup/RadioGroup.styl: -------------------------------------------------------------------------------- 1 | .radio-group 2 | --radius 8px 3 | --height 32px 4 | --padding 4px 5 | --font-size 16px 6 | --button-padding 0 10px 7 | --button-checked-shadow 0px 1px 0px rgba(0, 0, 0, 0.1), 0px 0px 0px 1px rgba(0, 0, 0, 0.02), 0px 5px 10px rgba(0, 0, 0, 0.15) 8 | 9 | height var(--height) 10 | border-radius var(--radius) 11 | padding var(--padding) 12 | background linear-gradient(0deg, rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.05)), #FFFFFF 13 | box-shadow inset 0px 1px 0px rgba(0, 0, 0, 0.05), inset 0px 0px 0px 1px rgba(0, 0, 0, 0.05) 14 | box-sizing border-box 15 | 16 | &__buttons 17 | height calc(var(--height) - calc(var(--padding) * 2)) 18 | display grid 19 | grid-auto-columns 1fr 20 | grid-auto-flow column 21 | 22 | ~/_simple & 23 | all unset 24 | display inline-block 25 | margin-bottom 16px 26 | 27 | ~/_horizontal & 28 | display grid 29 | grid-auto-columns min-content 30 | column-gap 16px 31 | align-items center 32 | grid-auto-flow column 33 | margin 0 34 | 35 | &__button 36 | display flex 37 | opacity 0.6 38 | padding var(--button-padding) 39 | cursor pointer 40 | font-weight 500 41 | position relative 42 | text-align center 43 | align-items center 44 | justify-content center 45 | font-size var(--font-size) 46 | border-radius calc(var(--radius) - var(--padding)) 47 | height calc(var(--height) - calc(var(--padding) * 2)) 48 | 49 | &_checked 50 | opacity 1 51 | background-color #fff 52 | box-shadow: var(--button-checked-shadow) 53 | 54 | &_disabled 55 | opacity 0.3 56 | cursor not-allowed 57 | 58 | ~/_simple & 59 | all unset 60 | display block 61 | margin-bottom 16px 62 | 63 | ~/_horizontal & 64 | margin 0 65 | 66 | 67 | &__input 68 | top 0 69 | left 0 70 | opacity 0 71 | width 100% 72 | height 100% 73 | position absolute 74 | 75 | &_size 76 | &_large 77 | --height 40px 78 | --radius 8px 79 | 80 | &_compact 81 | --height 32px 82 | --radius 8px 83 | 84 | &_small 85 | --height 24px 86 | --radius 4px 87 | --padding 2px 88 | --font-size 12px 89 | --button-padding 0 5px 90 | --button-checked-shadow 0px 1px 0px rgba(0, 0, 0, 0.1), 0px 0px 0px 1px rgba(0, 0, 0, 0.02), 0px 2px 4px rgba(0, 0, 0, 0.15) 91 | 92 | &_simple 93 | --height auto 94 | 95 | all unset 96 | display block 97 | -------------------------------------------------------------------------------- /src/components/Common/Form/Elements/Range/Range.js: -------------------------------------------------------------------------------- 1 | import { forwardRef } from "react"; 2 | import { Range } from "../../../Range/Range"; 3 | import { FormField } from "../../FormField"; 4 | import { useValueTracker } from "../../Utils"; 5 | import { default as Label } from "../Label/Label"; 6 | 7 | const RangeField = forwardRef(({ label, className, validate, required, skip, labelProps, description, ...props }, ref) => { 8 | const [value, setValue] = useValueTracker(props.value, props.defaultValue); 9 | 10 | const formField = ( 11 | setValue(val)} 19 | {...props} 20 | > 21 | {({ ref, context }) => ( 22 | { 27 | context.autosubmit(); 28 | props.onChange?.(e); 29 | }} 30 | value={value} 31 | /> 32 | )} 33 | 34 | ); 35 | 36 | return label ? ( 37 |