├── .all-contributorsrc ├── .babelrc ├── .dockerignore ├── .env.example ├── .eslintrc.json ├── .flowconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── build-desktop.yml │ ├── release-on-master.yml │ └── test.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── .releaserc.js ├── .storybook ├── addons.js ├── config.js └── storybook.css ├── CNAME ├── CONTRIBUTING.md ├── CREATING_PLUGINS.md ├── Dockerfile ├── LICENSE ├── README.md ├── Singularity ├── add-preloaded-app-config.js ├── cypress.json ├── cypress ├── .gitignore ├── README.md ├── fixtures │ ├── assets-dummies │ │ ├── audio.mp3 │ │ ├── image1.jpg │ │ ├── image2.jpg │ │ ├── pdf1.pdf │ │ ├── pdf2.pdf │ │ ├── text1.txt │ │ ├── text2.txt │ │ ├── text3.txt │ │ ├── timeSeries.json │ │ └── video.mp4 │ ├── cat.jpg │ └── samples-dummies │ │ ├── AudioTranscription.json │ │ ├── Composite.json │ │ ├── DataEntry.json │ │ ├── Empty.json │ │ ├── ImageClassification.json │ │ ├── ImageLandmark.json │ │ ├── ImageSegmentation.json │ │ ├── NamedEntityRecognition.json │ │ ├── NotSupported.json │ │ ├── PixelSegmentation.json │ │ ├── TextClassification.json │ │ ├── TextEntityRelations.json │ │ ├── TimeSeriesAudioUrl.json │ │ ├── TimeSeriestimeData.json │ │ └── VideoSegmentation.json ├── integration │ ├── aws-test │ │ ├── credentials.spec.js │ │ ├── export.spec.js │ │ └── import.spec.js │ ├── collaborative-session.spec.js │ ├── udt-test.spec.js │ └── utils │ │ ├── aws-unit-test │ │ └── warning-header-export.js │ │ ├── credentials-test │ │ ├── enter-credentials-cognito-s3.js │ │ └── enter-credentials-user.js │ │ ├── cypress-command │ │ ├── add-asset-to-aws-project.js │ │ ├── add-project-to-aws.js │ │ ├── clean-aws.js │ │ ├── credentials-aws.js │ │ ├── local-storage.js │ │ ├── remove-aws-project.js │ │ ├── set-language.js │ │ ├── set-template.js │ │ └── set-up-aws.js │ │ ├── function-test │ │ └── get-data-url-type.js │ │ ├── go-to-import-page.js │ │ ├── interface-test │ │ ├── click-on-100-samples-in-a-collaborative-session.js │ │ ├── create-and-visit-collaborative-session.js │ │ ├── create-new-file.js │ │ ├── default-template.js │ │ ├── image-classification.js │ │ ├── image-segmentation.js │ │ ├── keyboard-shortcuts.js │ │ ├── language-box.js │ │ ├── named-entity-recognition.js │ │ ├── paste-image-urls-with-csv.js │ │ ├── paste-image-urls.js │ │ ├── template-non-visble.js │ │ └── text-entity-classification.js │ │ └── remove-cypress-file-in-aws.js ├── plugins │ └── index.js └── support │ ├── commands.js │ └── index.js ├── desktop ├── entitlements.mac.plist ├── main.js ├── menu-template.js ├── package.json └── preload.js ├── package.json ├── public ├── favicon.ico ├── index.html ├── legal.html └── manifest.json ├── src ├── App.css ├── AppWithContexts.js ├── components │ ├── ActiveLearningView │ │ └── index.js │ ├── AddAuthFromTemplateDialog │ │ ├── authTemplates.js │ │ ├── cognito-icon.js │ │ └── index.js │ ├── AdvancedOptionsView │ │ └── index.js │ ├── App │ │ ├── index.js │ │ ├── index.story.js │ │ └── sample.udt.json │ ├── AppConfig │ │ └── index.js │ ├── AppErrorBoundary │ │ └── index.js │ ├── AudioTranscription │ │ ├── index.js │ │ └── index.story.js │ ├── BadDataset │ │ └── index.js │ ├── BigInterfaceSelect │ │ └── index.js │ ├── BrushButton │ │ └── index.js │ ├── CollaborateButton │ │ └── index.js │ ├── Composite │ │ └── index.js │ ├── Configure3D │ │ └── index.js │ ├── ConfigureAudioTranscription │ │ └── index.js │ ├── ConfigureComposite │ │ └── index.js │ ├── ConfigureDataEntry │ │ └── index.js │ ├── ConfigureImageClassification │ │ └── index.js │ ├── ConfigureImageLandmarkAnnotation │ │ └── index.js │ ├── ConfigureImagePixelSegmentation │ │ └── index.js │ ├── ConfigureImageSegmentation │ │ └── index.js │ ├── ConfigureInterface │ │ ├── index.js │ │ └── index.story.js │ ├── ConfigureTextClassification │ │ └── index.js │ ├── ConfigureTextEntityRecognition │ │ └── index.js │ ├── ConfigureTextEntityRelations │ │ └── index.js │ ├── ConfigureTimeSeries │ │ └── index.js │ ├── ConfigureVideoSegmentation │ │ └── index.js │ ├── CreateFromTemplateDialog │ │ └── index.js │ ├── DataEntry │ │ ├── index.js │ │ └── index.story.js │ ├── DatasetEditor │ │ ├── index.js │ │ └── index.story.js │ ├── DatasetJSONEditor │ │ └── index.js │ ├── DownloadButton │ │ └── index.js │ ├── DownloadURLsDialog │ │ └── index.js │ ├── EditSampleDialog │ │ └── index.js │ ├── EmptySampleContainer │ │ └── index.js │ ├── ErrorToasts │ │ └── index.js │ ├── ExportToCognitoS3Dialog │ │ ├── create-assets.js │ │ ├── fetch-a-file.js │ │ ├── index.js │ │ ├── init-config-export.js │ │ ├── interface-setting-export.js │ │ └── warning-header.js │ ├── FileContext │ │ ├── index.js │ │ └── use-active-dataset.js │ ├── Header │ │ ├── GithubIcon.js │ │ ├── HeaderWithContainer.js │ │ ├── index.js │ │ └── index.story.js │ ├── HeaderDrawer │ │ └── index.js │ ├── HeaderPopupBox │ │ └── index.js │ ├── HeaderToolbar │ │ ├── SlackIcon.js │ │ └── index.js │ ├── HotkeyStorage │ │ ├── default-hotkeys.js │ │ └── index.js │ ├── ImageClassification │ │ ├── index.js │ │ └── index.story.js │ ├── ImageLandmarkAnnotation │ │ ├── index.js │ │ └── index.story.js │ ├── ImageSegmentation │ │ ├── index.js │ │ └── index.story.js │ ├── ImportFromCOCODialog │ │ └── index.js │ ├── ImportFromCognitoS3Dialog │ │ ├── check-interface-and-sample-type.js │ │ ├── config-import-is-ready.js │ │ ├── get-sources.js │ │ ├── header-table-import.js │ │ ├── index.js │ │ ├── init-config-import.js │ │ ├── interface-setting-import.js │ │ ├── set-type-of-file-to-load-and-disable.js │ │ ├── set-url.js │ │ ├── table-expanded-row.js │ │ └── warning-header.js │ ├── ImportFromGoogleDriveDialog │ │ ├── index.js │ │ └── index.story.js │ ├── ImportFromS3Dialog │ │ ├── index.js │ │ └── index.story.js │ ├── ImportFromYoutubeUrls │ │ ├── download-youtube-video.js │ │ ├── get-youtube-video-information.js │ │ ├── index.js │ │ ├── progress.js │ │ └── split-urls-from-text-area.js │ ├── ImportPage │ │ ├── S3Icon.js │ │ ├── index.js │ │ └── prompt-and-get-samples-from-local-directory.js │ ├── ImportTextSnippetsDialog │ │ └── index.js │ ├── ImportToyDatasetDialog │ │ └── index.js │ ├── ImportUDTFileDialog │ │ └── index.js │ ├── InfoButton │ │ ├── EditableTitleText.js │ │ └── index.js │ ├── InterfaceIcon │ │ └── index.js │ ├── KeyboardShortcutManagerDialog │ │ ├── index.js │ │ └── index.story.js │ ├── LabelErrorBoundary │ │ └── index.js │ ├── LabelHelpView │ │ ├── LabelHelpProvider.js │ │ ├── api-key-entry.js │ │ ├── index.js │ │ ├── label-help-completed.js │ │ ├── label-help-dialog-content.js │ │ ├── label-help-running.js │ │ ├── label-help-setup.js │ │ └── use-label-help.js │ ├── LabelView │ │ ├── index.js │ │ └── index.story.js │ ├── Loading │ │ ├── index.js │ │ └── index.story.js │ ├── LoginDrawer │ │ ├── CompleteSignUp.js │ │ ├── SignIn.js │ │ └── index.js │ ├── ManagePluginsDialog │ │ └── index.js │ ├── MultiFileDrop │ │ └── index.js │ ├── PaperContainer │ │ └── index.js │ ├── PasteUrlsDialog │ │ ├── get-sample-from-url.js │ │ └── index.js │ ├── PluginDialog │ │ ├── index.js │ │ └── index.story.js │ ├── PluginProvider │ │ └── index.js │ ├── ProgressBar │ │ └── index.js │ ├── RawJSONEditor │ │ └── index.js │ ├── S3PathSelector │ │ ├── index.js │ │ └── index.story.js │ ├── SampleContainer │ │ ├── LinkButton.js │ │ ├── index.js │ │ └── index.story.js │ ├── SampleGrid │ │ └── index.js │ ├── SamplesTable │ │ └── index.js │ ├── SamplesView │ │ ├── index.js │ │ └── index.story.js │ ├── SetupPage │ │ ├── Protip.js │ │ ├── index.js │ │ └── index.story.js │ ├── SimpleDialog │ │ └── index.js │ ├── StartingPage │ │ ├── get-embed-youtube-url.js │ │ ├── index.js │ │ ├── index.story.js │ │ └── templates.js │ ├── Stats │ │ └── index.js │ ├── TextAreaWithUpload │ │ └── index.js │ ├── TextClassification │ │ ├── index.js │ │ └── index.story.js │ ├── TextEntityRecognition │ │ ├── convert-react-nlp-annotate-types.js │ │ ├── index.js │ │ └── index.story.js │ ├── TextEntityRelations │ │ ├── index.js │ │ └── index.story.js │ ├── Theme │ │ ├── index.js │ │ └── theme.css │ ├── TimeSeries │ │ ├── index.js │ │ └── index.story.js │ ├── Toasts │ │ ├── index.js │ │ └── index.story.js │ ├── TransformImageSamplesIntoSegmentsDialog │ │ └── index.js │ ├── TransformLocalFilesToWebURLs │ │ ├── functions │ │ │ ├── get-file-url-key.js │ │ │ ├── split-file-name-from-file-url.js │ │ │ ├── transform-file-urls-to-web-urls.js │ │ │ └── upload-file-to-transfersh.js │ │ └── index.js │ ├── TransformPage │ │ ├── Button.js │ │ ├── index.js │ │ └── index.story.js │ ├── TransformRemoveInvalidSamplesDialog │ │ └── index.js │ ├── TransformSegmentsIntoImageSamplesDialog │ │ └── index.js │ ├── TransformVideoFramesToImagesDialog │ │ └── index.js │ ├── TransformVideoKeyframesDialog │ │ └── index.js │ ├── UniversalSampleEditor │ │ ├── index.js │ │ └── index.story.js │ ├── UploadToS3Dialog │ │ ├── index.js │ │ └── index.story.js │ ├── VideoSegmentation │ │ ├── index.js │ │ └── index.story.js │ ├── Waveform │ │ ├── index.js │ │ └── index.story.js │ └── WorkspaceContainer │ │ ├── index.js │ │ └── index.story.js ├── hooks │ ├── use-active-dataset-manager │ │ ├── DatasetManagers.md │ │ ├── FileDatasetManager.js │ │ └── index.js │ ├── use-add-samples │ │ └── index.js │ ├── use-app-config.js │ ├── use-clobbered-state.js │ ├── use-dataset-property │ │ └── index.js │ ├── use-dataset-ready │ │ └── index.js │ ├── use-dataset │ │ └── index.js │ ├── use-desktop-menu-functions │ │ └── index.js │ ├── use-electron.js │ ├── use-enter-collaborative-session │ │ └── index.js │ ├── use-errors.js │ ├── use-interface │ │ └── index.js │ ├── use-is-desktop │ │ └── index.js │ ├── use-is-label-only-mode.js │ ├── use-local-storage.js │ ├── use-open-file │ │ └── index.js │ ├── use-open-template │ │ └── index.js │ ├── use-prevent-navigation.js │ ├── use-remove-samples │ │ └── index.js │ ├── use-sample │ │ └── index.js │ ├── use-summary │ │ └── index.js │ ├── use-time-to-complete-sample.js │ └── use-toasts │ │ └── index.js ├── i18n │ ├── index.js │ └── locales │ │ ├── cn │ │ └── translations.json │ │ ├── en │ │ └── translation.json │ │ ├── fr │ │ └── translations.json │ │ ├── nl │ │ └── translation.json │ │ └── pt-br │ │ └── translation.json ├── index.js ├── lib │ └── fix-deps.js ├── utils │ ├── auth-handlers │ │ ├── cognito-handler.js │ │ ├── example-cognito-config.js │ │ ├── use-auth.js │ │ └── use-iam-s3-api.js │ ├── compute-dataset-variable.js │ ├── extend-with-null.js │ ├── file-handlers │ │ ├── index.js │ │ ├── use-filesystem.js │ │ ├── use-local-storage.js │ │ └── use-server │ │ │ ├── get-human-readable-log.js │ │ │ ├── handler.js │ │ │ ├── handler.test.js │ │ │ ├── index.js │ │ │ └── server-hash.js │ ├── from-udt-csv.js │ ├── get-brush-color-palette.js │ ├── get-data-url-type.js │ ├── get-sample-name-from-url.js │ ├── get-task-description.js │ ├── ria-format.js │ ├── sentry.js │ └── to-udt-csv.js └── vanilla │ ├── image-classification-layouts.story.js │ ├── image-segmentation-layouts.story.js │ ├── index.js │ ├── open.story.js │ └── use-vanilla.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["react-app", { "absoluteRuntime": false }]] 3 | } 4 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | CYPRESS_AWS_IDENTITY_POOL_ID="XX-XXXX-X:XXXXXXXX-XXXX-1234-abcd-1234567890ab" 2 | CYPRESS_AWS_AUTH_REGION="XX-XXXX-X" 3 | CYPRESS_AWS_USER_POOL_ID="XX-XXXX-X_12ab34cd9" 4 | CYPRESS_AWS_USER_POOL_WEB_CLIENT_ID="26-char alphanumeric string" 5 | CYPRESS_AWS_MANDATORY_SIGN_IN=TRUE 6 | CYPRESS_AWS_AUTHENTICATION_FLOW_TYPE="XXX_XXXX_XXX" 7 | CYPRESS_AWS_STORAGE_BUCKET="XX-XXXXX" 8 | CYPRESS_AWS_STORAGE_REGION="XX-XXXX-X" 9 | CYPRESS_COGNITO_USER_NAME="abc@abc.com" 10 | CYPRESS_COGNITO_USER_PASS="abc123" 11 | CYPRESS_COGNITO_USER_PASS_LENGTH="0" 12 | CYPRESS_COGNITO_USER_PASS_REQUIRE_LOWERCASE=TRUE 13 | CYPRESS_COGNITO_USER_PASS_REQUIRE_UPPERCASE=TRUE 14 | CYPRESS_COGNITO_USER_PASS_REQUIRE_NUMBER=TRUE 15 | CYPRESS_COGNITO_USER_PASS_REQUIRE_SYMBOL=TRUE 16 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "plugins": ["cypress", "react"] 4 | } 5 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | 7 | [lints] 8 | 9 | [options] 10 | esproposal.optional_chaining=true 11 | 12 | [strict] 13 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: universaldatatool 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "" 5 | labels: bug 6 | assignees: "" 7 | --- 8 | 9 | **The bug:** 10 | A clear and concise description of what the bug is. 11 | 12 | **Steps to Reproduce?** 13 | Minimal example of code that reproduces the behavior 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "" 5 | labels: enhancement 6 | assignees: "" 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/workflows/build-desktop.yml: -------------------------------------------------------------------------------- 1 | name: Build Desktop Application 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - "buildtest/**" 8 | 9 | jobs: 10 | build: 11 | if: "contains(github.event.head_commit.message, 'chore(release)') || contains(github.ref, 'buildtest')" 12 | runs-on: ${{ matrix.os }} 13 | continue-on-error: true 14 | 15 | strategy: 16 | matrix: 17 | os: [macos-latest, ubuntu-latest, windows-latest] 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Install with long network timeout 22 | run: | 23 | yarn install --network-timeout 1000000 24 | - name: Prepare for app notarization 25 | if: startsWith(matrix.os, 'macos') 26 | # Import Apple API key for app notarization on macOS 27 | run: | 28 | mkdir -p ~/private_keys/ 29 | echo '${{ secrets.api_key }}' > ~/private_keys/AuthKey_${{ secrets.api_key_id }}.p8 30 | # Helpful reference https://github.com/samuelmeuli/mini-diary/blob/master/.github/workflows/release.yml 31 | - name: Electron Builder Action 32 | uses: samuelmeuli/action-electron-builder@v1 33 | # if: contains(github.ref, "v0.") 34 | with: 35 | github_token: ${{ secrets.github_token }} 36 | max_attempts: 2 37 | build_script_name: "build:desktop" 38 | mac_certs: ${{ secrets.mac_certs }} 39 | mac_certs_password: ${{ secrets.mac_certs_password }} 40 | release: true 41 | env: 42 | # macOS notarization API key 43 | API_KEY_ID: ${{ secrets.api_key_id }} 44 | API_KEY_ISSUER_ID: ${{ secrets.api_key_issuer_id }} 45 | - name: Upload Artifacts 46 | if: "contains(github.ref, 'buildtest')" 47 | uses: actions/upload-artifact@v2 48 | with: 49 | name: ${{matrix.os}}.dist 50 | path: dist 51 | -------------------------------------------------------------------------------- /.github/workflows/release-on-master.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | release: 8 | if: "!contains(github.event.head_commit.message, 'skip ci')" 9 | name: Release 10 | runs-on: ubuntu-18.04 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v1 14 | - name: Setup Node.js 15 | uses: actions/setup-node@v1 16 | with: 17 | node-version: 12 18 | - name: Install dependencies 19 | run: npm install 20 | - name: Build NPM package 21 | run: npm run build:lib 22 | - name: Release 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }} 25 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 26 | run: npx semantic-release 27 | - name: Publish github pages 28 | run: | 29 | git remote set-url origin https://git:${GITHUB_TOKEN}@github.com/UniversalDataTool/universal-data-tool.git 30 | npm run gh-pages -- -u "github-actions-bot " 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }} 33 | REACT_APP_GOOGLE_DRIVE_APP_ID: ${{ secrets.REACT_APP_GOOGLE_DRIVE_APP_ID }} 34 | REACT_APP_GOOGLE_DRIVE_CLIENT_ID: ${{ secrets.REACT_APP_GOOGLE_DRIVE_CLIENT_ID }} 35 | REACT_APP_GOOGLE_DRIVE_DEVELOPER_KEY: ${{ secrets.REACT_APP_GOOGLE_DRIVE_DEVELOPER_KEY }} 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # IDE related 15 | .vscode/ 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 | 28 | storybook-static 29 | dist 30 | 31 | desktop/node_modules 32 | 33 | /lib 34 | src/components/WebApp/myAWSconfig.js 35 | package-lock.json 36 | src/components/WebApp/myAWSconfig.js 37 | .env 38 | .cache 39 | logs 40 | # eventually we'll commit these 41 | cypress/snapshots 42 | cypress/screenshots 43 | 44 | .idea 45 | *.db 46 | .vercel 47 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .cache 2 | build/* 3 | dist/* 4 | lib/* 5 | logs/* 6 | node_modules/* 7 | .env 8 | .env.example 9 | yarn-error.log 10 | cypress/snapshots/* 11 | cypress/videos/* 12 | cypress/screenshots/* 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { "semi": false } 2 | -------------------------------------------------------------------------------- /.releaserc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | branch: "master", 3 | plugins: [ 4 | "@semantic-release/commit-analyzer", 5 | "@semantic-release/release-notes-generator", 6 | ["@semantic-release/npm", { npmPublish: false }], 7 | "@semantic-release/github", 8 | [ 9 | "@semantic-release/git", 10 | { 11 | assets: ["package.json"], 12 | message: 13 | "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}", 14 | }, 15 | ], 16 | ["@semantic-release/npm", { npmPublish: true, pkgRoot: "lib" }], 17 | ], 18 | } 19 | -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import "@storybook/addon-actions/register" 2 | import "@storybook/addon-links/register" 3 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { configure, addDecorator } from "@storybook/react" 3 | import Theme from "../src/components/Theme" 4 | import { RecoilRoot } from "recoil" 5 | import "../src/App.css" 6 | import "./storybook.css" 7 | import "../src/i18n" 8 | 9 | export const themeDecorator = (storyFn) => { 10 | // TODO wrap w/ theme 11 | return React.createElement( 12 | Theme, 13 | {}, 14 | React.createElement( 15 | "div", 16 | { 17 | className: "universaldatatool", 18 | style: { height: "100vh" }, 19 | }, 20 | storyFn() 21 | ) 22 | ) 23 | } 24 | 25 | export const recoilDecorator = (storyFn) => { 26 | return React.createElement(RecoilRoot, {}, storyFn()) 27 | } 28 | 29 | function loadStories() { 30 | addDecorator(themeDecorator) 31 | addDecorator(recoilDecorator) 32 | const importAll = (r) => r.keys().map(r) 33 | importAll(require.context("../src/components", true, /\.story\.js$/)) 34 | importAll(require.context("../src/vanilla", true, /\.story\.js$/)) 35 | } 36 | 37 | configure(loadStories, module) 38 | -------------------------------------------------------------------------------- /.storybook/storybook.css: -------------------------------------------------------------------------------- 1 | body, 2 | html { 3 | height: 100%; 4 | } 5 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | udt.dev 2 | -------------------------------------------------------------------------------- /CREATING_PLUGINS.md: -------------------------------------------------------------------------------- 1 | # Creating Plugins 2 | 3 | You could want to create a plugin for many reasons: 4 | 5 | - Create a way to import data 6 | - Create a way to transform samples 7 | - Create a way to label data (or a new data type) 8 | - Create a way to authenticate with a service 9 | 10 | ## Create a way to import data 11 | 12 | This will add a button and dialog to `Samples > Import`. You can customize the dialog to allow to user to import 13 | whatever source they want. 14 | 15 | ## Create a way to transform samples 16 | 17 | This will add a button and dialog to `Samples > Transoform`. You can customize the dialog to allow to user to transform 18 | samples. 19 | 20 | ## Create a way to label data 21 | 22 | This will create a new interface button under `Setup > Data Types`, a new configuration page in `Setup > Configure`, and a 23 | new way to view samples in the `Label` page. 24 | 25 | ## Create a way to authenticate with a service 26 | 27 | This will add a new Authentication Method on the home page of the Universal Data Tool. After the user configures the Authentication 28 | method, it will be saved, and the authentication can be used to access samples, import data etc. 29 | 30 | # Discoverability 31 | 32 | [npm](https://npmjs.org) is automatically scanned for packages that start with "udt-", so a package like "udt-transform-delete-samples" would be 33 | automatically discovered. If the package is valid, it will be automatically available under the "Community Plugins" in the appropriate locations in the 34 | Universal Data Tool. 35 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12 2 | 3 | WORKDIR /usr/src/app 4 | 5 | RUN npm install -g serve 6 | 7 | COPY package*.json ./ 8 | 9 | RUN npm install 10 | 11 | COPY . . 12 | 13 | RUN npm run build 14 | 15 | EXPOSE 3000 16 | 17 | CMD ["npm", "run", "start:web:static"] 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Open Human Annotation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Singularity: -------------------------------------------------------------------------------- 1 | # A singularity container recipe file for the UNIVERSAL-DATA-TOOL 2 | Bootstrap: docker 3 | From: node:12 4 | 5 | %environment 6 | export LISTEN_PORT=3000 7 | 8 | %post 9 | 10 | # Clone the repository 11 | bash -c "cd / \ 12 | && git clone https://github.com/UniversalDataTool/universal-data-tool.git \ 13 | && mv universal-data-tool/ /opt/" 14 | 15 | # Install the package 16 | npm install -g serv 17 | bash -c "cd /opt/universal-data-tool \ 18 | && npm install \ 19 | && npm run build" 20 | 21 | %startscript 22 | npx serve -s "/opt/universal-data-tool/build" -p $LISTEN_PORT 23 | 24 | %runscript 25 | npx serve -s /opt/universal-data-tool/build -p $LISTEN_PORT 26 | 27 | %help 28 | A singularity container for running the UNIVERSAL-DATA-TOOL. For more information 29 | see https://github.com/UniversalDataTool/universal-data-tool/. 30 | 31 | %labels 32 | Maintainer: Rick Staa 33 | Github: https://github.com/UniversalDataTool/universal-data-tool/ 34 | Version: v1.0.0 35 | Type: Public 36 | -------------------------------------------------------------------------------- /add-preloaded-app-config.js: -------------------------------------------------------------------------------- 1 | /* 2 | Modify the index.html files to include the UDT_* environment variables. This 3 | allows a default app_config to be loaded in the client application. 4 | */ 5 | 6 | const fs = require("fs") 7 | const path = require("path") 8 | 9 | const envObject = {} 10 | for (const key of Object.keys(process.env)) { 11 | if (key.startsWith("UDT_")) { 12 | envObject[key.slice(4).replace(/_/g, ".")] = process.env[key] 13 | } 14 | } 15 | 16 | if (Object.keys(envObject).length > 0) { 17 | console.table(envObject) 18 | } 19 | 20 | const newIndexFileContent = fs 21 | .readFileSync(path.resolve(__dirname, "build", "index.html")) 22 | .toString() 23 | .replace( 24 | /window\.preloaded_app_config={.+?<\/script>/, 25 | `window.preloaded_app_config=${JSON.stringify(envObject)}` 26 | ) 27 | 28 | fs.writeFileSync( 29 | path.resolve(__dirname, "build", "index.html"), 30 | newIndexFileContent 31 | ) 32 | fs.writeFileSync( 33 | path.resolve(__dirname, "build/app", "index.html"), 34 | newIndexFileContent 35 | ) 36 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "testFiles": ["**/*.spec.js"], 3 | "projectId": "w62sqq", 4 | "pageLoadTimeout": 60000, 5 | "defaultCommandTimeout": 4000, 6 | "baseUrl": "http://localhost:6001", 7 | "execTimeout": 60000, 8 | "requestTimeout": 60000, 9 | "responseTimeout": 60000 10 | } 11 | -------------------------------------------------------------------------------- /cypress/.gitignore: -------------------------------------------------------------------------------- 1 | videos 2 | snapshots 3 | screenshots 4 | .cache 5 | -------------------------------------------------------------------------------- /cypress/README.md: -------------------------------------------------------------------------------- 1 | # Integration Tests 2 | 3 | To run integration tests, you must have the Universal Data Tool running on 4 | http://localhost:6001. 5 | 6 | All the tests are executed by running `yarn test:integration` or `npm run test:integration` in the 7 | top-level directory. 8 | 9 | ## Adding / Deleting Tests 10 | 11 | Generally speaking, core functionality that is easy to test should be tested, 12 | and difficult parts, especially those with third-party APIs that is prone to 13 | frequent breakage should not be tested with an integration test. Electron/desktop-specific 14 | functionality is not currently tested because it requires a slightly different 15 | test fixture. 16 | 17 | If you have new functionality that breaks a test (and the breakage is 18 | intentional, e.g. you're replacing a page with a new design), you can delete 19 | the `*.spec.js` file that is failing. A project maintainer may ask you to create 20 | a new test to replace it but usually not. 21 | 22 | ## Test Examples 23 | 24 | Cypress provides a great directory of test examples here: 25 | 26 | https://github.com/cypress-io/cypress-example-kitchensink/tree/master/cypress/integration/examples 27 | 28 | Looking through the examples should show you what you can do. 29 | -------------------------------------------------------------------------------- /cypress/fixtures/assets-dummies/audio.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniversalDataTool/universal-data-tool/8e46659a64a54448c36223d00c6bf6b644227e32/cypress/fixtures/assets-dummies/audio.mp3 -------------------------------------------------------------------------------- /cypress/fixtures/assets-dummies/image1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniversalDataTool/universal-data-tool/8e46659a64a54448c36223d00c6bf6b644227e32/cypress/fixtures/assets-dummies/image1.jpg -------------------------------------------------------------------------------- /cypress/fixtures/assets-dummies/image2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniversalDataTool/universal-data-tool/8e46659a64a54448c36223d00c6bf6b644227e32/cypress/fixtures/assets-dummies/image2.jpg -------------------------------------------------------------------------------- /cypress/fixtures/assets-dummies/pdf1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniversalDataTool/universal-data-tool/8e46659a64a54448c36223d00c6bf6b644227e32/cypress/fixtures/assets-dummies/pdf1.pdf -------------------------------------------------------------------------------- /cypress/fixtures/assets-dummies/pdf2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniversalDataTool/universal-data-tool/8e46659a64a54448c36223d00c6bf6b644227e32/cypress/fixtures/assets-dummies/pdf2.pdf -------------------------------------------------------------------------------- /cypress/fixtures/assets-dummies/text1.txt: -------------------------------------------------------------------------------- 1 | This has made me so happy. I love this. -------------------------------------------------------------------------------- /cypress/fixtures/assets-dummies/text2.txt: -------------------------------------------------------------------------------- 1 | At first I wasn't sure. Then I thought, oh it's not very good. -------------------------------------------------------------------------------- /cypress/fixtures/assets-dummies/text3.txt: -------------------------------------------------------------------------------- 1 | This has made me so happy. I love this. -------------------------------------------------------------------------------- /cypress/fixtures/assets-dummies/timeSeries.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeData": [ 3 | { "time": 0, "value": 0 }, 4 | { "time": 500, "value": 0.75 }, 5 | { "time": 1000, "value": 1 } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /cypress/fixtures/assets-dummies/video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniversalDataTool/universal-data-tool/8e46659a64a54448c36223d00c6bf6b644227e32/cypress/fixtures/assets-dummies/video.mp4 -------------------------------------------------------------------------------- /cypress/fixtures/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniversalDataTool/universal-data-tool/8e46659a64a54448c36223d00c6bf6b644227e32/cypress/fixtures/cat.jpg -------------------------------------------------------------------------------- /cypress/fixtures/samples-dummies/AudioTranscription.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Audio Transcription", 3 | "interface": { 4 | "type": "audio_transcription", 5 | "description": "# Markdown description here" 6 | }, 7 | "samples": [ 8 | { 9 | "_id": "s5tghce7k", 10 | "audioUrl": "https://html5tutorial.info/media/vincent.mp3" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /cypress/fixtures/samples-dummies/Composite.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Composite", 3 | "interface": { 4 | "type": "composite", 5 | "fields": [ 6 | { 7 | "fieldName": "textInfo", 8 | "interface": { 9 | "type": "data_entry", 10 | "surveyjs": { 11 | "questions": [ 12 | { 13 | "type": "text", 14 | "name": "group_letter", 15 | "title": "Letter of Group" 16 | } 17 | ] 18 | } 19 | } 20 | }, 21 | { 22 | "fieldName": "segmentation", 23 | "interface": { 24 | "type": "image_segmentation", 25 | "labels": ["group text"], 26 | "regionTypesAllowed": ["bounding-box"] 27 | } 28 | } 29 | ] 30 | }, 31 | "description": "# Markdown description here", 32 | "samples": [ 33 | { 34 | "_id": "s2y1li73c", 35 | "imageUrl": "https://s3.amazonaws.com/asset.workaround.online/example-jobs/sticky-notes/image1.jpg" 36 | }, 37 | { 38 | "_id": "snodnb8eb", 39 | "imageUrl": "https://s3.amazonaws.com/asset.workaround.online/example-jobs/sticky-notes/image2.jpg" 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /cypress/fixtures/samples-dummies/DataEntry.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Data Entry", 3 | "interface": { 4 | "type": "data_entry", 5 | "description": "# Markdown description here", 6 | "surveyjs": { 7 | "questions": [ 8 | { 9 | "type": "text", 10 | "name": "document_title", 11 | "title": "Title of Document" 12 | } 13 | ] 14 | } 15 | }, 16 | "samples": [ 17 | { "_id": "sj2skbtg3", "pdfUrl": "https://arxiv.org/pdf/1906.01969.pdf" }, 18 | { "_id": "skszdkkb9", "pdfUrl": "https://arxiv.org/pdf/1908.07069.pdf" } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /cypress/fixtures/samples-dummies/Empty.json: -------------------------------------------------------------------------------- 1 | { "name": "Empty", "interface": {}, "samples": [] } 2 | -------------------------------------------------------------------------------- /cypress/fixtures/samples-dummies/ImageClassification.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Image Classification", 3 | "interface": { 4 | "type": "image_classification", 5 | "labels": ["valid", "invalid"] 6 | }, 7 | "samples": [ 8 | { 9 | "_id": "sww95cyio", 10 | "imageUrl": "https://s3.amazonaws.com/asset.workaround.online/example-jobs/sticky-notes/image1.jpg" 11 | }, 12 | { 13 | "_id": "strmmwo1g", 14 | "imageUrl": "https://s3.amazonaws.com/asset.workaround.online/example-jobs/sticky-notes/image2.jpg" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /cypress/fixtures/samples-dummies/ImageSegmentation.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Image Segmentation", 3 | "interface": { 4 | "type": "image_segmentation", 5 | "labels": ["valid", "invalid"], 6 | "regionTypesAllowed": ["bounding-box", "polygon", "point"] 7 | }, 8 | "samples": [ 9 | { 10 | "_id": "srrtsy1dv", 11 | "imageUrl": "https://s3.amazonaws.com/asset.workaround.online/example-jobs/sticky-notes/image1.jpg" 12 | }, 13 | { 14 | "_id": "sq7azli34", 15 | "imageUrl": "https://s3.amazonaws.com/asset.workaround.online/example-jobs/sticky-notes/image2.jpg" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /cypress/fixtures/samples-dummies/NamedEntityRecognition.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Named Entity Recognition", 3 | "interface": { 4 | "type": "text_entity_recognition", 5 | "overlapAllowed": false, 6 | "labels": [ 7 | { "id": "food", "displayName": "Food", "description": "Edible item." }, 8 | { 9 | "id": "hat", 10 | "displayName": "Hat", 11 | "description": "Something worn on the head." 12 | } 13 | ] 14 | }, 15 | "samples": [ 16 | { 17 | "_id": "sa6l4bt9n", 18 | "document": "This strainer makes a great hat, I'll wear it while I serve spaghetti!" 19 | }, 20 | { 21 | "_id": "s95oxk8n4", 22 | "document": "Why are all these dumpings covered in butter?!" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /cypress/fixtures/samples-dummies/NotSupported.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Not Supported", 3 | "interface": { 4 | "type": "audio_transcription", 5 | "description": "# Markdown description here" 6 | }, 7 | "samples": [ 8 | { 9 | "_id": "sww95cyio", 10 | "imageUrl": "https://s3.amazonaws.com/asset.workaround.online/example-jobs/sticky-notes/image1.jpg" 11 | }, 12 | { 13 | "_id": "sprtlkb6r", 14 | "videoUrl": "https://s3.amazonaws.com/asset.workaround.online/SampleVideo_1280x720_1mb.mp4" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /cypress/fixtures/samples-dummies/PixelSegmentation.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pixel Segmentation", 3 | "interface": { 4 | "type": "image_pixel_segmentation", 5 | "labels": ["hair", "mouth", "nose", "eyes"], 6 | "description": "These are AI-generated faces, not real people." 7 | }, 8 | "samples": [ 9 | { 10 | "_id": "s67fjg4xn", 11 | "imageUrl": "https://s3.amazonaws.com/datasets.workaround.online/faces/010041.jpg" 12 | }, 13 | { 14 | "_id": "sya6a14y5", 15 | "imageUrl": "https://s3.amazonaws.com/datasets.workaround.online/faces/010026.jpg" 16 | }, 17 | { 18 | "_id": "s4ldv4ltg", 19 | "imageUrl": "https://s3.amazonaws.com/datasets.workaround.online/faces/010025.jpg" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /cypress/fixtures/samples-dummies/TextClassification.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Text Classification", 3 | "interface": { 4 | "type": "text_classification", 5 | "labels": ["positive_sentiment", "negative_sentiment"] 6 | }, 7 | "samples": [ 8 | { "_id": "slohyn137", "document": "Wow this is terrible. I hated it." }, 9 | { 10 | "_id": "svkw5i7ci", 11 | "document": "This has made me so happy. I love this." 12 | }, 13 | { 14 | "_id": "spsd5ta7s", 15 | "document": "At first I wasn't sure. Then I thought, oh it's not very good." 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /cypress/fixtures/samples-dummies/TextEntityRelations.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Text Entity Relations", 3 | "interface": { 4 | "type": "text_entity_relations", 5 | "entityLabels": [ 6 | { "id": "food", "displayName": "Food", "description": "Edible item." }, 7 | { 8 | "id": "hat", 9 | "displayName": "Hat", 10 | "description": "Something worn on the head." 11 | } 12 | ], 13 | "relationLabels": [{ "id": "subject", "displayName": "Subject" }] 14 | }, 15 | "samples": [ 16 | { 17 | "_id": "s2wa87k79", 18 | "document": "This strainer makes a great hat, I'll wear it while I serve spaghetti!" 19 | }, 20 | { 21 | "_id": "sroom9j13", 22 | "document": "Why are all these dumpings covered in butter?!" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /cypress/fixtures/samples-dummies/TimeSeriesAudioUrl.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Time Series", 3 | "interface": { 4 | "type": "time_series", 5 | "timeFormat": "dates", 6 | "enabledTools": ["create-durations", "label-durations"], 7 | "durationLabels": ["@seveibar is speaking"] 8 | }, 9 | "samples": [ 10 | { 11 | "audioUrl": "https://s3.amazonaws.com/datasets.workaround.online/voice-samples/001/voice.mp3", 12 | "annotation": { 13 | "durations": [ 14 | { "start": 500, "end": 2000, "label": "@seveibar is speaking" } 15 | ] 16 | } 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /cypress/fixtures/samples-dummies/TimeSeriestimeData.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Time Series 2", 3 | "interface": { 4 | "type": "time_series", 5 | "timeFormat": "dates", 6 | "enabledTools": ["create-durations", "label-durations"], 7 | "durationLabels": ["@seveibar is speaking"] 8 | }, 9 | "samples": [ 10 | { 11 | "_id": "smzrtu13f", 12 | "timeData": [ 13 | { "time": 0, "value": 0 }, 14 | { "time": 500, "value": 0.75 }, 15 | { "time": 1000, "value": 1 } 16 | ], 17 | "annotation": { 18 | "durations": [ 19 | { "start": 500, "end": 2000, "label": "@seveibar is speaking" } 20 | ] 21 | } 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /cypress/fixtures/samples-dummies/VideoSegmentation.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Video Segmentation", 3 | "interface": { 4 | "type": "video_segmentation", 5 | "labels": ["valid", "invalid"], 6 | "regionTypesAllowed": ["bounding-box", "polygon", "point"] 7 | }, 8 | "samples": [ 9 | { 10 | "_id": "sprtlkb6r", 11 | "videoUrl": "https://s3.amazonaws.com/asset.workaround.online/SampleVideo_1280x720_1mb.mp4" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /cypress/integration/aws-test/credentials.spec.js: -------------------------------------------------------------------------------- 1 | import enterCredentialsCognitoS3 from "../utils/credentials-test/enter-credentials-cognito-s3" 2 | import enterCredentialsUser from "../utils/credentials-test/enter-credentials-user" 3 | import commandLocalStorage from "../utils/cypress-command/local-storage" 4 | import commandSetLanguage from "../utils/cypress-command/set-language" 5 | commandLocalStorage() 6 | commandSetLanguage() 7 | 8 | Cypress.config("defaultCommandTimeout", 3000) 9 | if (Cypress.env().AWS_IDENTITY_POOL_ID) 10 | describe("Credentials test", () => { 11 | before("Prepare tests", () => { 12 | cy.log("should be able to join the web site") 13 | cy.visit(`http://localhost:6001`) 14 | cy.setLanguage("en") 15 | }) 16 | 17 | beforeEach("Restore local storage", () => { 18 | cy.restoreLocalStorage() 19 | }) 20 | 21 | enterCredentialsCognitoS3() 22 | enterCredentialsUser() 23 | 24 | afterEach("Save local storage", () => { 25 | cy.saveLocalStorage() 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /cypress/integration/aws-test/export.spec.js: -------------------------------------------------------------------------------- 1 | import warningHeader from "../utils/aws-unit-test/warning-header-export" 2 | 3 | import commandSetLanguage from "../utils/cypress-command/set-language" 4 | import commandLocalStorage from "../utils/cypress-command/local-storage" 5 | import commandAddAssetToAwsProject from "../utils/cypress-command/add-asset-to-aws-project" 6 | import commandCredentialsAws from "../utils/cypress-command/credentials-aws" 7 | import commandAddProjectToAws from "../utils/cypress-command/add-project-to-aws" 8 | import commandCleanAws from "../utils/cypress-command/clean-aws" 9 | 10 | commandLocalStorage() 11 | commandCredentialsAws() 12 | commandAddAssetToAwsProject() 13 | commandAddProjectToAws() 14 | commandCleanAws() 15 | commandSetLanguage() 16 | 17 | Cypress.config("defaultCommandTimeout", 3000) 18 | if (Cypress.env().AWS_IDENTITY_POOL_ID) 19 | describe("Export aws test", () => { 20 | before("Prepare tests", () => { 21 | cy.log("should be able to join the web site") 22 | cy.visit(`http://localhost:6001`) 23 | cy.createCredentialsAws() 24 | cy.setLanguage("en") 25 | cy.saveLocalStorage() 26 | cy.cleanAws() 27 | cy.addProjectToAws("ImageClassification.json") 28 | cy.addAssetToAwsProject("Image Classification", "image1.jpg") 29 | }) 30 | 31 | beforeEach("Go to import page", () => { 32 | cy.restoreLocalStorage() 33 | }) 34 | 35 | warningHeader() 36 | 37 | //Comment below when debugging 1 test 38 | afterEach("Return to home page", () => { 39 | cy.get("button[title='Exit to Welcome Page']").click({ force: true }) 40 | }) 41 | 42 | //Comment below when debugging aws 43 | after("Clean AWS", () => { 44 | cy.cleanAws() 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /cypress/integration/aws-test/import.spec.js: -------------------------------------------------------------------------------- 1 | import commandLocalStorage from "../utils/cypress-command/local-storage" 2 | import commandCredentialsAws from "../utils/cypress-command/credentials-aws" 3 | import commandCleanAws from "../utils/cypress-command/clean-aws" 4 | import commandSetUpAws from "../utils/cypress-command/set-up-aws" 5 | import commandSetLanguage from "../utils/cypress-command/set-language" 6 | 7 | commandLocalStorage() 8 | commandCredentialsAws() 9 | commandSetUpAws() 10 | commandCleanAws() 11 | commandSetLanguage() 12 | 13 | Cypress.config("defaultCommandTimeout", 3000) 14 | if (Cypress.env().AWS_IDENTITY_POOL_ID) 15 | describe("Import aws test", () => { 16 | before("Prepare tests", () => { 17 | cy.log("should be able to join the web site") 18 | cy.visit(`http://localhost:6001`) 19 | cy.setLanguage("en") 20 | //the following are very long processus try to keep them here so they execute only once 21 | cy.createCredentialsAws() 22 | cy.saveLocalStorage() 23 | cy.cleanAws() 24 | cy.setUpAws() 25 | }) 26 | 27 | beforeEach("Go to import page", () => { 28 | cy.restoreLocalStorage() 29 | }) 30 | 31 | //Comment below when debugging 1 test 32 | afterEach("Return to home page", () => { 33 | cy.get("button[title='Exit to Welcome Page']").click({ force: true }) 34 | }) 35 | 36 | //Comment below when debugging aws 37 | after("Clean AWS", () => { 38 | cy.cleanAws() 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /cypress/integration/collaborative-session.spec.js: -------------------------------------------------------------------------------- 1 | import clickOn100SamplesInACollaborativeSession from "./utils/interface-test/click-on-100-samples-in-a-collaborative-session" 2 | import createAndVisitCollaborativeSession from "./utils/interface-test/create-and-visit-collaborative-session" 3 | import commandSetLanguage from "./utils/cypress-command/set-language" 4 | 5 | commandSetLanguage() 6 | 7 | Cypress.config("defaultCommandTimeout", 3000) 8 | describe("Create and Visit Collaborative Session", () => { 9 | beforeEach("Prepare test", () => { 10 | cy.visit(`http://localhost:6001`) 11 | cy.setLanguage("en") 12 | cy.contains("New File").click() 13 | }) 14 | createAndVisitCollaborativeSession() 15 | clickOn100SamplesInACollaborativeSession() 16 | }) 17 | -------------------------------------------------------------------------------- /cypress/integration/udt-test.spec.js: -------------------------------------------------------------------------------- 1 | import createNewFile from "./utils/interface-test/create-new-file" 2 | import imageClassification from "./utils/interface-test/image-classification" 3 | import imageSegmentation from "./utils/interface-test/image-segmentation" 4 | import keyboardShortcuts from "./utils/interface-test/keyboard-shortcuts" 5 | import namedEntityRecognition from "./utils/interface-test/named-entity-recognition" 6 | import pasteImageUrlsWithCSV from "./utils/interface-test/paste-image-urls-with-csv" 7 | import pasteImageUrls from "./utils/interface-test/paste-image-urls" 8 | import textEntityClassification from "./utils/interface-test/text-entity-classification" 9 | import defaultTemplate from "./utils/interface-test/default-template" 10 | import templateNonVisible from "./utils/interface-test/template-non-visble" 11 | import commandSetLanguage from "./utils/cypress-command/set-language" 12 | 13 | commandSetLanguage() 14 | 15 | Cypress.config("defaultCommandTimeout", 3000) 16 | describe("Udt test", () => { 17 | beforeEach("Prepare test", () => { 18 | cy.visit(`http://localhost:6001`) 19 | cy.setLanguage("en") 20 | }) 21 | templateNonVisible() 22 | defaultTemplate() 23 | createNewFile() 24 | imageClassification() 25 | imageSegmentation() 26 | keyboardShortcuts() 27 | namedEntityRecognition() 28 | pasteImageUrlsWithCSV() 29 | pasteImageUrls() 30 | textEntityClassification() 31 | }) 32 | -------------------------------------------------------------------------------- /cypress/integration/utils/aws-unit-test/warning-header-export.js: -------------------------------------------------------------------------------- 1 | import goToImportPage from "../go-to-import-page" 2 | const warningHeader = () => { 3 | it("Warning project name not set", () => { 4 | goToImportPage("Image Segmentation") 5 | cy.contains("Export to S3 (Cognito)").click() 6 | cy.contains("Warning : Please enter a project name.") 7 | cy.contains("Close").click() 8 | }) 9 | it("Warning project already exist", () => { 10 | goToImportPage("Image Segmentation") 11 | cy.contains("Export to S3 (Cognito)").click() 12 | cy.get("input[id='ProjectName']") 13 | .focus() 14 | .clear() 15 | .type("Image Classification") 16 | cy.contains( 17 | "Warning : This project name already exist. If you continue the existing project with the same name will be replaced." 18 | ) 19 | cy.contains("Close").click() 20 | }) 21 | } 22 | export default warningHeader 23 | -------------------------------------------------------------------------------- /cypress/integration/utils/credentials-test/enter-credentials-user.js: -------------------------------------------------------------------------------- 1 | const enterCredentialsUser = () => { 2 | cy.get('input[id="username"]').focus().type(Cypress.env().COGNITO_USER_NAME) 3 | cy.get('input[id="password"]').focus().type(Cypress.env().COGNITO_USER_PASS) 4 | } 5 | const test = () => { 6 | it("Enter credentials user cognito", () => { 7 | cy.log("should be able to set user cognito config") 8 | cy.contains("Cognito").click() 9 | enterCredentialsUser() 10 | cy.get("button[id='sign-in']").click() 11 | cy.get("button[id='sign-in']", { timeout: 30000 }).should("not.exist") 12 | }) 13 | } 14 | export default test 15 | -------------------------------------------------------------------------------- /cypress/integration/utils/cypress-command/add-asset-to-aws-project.js: -------------------------------------------------------------------------------- 1 | import datasetManagerCognito from "udt-dataset-managers/dist/CognitoDatasetManager" 2 | import mime from "mime-types" 3 | const command = () => { 4 | Cypress.Commands.add("addAssetToAwsProject", (nameProject, nameAsset) => { 5 | var credentials = Cypress.env() 6 | cy.log("Add test files : " + nameAsset) 7 | cy.fixture("assets-dummies/" + nameAsset, "base64").then( 8 | { timeout: 100000 }, 9 | async (asset) => { 10 | var blob 11 | if (nameAsset.match(".*\\.json")) { 12 | blob = asset 13 | } else { 14 | blob = await Cypress.Blob.base64StringToBlob( 15 | asset, 16 | mime.lookup(nameAsset) 17 | ) 18 | } 19 | const authConfig = { 20 | Auth: { 21 | identityPoolId: credentials.AWS_IDENTITY_POOL_ID, 22 | region: credentials.AWS_AUTH_REGION, 23 | userPoolId: credentials.AWS_USER_POOL_ID, 24 | userPoolWebClientId: credentials.AWS_USER_POOL_WEB_CLIENT_ID, 25 | mandatorySignIn: credentials.AWS_MANDATORY_SIGN_IN, 26 | authenticationFlowType: credentials.AWS_AUTHENTICATION_FLOW_TYPE, 27 | }, 28 | Storage: { 29 | AWSS3: { 30 | bucket: credentials.AWS_STORAGE_BUCKET, 31 | region: credentials.AWS_STORAGE_REGION, 32 | }, 33 | }, 34 | } 35 | const ds = await new datasetManagerCognito({ authConfig }) 36 | ds.setProject(nameProject) 37 | await ds.addAsset(nameAsset, blob) 38 | } 39 | ) 40 | }) 41 | } 42 | export default command 43 | -------------------------------------------------------------------------------- /cypress/integration/utils/cypress-command/add-project-to-aws.js: -------------------------------------------------------------------------------- 1 | import datasetManagerCognito from "udt-dataset-managers/dist/CognitoDatasetManager" 2 | const command = () => { 3 | Cypress.Commands.add("addProjectToAws", (nameDummies) => { 4 | var credentials = Cypress.env() 5 | cy.log("Add test files : " + nameDummies) 6 | cy.fixture("samples-dummies/" + nameDummies).then( 7 | { timeout: 100000 }, 8 | async (dummies) => { 9 | const authConfig = { 10 | Auth: { 11 | identityPoolId: credentials.AWS_IDENTITY_POOL_ID, 12 | region: credentials.AWS_AUTH_REGION, 13 | userPoolId: credentials.AWS_USER_POOL_ID, 14 | userPoolWebClientId: credentials.AWS_USER_POOL_WEB_CLIENT_ID, 15 | mandatorySignIn: credentials.AWS_MANDATORY_SIGN_IN, 16 | authenticationFlowType: credentials.AWS_AUTHENTICATION_FLOW_TYPE, 17 | }, 18 | Storage: { 19 | AWSS3: { 20 | bucket: credentials.AWS_STORAGE_BUCKET, 21 | region: credentials.AWS_STORAGE_REGION, 22 | }, 23 | }, 24 | } 25 | const ds = await new datasetManagerCognito({ authConfig }) 26 | await ds.setDataset(dummies) 27 | } 28 | ) 29 | }) 30 | } 31 | export default command 32 | -------------------------------------------------------------------------------- /cypress/integration/utils/cypress-command/clean-aws.js: -------------------------------------------------------------------------------- 1 | import commandRemoveAwsProject from "./remove-aws-project" 2 | const command = () => { 3 | commandRemoveAwsProject() 4 | Cypress.Commands.add("cleanAws", () => { 5 | cy.log("Start cleaning aws") 6 | cy.removeAwsProject("Not Supported") 7 | cy.removeAwsProject("Image Classification") 8 | cy.removeAwsProject("Image Segmentation") 9 | cy.removeAwsProject("Time Series") 10 | cy.removeAwsProject("Time Series 2") 11 | cy.removeAwsProject("Data Entry") 12 | cy.removeAwsProject("Text Classification") 13 | cy.removeAwsProject("Video Segmentation") 14 | cy.removeAwsProject("Audio Transcription") 15 | cy.removeAwsProject("Empty") 16 | cy.removeAwsProject("Rename Test") 17 | cy.removeAwsProject("CypressTestExportAnnotationOnlyTime") 18 | cy.removeAwsProject("CypressTestExportAnnotationOnlyImage") 19 | cy.removeAwsProject("CypressTestExportAnnotationOnlyVideo") 20 | cy.removeAwsProject("CypressTestExportAnnotationOnlyPDF") 21 | cy.removeAwsProject("CypressTestExportAnnotationOnlyAudio") 22 | cy.removeAwsProject("CypressTestExportAnnotationOnlyText") 23 | cy.removeAwsProject("CypressTestExportAssetsTime") 24 | cy.removeAwsProject("CypressTestExportAssetsTime2") 25 | cy.removeAwsProject("CypressTestExportAssetsImage") 26 | cy.removeAwsProject("CypressTestExportAssetsVideo") 27 | cy.removeAwsProject("CypressTestExportAssetsPDF") 28 | cy.removeAwsProject("CypressTestExportAssetsAudio") 29 | cy.removeAwsProject("CypressTestExportAssetsText") 30 | cy.log("End cleaning aws") 31 | }) 32 | } 33 | export default command 34 | -------------------------------------------------------------------------------- /cypress/integration/utils/cypress-command/local-storage.js: -------------------------------------------------------------------------------- 1 | //https://github.com/cypress-io/cypress/issues/461#issuecomment-325402086 2 | const command = () => { 3 | let LOCAL_STORAGE_MEMORY = {} 4 | Cypress.Commands.add("saveLocalStorage", () => { 5 | Object.keys(localStorage).forEach((key) => { 6 | LOCAL_STORAGE_MEMORY[key] = localStorage[key] 7 | }) 8 | }) 9 | Cypress.Commands.add("restoreLocalStorage", () => { 10 | Object.keys(LOCAL_STORAGE_MEMORY).forEach((key) => { 11 | localStorage.setItem(key, LOCAL_STORAGE_MEMORY[key]) 12 | }) 13 | }) 14 | } 15 | export default command 16 | -------------------------------------------------------------------------------- /cypress/integration/utils/cypress-command/remove-aws-project.js: -------------------------------------------------------------------------------- 1 | import datasetManagerCognito from "udt-dataset-managers/dist/CognitoDatasetManager" 2 | 3 | const command = () => { 4 | Cypress.Commands.add("removeAwsProject", async (name) => { 5 | var credentials = Cypress.env() 6 | const authConfig = { 7 | Auth: { 8 | identityPoolId: credentials.AWS_IDENTITY_POOL_ID, 9 | region: credentials.AWS_AUTH_REGION, 10 | userPoolId: credentials.AWS_USER_POOL_ID, 11 | userPoolWebClientId: credentials.AWS_USER_POOL_WEB_CLIENT_ID, 12 | mandatorySignIn: credentials.AWS_MANDATORY_SIGN_IN, 13 | authenticationFlowType: credentials.AWS_AUTHENTICATION_FLOW_TYPE, 14 | }, 15 | Storage: { 16 | AWSS3: { 17 | bucket: credentials.AWS_STORAGE_BUCKET, 18 | region: credentials.AWS_STORAGE_REGION, 19 | }, 20 | }, 21 | } 22 | const ds = await new datasetManagerCognito({ authConfig }) 23 | await ds.removeProject(name) 24 | }) 25 | } 26 | export default command 27 | -------------------------------------------------------------------------------- /cypress/integration/utils/cypress-command/set-language.js: -------------------------------------------------------------------------------- 1 | const command = (langChar) => { 2 | //Possible input en,fr,cn.dl and pt 3 | Cypress.Commands.add("setLanguage", () => { 4 | localStorage.setItem("i18nextLng", langChar) 5 | cy.reload() 6 | }) 7 | } 8 | export default command 9 | -------------------------------------------------------------------------------- /cypress/integration/utils/cypress-command/set-template.js: -------------------------------------------------------------------------------- 1 | const command = () => { 2 | Cypress.Commands.add("setTemplate", (template) => { 3 | cy.log("Set template") 4 | var appConfig = localStorage.getItem("app_config") 5 | appConfig = JSON.parse(appConfig) 6 | appConfig.defaultTemplate = template 7 | localStorage.setItem("app_config", JSON.stringify(appConfig)) 8 | }) 9 | } 10 | export default command 11 | -------------------------------------------------------------------------------- /cypress/integration/utils/cypress-command/set-up-aws.js: -------------------------------------------------------------------------------- 1 | import commandAddAssetToAwsProject from "./add-asset-to-aws-project" 2 | import commandAddProjectToAws from "./add-project-to-aws" 3 | const command = () => { 4 | commandAddAssetToAwsProject() 5 | commandAddProjectToAws() 6 | Cypress.Commands.add("setUpAws", () => { 7 | cy.addProjectToAws("ImageClassification.json") 8 | cy.addProjectToAws("Empty.json") 9 | cy.addProjectToAws("AudioTranscription.json") 10 | cy.addProjectToAws("VideoSegmentation.json") 11 | cy.addProjectToAws("TextClassification.json") 12 | cy.addProjectToAws("TimeSeriesTimeData.json") 13 | cy.addProjectToAws("TimeSeriesAudioUrl.json") 14 | cy.addProjectToAws("DataEntry.json") 15 | cy.addProjectToAws("NotSupported.json") 16 | cy.addAssetToAwsProject("Image Classification", "image1.jpg") 17 | cy.addAssetToAwsProject("Image Classification", "image2.jpg") 18 | cy.addAssetToAwsProject("Audio Transcription", "audio.mp3") 19 | cy.addAssetToAwsProject("Video Segmentation", "video.mp4") 20 | cy.addAssetToAwsProject("Data Entry", "pdf1.pdf") 21 | cy.addAssetToAwsProject("Data Entry", "pdf2.pdf") 22 | cy.addAssetToAwsProject("Text Classification", "text1.txt") 23 | cy.addAssetToAwsProject("Text Classification", "text2.txt") 24 | cy.addAssetToAwsProject("Text Classification", "text3.txt") 25 | cy.addAssetToAwsProject("Time Series", "audio.mp3") 26 | cy.addAssetToAwsProject("Time Series 2", "timeSeries.json") 27 | }) 28 | } 29 | export default command 30 | -------------------------------------------------------------------------------- /cypress/integration/utils/function-test/get-data-url-type.js: -------------------------------------------------------------------------------- 1 | import getDataUrlType from "../../../dist/utils/get-data-url-type" 2 | const getDataUrlTypeTest = () => { 3 | it("Check getDataUrlType", () => { 4 | expect( 5 | getDataUrlType( 6 | "https://s3.amazonaws.com/asset.workaround.online/example-jobs/sticky-notes/image2.jpg" 7 | ) 8 | ).to.equal("Image") 9 | expect( 10 | getDataUrlType( 11 | "https://s3.amazonaws.com/asset.workaround.online/SampleVideo_1280x720_1mb.mp4" 12 | ) 13 | ).to.equal("Video") 14 | expect( 15 | getDataUrlType("https://html5tutorial.info/media/vincent.mp3") 16 | ).to.equal("Audio") 17 | expect(getDataUrlType("https://arxiv.org/pdf/1908.07069.pdf")).to.equal( 18 | "PDF" 19 | ) 20 | expect(getDataUrlType("https://arxiv.org/pdf/1908.07069.txt")).to.equal( 21 | "Text" 22 | ) 23 | expect(getDataUrlType("https://arxiv.org/pdf/1908.07069.js")).to.equal( 24 | "File" 25 | ) 26 | }) 27 | } 28 | export default getDataUrlTypeTest 29 | -------------------------------------------------------------------------------- /cypress/integration/utils/go-to-import-page.js: -------------------------------------------------------------------------------- 1 | const test = (typeProject) => { 2 | cy.log("should be able to go to the import page") 3 | cy.contains("New File", { timeout: 50000 }).click() 4 | if (typeProject) cy.contains(typeProject).click() 5 | cy.get('button[id="tab-samples"]').click() 6 | cy.contains("Samples").click() 7 | cy.contains("Import").click() 8 | } 9 | 10 | export default test 11 | -------------------------------------------------------------------------------- /cypress/integration/utils/interface-test/click-on-100-samples-in-a-collaborative-session.js: -------------------------------------------------------------------------------- 1 | const clickOn100SamplesInACollaborativeSession = () => { 2 | it.skip("Should be able to click on 100 samples in a collaborative session", () => { 3 | cy.get("#tab-setup").click() 4 | cy.contains("Image Classification").click() 5 | 6 | cy.log("should be able to import cats dataset") 7 | cy.get("#tab-samples").click() 8 | cy.contains("Import").click() 9 | cy.contains("Import Toy Dataset").click() 10 | cy.contains("Cats").siblings("td").eq(2).click() 11 | 12 | cy.log("click on 100 samples") 13 | cy.get("#tab-label").click() 14 | cy.contains("16").click() 15 | for (let i = 0; i < 50; i++) { 16 | cy.contains("valid").click() 17 | cy.contains("invalid").click() 18 | } 19 | }) 20 | } 21 | 22 | export default clickOn100SamplesInACollaborativeSession 23 | -------------------------------------------------------------------------------- /cypress/integration/utils/interface-test/create-and-visit-collaborative-session.js: -------------------------------------------------------------------------------- 1 | const createAndVisitCollaborativeSession = () => { 2 | let collaborationUrl 3 | it("Should be able to create and visit collaborative session", () => { 4 | cy.log("should be able to import Elon Musk Tweets dataset") 5 | cy.get("#tab-samples", { timeout: 10000 }).click() 6 | cy.contains("Import").click() 7 | cy.contains("Import Toy Dataset").click() 8 | cy.contains("Elon Musk Tweets").siblings("td").eq(2).click() 9 | 10 | cy.log("should be able to create new session") 11 | cy.get("div[title='collaborate-icon']", { timeout: 10000 }).click() 12 | cy.contains("Create New Session", { timeout: 5000 }).click() 13 | cy.contains("Leave Session", { timeout: 20000 }) 14 | cy.get("div[title='collaborate-icon']").trigger("mouseleave") 15 | 16 | cy.log("should be able to store session url") 17 | cy.get("div[title='info-icon']", { timeout: 5000 }).click() 18 | cy.get("div[title='share-link']", { timeout: 20000 }) 19 | .children() 20 | .children() 21 | .then((elements) => { 22 | collaborationUrl = Cypress.$(elements[0]).val() 23 | 24 | cy.log("should be able to go for session url") 25 | cy.visit(collaborationUrl) 26 | 27 | cy.log("should be able to navigate to samples") 28 | cy.get("#tab-samples", { timeout: 20000 }).click() 29 | }) 30 | }) 31 | } 32 | export default createAndVisitCollaborativeSession 33 | -------------------------------------------------------------------------------- /cypress/integration/utils/interface-test/create-new-file.js: -------------------------------------------------------------------------------- 1 | const createNewFile = () => { 2 | it.skip("Should be able to select all the interfaces", () => { 3 | cy.contains("New File").click() 4 | cy.contains("Image Segmentation").click() 5 | cy.wait(200) 6 | cy.matchImageSnapshot("image_segmentation", { 7 | failureThresholdType: "percent", 8 | }) 9 | 10 | cy.contains("Data Type").click() 11 | cy.contains("Image Classification").click() 12 | cy.matchImageSnapshot("image_classification", { 13 | failureThresholdType: "percent", 14 | }) 15 | 16 | cy.contains("Data Type").click() 17 | cy.contains("Video Segmentation").click() 18 | cy.matchImageSnapshot("video_segmentation", { 19 | failureThresholdType: "percent", 20 | }) 21 | 22 | cy.contains("Data Type").click() 23 | cy.contains("Data Entry").click() 24 | cy.matchImageSnapshot("data_entry", { failureThresholdType: "percent" }) 25 | 26 | cy.contains("Data Type").click() 27 | cy.contains("Named Entity Recognition").click() 28 | cy.matchImageSnapshot("named_entity_recognition", { 29 | failureThresholdType: "percent", 30 | }) 31 | 32 | cy.contains("Data Type").click() 33 | cy.contains("Text Classification").click() 34 | cy.matchImageSnapshot("text_classification", { 35 | failureThresholdType: "percent", 36 | }) 37 | 38 | cy.contains("Data Type").click() 39 | cy.contains("Audio Transcription").click() 40 | cy.matchImageSnapshot("audio_transctiption", { 41 | failureThresholdType: "percent", 42 | }) 43 | 44 | cy.contains("Data Type").click() 45 | cy.contains("Composite").click() 46 | cy.matchImageSnapshot("composite", { failureThresholdType: "percent" }) 47 | 48 | cy.contains("Data Type").click() 49 | cy.contains("3D Bounding Box").click() 50 | cy.matchImageSnapshot("3d_bounding_box", { 51 | failureThresholdType: "percent", 52 | }) 53 | }) 54 | } 55 | export default createNewFile 56 | -------------------------------------------------------------------------------- /cypress/integration/utils/interface-test/default-template.js: -------------------------------------------------------------------------------- 1 | import commandSetTemplate from "../cypress-command/set-template" 2 | const defaultTemplate = () => { 3 | commandSetTemplate() 4 | it("Should be able to set a default template", () => { 5 | cy.setTemplate("Image Segmentation") 6 | cy.contains("New File").click() 7 | cy.get( 8 | "button[class='MuiButtonBase-root MuiTab-root MuiTab-textColorInherit MuiTab-labelIcon']", 9 | { timeout: 2000 } 10 | ) 11 | .eq(2) 12 | .click() 13 | cy.contains("image_segmentation") 14 | }) 15 | } 16 | export default defaultTemplate 17 | -------------------------------------------------------------------------------- /cypress/integration/utils/interface-test/image-classification.js: -------------------------------------------------------------------------------- 1 | const times = (howManyTimes) => (functionWillExecute) => { 2 | if (howManyTimes > 0) { 3 | functionWillExecute() 4 | times(howManyTimes - 1)(functionWillExecute) 5 | } 6 | } 7 | const imageClassification = () => { 8 | it("Should be able to use image classification", () => { 9 | cy.contains("New File").click() 10 | 11 | cy.log("should be able to import face image dataset") 12 | cy.get("#tab-samples").click() 13 | cy.contains("Import").click() 14 | cy.contains("Import Toy Dataset").click() 15 | cy.contains("AI Generated Faces").siblings("td").eq(2).click() 16 | 17 | cy.log("should be able to setup image classification") 18 | cy.get("#tab-setup", { timeout: 5000 }).click() 19 | cy.contains("Image Classification").click() 20 | cy.get("input[value=valid]").eq(0).focus().clear().type("ai generated") 21 | cy.get("input[value=invalid]") 22 | .eq(0) 23 | .focus() 24 | .clear() 25 | .type("not ai generated") 26 | 27 | cy.log("should be able to see samples") 28 | cy.get("#tab-samples", { timeout: 5000 }).click() 29 | 30 | cy.log("should be able to open 21st sample") 31 | cy.contains("21").click() 32 | 33 | cy.log("should be able to label images") 34 | times(4)(() => { 35 | cy.get("body").click().type("n") 36 | }) 37 | 38 | cy.log("should be able to return samples tab") 39 | cy.get("#tab-samples").click() 40 | 41 | cy.log("should be able to show label tab") 42 | cy.get("#tab-label").click() 43 | }) 44 | } 45 | 46 | export default imageClassification 47 | -------------------------------------------------------------------------------- /cypress/integration/utils/interface-test/image-segmentation.js: -------------------------------------------------------------------------------- 1 | const imageSegmentation = () => { 2 | it("Should be able to use image segmentation", () => { 3 | cy.contains("New File").click() 4 | cy.log("should be able to import cat images dataset") 5 | cy.get("#tab-samples").click() 6 | cy.contains("Import").click() 7 | cy.contains("Import Toy Dataset").click() 8 | cy.contains("Cats").siblings("td").eq(2).click() 9 | 10 | cy.log("should be able to setup image segmentation") 11 | cy.get("#tab-setup").click() 12 | cy.contains("Image Segmentation").click() 13 | cy.contains("bounding-box").click() 14 | cy.get("li[data-value=polygon]").click() 15 | cy.get("li[data-value=point]").click() 16 | cy.get("body").click() 17 | cy.get("input[value=valid]").each(($el, index, $list) => { 18 | cy.get($el).focus().clear().type("cat") 19 | }) 20 | cy.get("input[value=invalid]").each(($el, index, $list) => { 21 | cy.get($el).focus().clear() 22 | }) 23 | 24 | cy.log("should be able to see samples") 25 | cy.get("#tab-samples").click() 26 | 27 | cy.log("should be able start labeling images") 28 | cy.get("div").contains("21").click() 29 | cy.get("div").contains("21").click() 30 | 31 | cy.log('should be able to select bounding box tool with "b" key') 32 | cy.get("body").click().type("b") 33 | 34 | cy.log("should be able to draw a label box on canvas") 35 | cy.get("canvas") 36 | .eq(0) 37 | .trigger("mousedown", { button: 0, clientX: 0, clientY: 50 }) 38 | .trigger("mousemove", { button: 0, clientX: 0, clientY: 50 }) 39 | .trigger("mousemove", { button: 0, clientX: 50, clientY: 50 }) 40 | .trigger("mouseup", { button: 0 }) 41 | 42 | cy.log("should be able to label that box as a cat") 43 | cy.contains("Classification").click().type("cat{enter}") 44 | 45 | cy.log("should be able to go to next image") 46 | cy.contains("Next").click() 47 | }) 48 | } 49 | 50 | export default imageSegmentation 51 | -------------------------------------------------------------------------------- /cypress/integration/utils/interface-test/keyboard-shortcuts.js: -------------------------------------------------------------------------------- 1 | const keyboardShortcuts = () => { 2 | it.skip("Should be able to navigate to label tab with default shortcut", () => { 3 | cy.contains("New File").click() 4 | // TODO this doesn't trigger hot keys for some reason, I'm not sure how 5 | // good the support for testing keyboard shortcuts in cypress is 6 | cy.get("body").trigger("keydown", { 7 | keyCode: 51, 8 | release: false, 9 | location: 0, 10 | which: 51, 11 | key: "3", 12 | code: "Digit3", 13 | }) 14 | 15 | cy.contains("Percent Complete") 16 | }) 17 | } 18 | 19 | export default keyboardShortcuts 20 | -------------------------------------------------------------------------------- /cypress/integration/utils/interface-test/language-box.js: -------------------------------------------------------------------------------- 1 | const setLanguage = (language, langChar) => { 2 | cy.log("should be able to set the language to " + language) 3 | cy.get('input[id="react-select-2-input"]') 4 | .focus() 5 | .type(language) 6 | .type("{enter}") 7 | .should(() => { 8 | cy.expect(localStorage.getItem("i18nextLng"), { 9 | timeout: 2000, 10 | }).to.be.equal(langChar) 11 | }) 12 | } 13 | 14 | const test = () => { 15 | it("Should be able to set english language from text", () => { 16 | setLanguage("English", "en") 17 | }) 18 | it("Should be able to set french language from text", () => { 19 | setLanguage("French", "fr") 20 | }) 21 | it("Should be able to set chinese language from text", () => { 22 | setLanguage("Chinese", "cn") 23 | }) 24 | it("Should be able to set portugese language from text", () => { 25 | setLanguage("Portuguese", "pt") 26 | }) 27 | it("Should be able to set dutch language from text", () => { 28 | setLanguage("Dutch", "nl") 29 | }) 30 | } 31 | 32 | export default test 33 | -------------------------------------------------------------------------------- /cypress/integration/utils/interface-test/named-entity-recognition.js: -------------------------------------------------------------------------------- 1 | const times = (howManyTimes) => (functionWillExecute) => { 2 | if (howManyTimes > 0) { 3 | functionWillExecute() 4 | times(howManyTimes - 1)(functionWillExecute) 5 | } 6 | } 7 | const namedEntityRecognition = () => { 8 | it("Should be able to use named entity recognition", () => { 9 | cy.contains("New File").click() 10 | 11 | cy.log("should be able to import Elon Musk Tweets dataset") 12 | cy.get("#tab-samples").click() 13 | cy.contains("Import").click() 14 | cy.contains("Import Toy Dataset").click() 15 | cy.contains("Elon Musk Tweets").siblings("td").eq(2).click() 16 | 17 | cy.log("should be able to setup Named Entity Recognition") 18 | cy.get("#tab-setup", { timeout: 5000 }).click() 19 | cy.contains("Named Entity Recognition").click() 20 | cy.get("input[value=food]").focus().clear().type("mars") 21 | cy.get("input[value=Food]").focus().clear().type("About Mars") 22 | cy.get("input[value='Edible item.']") 23 | .focus() 24 | .clear() 25 | .type("what is Elon Musk obsessed with.") 26 | cy.get("input[value=hat]").focus().clear().type("not_mars") 27 | cy.get("input[value=Hat]").focus().clear().type("Not about Mars") 28 | cy.get("input[value='Something worn on the head.']") 29 | .focus() 30 | .clear() 31 | .type("not about Elon Musk's obsession") 32 | 33 | cy.log("should be able to see samples") 34 | cy.get("#tab-samples").click() 35 | 36 | cy.log("should be able start labeling images from 21st sample") 37 | cy.contains("div", "21").click() 38 | 39 | cy.log("should be able to label samples") 40 | times(4)(() => { 41 | cy.get("body").click().type("n") 42 | cy.get("body").click().type("{enter}") 43 | }) 44 | 45 | cy.log("should be able to return samples tab") 46 | cy.get("#tab-samples").click() 47 | 48 | cy.log("should be able to show label tab") 49 | cy.get("#tab-label").click() 50 | }) 51 | } 52 | 53 | export default namedEntityRecognition 54 | -------------------------------------------------------------------------------- /cypress/integration/utils/interface-test/paste-image-urls-with-csv.js: -------------------------------------------------------------------------------- 1 | const csvHeader = "customId,imageUrl,male_or_female.value" 2 | const csvRows = [ 3 | "faces/010260.jpg,https://wao.ai/app/api/download/0031a48c-635a-4ea9-833f-9381c88836d2,female", 4 | "faces/010265.jpg,https://wao.ai/app/api/download/00409ca5-1e28-43c6-9f10-6cccf47178a3,female", 5 | "faces/011094.jpg,https://wao.ai/app/api/download/0060b2fa-6f7d-49c3-a965-ab82fe8a9475,female", 6 | ] 7 | 8 | const pasteImageUrlsWithCSV = () => { 9 | it("Should be able to paste images urls with CSV", () => { 10 | cy.contains("New File").click() 11 | 12 | cy.log("should be able to open import dialog") 13 | cy.get("#tab-samples").click() 14 | cy.contains("Import").click() 15 | cy.contains("Import from CSV / JSON").click() 16 | 17 | cy.log("should be able to click textarea to paste") 18 | cy.get("textarea").click() 19 | 20 | cy.log("should be able to paste csv of image urls") 21 | cy.get("textarea").type(csvHeader).type("{enter}") 22 | for (const csvRow of csvRows) { 23 | cy.get("textarea").type(csvRow + "{enter}") 24 | } 25 | 26 | cy.log("should be able to add samples from csv") 27 | cy.contains("Add Samples").click() 28 | 29 | cy.log("should be able to go to setup") 30 | cy.get("#tab-setup").click() 31 | 32 | cy.log("should be able to go to Image Classification setup") 33 | cy.contains("Image Classification").click() 34 | 35 | cy.log("should be able to see samples") 36 | cy.get("#tab-samples").click() 37 | cy.contains("2").click() 38 | 39 | cy.log("should be able to label image") 40 | cy.get("body").click().type("{enter}") 41 | cy.get("body").click().type("{enter}") 42 | }) 43 | } 44 | 45 | export default pasteImageUrlsWithCSV 46 | -------------------------------------------------------------------------------- /cypress/integration/utils/interface-test/paste-image-urls.js: -------------------------------------------------------------------------------- 1 | const imageUrls = [ 2 | "https://wao.ai/app/api/download/fef3ee4d-f841-42f5-ba22-a7c8eb214628", 3 | "https://wao.ai/app/api/download/f80f660f-6641-4241-9c9d-7c3ac26c5a17", 4 | "https://wao.ai/app/api/download/ff03593a-6b7e-46dd-91bf-9e741c35c227", 5 | ] 6 | 7 | const pasteImageUrls = () => { 8 | it("should be able to paste image urls", () => { 9 | cy.contains("New File").click() 10 | 11 | cy.log("should be able to open paste") 12 | cy.get("#tab-samples").click() 13 | cy.contains("Import").click() 14 | cy.contains("Paste URLs").click() 15 | 16 | cy.log("should be able to paste image urls") 17 | const imagesWithNewLines = imageUrls.join("{enter}") 18 | cy.get("textarea").type(imagesWithNewLines) 19 | 20 | cy.log("should be able to add that samples") 21 | cy.contains("Auto Detect File Type").click({ force: true }) 22 | cy.contains("Image URLs").click({ force: true }) 23 | cy.contains("Add Samples").click() 24 | 25 | cy.log("should be able to go to setup") 26 | cy.get("#tab-setup").click() 27 | 28 | cy.log("should be able to go to Image Classification") 29 | cy.contains("Image Classification").click() 30 | 31 | cy.log("should be able to see samples") 32 | cy.get("#tab-samples").click() 33 | cy.contains("2").click() 34 | 35 | cy.log("should be able to label image") 36 | cy.get("body").click().type("{enter}") 37 | cy.get("body").click().type("{enter}") 38 | }) 39 | } 40 | 41 | export default pasteImageUrls 42 | -------------------------------------------------------------------------------- /cypress/integration/utils/interface-test/template-non-visble.js: -------------------------------------------------------------------------------- 1 | const templateNonVisible = () => { 2 | it("time series 2 should not be visible", () => { 3 | cy.contains("New File").click() 4 | cy.contains("Time Series 2").should("not.exist") 5 | }) 6 | } 7 | 8 | export default templateNonVisible 9 | -------------------------------------------------------------------------------- /cypress/integration/utils/remove-cypress-file-in-aws.js: -------------------------------------------------------------------------------- 1 | import datasetManagerCognito from "udt-dataset-managers/dist/CognitoDatasetManager" 2 | 3 | const removeAWSFile = (name) => { 4 | var credentials = Cypress.env() 5 | cy.then( 6 | async () => { 7 | const authConfig = { 8 | Auth: { 9 | identityPoolId: credentials.AWS_IDENTITY_POOL_ID, 10 | region: credentials.AWS_AUTH_REGION, 11 | userPoolId: credentials.AWS_USER_POOL_ID, 12 | userPoolWebClientId: credentials.AWS_USER_POOL_WEB_CLIENT_ID, 13 | mandatorySignIn: credentials.AWS_MANDATORY_SIGN_IN, 14 | authenticationFlowType: credentials.AWS_AUTHENTICATION_FLOW_TYPE, 15 | }, 16 | Storage: { 17 | AWSS3: { 18 | bucket: credentials.AWS_STORAGE_BUCKET, 19 | region: credentials.AWS_STORAGE_REGION, 20 | }, 21 | }, 22 | } 23 | const ds = await new datasetManagerCognito({ authConfig }) 24 | await ds.removeProject(name) 25 | }, 26 | { timeout: 10000 } 27 | ) 28 | } 29 | export default removeAWSFile 30 | -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | /** 16 | * @type {Cypress.PluginConfig} 17 | */ 18 | // https://github.com/palmerhq/cypress-image-snapshot 19 | const { addMatchImageSnapshotPlugin } = require("cypress-image-snapshot/plugin") 20 | 21 | module.exports = (on, config) => { 22 | const options = { 23 | outputRoot: config.projectRoot + "/logs/", 24 | outputTarget: { 25 | "out.txt": "txt", 26 | "out.json": "json", 27 | }, 28 | printLogsToConsole: "never", 29 | } 30 | // `on` is used to hook into various events Cypress emits 31 | // `config` is the resolved Cypress config 32 | addMatchImageSnapshotPlugin(on, config), 33 | require("@cypress/react/plugins/react-scripts")(on, config) 34 | require("cypress-terminal-report/src/installLogsPrinter")(on, options) 35 | return config 36 | } 37 | -------------------------------------------------------------------------------- /cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | import { addMatchImageSnapshotCommand } from "cypress-image-snapshot/command" 27 | // https://github.com/abramenal/cypress-file-upload 28 | import "cypress-file-upload" 29 | 30 | addMatchImageSnapshotCommand() 31 | -------------------------------------------------------------------------------- /cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import "./commands" 18 | require("cypress-terminal-report/src/installLogsCollector")() 19 | 20 | // Alternatively you can use CommonJS syntax: 21 | // require('./commands') 22 | -------------------------------------------------------------------------------- /desktop/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-jit 6 | 7 | com.apple.security.cs.allow-unsigned-executable-memory 8 | 9 | com.apple.security.cs.allow-dyld-environment-variables 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /desktop/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "universal-data-tool-desktop", 3 | "version": "0.0.0", 4 | "description": "Label images, text and more. Desktop Application for the Universal Data Tool.", 5 | "main": "main.js", 6 | "repository": "https://github.com/openhumanannotation/universal-data-tool-desktop", 7 | "keywords": [] 8 | } 9 | -------------------------------------------------------------------------------- /desktop/preload.js: -------------------------------------------------------------------------------- 1 | // All of the Node.js APIs are available in the preload process. 2 | // It has the same sandbox as a Chrome extension. 3 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniversalDataTool/universal-data-tool/8e46659a64a54448c36223d00c6bf6b644227e32/public/favicon.ico -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 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.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | border-spacing: 0; 6 | } 7 | 8 | .universaldatatool { 9 | font-family: "Inter"; 10 | } 11 | -------------------------------------------------------------------------------- /src/AppWithContexts.js: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from "react" 2 | import Theme from "./components/Theme" 3 | import App from "./components/App" 4 | import { RecoilRoot } from "recoil" 5 | import { ToastProvider } from "./components/Toasts" 6 | import { AppConfigProvider } from "./components/AppConfig" 7 | import { AuthProvider } from "./utils/auth-handlers/use-auth.js" 8 | import { LabelHelpProvider } from "./components/LabelHelpView" 9 | import { HotkeyStorageProvider } from "./components/HotkeyStorage" 10 | import "./App.css" 11 | 12 | import Loading from "./components/Loading" 13 | 14 | // Importing Internalization file 15 | import "./i18n" 16 | 17 | export const AppWithContexts = () => { 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ) 37 | } 38 | 39 | export default AppWithContexts 40 | -------------------------------------------------------------------------------- /src/components/ActiveLearningView/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { styled } from "@material-ui/core/styles" 3 | import * as colors from "@material-ui/core/colors" 4 | 5 | const Container = styled("div")({ 6 | fontSize: 18, 7 | padding: 32, 8 | textAlign: "center", 9 | color: colors.grey[700], 10 | "& a": { 11 | color: colors.blue[500], 12 | }, 13 | }) 14 | 15 | export const ActiveLearningView = () => { 16 | return ( 17 | 18 | Hey, this isn't available yet, but if you'd like this functionality please 19 | let us know by leaving a thumbs up on{" "} 20 | 21 | this github issue 22 | 23 | 24 | ) 25 | } 26 | 27 | export default ActiveLearningView 28 | -------------------------------------------------------------------------------- /src/components/AddAuthFromTemplateDialog/authTemplates.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import CognitoIcon from "./cognito-icon.js" 3 | import SdStorageOutlinedIcon from "@material-ui/icons/SdStorageOutlined" 4 | import AccountTreeIcon from "@material-ui/icons/AccountTree" 5 | 6 | export default [ 7 | { 8 | name: "AWS - Cognito", 9 | provider: "cognito", 10 | Icon: CognitoIcon, 11 | }, 12 | { 13 | name: "AWS - S3 (IAM)", 14 | provider: "s3iam", 15 | Icon: SdStorageOutlinedIcon, 16 | }, 17 | { 18 | name: "Proxy", 19 | provider: "proxy", 20 | Icon: AccountTreeIcon, 21 | }, 22 | ] 23 | -------------------------------------------------------------------------------- /src/components/App/index.story.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from "react" 4 | 5 | import { storiesOf } from "@storybook/react" 6 | 7 | import WebApp from "./" 8 | 9 | storiesOf("WebApp", module).add("Basic", () => ) 10 | -------------------------------------------------------------------------------- /src/components/App/sample.udt.json: -------------------------------------------------------------------------------- 1 | { 2 | "interface": { 3 | "type": "data_entry", 4 | "description": "# Markdown description here", 5 | "surveyjs": { 6 | "questions": [ 7 | { 8 | "type": "text", 9 | "name": "document_title", 10 | "title": "Title of Document" 11 | } 12 | ] 13 | } 14 | }, 15 | "samples": [ 16 | { 17 | "pdfUrl": "https://arxiv.org/pdf/1906.01969.pdf" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/components/AudioTranscription/index.js: -------------------------------------------------------------------------------- 1 | // @flow weak 2 | 3 | import React from "react" 4 | import NLPAnnotator from "react-nlp-annotate" 5 | import Box from "@material-ui/core/Box" 6 | 7 | export default ({ 8 | sampleIndex, 9 | sample, 10 | interface: iface, 11 | onModifySample, 12 | onExit, 13 | }) => { 14 | return ( 15 | Sample {sampleIndex}} 18 | type="transcribe" 19 | audio={sample.audioUrl} 20 | phraseBank={iface.phraseBank} 21 | initialTranscriptionText={sample.annotation || ""} 22 | onPrev={(result) => { 23 | onModifySample({ ...sample, annotation: result }) 24 | onExit("go-to-previous") 25 | }} 26 | onNext={(result) => { 27 | onModifySample({ ...sample, annotation: result }) 28 | onExit("go-to-next") 29 | }} 30 | onFinish={(result) => { 31 | onModifySample({ ...sample, annotation: result }) 32 | onExit() 33 | }} 34 | /> 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /src/components/AudioTranscription/index.story.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from "react" 4 | 5 | import { storiesOf } from "@storybook/react" 6 | import { action } from "@storybook/addon-actions" 7 | 8 | import AudioTranscription from "./" 9 | 10 | storiesOf("AudioTranscription", module).add("Basic", () => ( 11 | 22 | )) 23 | -------------------------------------------------------------------------------- /src/components/BadDataset/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from "react" 4 | import Typography from "@material-ui/core/Typography" 5 | import { makeStyles } from "@material-ui/core/styles" 6 | 7 | const useStyles = makeStyles({ 8 | root: { 9 | padding: 50, 10 | textAlign: "center", 11 | }, 12 | title: { 13 | margin: 50, 14 | }, 15 | explain: {}, 16 | }) 17 | 18 | export const BadDataset = ({ title, description, children }) => { 19 | const c = useStyles() 20 | return ( 21 |
22 | 23 | {title} 24 | 25 | 26 | {description || children} 27 | 28 |
29 | ) 30 | } 31 | 32 | export default BadDataset 33 | -------------------------------------------------------------------------------- /src/components/Configure3D/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { styled } from "@material-ui/core/styles" 3 | import * as colors from "@material-ui/core/colors" 4 | 5 | import { useTranslation } from "react-i18next" 6 | 7 | const ExplanationTextHeader = styled("div")({ 8 | textAlign: "center", 9 | paddingTop: 30, 10 | paddingBottom: 50, 11 | userSelect: "none", 12 | }) 13 | 14 | const ExplanationText = styled("h3")({ 15 | fontWeight: "bold", 16 | color: colors.grey[500], 17 | }) 18 | 19 | const GithubLink = styled("a")({ 20 | color: colors.blue[500], 21 | }) 22 | 23 | const Configure3D = () => { 24 | const { t } = useTranslation() 25 | 26 | return ( 27 | 28 | 29 | {t("configure-3d-explanation-text-part-1")}{" "} 30 | 35 | this 36 | {" "} 37 | {t("github-issue")}. 38 | 39 | 40 | ) 41 | } 42 | 43 | export default Configure3D 44 | -------------------------------------------------------------------------------- /src/components/ConfigureImageClassification/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { useMemo } from "react" 3 | import Survey from "material-survey/components/Survey" 4 | import { setIn } from "seamless-immutable" 5 | 6 | const form = { 7 | questions: [ 8 | { 9 | name: "multiple", 10 | title: "Allow multiple classifications per image?", 11 | type: "boolean", 12 | }, 13 | { 14 | name: "labels", 15 | title: "Labels", 16 | description: "Classifications or tags to be labeled.", 17 | type: "matrixdynamic", 18 | columns: [ 19 | { cellType: "text", name: "id", title: "id" }, 20 | { 21 | cellType: "text", 22 | name: "description", 23 | title: "Description (optional)", 24 | }, 25 | ], 26 | }, 27 | ], 28 | } 29 | 30 | export default ({ iface, onChange }) => { 31 | const defaultAnswers = useMemo( 32 | () => ({ 33 | multiple: iface.multiple ? iface.multiple : false, 34 | labels: 35 | (iface.labels || []).map((a) => { 36 | return typeof a === "string" ? { id: a, description: a } : a 37 | }) || [], 38 | }), 39 | [iface.labels, iface.multiple] 40 | ) 41 | return ( 42 | { 47 | var arrayId = [] 48 | if (Array.isArray(newValue)) 49 | newValue = newValue.filter((json) => { 50 | if (arrayId.includes(json.id)) return false 51 | arrayId.push(json.id) 52 | return true 53 | }) 54 | onChange(setIn(iface, [questionId], newValue)) 55 | }} 56 | form={form} 57 | /> 58 | ) 59 | } 60 | -------------------------------------------------------------------------------- /src/components/ConfigureImageLandmarkAnnotation/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | export default ({ iface, onChange }) => { 4 | return ( 5 |
6 | We'd like to put a little interface to edit poses or landmarks here, but 7 | we need help! Check out this{" "} 8 | 9 | Github Issue 10 | {" "} 11 | to learn more! For now just turn to the JSON tab to edit the landmarks 12 | manually! 13 |
14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /src/components/ConfigureInterface/index.story.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { useState } from "react" 4 | 5 | import { storiesOf } from "@storybook/react" 6 | import { action } from "@storybook/addon-actions" 7 | 8 | import ConfigureInterface from "./" 9 | 10 | storiesOf("ConfigureInterface", module).add("Data Entry", () => { 11 | const [iface, changeIFace] = useState({ type: "data_entry" }) 12 | const dataset = { interface: iface } 13 | return ( 14 | { 17 | action("onChange")(...args) 18 | changeIFace(args[0]) 19 | }} 20 | /> 21 | ) 22 | }) 23 | -------------------------------------------------------------------------------- /src/components/ConfigureTextClassification/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { useMemo } from "react" 4 | import Survey from "material-survey/components/Survey" 5 | import { setIn } from "seamless-immutable" 6 | 7 | const form = { 8 | questions: [ 9 | { 10 | name: "multiple", 11 | title: "Allow Multiple Classifications", 12 | type: "boolean", 13 | }, 14 | { 15 | name: "labels", 16 | title: "Labels", 17 | description: "Classifications or tags to be labeled.", 18 | type: "matrixdynamic", 19 | columns: [ 20 | { cellType: "text", name: "id", title: "id" }, 21 | { cellType: "text", name: "displayName", title: "Display Name" }, 22 | { 23 | cellType: "text", 24 | name: "description", 25 | title: "Description (optional)", 26 | }, 27 | ], 28 | }, 29 | ], 30 | } 31 | 32 | export default ({ iface, onChange }) => { 33 | const defaultAnswers = useMemo( 34 | () => ({ 35 | multiple: false, 36 | labels: 37 | (iface.labels || []).map((a) => 38 | typeof a === "string" ? { id: a, displayName: a, description: a } : a 39 | ) || [], 40 | }), 41 | [iface.labels] 42 | ) 43 | return ( 44 | { 49 | onChange(setIn(iface, [questionId], newValue)) 50 | }} 51 | form={form} 52 | /> 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /src/components/ConfigureTextEntityRecognition/index.js: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from "react" 2 | import Survey from "material-survey/components/Survey" 3 | import { setIn } from "seamless-immutable" 4 | 5 | const form = { 6 | questions: [ 7 | // TODO uncomment when description can be seen in the interface somewhere 8 | // { 9 | // name: "description", 10 | // title: "Description", 11 | // type: "multiline-text", 12 | // }, 13 | // TODO enable when we support overlap 14 | // { 15 | // name: "overlapAllowed", 16 | // title: "Overlap Allowed", 17 | // type: "boolean", 18 | // }, 19 | { 20 | name: "labels", 21 | title: "Labels", 22 | type: "matrixdynamic", 23 | hasOther: true, 24 | columns: [ 25 | { 26 | cellType: "text", 27 | name: "id", 28 | title: "ID", 29 | }, 30 | { 31 | cellType: "text", 32 | name: "displayName", 33 | title: "Display Name", 34 | }, 35 | { 36 | cellType: "text", 37 | name: "description", 38 | title: "Description", 39 | }, 40 | ], 41 | }, 42 | ], 43 | } 44 | 45 | export default ({ iface, onChange }) => { 46 | const defaultAnswers = useMemo( 47 | () => ({ 48 | description: iface.description, 49 | overlapAllowed: Boolean( 50 | iface.overlapAllowed || iface.overlapAllowed === undefined 51 | ), 52 | labels: (iface.labels || []).map((column) => 53 | typeof column === "string" 54 | ? { 55 | id: column, 56 | displayName: column, 57 | description: column, 58 | } 59 | : column 60 | ), 61 | }), 62 | [iface] 63 | ) 64 | return ( 65 | { 69 | onChange(setIn(iface, [questionId], newValue)) 70 | }} 71 | form={form} 72 | defaultAnswers={defaultAnswers} 73 | /> 74 | ) 75 | } 76 | -------------------------------------------------------------------------------- /src/components/CreateFromTemplateDialog/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from "react" 4 | import Button from "@material-ui/core/Button" 5 | import { makeStyles } from "@material-ui/core/styles" 6 | import templates from "../StartingPage/templates" 7 | import SimpleDialog from "../SimpleDialog" 8 | 9 | const useStyles = makeStyles({ 10 | bigButton: { 11 | padding: 10, 12 | width: 150, 13 | height: 120, 14 | border: "1px solid #ccc", 15 | margin: 10, 16 | }, 17 | bigIcon: { 18 | width: 48, 19 | height: 48, 20 | }, 21 | }) 22 | 23 | export default ({ open, onClose, onSelect }) => { 24 | const c = useStyles() 25 | return ( 26 | 27 | {templates.map((template) => ( 28 | 40 | ))} 41 | 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /src/components/DataEntry/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Survey from "material-survey/components/Survey" 3 | import getTaskDescription from "../../utils/get-task-description.js" 4 | import SampleContainer from "../SampleContainer" 5 | 6 | export const DataEntry = ({ 7 | containerProps, 8 | interface: iface, 9 | sample, 10 | sampleIndex, 11 | onModifySample, 12 | }) => { 13 | const form = sample.surveyjs || iface.surveyjs 14 | if (!form) 15 | throw new Error("No survey/form created. Try adding some inputs in Setup") 16 | return ( 17 | 23 | { 30 | onModifySample({ ...sample, annotation: answers }) 31 | containerProps.onExit("go-to-next") 32 | }} 33 | /> 34 | 35 | ) 36 | } 37 | 38 | export default DataEntry 39 | -------------------------------------------------------------------------------- /src/components/DatasetJSONEditor/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import RawJSONEditor from "../RawJSONEditor" 4 | import useDataset from "../../hooks/use-dataset" 5 | 6 | export const DatasetJSONEditor = () => { 7 | const [dataset, setDataset] = useDataset() 8 | return ( 9 | 10 | ) 11 | } 12 | 13 | export default DatasetJSONEditor 14 | -------------------------------------------------------------------------------- /src/components/EditSampleDialog/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { useState, useEffect } from "react" 4 | import Box from "@material-ui/core/Box" 5 | import AceEditor from "react-ace" 6 | import SimpleDialog from "../SimpleDialog" 7 | 8 | export default ({ open, sampleIndex, sampleInput, onChange, onClose }) => { 9 | const [text, changeText] = useState() 10 | const [error, changeError] = useState() 11 | useEffect(() => { 12 | const newText = JSON.stringify(sampleInput, null, " ") 13 | if (newText !== text) { 14 | changeText(newText) 15 | changeError(null) 16 | } 17 | }, [sampleIndex, sampleInput, text]) 18 | 19 | return ( 20 | 25 | { 32 | changeText(t) 33 | changeError(null) 34 | try { 35 | onChange(JSON.parse(t)) 36 | } catch (e) { 37 | changeError(e.toString()) 38 | } 39 | }} 40 | /> 41 | {error} 42 | 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /src/components/EmptySampleContainer/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from "react" 4 | import BadDataset from "../BadDataset" 5 | 6 | export default () => { 7 | return ( 8 | 9 | Make sure that samples is defined and not empty. 10 |
11 |
12 | Need help setting up? {/*Check out this tutorial.*/} 13 |
14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /src/components/ErrorToasts/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from "react" 3 | import { makeStyles } from "@material-ui/core/styles" 4 | import { red } from "@material-ui/core/colors" 5 | import Collapse from "@material-ui/core/Collapse" 6 | import Fade from "@material-ui/core/Fade" 7 | 8 | const useStyles = makeStyles({ 9 | root: { 10 | position: "fixed", 11 | display: "flex", 12 | flexDirection: "column", 13 | bottom: 0, 14 | left: 0, 15 | right: 0, 16 | alignItems: "center", 17 | pointerEvents: "none", 18 | }, 19 | errorBox: { 20 | display: "flex", 21 | backgroundColor: red[700], 22 | color: "#fff", 23 | padding: 4, 24 | marginBottom: 4, 25 | }, 26 | }) 27 | 28 | export default ({ errors }) => { 29 | const c = useStyles() 30 | return ( 31 |
32 | {errors.map((err) => ( 33 | 34 | 500}> 35 |
{err.message}
36 |
37 |
38 | ))} 39 |
40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /src/components/ExportToCognitoS3Dialog/create-assets.js: -------------------------------------------------------------------------------- 1 | import fetchAFile from "./fetch-a-file" 2 | const createAssets = async (dataset, configExport, dm) => { 3 | await Promise.all( 4 | dataset.samples.map(async (sample, index, samples) => { 5 | var sampleName = await dm.getSampleName(sample) 6 | var blob = await fetchAFile(sample, configExport, dm) 7 | if (!blob) return 8 | await dm.addAsset(sampleName, blob, dataset.name) 9 | }) 10 | ) 11 | } 12 | export default createAssets 13 | -------------------------------------------------------------------------------- /src/components/ExportToCognitoS3Dialog/fetch-a-file.js: -------------------------------------------------------------------------------- 1 | const fetchAFile = async (element, configExport, dm) => { 2 | var response 3 | var url 4 | if (dm.getSampleUrl(element) !== undefined) { 5 | url = configExport.proxyUrl + dm.getSampleUrl(element) 6 | response = await fetch(url, { 7 | method: "GET", 8 | headers: { 9 | "X-Requested-With": "xmlhttprequest", 10 | }, 11 | }).catch((error) => { 12 | console.log("Looks like there was a problem: \n", error) 13 | }) 14 | if (response) { 15 | const blob = await response.blob() 16 | return blob 17 | } 18 | } else { 19 | var text = await dm.getAssetText(element) 20 | if (text) return text 21 | var time = await dm.getAssetTime(element) 22 | if (time) return time 23 | } 24 | } 25 | export default fetchAFile 26 | -------------------------------------------------------------------------------- /src/components/ExportToCognitoS3Dialog/init-config-export.js: -------------------------------------------------------------------------------- 1 | const basicConfig = { 2 | contentDialogBoxIsSetting: false, 3 | typeAssetExport: "none", 4 | proxyUrl: "https://cors-anywhere.herokuapp.com/", 5 | } 6 | export default basicConfig 7 | -------------------------------------------------------------------------------- /src/components/ExportToCognitoS3Dialog/warning-header.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | const WarningHeader = ({ nameProjectExist, nameProjectToCreate }) => { 3 | const orangeText = { color: "orange", textAlign: "center" } 4 | var text = `Warning :` 5 | if (nameProjectExist) 6 | text += ` This project name already exist. If you continue the existing project with the same name will be replaced.\n` 7 | if (nameProjectToCreate === "") text += ` Please enter a project name.\n` 8 | return text === `Warning :` ? ( 9 |
10 | ) : ( 11 |
12 | {text.split("\n").map((obj, i) => { 13 | return
{obj}
14 | })} 15 |
16 | ) 17 | } 18 | export default WarningHeader 19 | -------------------------------------------------------------------------------- /src/components/FileContext/index.js: -------------------------------------------------------------------------------- 1 | import { useContext, createContext } from "react" 2 | 3 | export const FileContext = createContext({}) 4 | 5 | export const useFileContext = () => useContext(FileContext) 6 | 7 | export { useActiveDataset } from "./use-active-dataset.js" 8 | -------------------------------------------------------------------------------- /src/components/FileContext/use-active-dataset.js: -------------------------------------------------------------------------------- 1 | import { useFileContext } from "./" 2 | import { useMemo } from "react" 3 | import { setIn } from "seamless-immutable" 4 | 5 | export const useActiveDataset = () => { 6 | const { file, setFile } = useFileContext() 7 | const result = useMemo( 8 | () => 9 | !file || !file.content 10 | ? null 11 | : { 12 | dataset: file.content, 13 | setDataset: (newDataset) => { 14 | setFile(setIn(file, ["content"], newDataset)) 15 | }, 16 | }, 17 | [file, setFile] 18 | ) 19 | return result || {} 20 | } 21 | -------------------------------------------------------------------------------- /src/components/Header/GithubIcon.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from "react" 4 | import SvgIcon from "@material-ui/core/SvgIcon" 5 | 6 | function GitHub(props: Object) { 7 | return ( 8 | 9 | 10 | 11 | ) 12 | } 13 | 14 | export default GitHub 15 | -------------------------------------------------------------------------------- /src/components/Header/HeaderWithContainer.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { styled } from "@material-ui/core/styles" 3 | import { Header } from "./" 4 | 5 | const Container = styled("div")({ 6 | display: "flex", 7 | flexDirection: "column", 8 | overflowX: "hidden", 9 | }) 10 | const Body = styled("div")({ 11 | display: "flex", 12 | }) 13 | const Content = styled("div")({ 14 | flexGrow: 1, 15 | }) 16 | 17 | export const HeaderWithContainer = (props) => { 18 | return ( 19 | 20 | 21 |
22 | {props.children} 23 | 24 | 25 | ) 26 | } 27 | 28 | export default HeaderWithContainer 29 | -------------------------------------------------------------------------------- /src/components/Header/index.story.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from "react" 4 | 5 | import { storiesOf } from "@storybook/react" 6 | 7 | import Header from "./" 8 | 9 | storiesOf("Header", module) 10 | .add("Tabs", () => ( 11 |
16 | )) 17 | .add("Sample Color", () => ( 18 |
23 | )) 24 | -------------------------------------------------------------------------------- /src/components/HeaderPopupBox/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from "react" 4 | import { styled } from "@material-ui/core/styles" 5 | import * as colors from "@material-ui/core/colors" 6 | 7 | const WIDTH = 300 8 | const borderColor = colors.grey[500] 9 | const popupBoxBeforeAndAfter = { 10 | right: "100%", 11 | top: 43, 12 | border: "solid transparent", 13 | content: '" "', 14 | height: 0, 15 | width: 0, 16 | position: "absolute", 17 | pointerEvents: "none", 18 | } 19 | const PopupBox = styled("div")({ 20 | position: "absolute", 21 | zIndex: 10, 22 | top: -20, 23 | padding: 16, 24 | boxSizing: "border-box", 25 | borderRadius: 4, 26 | // left: -WIDTH / 4 + 22, 27 | left: 48, 28 | backgroundColor: "#fff", 29 | border: `1px solid ${borderColor}`, 30 | width: WIDTH, 31 | minHeight: 200, 32 | boxShadow: "0px 3px 12px rgba(0,0,0,0.3)", 33 | "&:before": { 34 | ...popupBoxBeforeAndAfter, 35 | borderColor: "rgba(0,0,0, 0)", 36 | borderRightColor: borderColor, 37 | borderWidth: 12, 38 | marginTop: -12, 39 | }, 40 | "&:after": { 41 | ...popupBoxBeforeAndAfter, 42 | borderColor: "rgba(255,255,255, 0)", 43 | borderRightColor: "#fff", 44 | borderWidth: 10, 45 | marginTop: -10, 46 | }, 47 | "& h1": { 48 | fontSize: 18, 49 | marginTop: 0, 50 | color: colors.blue[800], 51 | }, 52 | "& h2": { 53 | fontSize: 14, 54 | color: colors.grey[800], 55 | }, 56 | opacity: 1, 57 | transition: "opacity 200ms linear, transform 200ms ease", 58 | "&.hidden": { 59 | opacity: 0, 60 | transform: "translate(0, 10px)", 61 | pointerEvents: "none", 62 | }, 63 | }) 64 | 65 | export default ({ open, children }) => { 66 | return {children} 67 | } 68 | -------------------------------------------------------------------------------- /src/components/HeaderToolbar/SlackIcon.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import SvgIcon from "@material-ui/core/SvgIcon" 3 | 4 | const SlackIcon = (props) => { 5 | return ( 6 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | ) 35 | } 36 | 37 | export default SlackIcon 38 | -------------------------------------------------------------------------------- /src/components/HotkeyStorage/default-hotkeys.js: -------------------------------------------------------------------------------- 1 | export const defaultHotkeys = [ 2 | { 3 | id: "switch_to_label", 4 | description: "Go to Labels Tab", 5 | binding: "shift+3", 6 | }, 7 | { 8 | id: "switch_to_setup", 9 | description: "Go to Setup Tab", 10 | binding: "shift+1", 11 | }, 12 | { 13 | id: "switch_to_samples", 14 | description: "Go to Samples Tab", 15 | binding: "shift+2", 16 | }, 17 | { 18 | id: "select_tool", 19 | description: "Switch to the Select Tool", 20 | binding: "escape", 21 | }, 22 | { 23 | id: "zoom_tool", 24 | description: "Select the Zoom Tool", 25 | binding: "z", 26 | }, 27 | { 28 | id: "create_point", 29 | description: "Create a point", 30 | }, 31 | { 32 | id: "create_bounding_box", 33 | description: "Create a bounding box", 34 | binding: "b", 35 | }, 36 | { 37 | id: "pan_tool", 38 | description: "Select the Pan Tool", 39 | binding: "p", 40 | }, 41 | { 42 | id: "create_polygon", 43 | description: "Create a Polygon", 44 | }, 45 | { 46 | id: "create_pixel", 47 | description: "Create a Pixel Mask", 48 | }, 49 | { 50 | id: "save_and_previous_sample", 51 | description: "Save and go to previous sample", 52 | binding: "a", 53 | }, 54 | { 55 | id: "save_and_next_sample", 56 | description: "Save and go to next sample", 57 | binding: "d", 58 | }, 59 | { 60 | id: "save_and_exit_sample", 61 | description: "Save and exit current sample", 62 | }, 63 | { 64 | id: "exit_sample", 65 | description: "Exit sample without saving", 66 | }, 67 | ] 68 | export default defaultHotkeys 69 | -------------------------------------------------------------------------------- /src/components/HotkeyStorage/index.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useMemo } from "react" 2 | import { defaultHotkeys } from "./default-hotkeys" 3 | import { useAppConfig } from "../AppConfig" 4 | 5 | export const HotkeyContext = createContext({ 6 | hotkeys: defaultHotkeys, 7 | changeHotkey: (id, newBinding) => null, 8 | }) 9 | 10 | export const useHotkeyStorage = () => useContext(HotkeyContext) 11 | 12 | export const HotkeyStorageProvider = ({ children }) => { 13 | const { fromConfig, setInConfig, clearInConfig } = useAppConfig() 14 | 15 | const hotkeys = useMemo( 16 | () => 17 | defaultHotkeys.map((item) => { 18 | if (fromConfig(`hotkeys.${item.id}`)) { 19 | return { ...item, binding: fromConfig(`hotkeys.${item.id}`) } 20 | } else { 21 | return item 22 | } 23 | }), 24 | [fromConfig] 25 | ) 26 | 27 | const keyMap = useMemo(() => { 28 | const keyMap = {} 29 | for (const { id, binding } of hotkeys) { 30 | if (!binding) continue 31 | keyMap[id] = binding 32 | } 33 | return keyMap 34 | }, [hotkeys]) 35 | 36 | const contextValue = useMemo( 37 | () => ({ 38 | hotkeys, 39 | keyMap, 40 | clearHotkeys: () => { 41 | clearInConfig(hotkeys.map((key) => `hotkeys.${key.id}`)) 42 | }, 43 | changeHotkey: (id, newBinding) => 44 | setInConfig(`hotkeys.${id}`, newBinding), 45 | }), 46 | [hotkeys, keyMap, clearInConfig, setInConfig] 47 | ) 48 | 49 | return ( 50 | 51 | {children} 52 | 53 | ) 54 | } 55 | 56 | export { defaultHotkeys } 57 | -------------------------------------------------------------------------------- /src/components/ImageClassification/index.story.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from "react" 4 | 5 | import { storiesOf } from "@storybook/react" 6 | import { action } from "@storybook/addon-actions" 7 | 8 | import ImageClassification from "./" 9 | 10 | storiesOf("ImageClassification", module) 11 | .add("Basic", () => ( 12 | 27 | )) 28 | .add("Allow Multiple", () => ( 29 | 45 | )) 46 | -------------------------------------------------------------------------------- /src/components/ImportFromCognitoS3Dialog/check-interface-and-sample-type.js: -------------------------------------------------------------------------------- 1 | import isEmpty from "lodash/isEmpty" 2 | const interfaceFileType = (type) => { 3 | if ( 4 | type === "image_classification" || 5 | type === "image_segmentation" || 6 | type === "composite" 7 | ) 8 | return "Image" 9 | if (type === "video_segmentation") return "Video" 10 | if (type === "audio_transcription") return "Audio" 11 | if (type === "data_entry") return "PDF" 12 | if (type === "text_entity_recognition" || type === "text_classification") 13 | return "Text" 14 | if (type === "time_series") return "Time" 15 | if (isEmpty(type)) return "Empty" 16 | return "File" 17 | } 18 | 19 | const typeAssetsFromSample = (assets) => { 20 | if (isEmpty(assets) || isEmpty(assets[0])) return "Empty" 21 | if (!isEmpty(assets[0].imageUrl)) return "Image" 22 | if (!isEmpty(assets[0].videoUrl)) return "Video" 23 | if (!isEmpty(assets[0].audioUrl)) return "Audio" 24 | if (!isEmpty(assets[0].pdfUrl)) return "PDF" 25 | if (!isEmpty(assets[0].document)) return "Text" 26 | if (!isEmpty(assets[0].timeData)) return "Time" 27 | return "File" 28 | } 29 | export default (typeAuthorizeInterface, typeAuthorizeAssets, file) => { 30 | var result = [null, null] 31 | if (isEmpty(file)) return false 32 | result[0] = interfaceFileType(file.interface.type) 33 | result[1] = typeAssetsFromSample(file.samples) 34 | if ( 35 | typeAuthorizeInterface.includes(result[0]) && 36 | typeAuthorizeAssets.includes(result[1]) 37 | ) 38 | return true 39 | return false 40 | } 41 | -------------------------------------------------------------------------------- /src/components/ImportFromCognitoS3Dialog/config-import-is-ready.js: -------------------------------------------------------------------------------- 1 | const ConfigImportIsReady = (projectToFetch, configImport) => { 2 | if (!projectToFetch || projectToFetch === "") return false 3 | if ( 4 | configImport.loadAssetsIsSelected && 5 | (configImport.typeOfFileToLoad === "None" || 6 | configImport.typeOfFileToLoad === undefined) 7 | ) 8 | return false 9 | 10 | return true 11 | } 12 | export default ConfigImportIsReady 13 | -------------------------------------------------------------------------------- /src/components/ImportFromCognitoS3Dialog/get-sources.js: -------------------------------------------------------------------------------- 1 | const getSources = (annotations) => { 2 | return annotations 3 | .map((obj) => obj.source) 4 | .filter((source, index, self) => self.indexOf(source) === index) 5 | } 6 | export default getSources 7 | -------------------------------------------------------------------------------- /src/components/ImportFromCognitoS3Dialog/header-table-import.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { 3 | Settings as SettingsIcon, 4 | Storage as StorageIcon, 5 | } from "@material-ui/icons/" 6 | import { useTranslation } from "react-i18next" 7 | import { Button, IconButton } from "@material-ui/core/" 8 | 9 | const selectedStyle = { color: "DodgerBlue" } 10 | 11 | const HeaderTable = ({ configImport, setConfigImport }) => { 12 | const { t } = useTranslation() 13 | const loadAssetsOrAnnotations = () => { 14 | setConfigImport({ 15 | ...configImport, 16 | loadAssetsIsSelected: !configImport.loadAssetsIsSelected, 17 | }) 18 | } 19 | 20 | return ( 21 |
22 | {configImport.loadAssetsIsSelected ? ( 23 | 30 | ) : ( 31 | 32 | )} 33 | {configImport.loadAssetsIsSelected ? ( 34 | 37 | ) : ( 38 | 45 | )} 46 | { 48 | setConfigImport({ 49 | ...configImport, 50 | contentDialogBoxIsSetting: !configImport.contentDialogBoxIsSetting, 51 | }) 52 | }} 53 | > 54 | {configImport.contentDialogBoxIsSetting ? ( 55 | 56 | ) : ( 57 | 58 | )} 59 | 60 |
61 | ) 62 | } 63 | export default HeaderTable 64 | -------------------------------------------------------------------------------- /src/components/ImportFromCognitoS3Dialog/warning-header.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | const WarningHeader = ({ configImport, projectToFetch }) => { 3 | const orangeText = { color: "orange", textAlign: "center" } 4 | var text = `` 5 | if (!configImport.isReady) text += `Warning :` 6 | else return

{text}

7 | if (!projectToFetch || projectToFetch === "") 8 | text += ` You need to select a project.\n` 9 | if ( 10 | configImport.loadAssetsIsSelected && 11 | (configImport.typeOfFileToLoad === "None" || 12 | configImport.typeOfFileToLoad === undefined) 13 | ) 14 | text += ` You need to select a type of file to load.\n` 15 | 16 | return ( 17 |
18 | {text.split("\n").map((obj, i) => { 19 | return
{obj}
20 | })} 21 |
22 | ) 23 | } 24 | export default WarningHeader 25 | -------------------------------------------------------------------------------- /src/components/ImportFromGoogleDriveDialog/index.story.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import { storiesOf } from "@storybook/react" 4 | import ImportFromGoogleDrive from "./" 5 | 6 | storiesOf("ImportFromGoogleDrive", module).add("Basic", () => ( 7 | 8 | )) 9 | -------------------------------------------------------------------------------- /src/components/ImportFromS3Dialog/index.story.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from "react" 4 | 5 | import { storiesOf } from "@storybook/react" 6 | import { action } from "@storybook/addon-actions" 7 | 8 | import ImportFromS3Dialog from "./" 9 | 10 | // NOTE: THIS STORY REQUIRES the localStorage app_config to have s3 auth options!! It won't 11 | // work by default! 12 | 13 | storiesOf("ImportFromS3Dialog", module).add( 14 | "Basic (doesn't work by default)", 15 | () => 16 | ) 17 | -------------------------------------------------------------------------------- /src/components/ImportFromYoutubeUrls/get-youtube-video-information.js: -------------------------------------------------------------------------------- 1 | const getYoutubeVideoInformation = async ( 2 | remote, 3 | splittedURLsArray, 4 | progressCallback = () => null 5 | ) => { 6 | const ytdl = remote.require("ytdl-core") 7 | const checkedVideos = [] 8 | if (splittedURLsArray.length > 0) { 9 | for (let i = 0; i < splittedURLsArray.length; i++) { 10 | const url = splittedURLsArray[i] 11 | const video = await new Promise((resolve, reject) => { 12 | ytdl.getBasicInfo(url, (err, info) => { 13 | if (info && !err) { 14 | resolve({ 15 | url, 16 | title: info.title, 17 | }) 18 | } else { 19 | const errorText = `Error with video at "${url}"\n\n${err.toString()}` 20 | reject(new Error(errorText)) 21 | } 22 | }) 23 | }) 24 | checkedVideos.push(video) 25 | progressCallback({ 26 | progress: (i / (splittedURLsArray.length - 1)) * 100, 27 | text: "Inspecting Video Information...", 28 | }) 29 | } 30 | } 31 | return checkedVideos 32 | } 33 | 34 | export default getYoutubeVideoInformation 35 | -------------------------------------------------------------------------------- /src/components/ImportFromYoutubeUrls/progress.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { styled } from "@material-ui/core/styles" 3 | import ProgressBar from "../ProgressBar" 4 | import ExpansionPanel from "@material-ui/core/ExpansionPanel" 5 | import ExpandMoreIcon from "@material-ui/icons/ExpandMore" 6 | import ExpansionPanelSummary from "@material-ui/core/ExpansionPanelSummary" 7 | import ExpansionPanelDetails from "@material-ui/core/ExpansionPanelDetails" 8 | 9 | const ProgressContainer = styled("div")({ 10 | width: "100%", 11 | height: "100%", 12 | }) 13 | 14 | const TotalPercentageWrapper = styled("div")({ 15 | flexDirection: "row", 16 | width: "100%", 17 | display: "flex", 18 | justifyContent: "space-between", 19 | }) 20 | 21 | const Progress = ({ unitProgress, overallProgress, completedVideoTitles }) => { 22 | return ( 23 | 24 | 25 | 26 | 27 |

Downloading: {unitProgress.title}

28 | 29 | }> 30 | 31 |

Downloaded Video Titles

32 |
33 |
34 | 35 |
    36 | {completedVideoTitles && completedVideoTitles.length > 0 37 | ? completedVideoTitles 38 | : null} 39 |
40 |
41 |
42 |
43 | ) 44 | } 45 | 46 | export default Progress 47 | -------------------------------------------------------------------------------- /src/components/ImportFromYoutubeUrls/split-urls-from-text-area.js: -------------------------------------------------------------------------------- 1 | const splitURLsFromTextArea = (stringURLs) => { 2 | const urlsHasHTTPS = [] 3 | const splittedURLsByNewLines = stringURLs.split("\n") 4 | for (const url of splittedURLsByNewLines) { 5 | if (url.includes("https://")) { 6 | urlsHasHTTPS.push(url) 7 | } 8 | } 9 | return urlsHasHTTPS 10 | } 11 | 12 | export default splitURLsFromTextArea 13 | -------------------------------------------------------------------------------- /src/components/ImportPage/prompt-and-get-samples-from-local-directory.js: -------------------------------------------------------------------------------- 1 | // @flow weak 2 | 3 | const convertToSamplesObject = (fp) => { 4 | const ext = fp.split(".").slice(-1)[0].toLowerCase() 5 | if (["png", "jpg", "jpeg"].includes(ext)) { 6 | return { imageUrl: `file://${fp}` } 7 | } 8 | if (["pdf"].includes(ext)) { 9 | return { pdfUrl: `file://${fp}` } 10 | } 11 | if (["mp4", "webm", "mkv"].includes(ext)) { 12 | return { videoUrl: `file://${fp}` } 13 | } 14 | return null 15 | } 16 | 17 | async function promptAndGetSamplesLocalDirectory({ electron }) { 18 | const { 19 | canceled, 20 | filePaths: dirPaths, 21 | } = await electron.remote.dialog.showOpenDialog({ 22 | title: "Select Directory to Import Files", 23 | properties: ["openDirectory"], 24 | }) 25 | if (canceled) return 26 | const dirPath = dirPaths[0] 27 | const fs = electron.remote.require("fs") 28 | const path = electron.remote.require("path") 29 | return (await fs.promises.readdir(dirPath)) 30 | .filter((fn) => fn.includes(".")) 31 | .map((fileName) => path.join(dirPath, fileName)) 32 | .map(convertToSamplesObject) 33 | .filter(Boolean) 34 | } 35 | 36 | export default promptAndGetSamplesLocalDirectory 37 | -------------------------------------------------------------------------------- /src/components/ImportTextSnippetsDialog/index.js: -------------------------------------------------------------------------------- 1 | // @flow weak 2 | 3 | import React, { useState } from "react" 4 | import SimpleDialog from "../SimpleDialog" 5 | import TextAreaWithUpload from "../TextAreaWithUpload" 6 | 7 | const ImportTextSnippetsDialog = ({ open, onClose, onAddSamples }) => { 8 | const [content, changeContent] = useState("") 9 | 10 | return ( 11 | { 19 | onAddSamples( 20 | content 21 | .split("\n") 22 | .map((l) => l.trim()) 23 | .filter(Boolean) 24 | .map((s) => ({ document: s })) 25 | ) 26 | }, 27 | }, 28 | ]} 29 | > 30 | 37 | 38 | ) 39 | } 40 | 41 | export default ImportTextSnippetsDialog 42 | -------------------------------------------------------------------------------- /src/components/InfoButton/EditableTitleText.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { useState } from "react" 3 | import TextField from "@material-ui/core/TextField" 4 | import { makeStyles } from "@material-ui/core/styles" 5 | import isEmpty from "lodash/isEmpty" 6 | 7 | const useStyles = makeStyles({ 8 | textField: { 9 | marginLeft: 8, 10 | }, 11 | }) 12 | 13 | export default ({ value, onChange }) => { 14 | const c = useStyles() 15 | const [newValue, setNewValue] = useState(value || "") 16 | 17 | const onBlur = () => { 18 | if (!isEmpty(newValue) && newValue !== "unnamed") { 19 | onChange(newValue) 20 | setNewValue(newValue) 21 | } else { 22 | setNewValue(value) 23 | } 24 | } 25 | 26 | return ( 27 | { 37 | if (e.key === "Enter") { 38 | e.preventDefault() 39 | e.stopPropagation() 40 | e.target.blur() 41 | } 42 | }} 43 | onKeyPress={(e) => { 44 | e.stopPropagation() 45 | }} 46 | onChange={(e) => { 47 | setNewValue(e.target.value) 48 | }} 49 | value={newValue || ""} 50 | /> 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /src/components/InfoButton/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react" 2 | import { styled } from "@material-ui/core" 3 | import InfoIcon from "@material-ui/icons/Info" 4 | import IconButton from "@material-ui/core/IconButton" 5 | import HeaderPopupBox from "../HeaderPopupBox" 6 | import TextField from "@material-ui/core/TextField" 7 | import EditableTitleText from "./EditableTitleText.js" 8 | import useActiveDatasetManager from "../../hooks/use-active-dataset-manager" 9 | import useDatasetProperty from "../../hooks/use-dataset-property" 10 | import { colors } from "@material-ui/core" 11 | 12 | const Container = styled("div")({ position: "relative" }) 13 | const IconButtonStyle = styled(IconButton)({ 14 | color: colors.grey[300], 15 | }) 16 | 17 | export const InfoButton = () => { 18 | const [sessionBoxOpen, setSessionBoxOpen] = useState(false) 19 | const [dm] = useActiveDatasetManager() 20 | const { name, updateName } = useDatasetProperty("name") 21 | 22 | let shareURL 23 | if (dm.type === "collaborative-session") { 24 | shareURL = `${window.location.origin}?s=${dm.sessionId}` 25 | } 26 | 27 | return ( 28 | setSessionBoxOpen(true)} 31 | onMouseLeave={() => setSessionBoxOpen(false)} 32 | > 33 | 34 | 35 | 36 | 37 |

Info

38 | {dm.type === "collaborative-session" ? ( 39 | 46 | ) : ( 47 | { 50 | updateName(newName) 51 | }} 52 | value={name} 53 | /> 54 | )} 55 |
56 |
57 | ) 58 | } 59 | 60 | export default InfoButton 61 | -------------------------------------------------------------------------------- /src/components/InterfaceIcon/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import RemoveRedEye from "@material-ui/icons/RemoveRedEye" 4 | import TextFormat from "@material-ui/icons/TextFormat" 5 | import Edit from "@material-ui/icons/Edit" 6 | import Audiotrack from "@material-ui/icons/Audiotrack" 7 | import Help from "@material-ui/icons/Help" 8 | import ThreeDRotation from "@material-ui/icons/ThreeDRotation" 9 | 10 | export default ({ type, ...props }) => { 11 | switch (type) { 12 | case "image_label": 13 | case "image_segmentation": 14 | return 15 | case "audio_transcription": 16 | return 17 | case "data_entry": 18 | return 19 | case "text_entity_recognition": 20 | return 21 | case "3d_bounding_box": 22 | return 23 | default: 24 | return 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/KeyboardShortcutManagerDialog/index.story.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import { storiesOf } from "@storybook/react" 4 | import { action } from "@storybook/addon-actions" 5 | 6 | import KeyboardShortcutManagerDialog from "./index.js" 7 | 8 | storiesOf("KeyboardShortcutManagerDialog", module).add("Basic", () => { 9 | return ( 10 | 20 | ) 21 | }) 22 | -------------------------------------------------------------------------------- /src/components/LabelErrorBoundary/index.js: -------------------------------------------------------------------------------- 1 | // @flow weak 2 | 3 | import React from "react" 4 | import Sentry from "../../utils/sentry.js" 5 | import BadDataset from "../BadDataset" 6 | 7 | class ErrorBoundary extends React.Component { 8 | constructor(props) { 9 | super(props) 10 | this.state = { hasError: false } 11 | } 12 | 13 | static getDerivedStateFromError(error) { 14 | // Update state so the next render will show the fallback UI. 15 | console.error( 16 | "The following error occurred when loading a labeling interface:", 17 | error 18 | ) 19 | return { 20 | hasError: true, 21 | errorString: error.toString(), 22 | } 23 | } 24 | 25 | componentDidCatch(error, errorInfo) { 26 | // You can also log the error to an error reporting service 27 | Sentry.withScope((scope) => { 28 | scope.setExtras(errorInfo) 29 | const eventId = Sentry.captureException(error) 30 | this.setState({ eventId }) 31 | }) 32 | } 33 | 34 | render() { 35 | if (this.state.hasError) { 36 | // You can render any custom fallback UI 37 | return ( 38 | 42 | ) 43 | } 44 | 45 | return this.props.children 46 | } 47 | } 48 | 49 | export default ErrorBoundary 50 | -------------------------------------------------------------------------------- /src/components/LabelHelpView/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { styled } from "@material-ui/core/styles" 3 | import APIKeyEntry from "./api-key-entry.js" 4 | import PaperContainer from "../PaperContainer" 5 | import LabelHelpDialogContent from "./label-help-dialog-content" 6 | import { useAppConfig } from "../AppConfig" 7 | 8 | export { LabelHelpProvider, LabelHelpContext } from "./LabelHelpProvider.js" 9 | export { useLabelHelp } from "./use-label-help" 10 | 11 | const Container = styled("div")({ 12 | display: "flex", 13 | flexDirection: "column", 14 | alignItems: "center", 15 | }) 16 | 17 | export const LabelHelpView = () => { 18 | const { fromConfig } = useAppConfig() 19 | const hasAPIKey = Boolean(fromConfig("labelhelp.apikey")) 20 | 21 | return ( 22 | 23 | {!hasAPIKey ? ( 24 | 25 | ) : ( 26 | 30 | 31 | 32 | )} 33 | 34 | ) 35 | } 36 | 37 | export default LabelHelpView 38 | -------------------------------------------------------------------------------- /src/components/LabelHelpView/label-help-completed.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import Box from "@material-ui/core/Box" 4 | import Table from "@material-ui/core/Table" 5 | import TableBody from "@material-ui/core/TableBody" 6 | import TableRow from "@material-ui/core/TableRow" 7 | import TableCell from "@material-ui/core/TableCell" 8 | import Button from "@material-ui/core/Button" 9 | 10 | import useDatasetProperty from "../../hooks/use-dataset-property" 11 | 12 | const usdFormatter = new Intl.NumberFormat("en-US", { 13 | style: "currency", 14 | currency: "USD", 15 | }) 16 | 17 | export const LabelHelpCompleted = ({ onChangeActiveStep }) => { 18 | const { labelHelp, updateLabelHelp } = useDatasetProperty("labelHelp") 19 | return ( 20 | 21 | 22 | 23 | 24 | Percent Complete 25 | 100% 26 | 27 | {labelHelp?.totalCost && ( 28 | 29 | Total Cost 30 | {usdFormatter.format(labelHelp?.totalCost)} 31 | 32 | )} 33 | 34 |
35 | 36 | 45 | {/* TODO sync samples merges samples from link */} 46 | {/* */} 53 | 54 |
55 | ) 56 | } 57 | 58 | export default LabelHelpCompleted 59 | -------------------------------------------------------------------------------- /src/components/LabelView/index.story.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from "react" 4 | 5 | import { storiesOf } from "@storybook/react" 6 | import { action } from "@storybook/addon-actions" 7 | import { templateMap } from "../StartingPage/templates.js" 8 | 9 | import LabelView from "./" 10 | 11 | storiesOf("LabelView", module).add("Basic", () => ( 12 | 21 | )) 22 | -------------------------------------------------------------------------------- /src/components/Loading/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { makeStyles } from "@material-ui/core/styles" 3 | import CircularProgress from "@material-ui/core/CircularProgress" 4 | 5 | const useStyles = makeStyles((theme) => ({ 6 | wrapper: { 7 | width: "100vw", 8 | height: "100vh", 9 | display: "flex", 10 | justifyContent: "center", 11 | alignItems: "center", 12 | }, 13 | root: { 14 | display: "flex", 15 | "& > * + *": { 16 | marginLeft: theme.spacing(2), 17 | }, 18 | }, 19 | })) 20 | 21 | export default function Loading() { 22 | const classes = useStyles() 23 | 24 | return ( 25 |
26 |
27 | 28 |
29 |
30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /src/components/Loading/index.story.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import { storiesOf } from "@storybook/react" 4 | import Loading from "./" 5 | 6 | storiesOf("Loading", module).add("Basic", () => ) 7 | -------------------------------------------------------------------------------- /src/components/ManagePluginsDialog/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react" 2 | import SimpleDialog from "../SimpleDialog" 3 | import TextAreaWithUpload from "../TextAreaWithUpload" 4 | import { useAppConfig } from "../AppConfig" 5 | 6 | export const ManagePluginsDialog = ({ onClose, open }) => { 7 | const { fromConfig, setInConfig } = useAppConfig() 8 | const [content, setContent] = useState() 9 | useEffect(() => { 10 | if (!open) return 11 | setContent(fromConfig("pluginUrls") || "") 12 | //eslint-disable-next-line 13 | }, [open]) 14 | 15 | return ( 16 | { 24 | setInConfig( 25 | "pluginUrls", 26 | content 27 | .trim() 28 | .split("\n") 29 | .map((line) => line.trim()) 30 | .filter(Boolean) 31 | .join("\n") 32 | ) 33 | onClose() 34 | }, 35 | }, 36 | ]} 37 | > 38 | 45 | 46 | ) 47 | } 48 | 49 | export default ManagePluginsDialog 50 | -------------------------------------------------------------------------------- /src/components/PaperContainer/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { styled } from "@material-ui/core/styles" 4 | 5 | export const PaperContainer = styled("div")({ 6 | margin: 16, 7 | padding: 16, 8 | border: "1px solid #ccc", 9 | borderRadius: 4, 10 | boxShadow: "0px 2px 2px rgba(0,0,0,0.1)", 11 | }) 12 | 13 | export default PaperContainer 14 | -------------------------------------------------------------------------------- /src/components/PasteUrlsDialog/get-sample-from-url.js: -------------------------------------------------------------------------------- 1 | export default (s, opts = {}) => { 2 | let extension = s.replace(/\?.*/g, "").split(".").slice(-1)[0] 3 | if (s.includes("gstatic.com/images")) { 4 | extension = "jpg" 5 | } 6 | switch (extension.toLowerCase()) { 7 | case "png": 8 | case "jpg": 9 | case "gif": 10 | case "jpeg": 11 | case "bmp": { 12 | return { imageUrl: s } 13 | } 14 | case "pdf": { 15 | return { pdfUrl: s } 16 | } 17 | case "mp3": 18 | case "wav": { 19 | return { audioUrl: s } 20 | } 21 | default: { 22 | if (opts.returnNulls) return null 23 | throw new Error(`extension not recognized: "${extension}" in "${s}"`) 24 | // TODO if the user doesn't care, return null (this 25 | // behavior could be enabled with textfield option) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/PluginDialog/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react" 2 | import SimpleDialog from "../SimpleDialog" 3 | import { setIn } from "seamless-immutable" 4 | 5 | export default ({ 6 | open, 7 | onChangeDataset, 8 | onClose, 9 | dataset, 10 | name, 11 | renderDialog, 12 | }) => { 13 | const [ref, setRef] = useState() 14 | const [actions, setActions] = useState([]) 15 | useEffect(() => { 16 | if (!open) return 17 | if (!ref) return 18 | 19 | let actions = [] 20 | 21 | renderDialog({ 22 | elm: ref, 23 | onSuccess: () => { 24 | onClose() 25 | }, 26 | addAction: (action) => { 27 | actions.push(action) 28 | setActions(actions) 29 | }, 30 | removeAction: (action) => { 31 | actions = actions.filter((a) => a !== action) 32 | setActions(actions) 33 | }, 34 | dataset, 35 | setInDataset: (path, val) => { 36 | onChangeDataset(setIn(dataset, path, val)) 37 | }, 38 | }) 39 | //eslint-disable-next-line 40 | }, [open, ref]) 41 | 42 | return ( 43 | 44 |
setRef(newRef)} /> 45 | 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /src/components/PluginDialog/index.story.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from "react" 4 | 5 | import { storiesOf } from "@storybook/react" 6 | 7 | import PluginDialog from "./" 8 | 9 | storiesOf("PluginDialog", module).add("Basic", () => { 10 | const renderDialog = ({ 11 | elm, 12 | dataset, 13 | setInDataset, 14 | addAction, 15 | removeAction, 16 | onSuccess, 17 | }) => { 18 | elm.innerHTML = ` 19 | 20 | Click the "Delete All Samples" button to delete all of your samples! 21 | 22 | `.trim() 23 | 24 | addAction({ 25 | text: "Delete All Samples", 26 | onClick: () => { 27 | setInDataset(dataset, ["samples"], []) 28 | onSuccess("All samples deleted!") 29 | }, 30 | }) 31 | } 32 | return ( 33 | 38 | ) 39 | }) 40 | -------------------------------------------------------------------------------- /src/components/PluginProvider/index.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react" 2 | import { useAppConfig } from "../AppConfig" 3 | import { atom, useRecoilState, useRecoilValue } from "recoil" 4 | import { useToasts } from "../Toasts" 5 | 6 | const pluginsState = atom({ 7 | key: "plugins", 8 | default: [], 9 | }) 10 | 11 | export const usePlugins = () => useRecoilValue(pluginsState) 12 | 13 | export default () => { 14 | // eslint-disable-next-line 15 | const [plugins, setPlugins] = useRecoilState(pluginsState) 16 | const { fromConfig } = useAppConfig() 17 | const { addToast } = useToasts() 18 | 19 | useEffect(() => { 20 | async function loadPlugins() { 21 | const pluginUrls = (fromConfig("pluginUrls") || "") 22 | .split("\n") 23 | .filter(Boolean) 24 | 25 | const plugins = [] 26 | 27 | for (const pluginUrl of pluginUrls) { 28 | try { 29 | const { 30 | transformPlugins, 31 | importPlugins, 32 | interfacePlugins, 33 | authenticationPlugins, 34 | } = (await import(/* webpackIgnore: true */ pluginUrl)).default() 35 | 36 | plugins.push( 37 | ...transformPlugins.map((p) => ({ ...p, type: "transform" })) 38 | ) 39 | plugins.push(...importPlugins.map((p) => ({ ...p, type: "import" }))) 40 | plugins.push( 41 | ...interfacePlugins.map((p) => ({ ...p, type: "interface" })) 42 | ) 43 | plugins.push( 44 | ...authenticationPlugins.map((p) => ({ 45 | ...p, 46 | type: "authentication", 47 | })) 48 | ) 49 | } catch (e) { 50 | // TODO display broken plugin more nicely, using regex extraction of 51 | // package and version 52 | addToast( 53 | "Couldn't load plugin: " + pluginUrl + "\n\n" + e.toString(), 54 | "error" 55 | ) 56 | } 57 | } 58 | setPlugins(plugins) 59 | } 60 | loadPlugins() 61 | // eslint-disable-next-line 62 | }, [fromConfig("pluginUrls")]) 63 | } 64 | -------------------------------------------------------------------------------- /src/components/ProgressBar/index.js: -------------------------------------------------------------------------------- 1 | // @flow weak 2 | 3 | import React from "react" 4 | import { styled } from "@material-ui/core/styles" 5 | import * as colors from "@material-ui/core/colors" 6 | 7 | const Container = styled("div")({ 8 | height: 36, 9 | width: "100%", 10 | boxSizing: "border-box", 11 | position: "relative", 12 | marginTop: 8, 13 | marginBottom: 8, 14 | backgroundColor: colors.blue[50], 15 | }) 16 | const Text = styled("div")({ 17 | position: "absolute", 18 | left: 0, 19 | right: 0, 20 | top: 0, 21 | bottom: 0, 22 | paddingTop: 4, 23 | fontSize: 18, 24 | textAlign: "center", 25 | fontWeight: "bold", 26 | }) 27 | const Bar = styled("div")({ 28 | position: "absolute", 29 | left: 0, 30 | bottom: 0, 31 | top: 0, 32 | backgroundColor: colors.blue[300], 33 | }) 34 | 35 | export default ({ progress }) => { 36 | return ( 37 | 38 | 39 | {Math.floor(progress)}% 40 | 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /src/components/RawJSONEditor/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react" 2 | import AceEditor from "react-ace" 3 | import Box from "@material-ui/core/Box" 4 | import Button from "@material-ui/core/Button" 5 | import { useToasts } from "../Toasts" 6 | 7 | export const RawJSONEditor = ({ content, onSave }) => { 8 | const [jsonText, setJSONText] = useState("") 9 | const { addToast } = useToasts() 10 | useEffect(() => { 11 | if (!content) return 12 | if (typeof content === "string") { 13 | setJSONText(content) 14 | } else if (typeof content === "object") { 15 | setJSONText(JSON.stringify(content, null, " ")) 16 | } 17 | }, [content]) 18 | return ( 19 | 20 | setJSONText(t)} 28 | /> 29 | 30 | 43 | 44 | 45 | ) 46 | } 47 | 48 | export default RawJSONEditor 49 | -------------------------------------------------------------------------------- /src/components/S3PathSelector/index.story.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from "react" 4 | 5 | import { storiesOf } from "@storybook/react" 6 | import { action } from "@storybook/addon-actions" 7 | 8 | import S3PathSelector from "./" 9 | 10 | storiesOf("S3PathSelector", module) 11 | .add("Select Bucket", () => ( 12 | 20 | )) 21 | .add("Select Directory", () => ( 22 | 37 | )) 38 | -------------------------------------------------------------------------------- /src/components/SampleContainer/LinkButton.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from "react" 3 | import { makeStyles } from "@material-ui/core/styles" 4 | 5 | const useStyles = makeStyles({ 6 | linkButtonContainer: { 7 | marginLeft: 8, 8 | marginRight: 8, 9 | }, 10 | linkButton: { 11 | textTransform: "none", 12 | cursor: "pointer", 13 | textDecoration: "underline", 14 | }, 15 | }) 16 | 17 | export default ({ onClick, text }: { onClick: Function, text: string }) => { 18 | const c = useStyles() 19 | return ( 20 | 21 | ( 22 | 23 | {text} 24 | 25 | ) 26 | 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/components/SampleContainer/index.story.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from "react" 4 | 5 | import { storiesOf } from "@storybook/react" 6 | import { action } from "@storybook/addon-actions" 7 | 8 | import SampleContainer from "./" 9 | 10 | storiesOf("SampleContainer", module) 11 | .add("Basic", () => ( 12 | 23 | This is where the task data and interface goes. 24 | 25 | )) 26 | .add("Hide Title", () => ( 27 | 39 | This is where the task data and interface goes. 40 | 41 | )) 42 | .add("Hide Description", () => ( 43 | 55 | This is where the task data and interface goes. 56 | 57 | )) 58 | -------------------------------------------------------------------------------- /src/components/SetupPage/Protip.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { styled, colors, Box } from "@material-ui/core" 3 | import EmojiObjectsIcon from "@material-ui/icons/EmojiObjects" 4 | 5 | const Container = styled("div")({ 6 | display: "flex", 7 | alignItems: "center", 8 | padding: 32, 9 | paddingLeft: 64, 10 | paddingRight: 64, 11 | "& .icon": { 12 | width: 32, 13 | height: 32, 14 | color: colors.grey[500], 15 | }, 16 | }) 17 | const Text = styled("div")({ 18 | fontSize: 18, 19 | paddingLeft: 8, 20 | color: colors.grey[500], 21 | }) 22 | 23 | export const Protip = ({ tip }) => ( 24 | 25 | 26 | 27 | {tip} 28 | 29 | 30 | ) 31 | 32 | export default Protip 33 | -------------------------------------------------------------------------------- /src/components/SetupPage/index.story.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { useState } from "react" 4 | 5 | import { storiesOf } from "@storybook/react" 6 | import { action } from "@storybook/addon-actions" 7 | 8 | import SetupPage from "./" 9 | 10 | storiesOf("SetupPage", module).add("Empty Dataset", () => { 11 | const [dataset, setDataset] = useState({ 12 | interface: {}, 13 | }) 14 | 15 | return ( 16 | 21 | ) 22 | }) 23 | -------------------------------------------------------------------------------- /src/components/StartingPage/get-embed-youtube-url.js: -------------------------------------------------------------------------------- 1 | export default (url) => { 2 | const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/ 3 | const match = url.match(regExp) 4 | 5 | const id = match && match[2].length === 11 ? match[2] : null 6 | 7 | if (!id) return null 8 | 9 | return `https://youtube.com/embed/${id}` 10 | } 11 | -------------------------------------------------------------------------------- /src/components/StartingPage/index.story.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from "react" 4 | 5 | import { storiesOf } from "@storybook/react" 6 | import { action } from "@storybook/addon-actions" 7 | 8 | import StartingPage from "./" 9 | 10 | storiesOf("StartingPage", module).add("Basic", () => ( 11 | 12 | )) 13 | -------------------------------------------------------------------------------- /src/components/Stats/index.js: -------------------------------------------------------------------------------- 1 | // @flow weak 2 | 3 | import React from "react" 4 | import { styled } from "@material-ui/core/styles" 5 | import * as colors from "@material-ui/core/colors" 6 | 7 | const Container = styled("div")({}) 8 | const Stat = styled("div")({ 9 | display: "inline-flex", 10 | flexDirection: "column", 11 | margin: 8, 12 | }) 13 | const Label = styled("div")({ 14 | fontSize: 14, 15 | fontWeight: "bold", 16 | color: colors.grey[700], 17 | }) 18 | const Value = styled("div")({ 19 | fontSize: 32, 20 | marginTop: 8, 21 | }) 22 | 23 | export default ({ stats }) => { 24 | return ( 25 | 26 | {stats.map((s) => ( 27 | 28 | 29 | {s.value} 30 | 31 | ))} 32 | 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /src/components/TextAreaWithUpload/index.js: -------------------------------------------------------------------------------- 1 | // @flow weak 2 | 3 | import React, { useState } from "react" 4 | import { styled } from "@material-ui/core/styles" 5 | import useEventCallback from "use-event-callback" 6 | import { useDropzone } from "react-dropzone" 7 | import * as colors from "@material-ui/core/colors" 8 | 9 | const TextArea = styled("textarea")({ 10 | width: "100%", 11 | minHeight: 300, 12 | }) 13 | 14 | const UploadHover = styled("div")({ 15 | fontSize: 24, 16 | color: colors.grey[600], 17 | textAlign: "center", 18 | padding: 48, 19 | }) 20 | 21 | const emptyFunc = () => null 22 | 23 | export default ({ content, onChangeContent, placeholder }) => { 24 | const onDrop = useEventCallback((acceptedFiles) => { 25 | const reader = new FileReader() 26 | reader.onload = (e) => { 27 | const fileContent = e.target.result 28 | onChangeContent(fileContent) 29 | } 30 | reader.readAsText(acceptedFiles[0]) 31 | }) 32 | 33 | const [lastClickTime, changeLastClickTime] = useState(0) 34 | const onClick = useEventCallback((e) => { 35 | if (Date.now() - lastClickTime < 400) { 36 | getRootProps().onClick(e) 37 | } 38 | changeLastClickTime(Date.now()) 39 | }) 40 | 41 | const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop }) 42 | 43 | return ( 44 |
45 | 46 | {isDragActive ? ( 47 | Drop the text or csv file here. 48 | ) : ( 49 |