├── .dockerignore
├── .env.template
├── .gitattributes
├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE
│ ├── bug.yaml
│ ├── config.yml
│ ├── discussion.yml
│ ├── enhancement.yaml
│ └── feature.yaml
├── assets
│ └── logo.png
├── scripts
│ ├── issues.cjs
│ └── lib.js
└── workflows
│ ├── delete-package-versions.yaml
│ ├── github-pages.yml
│ ├── issues.yaml
│ └── release.yaml
├── .gitignore
├── .husky
├── commit-msg
└── pre-commit
├── .nvmrc
├── .prettierignore
├── CONTRIBUTING.md
├── Caddyfile
├── LICENSE
├── README.md
├── SECURITY.md
├── apps
├── api
│ ├── Dockerfile
│ ├── libnest.config.ts
│ ├── package.json
│ ├── prisma
│ │ └── schema.prisma
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ └── styles.css
│ ├── src
│ │ ├── assignments
│ │ │ ├── assignments.controller.ts
│ │ │ ├── assignments.module.ts
│ │ │ ├── assignments.service.ts
│ │ │ └── dto
│ │ │ │ ├── create-assignment.dto.ts
│ │ │ │ └── update-assignment.dto.ts
│ │ ├── core
│ │ │ ├── env.schema.ts
│ │ │ └── types.ts
│ │ ├── demo
│ │ │ ├── demo.module.ts
│ │ │ └── demo.service.ts
│ │ ├── gateway
│ │ │ ├── gateway.controller.ts
│ │ │ ├── gateway.module.ts
│ │ │ ├── gateway.service.ts
│ │ │ └── gateway.synchronizer.ts
│ │ ├── groups
│ │ │ ├── __tests__
│ │ │ │ ├── groups.controller.spec.ts
│ │ │ │ └── groups.service.spec.ts
│ │ │ ├── dto
│ │ │ │ ├── create-group.dto.ts
│ │ │ │ └── update-group.dto.ts
│ │ │ ├── groups.controller.ts
│ │ │ ├── groups.module.ts
│ │ │ └── groups.service.ts
│ │ ├── instrument-records
│ │ │ ├── dto
│ │ │ │ ├── create-instrument-record.dto.ts
│ │ │ │ └── upload-instrument-record.dto.ts
│ │ │ ├── instrument-measures.service.ts
│ │ │ ├── instrument-records.controller.ts
│ │ │ ├── instrument-records.module.ts
│ │ │ └── instrument-records.service.ts
│ │ ├── instruments
│ │ │ ├── __tests__
│ │ │ │ ├── instruments.controller.spec.ts
│ │ │ │ └── instruments.service.spec.ts
│ │ │ ├── dto
│ │ │ │ └── create-instrument.dto.ts
│ │ │ ├── instruments.controller.ts
│ │ │ ├── instruments.module.ts
│ │ │ └── instruments.service.ts
│ │ ├── main.ts
│ │ ├── sessions
│ │ │ ├── dto
│ │ │ │ └── create-session.dto.ts
│ │ │ ├── sessions.controller.ts
│ │ │ ├── sessions.module.ts
│ │ │ └── sessions.service.ts
│ │ ├── setup
│ │ │ ├── dto
│ │ │ │ ├── init-app.dto.ts
│ │ │ │ └── update-setup-state.dto.ts
│ │ │ ├── setup.controller.ts
│ │ │ ├── setup.module.ts
│ │ │ └── setup.service.ts
│ │ ├── subjects
│ │ │ ├── __tests__
│ │ │ │ └── subjects.service.spec.ts
│ │ │ ├── dto
│ │ │ │ └── create-subject.dto.ts
│ │ │ ├── subjects.controller.ts
│ │ │ ├── subjects.module.ts
│ │ │ └── subjects.service.ts
│ │ ├── summary
│ │ │ ├── summary.controller.ts
│ │ │ ├── summary.module.ts
│ │ │ └── summary.service.ts
│ │ ├── typings
│ │ │ ├── global.d.ts
│ │ │ └── prisma-json-types-generator.d.ts
│ │ ├── users
│ │ │ ├── dto
│ │ │ │ ├── create-user.dto.ts
│ │ │ │ └── update-user.dto.ts
│ │ │ ├── users.controller.ts
│ │ │ ├── users.module.ts
│ │ │ └── users.service.ts
│ │ └── vendor
│ │ │ └── configured.auth.module.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── gateway
│ ├── Dockerfile
│ ├── index.html
│ ├── package.json
│ ├── prisma
│ │ └── schema.prisma
│ ├── public
│ │ └── favicon.ico
│ ├── scripts
│ │ ├── build.ts
│ │ ├── dev.ts
│ │ └── run.sh
│ ├── src
│ │ ├── Root.tsx
│ │ ├── config.ts
│ │ ├── entry-client.tsx
│ │ ├── entry-server.tsx
│ │ ├── lib
│ │ │ └── prisma.ts
│ │ ├── main.ts
│ │ ├── middleware
│ │ │ ├── api-key.middleware.ts
│ │ │ ├── error-handler.middleware.ts
│ │ │ └── not-found.middleware.ts
│ │ ├── routers
│ │ │ ├── api.router.ts
│ │ │ └── root.router.ts
│ │ ├── server
│ │ │ ├── index.ts
│ │ │ ├── server.base.ts
│ │ │ ├── server.development.ts
│ │ │ └── server.production.ts
│ │ ├── services
│ │ │ ├── axios.ts
│ │ │ └── i18n.ts
│ │ ├── typings
│ │ │ └── express.d.ts
│ │ ├── utils
│ │ │ ├── async-handler.ts
│ │ │ ├── auth.ts
│ │ │ └── http-exception.ts
│ │ └── vite-env.d.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── outreach
│ ├── astro.config.ts
│ ├── package.json
│ ├── public
│ │ └── favicon.ico
│ ├── src
│ │ ├── assets
│ │ │ ├── examples
│ │ │ │ └── 4.1-instruments
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── styles.css
│ │ │ ├── headshots
│ │ │ │ ├── cian-monnin.webp
│ │ │ │ ├── david-roper.webp
│ │ │ │ ├── gabriel-devenyi.webp
│ │ │ │ ├── joshua-unrau.webp
│ │ │ │ ├── mallar-chakravarty.webp
│ │ │ │ ├── martin-lepage.webp
│ │ │ │ ├── massimiliano-orri.webp
│ │ │ │ ├── maxime-montembeault.webp
│ │ │ │ ├── simon-ducharme.webp
│ │ │ │ ├── thomas-beaudry.webp
│ │ │ │ ├── vanessa-valiquette.webp
│ │ │ │ └── weijie-tan.webp
│ │ │ └── screenshots
│ │ │ │ ├── dashboard.en.dark.png
│ │ │ │ ├── dashboard.en.light.png
│ │ │ │ ├── data-hub.en.dark.png
│ │ │ │ ├── data-hub.en.light.png
│ │ │ │ ├── data-hub.fr.dark.png
│ │ │ │ ├── data-hub.fr.light.png
│ │ │ │ ├── login.en.dark.png
│ │ │ │ ├── login.en.light.png
│ │ │ │ ├── playground-editor.en.dark.png
│ │ │ │ ├── playground-editor.en.light.png
│ │ │ │ ├── playground-upload-bundle-button.en.dark.png
│ │ │ │ ├── playground-upload-bundle-button.en.light.png
│ │ │ │ ├── start-session-hash.en.dark.png
│ │ │ │ ├── start-session-hash.en.light.png
│ │ │ │ ├── start-session-hash.fr.dark.png
│ │ │ │ ├── start-session-hash.fr.light.png
│ │ │ │ ├── upload-bundle.en.dark.png
│ │ │ │ └── upload-bundle.en.light.png
│ │ ├── components
│ │ │ ├── InstrumentProperties.astro
│ │ │ ├── blog
│ │ │ │ ├── NoContent.astro
│ │ │ │ └── PostInfoCard.astro
│ │ │ ├── common
│ │ │ │ ├── Headshot.astro
│ │ │ │ ├── Logo.astro
│ │ │ │ ├── PageHeading.astro
│ │ │ │ ├── Screenshot.astro
│ │ │ │ └── SiteTitle.astro
│ │ │ ├── icons
│ │ │ │ ├── ArrowLeftIcon.astro
│ │ │ │ ├── ArrowRightIcon.astro
│ │ │ │ ├── ChevronDownIcon.astro
│ │ │ │ ├── MenuIcon.astro
│ │ │ │ ├── MoonIcon.astro
│ │ │ │ ├── SunIcon.astro
│ │ │ │ └── XMarkIcon.astro
│ │ │ ├── layout
│ │ │ │ ├── Footer.astro
│ │ │ │ ├── Head.astro
│ │ │ │ ├── Header.astro
│ │ │ │ ├── LanguageToggle.astro
│ │ │ │ ├── NavLink.astro
│ │ │ │ └── ThemeToggle.astro
│ │ │ └── overview
│ │ │ │ ├── Feature.astro
│ │ │ │ ├── Hero.astro
│ │ │ │ ├── Medical.astro
│ │ │ │ └── Testimonials.astro
│ │ ├── content
│ │ │ ├── blog
│ │ │ ├── config.ts
│ │ │ ├── docs
│ │ │ │ ├── en
│ │ │ │ │ └── docs
│ │ │ │ └── fr
│ │ │ │ │ └── docs
│ │ │ ├── faq
│ │ │ │ ├── fundamental-concepts.yaml
│ │ │ │ └── general-information.yaml
│ │ │ ├── team
│ │ │ │ ├── cian-monnin.yaml
│ │ │ │ ├── david-roper.yaml
│ │ │ │ ├── gabriel-devenyi.yaml
│ │ │ │ ├── joshua-unrau.yaml
│ │ │ │ ├── mallar-chakravarty.yaml
│ │ │ │ ├── martin-lepage.yaml
│ │ │ │ ├── thomas-beaudry.yaml
│ │ │ │ ├── vanessa-valiquette.yaml
│ │ │ │ └── weijie-tan.yaml
│ │ │ └── testimonials
│ │ │ │ ├── massimiliano-orri.yaml
│ │ │ │ ├── maxime-montembeault.yaml
│ │ │ │ └── simon-ducharme.yaml
│ │ ├── env.d.ts
│ │ ├── i18n
│ │ │ ├── index.ts
│ │ │ └── translations
│ │ │ │ ├── blog.json
│ │ │ │ ├── common.json
│ │ │ │ ├── docs.json
│ │ │ │ ├── faq.json
│ │ │ │ ├── landing.json
│ │ │ │ ├── meta.json
│ │ │ │ └── team.json
│ │ ├── layouts
│ │ │ └── Page.astro
│ │ ├── modules
│ │ │ └── animate.ts
│ │ ├── pages
│ │ │ ├── 404.astro
│ │ │ ├── [locale]
│ │ │ │ ├── blog
│ │ │ │ │ ├── [slug].astro
│ │ │ │ │ └── index.astro
│ │ │ │ ├── faq.astro
│ │ │ │ ├── index.astro
│ │ │ │ └── team.astro
│ │ │ └── index.astro
│ │ ├── plugins
│ │ │ ├── astro-plugin-symlink.ts
│ │ │ └── starlight-plugin-typedoc
│ │ │ │ ├── index.ts
│ │ │ │ ├── logger.ts
│ │ │ │ ├── markdown.ts
│ │ │ │ ├── starlight.ts
│ │ │ │ ├── theme.ts
│ │ │ │ └── typedoc.ts
│ │ ├── scripts
│ │ │ ├── theme-init.js
│ │ │ └── theme-observer.js
│ │ ├── styles
│ │ │ ├── common.css
│ │ │ ├── main.css
│ │ │ └── starlight.css
│ │ └── utils.ts
│ └── tsconfig.json
├── playground
│ ├── Dockerfile
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ └── favicon.ico
│ ├── src
│ │ ├── App.tsx
│ │ ├── components
│ │ │ ├── Editor
│ │ │ │ ├── DeleteFileDialog.tsx
│ │ │ │ ├── Editor.stories.tsx
│ │ │ │ ├── Editor.tsx
│ │ │ │ ├── EditorAddFileButton.tsx
│ │ │ │ ├── EditorButton.tsx
│ │ │ │ ├── EditorFileButton.tsx
│ │ │ │ ├── EditorFileIcon.tsx
│ │ │ │ ├── EditorInput.tsx
│ │ │ │ ├── EditorPane.tsx
│ │ │ │ ├── EditorPanePlaceholder.tsx
│ │ │ │ ├── EditorTab.tsx
│ │ │ │ ├── VimStatusBar.tsx
│ │ │ │ ├── index.ts
│ │ │ │ ├── setup.ts
│ │ │ │ └── types.ts
│ │ │ ├── FileUploadDialog
│ │ │ │ ├── FileUploadDialog.stories.tsx
│ │ │ │ ├── FileUploadDialog.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Header
│ │ │ │ ├── ActionButton
│ │ │ │ │ ├── ActionButton.stories.tsx
│ │ │ │ │ ├── ActionButton.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── ActionsDropdown
│ │ │ │ │ ├── ActionsDropdown.stories.tsx
│ │ │ │ │ ├── ActionsDropdown.tsx
│ │ │ │ │ ├── DeleteInstrumentDialog.tsx
│ │ │ │ │ ├── RestoreDefaultsDialog.tsx
│ │ │ │ │ ├── UploadBundleDialog.tsx
│ │ │ │ │ ├── UserSettingsDialog.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── CloneButton
│ │ │ │ │ ├── CloneButton.stories.tsx
│ │ │ │ │ ├── CloneButton.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── DownloadButton
│ │ │ │ │ ├── DownloadButton.stories.tsx
│ │ │ │ │ ├── DownloadButton.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── Header.stories.tsx
│ │ │ │ ├── Header.tsx
│ │ │ │ ├── InstrumentSelector
│ │ │ │ │ ├── InstrumentSelector.stories.tsx
│ │ │ │ │ ├── InstrumentSelector.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── RefreshButton
│ │ │ │ │ ├── RefreshButton.stories.tsx
│ │ │ │ │ ├── RefreshButton.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── SaveButton
│ │ │ │ │ ├── SaveButton.stories.tsx
│ │ │ │ │ ├── SaveButton.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── ShareButton
│ │ │ │ │ ├── ShareButton.stories.tsx
│ │ │ │ │ ├── ShareButton.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── UploadButton
│ │ │ │ │ ├── UploadButton.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ └── index.ts
│ │ │ ├── MainContent
│ │ │ │ ├── MainContent.stories.tsx
│ │ │ │ ├── MainContent.tsx
│ │ │ │ └── index.ts
│ │ │ └── Viewer
│ │ │ │ ├── CompileErrorFallback.tsx
│ │ │ │ ├── RuntimeErrorFallback.tsx
│ │ │ │ ├── Viewer.stories.tsx
│ │ │ │ ├── Viewer.tsx
│ │ │ │ ├── ViewerErrorFallback
│ │ │ │ ├── CodeErrorBlock.tsx
│ │ │ │ ├── ErrorMessage.tsx
│ │ │ │ ├── StackTrace.tsx
│ │ │ │ ├── ToggledContent.tsx
│ │ │ │ ├── ViewerErrorFallback.stories.tsx
│ │ │ │ ├── ViewerErrorFallback.tsx
│ │ │ │ └── index.ts
│ │ │ │ └── index.ts
│ │ ├── hooks
│ │ │ ├── useFilesRef.ts
│ │ │ └── useRuntime.ts
│ │ ├── instruments
│ │ │ ├── examples
│ │ │ │ ├── form
│ │ │ │ │ ├── Form-Reference
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ ├── Form-With-Computed-Measures
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ ├── Form-With-Groups
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ └── Multilingual-Form-With-Dynamic-Field
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ └── translations.ts
│ │ │ │ └── interactive
│ │ │ │ │ ├── Interactive-With-CSS
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── logo.png
│ │ │ │ │ └── styles.css
│ │ │ │ │ ├── Interactive-With-Embedded-Media
│ │ │ │ │ ├── CatVideo.tsx
│ │ │ │ │ ├── CowAudio.tsx
│ │ │ │ │ ├── Task.tsx
│ │ │ │ │ ├── cat-video.mp4
│ │ │ │ │ ├── cow-moo.mp3
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── styles.css
│ │ │ │ │ ├── Interactive-With-JSPsych
│ │ │ │ │ ├── blue.png
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── orange.png
│ │ │ │ │ ├── Interactive-With-Legacy-Script
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── legacy.d.ts
│ │ │ │ │ └── legacy.js
│ │ │ │ │ ├── Interactive-With-React
│ │ │ │ │ ├── App.css
│ │ │ │ │ ├── App.tsx
│ │ │ │ │ ├── index.css
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── react.svg
│ │ │ │ │ ├── Interactive-With-Vanilla
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── styles.css
│ │ │ │ │ └── Multilingual-Interactive
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── translator.ts
│ │ │ ├── index.ts
│ │ │ └── templates
│ │ │ │ ├── form
│ │ │ │ ├── Multilingual-Form
│ │ │ │ │ └── index.ts
│ │ │ │ └── Unilingual-Form
│ │ │ │ │ └── index.ts
│ │ │ │ └── interactive
│ │ │ │ └── Interactive-Instrument
│ │ │ │ └── index.ts
│ │ ├── main.tsx
│ │ ├── models
│ │ │ ├── editor-file.model.ts
│ │ │ ├── instrument-repository.model.ts
│ │ │ └── settings.model.ts
│ │ ├── pages
│ │ │ └── IndexPage.tsx
│ │ ├── store
│ │ │ ├── index.ts
│ │ │ ├── slices
│ │ │ │ ├── editor.slice.ts
│ │ │ │ ├── instrument.slice.ts
│ │ │ │ ├── settings.slice.ts
│ │ │ │ ├── transpiler.slice.ts
│ │ │ │ └── viewer.slice.ts
│ │ │ └── types.ts
│ │ ├── typings
│ │ │ └── monaco-editor.d.ts
│ │ ├── utils
│ │ │ ├── encode.ts
│ │ │ ├── file.ts
│ │ │ └── load.ts
│ │ ├── vim
│ │ │ ├── actions.ts
│ │ │ ├── adapter.ts
│ │ │ ├── command-dispatcher.ts
│ │ │ ├── common.ts
│ │ │ ├── default-key-map.ts
│ │ │ ├── digraph.b64.ts
│ │ │ ├── digraph.ts
│ │ │ ├── ex-command-dispatcher.ts
│ │ │ ├── global.ts
│ │ │ ├── history-controller.ts
│ │ │ ├── index.ts
│ │ │ ├── input-state.ts
│ │ │ ├── jump-list.ts
│ │ │ ├── keymap_vim.ts
│ │ │ ├── macro-mode-state.ts
│ │ │ ├── motions.ts
│ │ │ ├── operators.ts
│ │ │ ├── options.ts
│ │ │ ├── register-controller.ts
│ │ │ ├── search.ts
│ │ │ ├── statusbar.ts
│ │ │ ├── string-stream.ts
│ │ │ ├── types.ts
│ │ │ └── vim-api.ts
│ │ └── vite-env.d.ts
│ ├── tsconfig.json
│ └── vite.config.ts
└── web
│ ├── .env.public
│ ├── Dockerfile
│ ├── index.html
│ ├── package.json
│ ├── public
│ ├── favicon.ico
│ └── robots.txt
│ ├── src
│ ├── App.tsx
│ ├── Routes.tsx
│ ├── components
│ │ ├── Footer
│ │ │ ├── Footer.stories.tsx
│ │ │ ├── Footer.tsx
│ │ │ └── index.ts
│ │ ├── IdentificationForm
│ │ │ ├── IdentificationForm.stories.tsx
│ │ │ ├── IdentificationForm.tsx
│ │ │ └── index.ts
│ │ ├── Layout
│ │ │ ├── Layout.stories.tsx
│ │ │ ├── Layout.tsx
│ │ │ └── index.ts
│ │ ├── LoadingFallback
│ │ │ ├── LoadingFallback.stories.tsx
│ │ │ ├── LoadingFallback.tsx
│ │ │ └── index.ts
│ │ ├── NavButton
│ │ │ ├── NavButton.stories.tsx
│ │ │ ├── NavButton.tsx
│ │ │ └── index.ts
│ │ ├── Navbar
│ │ │ ├── Navbar.stories.tsx
│ │ │ ├── Navbar.tsx
│ │ │ └── index.ts
│ │ ├── PageHeader
│ │ │ ├── PageHeader.stories.tsx
│ │ │ ├── PageHeader.tsx
│ │ │ └── index.ts
│ │ ├── Sidebar
│ │ │ ├── Sidebar.stories.tsx
│ │ │ ├── Sidebar.tsx
│ │ │ └── index.ts
│ │ ├── UserDropup
│ │ │ ├── UserDropup.stories.tsx
│ │ │ ├── UserDropup.tsx
│ │ │ └── index.ts
│ │ ├── UserIcon
│ │ │ ├── UserIcon.stories.tsx
│ │ │ ├── UserIcon.tsx
│ │ │ └── index.ts
│ │ └── WithFallback.tsx
│ ├── config.ts
│ ├── features
│ │ ├── about
│ │ │ ├── components
│ │ │ │ ├── InfoBlock.tsx
│ │ │ │ └── TimeValue.tsx
│ │ │ ├── hooks
│ │ │ │ └── useGatewayHealthcheckQuery.ts
│ │ │ ├── index.tsx
│ │ │ └── pages
│ │ │ │ └── AboutPage.tsx
│ │ ├── admin
│ │ │ ├── components
│ │ │ │ ├── AppSettingsForm.tsx
│ │ │ │ ├── UpdateUserForm.stories.tsx
│ │ │ │ └── UpdateUserForm.tsx
│ │ │ ├── hooks
│ │ │ │ ├── useCreateGroupMutation.ts
│ │ │ │ ├── useCreateUserMutation.ts
│ │ │ │ ├── useDeleteGroupMutation.ts
│ │ │ │ ├── useDeleteUserMutation.ts
│ │ │ │ ├── useGroupsQuery.ts
│ │ │ │ ├── useUpdateSetupStateMutation.ts
│ │ │ │ ├── useUpdateUserMutation.ts
│ │ │ │ └── useUsersQuery.ts
│ │ │ ├── index.tsx
│ │ │ └── pages
│ │ │ │ ├── AppSettingsPage.tsx
│ │ │ │ ├── CreateGroupPage.tsx
│ │ │ │ ├── CreateUserPage.tsx
│ │ │ │ ├── ManageGroupsPage.tsx
│ │ │ │ └── ManageUsersPage.tsx
│ │ ├── auth
│ │ │ ├── components
│ │ │ │ ├── DemoBanner
│ │ │ │ │ ├── DemoBanner.stories.tsx
│ │ │ │ │ ├── DemoBanner.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ └── LoginForm
│ │ │ │ │ ├── LoginForm.stories.tsx
│ │ │ │ │ ├── LoginForm.tsx
│ │ │ │ │ └── index.ts
│ │ │ ├── index.tsx
│ │ │ └── pages
│ │ │ │ └── LoginPage.tsx
│ │ ├── contact
│ │ │ ├── components
│ │ │ │ └── ContactForm
│ │ │ │ │ ├── ContactForm.stories.tsx
│ │ │ │ │ ├── ContactForm.tsx
│ │ │ │ │ └── index.ts
│ │ │ ├── index.tsx
│ │ │ └── pages
│ │ │ │ └── ContactPage.tsx
│ │ ├── dashboard
│ │ │ ├── components
│ │ │ │ ├── GroupSwitcher.tsx
│ │ │ │ ├── StatisticCard.tsx
│ │ │ │ └── Summary.tsx
│ │ │ ├── hooks
│ │ │ │ └── useSummaryQuery.ts
│ │ │ ├── index.tsx
│ │ │ └── pages
│ │ │ │ └── DashboardPage.tsx
│ │ ├── datahub
│ │ │ ├── components
│ │ │ │ ├── AssignmentSlider.tsx
│ │ │ │ ├── AssignmentsTable.tsx
│ │ │ │ ├── MasterDataTable.tsx
│ │ │ │ ├── SelectInstrument.tsx
│ │ │ │ ├── SubjectLayout.tsx
│ │ │ │ ├── TabLink.tsx
│ │ │ │ └── TimeDropdown.tsx
│ │ │ ├── hooks
│ │ │ │ ├── useGraphData.ts
│ │ │ │ ├── useGraphLines.ts
│ │ │ │ ├── useInstrumentVisualization.ts
│ │ │ │ ├── useLinearModelQuery.ts
│ │ │ │ ├── useMeasureOptions.ts
│ │ │ │ └── useSubjectsQuery.ts
│ │ │ ├── index.tsx
│ │ │ └── pages
│ │ │ │ ├── DataHubPage.tsx
│ │ │ │ ├── SubjectAssignmentsPage.tsx
│ │ │ │ ├── SubjectGraphPage.tsx
│ │ │ │ └── SubjectTablePage.tsx
│ │ ├── group
│ │ │ ├── components
│ │ │ │ └── ManageGroupForm.tsx
│ │ │ ├── hooks
│ │ │ │ └── useUpdateGroup.ts
│ │ │ ├── index.tsx
│ │ │ └── pages
│ │ │ │ └── ManageGroupPage.tsx
│ │ ├── instruments
│ │ │ ├── components
│ │ │ │ ├── InstrumentCard
│ │ │ │ │ ├── InstrumentCard.stories.tsx
│ │ │ │ │ ├── InstrumentCard.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ └── InstrumentShowcase
│ │ │ │ │ ├── InstrumentKindDropdown.tsx
│ │ │ │ │ ├── InstrumentLanguageDropdown.tsx
│ │ │ │ │ ├── InstrumentShowcase.stories.tsx
│ │ │ │ │ ├── InstrumentShowcase.tsx
│ │ │ │ │ └── index.ts
│ │ │ ├── index.tsx
│ │ │ └── pages
│ │ │ │ ├── AccessibleInstrumentsPage.tsx
│ │ │ │ └── InstrumentRenderPage.tsx
│ │ ├── session
│ │ │ ├── components
│ │ │ │ └── StartSessionForm
│ │ │ │ │ ├── StartSessionForm.stories.tsx
│ │ │ │ │ ├── StartSessionForm.tsx
│ │ │ │ │ └── index.ts
│ │ │ ├── hooks
│ │ │ │ └── useCreateSession.ts
│ │ │ ├── index.tsx
│ │ │ └── pages
│ │ │ │ └── StartSessionPage.tsx
│ │ ├── setup
│ │ │ ├── hooks
│ │ │ │ └── useCreateSetupState.ts
│ │ │ ├── index.ts
│ │ │ ├── pages
│ │ │ │ ├── SetupLoadingPage
│ │ │ │ │ ├── SetupLoadingPage.stories.tsx
│ │ │ │ │ ├── SetupLoadingPage.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ └── SetupPage
│ │ │ │ │ ├── SetupPage.stories.tsx
│ │ │ │ │ ├── SetupPage.tsx
│ │ │ │ │ └── index.ts
│ │ │ └── providers
│ │ │ │ └── SetupProvider.tsx
│ │ ├── upload
│ │ │ ├── components
│ │ │ │ └── UploadSelectTable.tsx
│ │ │ ├── hooks
│ │ │ │ └── useUploadInstrumentRecords.ts
│ │ │ ├── index.tsx
│ │ │ ├── pages
│ │ │ │ ├── UploadPage.tsx
│ │ │ │ └── UploadSelectPage.tsx
│ │ │ ├── utils.test.ts
│ │ │ └── utils.ts
│ │ └── user
│ │ │ ├── index.tsx
│ │ │ └── pages
│ │ │ └── UserPage.tsx
│ ├── hooks
│ │ ├── useAssignmentsQuery.ts
│ │ ├── useCreateAssignment.ts
│ │ ├── useInstrument.ts
│ │ ├── useInstrumentBundle.ts
│ │ ├── useInstrumentInfoQuery.ts
│ │ ├── useInstrumentInterpreter.ts
│ │ ├── useInstrumentRecords.ts
│ │ ├── useIsDesktop.ts
│ │ ├── useNavItems.ts
│ │ ├── useSearch.ts
│ │ ├── useSetupState.ts
│ │ └── useUpdateAssignment.ts
│ ├── main.tsx
│ ├── providers
│ │ ├── DisclaimerProvider.tsx
│ │ ├── ForceClearQueryCacheProvider.tsx
│ │ └── WalkthroughProvider.tsx
│ ├── services
│ │ ├── axios.ts
│ │ ├── i18n.ts
│ │ ├── react-query.ts
│ │ └── zod.ts
│ ├── store
│ │ ├── index.ts
│ │ ├── slices
│ │ │ ├── auth.slice.ts
│ │ │ ├── disclaimer.slice.ts
│ │ │ ├── session.slice.ts
│ │ │ └── walkthrough.slice.ts
│ │ └── types.ts
│ ├── styles.css
│ ├── testing
│ │ └── stubs.ts
│ ├── translations
│ │ ├── auth.json
│ │ ├── common.json
│ │ ├── contact.json
│ │ ├── core.json
│ │ ├── datahub.json
│ │ ├── group.json
│ │ ├── instruments.json
│ │ ├── layout.json
│ │ ├── session.json
│ │ ├── setup.json
│ │ ├── upload.json
│ │ └── user.json
│ ├── utils
│ │ ├── __tests__
│ │ │ └── excel.test.ts
│ │ └── excel.ts
│ └── vite-env.d.ts
│ ├── tsconfig.json
│ ├── vite.config.ts
│ └── vitest.config.ts
├── blog
├── follow-good-naming-conventions.md
├── navigating-bureaucracy.md
├── open-data-capture-vs-redcap.md
├── open-source-data-platforms.md
└── the-cloud-in-healthcare.md
├── docker-compose.dev.yaml
├── docker-compose.yaml
├── docs
├── en
│ ├── 1-introduction
│ │ ├── 1.0-home.md
│ │ ├── 1.1-about.md
│ │ ├── 1.2-background.md
│ │ ├── 1.3-license.md
│ │ └── 1.4-scope.md
│ ├── 2-tutorials
│ │ ├── 2.0-development.mdx
│ │ └── 2.1-deployment.md
│ ├── 3-guides
│ │ ├── 3.0-how-to-create-an-instrument.mdx
│ │ ├── 3.1-how-to-export-data.mdx
│ │ ├── 3.2-how-to-visualize-data.mdx
│ │ └── 3.3-how-to-upload-data.md
│ ├── 4-concepts
│ │ ├── 4.0-architecture.mdx
│ │ ├── 4.1-instruments.mdx
│ │ ├── 4.2-hash-based-identification.md
│ │ └── 4.3-runtime-execution-context.md
│ ├── 5-reference
│ │ ├── 5.0-form-instrument.md
│ │ ├── 5.1-security.md
│ │ ├── 5.2-tooling.md
│ │ ├── 5.3-api.md
│ │ ├── 5.4-web.mdx
│ │ ├── 5.5-gateway.md
│ │ ├── 5.6-docker.md
│ │ └── 5.7-runtime.md
│ └── 6-updating
│ │ └── v1.7.0.md
└── fr
│ └── 1-introduction
│ ├── 1.0-home.md
│ ├── 1.1-about.md
│ ├── 1.2-background.md
│ ├── 1.3-license.md
│ └── 1.4-scope.md
├── eslint.config.js
├── knip.ts
├── package.json
├── packages
├── demo
│ ├── package.json
│ ├── src
│ │ └── index.ts
│ └── tsconfig.json
├── evaluate-instrument
│ ├── package.json
│ ├── src
│ │ ├── index.d.ts
│ │ └── index.js
│ └── tsconfig.json
├── instrument-bundler
│ ├── package.json
│ ├── src
│ │ ├── __tests__
│ │ │ ├── build.test.ts
│ │ │ ├── parse.test.ts
│ │ │ ├── preprocess.test.ts
│ │ │ ├── repositories
│ │ │ │ ├── form
│ │ │ │ │ └── index.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── interactive
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── styles.css
│ │ │ ├── resolve.test.ts
│ │ │ ├── schemas.test.ts
│ │ │ ├── transform.test.ts
│ │ │ └── utils.test.ts
│ │ ├── build.ts
│ │ ├── bundle.ts
│ │ ├── cli.ts
│ │ ├── error.ts
│ │ ├── index.ts
│ │ ├── parse.ts
│ │ ├── plugin.ts
│ │ ├── preprocess.ts
│ │ ├── resolve.ts
│ │ ├── schemas.ts
│ │ ├── transform.ts
│ │ ├── types.ts
│ │ ├── utils.ts
│ │ └── vendor
│ │ │ └── esbuild.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── instrument-interpreter
│ ├── package.json
│ ├── src
│ │ └── index.ts
│ └── tsconfig.json
├── instrument-library
│ ├── package.json
│ ├── scripts
│ │ └── available.ts
│ ├── src
│ │ ├── forms
│ │ │ ├── ADHD_ASRS_1.1
│ │ │ │ └── index.ts
│ │ │ ├── AUDIT_C
│ │ │ │ └── index.ts
│ │ │ ├── CUDIT_R
│ │ │ │ └── index.ts
│ │ │ ├── DNP_ENHANCED_DEMOGRAPHICS_QUESTIONNAIRE
│ │ │ │ └── index.ts
│ │ │ ├── DNP_GENERAL_CONSENT_FORM
│ │ │ │ └── index.ts
│ │ │ ├── DNP_HAPPINESS_QUESTIONNAIRE
│ │ │ │ └── index.ts
│ │ │ ├── FAGERSTRÖM_NICOTINE_DEPENDENCE
│ │ │ │ └── index.ts
│ │ │ ├── GAD_7
│ │ │ │ └── index.ts
│ │ │ ├── OLDER_AMERICANS_RESOURCES_AND_SERVICES
│ │ │ │ └── index.ts
│ │ │ ├── PHQ_9
│ │ │ │ └── index.ts
│ │ │ ├── STARKSTEIN_APATHY_SCALE
│ │ │ │ └── index.ts
│ │ │ └── TEN_ITEM_PERSONALITY_INVENTORY
│ │ │ │ └── index.ts
│ │ ├── interactive
│ │ │ ├── DNP_BREAKOUT_TASK
│ │ │ │ ├── index.ts
│ │ │ │ └── styles.css
│ │ │ └── DNP_STROOP_TASK
│ │ │ │ ├── StroopTask.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.css
│ │ └── series
│ │ │ └── DNP_HAPPINESS_QUESTIONNAIRE_WITH_CONSENT
│ │ │ └── index.ts
│ └── tsconfig.json
├── instrument-stubs
│ ├── README.md
│ ├── package.json
│ └── src
│ │ ├── forms.js
│ │ ├── interactive.js
│ │ ├── series.js
│ │ └── utils.js
├── instrument-utils
│ ├── package.json
│ ├── src
│ │ ├── form.ts
│ │ ├── guards.ts
│ │ ├── index.ts
│ │ ├── measures.ts
│ │ └── translate.ts
│ └── tsconfig.json
├── licenses
│ ├── package.json
│ ├── src
│ │ ├── index.d.ts
│ │ └── index.js
│ └── tsconfig.json
├── react-core
│ ├── package.json
│ ├── src
│ │ ├── components
│ │ │ ├── Branding
│ │ │ │ ├── Branding.stories.tsx
│ │ │ │ ├── Branding.tsx
│ │ │ │ └── index.ts
│ │ │ ├── CopyButton
│ │ │ │ ├── CopyButton.stories.tsx
│ │ │ │ ├── CopyButton.tsx
│ │ │ │ └── index.ts
│ │ │ ├── ErrorFallback
│ │ │ │ ├── ErrorFallback.stories.tsx
│ │ │ │ ├── ErrorFallback.tsx
│ │ │ │ └── index.ts
│ │ │ ├── ErrorPage
│ │ │ │ ├── ErrorPage.stories.tsx
│ │ │ │ ├── ErrorPage.tsx
│ │ │ │ └── index.ts
│ │ │ ├── FormContent
│ │ │ │ ├── FormContent.stories.tsx
│ │ │ │ ├── FormContent.tsx
│ │ │ │ └── index.ts
│ │ │ ├── InstrumentIcon
│ │ │ │ ├── InstrumentIcon.stories.tsx
│ │ │ │ ├── InstrumentIcon.tsx
│ │ │ │ └── index.ts
│ │ │ ├── InstrumentOverview
│ │ │ │ ├── InstrumentOverview.stories.tsx
│ │ │ │ ├── InstrumentOverview.tsx
│ │ │ │ └── index.ts
│ │ │ ├── InstrumentRenderer
│ │ │ │ ├── ContentPlaceholder.tsx
│ │ │ │ ├── InstrumentRenderer.stories.tsx
│ │ │ │ ├── InstrumentRenderer.tsx
│ │ │ │ ├── InstrumentRendererContainer.tsx
│ │ │ │ ├── ScalarInstrumentRenderer.tsx
│ │ │ │ ├── SeriesInstrumentContent.tsx
│ │ │ │ ├── SeriesInstrumentRenderer.tsx
│ │ │ │ └── index.ts
│ │ │ ├── InstrumentSummary
│ │ │ │ ├── InstrumentSummary.stories.tsx
│ │ │ │ ├── InstrumentSummary.tsx
│ │ │ │ ├── InstrumentSummaryGroup.tsx
│ │ │ │ └── index.ts
│ │ │ ├── InteractiveContent
│ │ │ │ ├── InteractiveContent.stories.tsx
│ │ │ │ ├── InteractiveContent.tsx
│ │ │ │ ├── bootstrap.js
│ │ │ │ └── index.ts
│ │ │ ├── LoadingPage
│ │ │ │ ├── LoadingPage.stories.tsx
│ │ │ │ ├── LoadingPage.tsx
│ │ │ │ └── index.ts
│ │ │ └── Logo
│ │ │ │ ├── Logo.stories.tsx
│ │ │ │ ├── Logo.tsx
│ │ │ │ └── index.ts
│ │ ├── globals.css
│ │ ├── hooks
│ │ │ └── useInterpretedInstrument.ts
│ │ ├── index.ts
│ │ └── types.ts
│ └── tsconfig.json
├── release-info
│ ├── package.json
│ ├── src
│ │ ├── __tests__
│ │ │ └── index.test.ts
│ │ └── index.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── runtime-bundler
│ ├── package.json
│ ├── src
│ │ ├── __tests__
│ │ │ ├── bundler.test.ts
│ │ │ ├── resolver.test.ts
│ │ │ ├── schemas.test.ts
│ │ │ └── utils.test.ts
│ │ ├── bundler.ts
│ │ ├── cli.ts
│ │ ├── index.ts
│ │ ├── plugin.ts
│ │ ├── resolver.ts
│ │ ├── schemas.ts
│ │ ├── types.ts
│ │ └── utils.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── runtime-core
│ ├── config
│ │ └── api-extractor.json
│ ├── package.json
│ ├── src
│ │ ├── __tests__
│ │ │ └── define.test-d.ts
│ │ ├── define.ts
│ │ ├── i18n.ts
│ │ ├── index.ts
│ │ ├── notifications.ts
│ │ ├── types
│ │ │ ├── __tests__
│ │ │ │ └── instrument.form.test-d.ts
│ │ │ ├── core.ts
│ │ │ ├── instrument.base.ts
│ │ │ ├── instrument.core.ts
│ │ │ ├── instrument.form.ts
│ │ │ ├── instrument.interactive.ts
│ │ │ └── instrument.series.ts
│ │ └── utils.ts
│ ├── tsconfig.build.json
│ └── tsconfig.json
├── schemas
│ ├── package.json
│ ├── src
│ │ ├── assignment
│ │ │ └── assignment.ts
│ │ ├── auth
│ │ │ └── auth.ts
│ │ ├── core
│ │ │ ├── core.test.ts
│ │ │ └── core.ts
│ │ ├── gateway
│ │ │ └── gateway.ts
│ │ ├── group
│ │ │ └── group.ts
│ │ ├── instrument-records
│ │ │ └── instrument-records.ts
│ │ ├── instrument
│ │ │ ├── __tests__
│ │ │ │ ├── instrument.base.test.ts
│ │ │ │ ├── instrument.core.test.ts
│ │ │ │ ├── instrument.form.test.ts
│ │ │ │ └── instrument.interactive.test.ts
│ │ │ ├── instrument.base.ts
│ │ │ ├── instrument.core.ts
│ │ │ ├── instrument.form.ts
│ │ │ ├── instrument.interactive.ts
│ │ │ ├── instrument.series.ts
│ │ │ └── instrument.ts
│ │ ├── session
│ │ │ └── session.ts
│ │ ├── setup
│ │ │ └── setup.ts
│ │ ├── subject
│ │ │ └── subject.ts
│ │ ├── summary
│ │ │ ├── summary.test.ts
│ │ │ └── summary.ts
│ │ └── user
│ │ │ └── user.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── subject-utils
│ ├── package.json
│ ├── src
│ │ ├── index.ts
│ │ └── test.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
└── vite-plugin-runtime
│ ├── package.json
│ ├── src
│ ├── __tests__
│ │ ├── index.test.ts
│ │ ├── load-resource.test.ts
│ │ ├── resolve.test.ts
│ │ └── runtime-middleware.test.ts
│ ├── index.d.ts
│ ├── index.js
│ ├── load-resource.js
│ ├── resolve.js
│ └── runtime-middleware.js
│ ├── tsconfig.json
│ └── vitest.config.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── prettier.config.js
├── runtime
└── v1
│ ├── env.d.ts
│ ├── global.d.ts
│ ├── package.json
│ ├── runtime.config.js
│ └── tsconfig.json
├── scripts
├── clean-workflows.sh
├── drop-database.sh
├── generate-env.sh
├── publish.sh
├── purge-workflow.sh
└── workspace.sh
├── storybook
├── config
│ ├── main.ts
│ └── preview.ts
├── package.json
├── tsconfig.json
└── vite.config.js
├── testing
├── cypress
│ ├── cypress.config.ts
│ ├── package.json
│ ├── src
│ │ ├── e2e
│ │ │ └── spec.cy.ts
│ │ ├── fixtures
│ │ │ └── example.json
│ │ ├── stubs.ts
│ │ ├── support
│ │ │ ├── commands.ts
│ │ │ └── e2e.ts
│ │ └── utils.ts
│ └── tsconfig.json
└── k6
│ ├── package.json
│ ├── scripts
│ ├── build.js
│ └── visualize.py
│ ├── src
│ ├── client.ts
│ ├── config.ts
│ └── index.ts
│ └── tsconfig.json
├── tsconfig.base.json
├── turbo.json
├── vendor
├── @jspsych
│ ├── plugin-html-button-response@1.x
│ │ ├── LICENSE
│ │ ├── package.json
│ │ └── src
│ │ │ ├── index.d.ts
│ │ │ └── index.js
│ ├── plugin-html-button-response@2.x
│ │ ├── LICENSE
│ │ ├── package.json
│ │ └── src
│ │ │ ├── index.d.ts
│ │ │ └── index.js
│ ├── plugin-html-keyboard-response@1.x
│ │ ├── LICENSE
│ │ ├── package.json
│ │ └── src
│ │ │ ├── index.d.ts
│ │ │ └── index.js
│ ├── plugin-html-keyboard-response@2.x
│ │ ├── LICENSE
│ │ ├── package.json
│ │ └── src
│ │ │ ├── index.d.ts
│ │ │ └── index.js
│ ├── plugin-image-button-response@1.x
│ │ ├── LICENSE
│ │ ├── package.json
│ │ └── src
│ │ │ ├── index.d.ts
│ │ │ └── index.js
│ ├── plugin-image-button-response@2.x
│ │ ├── LICENSE
│ │ ├── package.json
│ │ └── src
│ │ │ ├── index.d.ts
│ │ │ └── index.js
│ ├── plugin-image-keyboard-response@1.x
│ │ ├── LICENSE
│ │ ├── package.json
│ │ └── src
│ │ │ ├── index.d.ts
│ │ │ └── index.js
│ ├── plugin-image-keyboard-response@2.x
│ │ ├── LICENSE
│ │ ├── package.json
│ │ └── src
│ │ │ ├── index.d.ts
│ │ │ └── index.js
│ ├── plugin-instructions@2.x
│ │ ├── LICENSE
│ │ ├── package.json
│ │ └── src
│ │ │ ├── index.d.ts
│ │ │ └── index.js
│ ├── plugin-preload@1.x
│ │ ├── LICENSE
│ │ ├── package.json
│ │ └── src
│ │ │ ├── index.d.ts
│ │ │ └── index.js
│ ├── plugin-preload@2.x
│ │ ├── LICENSE
│ │ ├── package.json
│ │ └── src
│ │ │ ├── index.d.ts
│ │ │ └── index.js
│ ├── plugin-survey-html-form@1.x
│ │ ├── LICENSE
│ │ ├── package.json
│ │ └── src
│ │ │ ├── index.d.ts
│ │ │ └── index.js
│ ├── plugin-survey-html-form@2.x
│ │ ├── LICENSE
│ │ ├── package.json
│ │ └── src
│ │ │ ├── index.d.ts
│ │ │ └── index.js
│ ├── plugin-survey-text@1.x
│ │ ├── LICENSE
│ │ ├── package.json
│ │ └── src
│ │ │ ├── index.d.ts
│ │ │ └── index.js
│ └── plugin-survey-text@2.x
│ │ ├── LICENSE
│ │ ├── package.json
│ │ └── src
│ │ ├── index.d.ts
│ │ └── index.js
├── csstype@3.x
│ ├── LICENSE
│ ├── package.json
│ └── src
│ │ └── index.d.ts
├── dompurify@3.x
│ ├── LICENSE
│ ├── package.json
│ └── src
│ │ ├── index.d.ts
│ │ └── index.js
├── jquery-ui@1.x
│ ├── LICENSE
│ ├── package.json
│ └── src
│ │ ├── index.d.ts
│ │ └── index.js
├── jquery@1.12.4
│ ├── LICENSE
│ ├── package.json
│ └── src
│ │ ├── index.d.ts
│ │ └── index.js
├── jquery@3.x
│ ├── LICENSE
│ ├── package.json
│ └── src
│ │ ├── index.d.ts
│ │ └── index.js
├── jspsych@7.x
│ ├── LICENSE
│ ├── package.json
│ └── src
│ │ ├── index.css
│ │ ├── index.d.ts
│ │ └── index.js
├── jspsych@8.x
│ ├── LICENSE
│ ├── package.json
│ └── src
│ │ ├── index.css
│ │ ├── index.d.ts
│ │ └── index.js
├── lodash-es@4.x
│ ├── LICENSE
│ ├── package.json
│ └── src
│ │ ├── index.d.ts
│ │ └── index.js
├── normalize.css@8.x
│ ├── LICENSE
│ ├── package.json
│ └── src
│ │ └── index.css
├── papaparse@5.x
│ ├── LICENSE
│ ├── package.json
│ └── src
│ │ ├── index.d.ts
│ │ └── index.js
├── preloadjs@1.0.1
│ ├── LICENSE
│ ├── package.json
│ └── src
│ │ ├── index.d.ts
│ │ └── index.js
├── prop-types@15.x
│ ├── LICENSE
│ ├── package.json
│ └── src
│ │ ├── index.d.ts
│ │ └── index.js
├── psychojs@2023.1.3
│ ├── LICENSE
│ ├── package.json
│ └── src
│ │ ├── index.css
│ │ ├── index.d.ts
│ │ └── index.js
├── pure-rand@6.x
│ ├── LICENSE
│ ├── package.json
│ └── src
│ │ ├── index.d.ts
│ │ └── index.js
├── react-dom@18.x
│ ├── LICENSE
│ ├── package.json
│ └── src
│ │ ├── client.d.ts
│ │ ├── client.js
│ │ ├── index.d.ts
│ │ ├── index.js
│ │ ├── server.d.ts
│ │ └── server.js
├── react-dom@19.x
│ ├── LICENSE
│ ├── package.json
│ └── src
│ │ ├── client.d.ts
│ │ ├── client.js
│ │ ├── index.d.ts
│ │ ├── index.js
│ │ ├── profiling.d.ts
│ │ ├── profiling.js
│ │ ├── server.d.ts
│ │ ├── server.js
│ │ ├── static.d.ts
│ │ ├── static.js
│ │ ├── test-utils.d.ts
│ │ └── test-utils.js
├── react@18.x
│ ├── LICENSE
│ ├── package.json
│ └── src
│ │ ├── index.d.ts
│ │ ├── index.js
│ │ ├── jsx-dev-runtime.d.ts
│ │ ├── jsx-dev-runtime.js
│ │ ├── jsx-runtime.d.ts
│ │ └── jsx-runtime.js
├── react@19.x
│ ├── LICENSE
│ ├── package.json
│ └── src
│ │ ├── compiler-runtime.d.ts
│ │ ├── compiler-runtime.js
│ │ ├── index.d.ts
│ │ ├── index.js
│ │ ├── jsx-dev-runtime.d.ts
│ │ ├── jsx-dev-runtime.js
│ │ ├── jsx-runtime.d.ts
│ │ └── jsx-runtime.js
├── simple-statistics@7.x
│ ├── LICENSE
│ ├── package.json
│ └── src
│ │ ├── index.d.ts
│ │ └── index.js
├── ts-pattern@5.x
│ ├── LICENSE
│ ├── package.json
│ └── src
│ │ ├── index.d.ts
│ │ └── index.js
├── type-fest@4.x
│ ├── LICENSE
│ ├── package.json
│ └── src
│ │ └── index.d.ts
├── zod@3.23.x
│ ├── LICENSE
│ ├── package.json
│ └── src
│ │ ├── index.d.ts
│ │ └── index.js
└── zod@3.x
│ ├── LICENSE
│ ├── package.json
│ └── src
│ ├── index.d.ts
│ ├── index.js
│ ├── v3
│ ├── index.d.ts
│ └── index.js
│ ├── v4-mini
│ ├── index.d.ts
│ └── index.js
│ └── v4
│ ├── index.d.ts
│ └── index.js
├── vitest.config.ts
└── vitest.workspace.ts
/.dockerignore:
--------------------------------------------------------------------------------
1 | **/.astro
2 | **/.turbo
3 | **/dist
4 | **/node_modules
5 | .git
6 | .gitignore
7 | blog
8 | docs
9 | misc
10 | mongo
11 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | vendor/** linguist-vendored
2 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @joshunrau
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: true
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/discussion.yml:
--------------------------------------------------------------------------------
1 | name: Discussion
2 | description: Discuss a potential feature or enhancement
3 | labels: ["Discussion"]
4 | body:
5 | - type: textarea
6 | attributes:
7 | label: Content
8 | validations:
9 | required: true
10 |
--------------------------------------------------------------------------------
/.github/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/.github/assets/logo.png
--------------------------------------------------------------------------------
/.github/workflows/issues.yaml:
--------------------------------------------------------------------------------
1 | name: Issues
2 | on:
3 | issues:
4 | types:
5 | - reopened
6 | - opened
7 | jobs:
8 | label:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout Repository
12 | uses: actions/checkout@v4
13 | - name: Run Script
14 | uses: actions/github-script@v7
15 | with:
16 | script: |
17 | require('./.github/scripts/issues.cjs')()
18 |
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | name: Release
2 | on:
3 | push:
4 | branches: ['main']
5 | workflow_dispatch:
6 | jobs:
7 | release:
8 | permissions:
9 | contents: write
10 | packages: write
11 | uses: DouglasNeuroinformatics/.github/.github/workflows/app-release.yaml@main
12 | with:
13 | org: DouglasNeuroinformatics
14 | packages: open-data-capture-api, open-data-capture-gateway, open-data-capture-web
15 | validate_command: GATEWAY_DATABASE_URL=file:${TMPDIR}tmp.db pnpm lint
16 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | pnpm exec commitlint --edit $1
4 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | FILES=$(git diff --cached --name-only --diff-filter=ACMR | sed 's| |\\ |g')
4 | [ -z "$FILES" ] && exit 0
5 |
6 | # Prettify all selected files
7 | echo "$FILES" | xargs pnpm exec prettier --ignore-unknown --write
8 |
9 | # Add back the modified/prettified files to staging
10 | echo "$FILES" | xargs git add
11 |
12 | exit 0
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/jod
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Add files here to ignore them from prettier formatting
2 |
3 | /dist
4 | /coverage
5 |
--------------------------------------------------------------------------------
/Caddyfile:
--------------------------------------------------------------------------------
1 | {$SITE_ADDRESS} {
2 | redir /api /api/
3 |
4 | handle_path /api/* {
5 | reverse_proxy api
6 | }
7 |
8 | handle {
9 | reverse_proxy web
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | The most recent version of the platform will receive security updates.
6 |
7 | ## Reporting a Vulnerability
8 |
9 | We take security and data privacy very seriously. If you believe you have found a vulnerability, or have any type of security concern with our application, please contact us at support@douglasneuroinformatics.ca.
10 |
--------------------------------------------------------------------------------
/apps/api/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/api/public/favicon.ico
--------------------------------------------------------------------------------
/apps/api/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Open Data Capture API
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/apps/api/public/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
--------------------------------------------------------------------------------
/apps/api/src/assignments/assignments.module.ts:
--------------------------------------------------------------------------------
1 | import { forwardRef, Module } from '@nestjs/common';
2 |
3 | import { GatewayModule } from '@/gateway/gateway.module';
4 |
5 | import { AssignmentsController } from './assignments.controller';
6 | import { AssignmentsService } from './assignments.service';
7 |
8 | @Module({
9 | controllers: [AssignmentsController],
10 | exports: [AssignmentsService],
11 | imports: [forwardRef(() => GatewayModule)],
12 | providers: [AssignmentsService]
13 | })
14 | export class AssignmentsModule {}
15 |
--------------------------------------------------------------------------------
/apps/api/src/assignments/dto/create-assignment.dto.ts:
--------------------------------------------------------------------------------
1 | import { ValidationSchema } from '@douglasneuroinformatics/libnest';
2 | import { ApiProperty } from '@nestjs/swagger';
3 | import { $CreateAssignmentData } from '@opendatacapture/schemas/assignment';
4 | import type { CreateAssignmentData } from '@opendatacapture/schemas/assignment';
5 |
6 | @ValidationSchema($CreateAssignmentData)
7 | export class CreateAssignmentDto implements CreateAssignmentData {
8 | @ApiProperty()
9 | expiresAt: Date;
10 |
11 | @ApiProperty()
12 | groupId?: null | string;
13 |
14 | @ApiProperty()
15 | instrumentId: string;
16 |
17 | @ApiProperty()
18 | subjectId: string;
19 | }
20 |
--------------------------------------------------------------------------------
/apps/api/src/assignments/dto/update-assignment.dto.ts:
--------------------------------------------------------------------------------
1 | import { ValidationSchema } from '@douglasneuroinformatics/libnest';
2 | import { ApiProperty } from '@nestjs/swagger';
3 | import { $UpdateAssignmentData } from '@opendatacapture/schemas/assignment';
4 | import type { AssignmentStatus, UpdateAssignmentData } from '@opendatacapture/schemas/assignment';
5 | import { z } from 'zod';
6 |
7 | @ValidationSchema(
8 | $UpdateAssignmentData.extend({
9 | status: z.literal('CANCELED')
10 | })
11 | )
12 | export class UpdateAssignmentDto implements UpdateAssignmentData {
13 | @ApiProperty()
14 | status: Extract;
15 | }
16 |
--------------------------------------------------------------------------------
/apps/api/src/core/types.ts:
--------------------------------------------------------------------------------
1 | import type { AppAbility } from '@douglasneuroinformatics/libnest';
2 |
3 | export type EntityOperationOptions = {
4 | ability?: AppAbility;
5 | };
6 |
--------------------------------------------------------------------------------
/apps/api/src/gateway/gateway.controller.ts:
--------------------------------------------------------------------------------
1 | import { RouteAccess } from '@douglasneuroinformatics/libnest';
2 | import { Controller, Get } from '@nestjs/common';
3 | import { ApiTags } from '@nestjs/swagger';
4 | import type { GatewayHealthcheckResult } from '@opendatacapture/schemas/gateway';
5 |
6 | import { GatewayService } from './gateway.service';
7 |
8 | @ApiTags('Gateway')
9 | @Controller({ path: 'gateway' })
10 | export class GatewayController {
11 | constructor(private readonly gatewayService: GatewayService) {}
12 |
13 | @Get('healthcheck')
14 | @RouteAccess([])
15 | healthcheck(): Promise {
16 | return this.gatewayService.healthcheck();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/apps/api/src/groups/dto/create-group.dto.ts:
--------------------------------------------------------------------------------
1 | import { ValidationSchema } from '@douglasneuroinformatics/libnest';
2 | import { ApiProperty } from '@nestjs/swagger';
3 | import { $CreateGroupData } from '@opendatacapture/schemas/group';
4 | import type { CreateGroupData, GroupSettings, GroupType } from '@opendatacapture/schemas/group';
5 |
6 | @ValidationSchema($CreateGroupData)
7 | export class CreateGroupDto implements CreateGroupData {
8 | @ApiProperty({ example: 'Depression Clinic' })
9 | name: string;
10 | settings?: GroupSettings;
11 | type: GroupType;
12 | }
13 |
--------------------------------------------------------------------------------
/apps/api/src/groups/dto/update-group.dto.ts:
--------------------------------------------------------------------------------
1 | import { ValidationSchema } from '@douglasneuroinformatics/libnest';
2 | import { $UpdateGroupData } from '@opendatacapture/schemas/group';
3 | import type { GroupSettings, GroupType, UpdateGroupData } from '@opendatacapture/schemas/group';
4 |
5 | @ValidationSchema($UpdateGroupData)
6 | export class UpdateGroupDto implements UpdateGroupData {
7 | accessibleInstrumentIds?: string[];
8 | name?: string;
9 | settings?: Partial;
10 | type?: GroupType;
11 | }
12 |
--------------------------------------------------------------------------------
/apps/api/src/groups/groups.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 |
3 | import { InstrumentsModule } from '@/instruments/instruments.module';
4 |
5 | import { GroupsController } from './groups.controller';
6 | import { GroupsService } from './groups.service';
7 |
8 | @Module({
9 | controllers: [GroupsController],
10 | exports: [GroupsService],
11 | imports: [InstrumentsModule],
12 | providers: [GroupsService]
13 | })
14 | export class GroupsModule {}
15 |
--------------------------------------------------------------------------------
/apps/api/src/instrument-records/dto/create-instrument-record.dto.ts:
--------------------------------------------------------------------------------
1 | import { ValidationSchema } from '@douglasneuroinformatics/libnest';
2 | import type { Json } from '@opendatacapture/schemas/core';
3 | import { $CreateInstrumentRecordData } from '@opendatacapture/schemas/instrument-records';
4 |
5 | @ValidationSchema($CreateInstrumentRecordData)
6 | export class CreateInstrumentRecordDto {
7 | data: Json;
8 | date: Date;
9 | groupId?: string;
10 | instrumentId: string;
11 | sessionId: string;
12 | subjectId: string;
13 | }
14 |
--------------------------------------------------------------------------------
/apps/api/src/instrument-records/dto/upload-instrument-record.dto.ts:
--------------------------------------------------------------------------------
1 | import { ValidationSchema } from '@douglasneuroinformatics/libnest';
2 | import type { Json } from '@opendatacapture/schemas/core';
3 | import { $UploadInstrumentRecordsData } from '@opendatacapture/schemas/instrument-records';
4 |
5 | @ValidationSchema($UploadInstrumentRecordsData)
6 | export class UploadInstrumentRecordsDto {
7 | groupId?: string;
8 | instrumentId: string;
9 | records: {
10 | data: Json;
11 | date: Date;
12 | subjectId: string;
13 | }[];
14 | }
15 |
--------------------------------------------------------------------------------
/apps/api/src/instruments/dto/create-instrument.dto.ts:
--------------------------------------------------------------------------------
1 | import { ValidationSchema } from '@douglasneuroinformatics/libnest';
2 | import { $CreateInstrumentData } from '@opendatacapture/schemas/instrument';
3 |
4 | @ValidationSchema($CreateInstrumentData)
5 | export class CreateInstrumentDto {
6 | bundle: string;
7 | }
8 |
--------------------------------------------------------------------------------
/apps/api/src/sessions/dto/create-session.dto.ts:
--------------------------------------------------------------------------------
1 | import { ValidationSchema } from '@douglasneuroinformatics/libnest';
2 | import { $CreateSessionData } from '@opendatacapture/schemas/session';
3 | import type { SessionType } from '@opendatacapture/schemas/session';
4 | import type { CreateSubjectData } from '@opendatacapture/schemas/subject';
5 |
6 | @ValidationSchema($CreateSessionData)
7 | export class CreateSessionDto {
8 | date: Date;
9 | groupId: null | string;
10 | subjectData: CreateSubjectData;
11 | type: SessionType;
12 | }
13 |
--------------------------------------------------------------------------------
/apps/api/src/sessions/sessions.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 |
3 | import { GroupsModule } from '@/groups/groups.module';
4 | import { SubjectsModule } from '@/subjects/subjects.module';
5 |
6 | import { SessionsController } from './sessions.controller';
7 | import { SessionsService } from './sessions.service';
8 |
9 | @Module({
10 | controllers: [SessionsController],
11 | exports: [SessionsService],
12 | imports: [GroupsModule, SubjectsModule],
13 | providers: [SessionsService]
14 | })
15 | export class SessionsModule {}
16 |
--------------------------------------------------------------------------------
/apps/api/src/setup/dto/init-app.dto.ts:
--------------------------------------------------------------------------------
1 | import { ValidationSchema } from '@douglasneuroinformatics/libnest';
2 | import { ApiProperty } from '@nestjs/swagger';
3 | import { $InitAppOptions } from '@opendatacapture/schemas/setup';
4 | import type { CreateAdminData, InitAppOptions } from '@opendatacapture/schemas/setup';
5 |
6 | @ValidationSchema($InitAppOptions)
7 | export class InitAppDto implements InitAppOptions {
8 | @ApiProperty()
9 | admin: CreateAdminData;
10 |
11 | @ApiProperty()
12 | dummySubjectCount?: number;
13 |
14 | @ApiProperty()
15 | enableExperimentalFeatures: boolean;
16 |
17 | @ApiProperty()
18 | initDemo: boolean;
19 |
20 | @ApiProperty()
21 | recordsPerSubject?: number;
22 | }
23 |
--------------------------------------------------------------------------------
/apps/api/src/setup/dto/update-setup-state.dto.ts:
--------------------------------------------------------------------------------
1 | import { ValidationSchema } from '@douglasneuroinformatics/libnest';
2 | import { ApiProperty } from '@nestjs/swagger';
3 | import { $UpdateSetupStateData } from '@opendatacapture/schemas/setup';
4 | import type { UpdateSetupStateData } from '@opendatacapture/schemas/setup';
5 |
6 | @ValidationSchema($UpdateSetupStateData)
7 | export class UpdateSetupStateDto implements UpdateSetupStateData {
8 | @ApiProperty()
9 | isExperimentalFeaturesEnabled?: boolean;
10 | }
11 |
--------------------------------------------------------------------------------
/apps/api/src/setup/setup.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 |
3 | import { DemoModule } from '@/demo/demo.module';
4 | import { UsersModule } from '@/users/users.module';
5 |
6 | import { SetupController } from './setup.controller';
7 | import { SetupService } from './setup.service';
8 |
9 | @Module({
10 | controllers: [SetupController],
11 | exports: [SetupService],
12 | imports: [DemoModule, UsersModule],
13 | providers: [SetupService]
14 | })
15 | export class SetupModule {}
16 |
--------------------------------------------------------------------------------
/apps/api/src/subjects/dto/create-subject.dto.ts:
--------------------------------------------------------------------------------
1 | import { ValidationSchema } from '@douglasneuroinformatics/libnest';
2 | import { ApiProperty } from '@nestjs/swagger';
3 | import { $CreateSubjectData } from '@opendatacapture/schemas/subject';
4 | import type { Sex } from '@opendatacapture/schemas/subject';
5 |
6 | @ValidationSchema($CreateSubjectData)
7 | export class CreateSubjectDto {
8 | @ApiProperty()
9 | dateOfBirth?: Date | null;
10 |
11 | @ApiProperty()
12 | firstName?: null | string;
13 |
14 | @ApiProperty()
15 | id: string;
16 |
17 | @ApiProperty()
18 | lastName?: null | string;
19 |
20 | @ApiProperty()
21 | sex?: null | Sex;
22 | }
23 |
--------------------------------------------------------------------------------
/apps/api/src/subjects/subjects.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 |
3 | import { SubjectsController } from './subjects.controller';
4 | import { SubjectsService } from './subjects.service';
5 |
6 | @Module({
7 | controllers: [SubjectsController],
8 | exports: [SubjectsService],
9 | providers: [SubjectsService]
10 | })
11 | export class SubjectsModule {}
12 |
--------------------------------------------------------------------------------
/apps/api/src/typings/global.d.ts:
--------------------------------------------------------------------------------
1 | import type { ReleaseInfo } from '@opendatacapture/schemas/setup';
2 |
3 | declare global {
4 | const __RELEASE__: ReleaseInfo;
5 | }
6 |
--------------------------------------------------------------------------------
/apps/api/src/typings/prisma-json-types-generator.d.ts:
--------------------------------------------------------------------------------
1 | import type { InstrumentMeasureValue } from '@opendatacapture/runtime-core';
2 |
3 | declare global {
4 | namespace PrismaJson {
5 | type ComputedMeasures = null | undefined | { [key: string]: InstrumentMeasureValue };
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/apps/api/src/users/dto/update-user.dto.ts:
--------------------------------------------------------------------------------
1 | import { ValidationSchema } from '@douglasneuroinformatics/libnest';
2 | import { PartialType } from '@nestjs/swagger';
3 | import { $UpdateUserData } from '@opendatacapture/schemas/user';
4 |
5 | import { CreateUserDto } from './create-user.dto';
6 |
7 | @ValidationSchema($UpdateUserData)
8 | export class UpdateUserDto extends PartialType(CreateUserDto) {}
9 |
--------------------------------------------------------------------------------
/apps/api/src/users/users.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 |
3 | import { GroupsModule } from '@/groups/groups.module';
4 |
5 | import { UsersController } from './users.controller';
6 | import { UsersService } from './users.service';
7 |
8 | @Module({
9 | controllers: [UsersController],
10 | exports: [UsersService],
11 | imports: [GroupsModule],
12 | providers: [UsersService]
13 | })
14 | export class UsersModule {}
15 |
--------------------------------------------------------------------------------
/apps/api/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "baseUrl": ".",
5 | "emitDecoratorMetadata": true,
6 | "experimentalDecorators": true,
7 | "paths": {
8 | "@/*": ["src/*"],
9 | "#runtime/v1/*": ["../../runtime/v1/dist/*"]
10 | },
11 | "strictPropertyInitialization": false
12 | },
13 | "include": ["src/**/*", "libnest.config.ts", "vitest.config.ts"]
14 | }
15 |
--------------------------------------------------------------------------------
/apps/gateway/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Open Data Capture - Gateway
8 |
14 |
15 |
16 | {{ ROOT_SSR_OUTLET }}
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/apps/gateway/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/gateway/public/favicon.ico
--------------------------------------------------------------------------------
/apps/gateway/scripts/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -eu
4 |
5 | DATABASE_FILE=${GATEWAY_DATABASE_URL#file:}
6 | EMPTY_DATABASE_FILE="/app/gateway.tmpl.db"
7 |
8 | if [ ! -s "$DATABASE_FILE" ]; then
9 | cp "$EMPTY_DATABASE_FILE" "$DATABASE_FILE"
10 | fi
11 |
12 | exec node ./dist/main.js
13 |
--------------------------------------------------------------------------------
/apps/gateway/src/entry-client.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 |
4 | import { Root } from './Root';
5 |
6 | import '@opendatacapture/react-core/globals.css';
7 |
8 | const ROOT_PROPS = window.__ROOT_PROPS__;
9 |
10 | ReactDOM.hydrateRoot(
11 | document.getElementById('root')!,
12 |
13 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/apps/gateway/src/entry-server.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOMServer from 'react-dom/server';
3 |
4 | import { Root } from './Root';
5 |
6 | import type { RootProps } from './Root';
7 |
8 | export type RenderFunction = (props: RootProps) => { html: string };
9 |
10 | export const render: RenderFunction = (props) => {
11 | const html = ReactDOMServer.renderToString(
12 |
13 |
14 |
15 | );
16 | return { html };
17 | };
18 |
--------------------------------------------------------------------------------
/apps/gateway/src/lib/prisma.ts:
--------------------------------------------------------------------------------
1 | import { HybridCrypto } from '@douglasneuroinformatics/libcrypto';
2 | import { PrismaClient } from '@prisma/generated-client';
3 |
4 | export const prisma = new PrismaClient().$extends({
5 | result: {
6 | remoteAssignmentModel: {
7 | getPublicKey: {
8 | compute({ rawPublicKey }) {
9 | return () => HybridCrypto.deserializePublicKey(rawPublicKey);
10 | },
11 | needs: { rawPublicKey: true }
12 | }
13 | }
14 | }
15 | });
16 |
--------------------------------------------------------------------------------
/apps/gateway/src/main.ts:
--------------------------------------------------------------------------------
1 | import { Server } from '@/server';
2 |
3 | const server = new Server();
4 |
5 | server.listen();
6 |
--------------------------------------------------------------------------------
/apps/gateway/src/middleware/error-handler.middleware.ts:
--------------------------------------------------------------------------------
1 | import type { ErrorRequestHandler } from 'express';
2 |
3 | import { HttpException } from '@/utils/http-exception';
4 |
5 | export const errorHandlerMiddleware: ErrorRequestHandler = (err, _, res, next) => {
6 | console.error(err);
7 | if (res.headersSent) {
8 | return next(err);
9 | } else if (err instanceof HttpException) {
10 | res.status(err.status).send({ message: err.message, statusCode: err.status });
11 | } else {
12 | res.status(500).send({ message: 'Internal Server Error', statusCode: 500 });
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/apps/gateway/src/middleware/not-found.middleware.ts:
--------------------------------------------------------------------------------
1 | import type { RequestHandler } from 'express';
2 |
3 | export const notFoundMiddleware: RequestHandler = (_, res) => {
4 | res
5 | .status(404)
6 | .set({ 'Content-Type': 'text/html' })
7 | .end(
8 | `
9 |
404 - Not Found
10 | `
11 | );
12 | };
13 |
--------------------------------------------------------------------------------
/apps/gateway/src/server/index.ts:
--------------------------------------------------------------------------------
1 | import type { AppServer } from './server.base';
2 |
3 | let Server: AppServer;
4 | if (import.meta.env.DEV) {
5 | Server = (await import('./server.development')).DevelopmentServer;
6 | } else {
7 | Server = (await import('./server.production')).ProductionServer;
8 | }
9 |
10 | export { Server };
11 |
--------------------------------------------------------------------------------
/apps/gateway/src/services/i18n.ts:
--------------------------------------------------------------------------------
1 | import { i18n } from '@douglasneuroinformatics/libui/i18n';
2 |
3 | i18n.init();
4 |
--------------------------------------------------------------------------------
/apps/gateway/src/typings/express.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/consistent-type-definitions */
2 |
3 | import type { RootProps } from '@/Root';
4 |
5 | declare global {
6 | namespace Express {
7 | interface Locals {
8 | loadRoot: (props: RootProps) => string;
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/apps/gateway/src/utils/async-handler.ts:
--------------------------------------------------------------------------------
1 | import type { NextFunction, Request, RequestHandler, Response } from 'express';
2 |
3 | type AsyncRequestHandler = (req: Request, res: Response, next: NextFunction) => Promise;
4 |
5 | export function ah(fn: T): RequestHandler {
6 | return (req, res, next) => void fn(req, res, next).catch(next);
7 | }
8 |
--------------------------------------------------------------------------------
/apps/gateway/src/utils/auth.ts:
--------------------------------------------------------------------------------
1 | import crypto from 'crypto';
2 |
3 | import { config } from '@/config';
4 |
5 | export function generateToken(assignmentId: string) {
6 | return crypto
7 | .createHash('sha256')
8 | .update(config.apiKey + assignmentId, 'utf8')
9 | .digest()
10 | .toString('hex');
11 | }
12 |
--------------------------------------------------------------------------------
/apps/gateway/src/utils/http-exception.ts:
--------------------------------------------------------------------------------
1 | export class HttpException extends Error {
2 | constructor(
3 | public readonly status: number,
4 | message: string
5 | ) {
6 | super(message);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/apps/gateway/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/consistent-type-definitions */
2 |
3 | ///
4 | ///
5 | ///
6 |
7 | import type { ReleaseInfo } from '@opendatacapture/schemas/setup';
8 | import type express from 'express';
9 |
10 | import type { RootProps } from './Root';
11 |
12 | declare global {
13 | type App = ReturnType;
14 |
15 | interface Window {
16 | __ROOT_PROPS__: RootProps;
17 | }
18 |
19 | interface ImportMeta {
20 | readonly env: ImportMetaEnv;
21 | }
22 |
23 | const __RELEASE__: ReleaseInfo;
24 | }
25 |
--------------------------------------------------------------------------------
/apps/gateway/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "baseUrl": ".",
5 | "paths": {
6 | "@/*": ["./src/*"],
7 | "/runtime/v1/*": ["../../runtime/v1/dist/*"]
8 | }
9 | },
10 | "include": ["scripts/*", "src/**/*", "vite.config.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/apps/outreach/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/public/favicon.ico
--------------------------------------------------------------------------------
/apps/outreach/src/assets/examples/4.1-instruments/styles.css:
--------------------------------------------------------------------------------
1 | button {
2 | background-color: blue;
3 | border-radius: 0.5rem;
4 | color: white;
5 | padding: 1rem 5rem;
6 | }
7 |
--------------------------------------------------------------------------------
/apps/outreach/src/assets/headshots/cian-monnin.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/headshots/cian-monnin.webp
--------------------------------------------------------------------------------
/apps/outreach/src/assets/headshots/david-roper.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/headshots/david-roper.webp
--------------------------------------------------------------------------------
/apps/outreach/src/assets/headshots/gabriel-devenyi.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/headshots/gabriel-devenyi.webp
--------------------------------------------------------------------------------
/apps/outreach/src/assets/headshots/joshua-unrau.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/headshots/joshua-unrau.webp
--------------------------------------------------------------------------------
/apps/outreach/src/assets/headshots/mallar-chakravarty.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/headshots/mallar-chakravarty.webp
--------------------------------------------------------------------------------
/apps/outreach/src/assets/headshots/martin-lepage.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/headshots/martin-lepage.webp
--------------------------------------------------------------------------------
/apps/outreach/src/assets/headshots/massimiliano-orri.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/headshots/massimiliano-orri.webp
--------------------------------------------------------------------------------
/apps/outreach/src/assets/headshots/maxime-montembeault.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/headshots/maxime-montembeault.webp
--------------------------------------------------------------------------------
/apps/outreach/src/assets/headshots/simon-ducharme.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/headshots/simon-ducharme.webp
--------------------------------------------------------------------------------
/apps/outreach/src/assets/headshots/thomas-beaudry.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/headshots/thomas-beaudry.webp
--------------------------------------------------------------------------------
/apps/outreach/src/assets/headshots/vanessa-valiquette.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/headshots/vanessa-valiquette.webp
--------------------------------------------------------------------------------
/apps/outreach/src/assets/headshots/weijie-tan.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/headshots/weijie-tan.webp
--------------------------------------------------------------------------------
/apps/outreach/src/assets/screenshots/dashboard.en.dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/screenshots/dashboard.en.dark.png
--------------------------------------------------------------------------------
/apps/outreach/src/assets/screenshots/dashboard.en.light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/screenshots/dashboard.en.light.png
--------------------------------------------------------------------------------
/apps/outreach/src/assets/screenshots/data-hub.en.dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/screenshots/data-hub.en.dark.png
--------------------------------------------------------------------------------
/apps/outreach/src/assets/screenshots/data-hub.en.light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/screenshots/data-hub.en.light.png
--------------------------------------------------------------------------------
/apps/outreach/src/assets/screenshots/data-hub.fr.dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/screenshots/data-hub.fr.dark.png
--------------------------------------------------------------------------------
/apps/outreach/src/assets/screenshots/data-hub.fr.light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/screenshots/data-hub.fr.light.png
--------------------------------------------------------------------------------
/apps/outreach/src/assets/screenshots/login.en.dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/screenshots/login.en.dark.png
--------------------------------------------------------------------------------
/apps/outreach/src/assets/screenshots/login.en.light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/screenshots/login.en.light.png
--------------------------------------------------------------------------------
/apps/outreach/src/assets/screenshots/playground-editor.en.dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/screenshots/playground-editor.en.dark.png
--------------------------------------------------------------------------------
/apps/outreach/src/assets/screenshots/playground-editor.en.light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/screenshots/playground-editor.en.light.png
--------------------------------------------------------------------------------
/apps/outreach/src/assets/screenshots/playground-upload-bundle-button.en.dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/screenshots/playground-upload-bundle-button.en.dark.png
--------------------------------------------------------------------------------
/apps/outreach/src/assets/screenshots/playground-upload-bundle-button.en.light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/screenshots/playground-upload-bundle-button.en.light.png
--------------------------------------------------------------------------------
/apps/outreach/src/assets/screenshots/start-session-hash.en.dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/screenshots/start-session-hash.en.dark.png
--------------------------------------------------------------------------------
/apps/outreach/src/assets/screenshots/start-session-hash.en.light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/screenshots/start-session-hash.en.light.png
--------------------------------------------------------------------------------
/apps/outreach/src/assets/screenshots/start-session-hash.fr.dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/screenshots/start-session-hash.fr.dark.png
--------------------------------------------------------------------------------
/apps/outreach/src/assets/screenshots/start-session-hash.fr.light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/screenshots/start-session-hash.fr.light.png
--------------------------------------------------------------------------------
/apps/outreach/src/assets/screenshots/upload-bundle.en.dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/screenshots/upload-bundle.en.dark.png
--------------------------------------------------------------------------------
/apps/outreach/src/assets/screenshots/upload-bundle.en.light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/outreach/src/assets/screenshots/upload-bundle.en.light.png
--------------------------------------------------------------------------------
/apps/outreach/src/components/blog/NoContent.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { useTranslations } from '@/i18n';
3 |
4 | const { altURL, t } = useTranslations(Astro.url);
5 | ---
6 |
7 |
8 |
{t('common.noContent')}
9 |
10 | {t('common.viewAltLangPrefix')}
11 |
12 | {t('common.viewAltLangSuffix')}
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/apps/outreach/src/components/common/Headshot.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { Image } from 'astro:assets';
3 |
4 | import { cn } from '@/utils';
5 |
6 | type Props = {
7 | alt: string;
8 | className?: string;
9 | src: ImageMetadata;
10 | };
11 |
12 | const { className, ...props } = Astro.props;
13 | ---
14 |
15 |
22 |
--------------------------------------------------------------------------------
/apps/outreach/src/components/common/PageHeading.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { cn } from '@/utils';
3 |
4 | type Props = {
5 | className?: string;
6 | title: string;
7 | };
8 |
9 | const { className, title } = Astro.props;
10 | ---
11 |
12 |
13 | {title}
14 |
15 |
--------------------------------------------------------------------------------
/apps/outreach/src/components/common/SiteTitle.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Logo from './Logo.astro';
3 |
4 | import { useTranslations } from '@/i18n';
5 | import { cn } from '@/utils';
6 |
7 | type Props = { className?: string };
8 |
9 | const { t, translatePath } = useTranslations(Astro.url);
10 | const { className } = Astro.props;
11 | ---
12 |
13 | svg]:h-full [&>svg]:w-auto', className)} href={translatePath('/')}>
14 |
15 |
16 | {t('meta.title')}
17 |
18 |
19 |
--------------------------------------------------------------------------------
/apps/outreach/src/components/icons/ArrowLeftIcon.astro:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/apps/outreach/src/components/icons/ArrowRightIcon.astro:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/apps/outreach/src/components/icons/ChevronDownIcon.astro:
--------------------------------------------------------------------------------
1 | ---
2 | type Props = Pick;
3 | ---
4 |
5 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/apps/outreach/src/components/icons/MenuIcon.astro:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/apps/outreach/src/components/icons/MoonIcon.astro:
--------------------------------------------------------------------------------
1 | ---
2 | type Props = Pick;
3 | ---
4 |
5 |
13 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/apps/outreach/src/components/icons/SunIcon.astro:
--------------------------------------------------------------------------------
1 |
9 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/apps/outreach/src/components/icons/XMarkIcon.astro:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/apps/outreach/src/components/layout/NavLink.astro:
--------------------------------------------------------------------------------
1 | ---
2 | type Props = { href: string; label: string };
3 | const { label, ...props } = Astro.props;
4 | ---
5 |
6 |
7 | {label}
8 |
9 |
--------------------------------------------------------------------------------
/apps/outreach/src/content/blog:
--------------------------------------------------------------------------------
1 | ../../../../blog
--------------------------------------------------------------------------------
/apps/outreach/src/content/docs/en/docs:
--------------------------------------------------------------------------------
1 | ../../../../../../docs/en
--------------------------------------------------------------------------------
/apps/outreach/src/content/docs/fr/docs:
--------------------------------------------------------------------------------
1 | ../../../../../../docs/fr
--------------------------------------------------------------------------------
/apps/outreach/src/content/team/cian-monnin.yaml:
--------------------------------------------------------------------------------
1 | fullName: Cian Monnin
2 | position:
3 | en: Junior Developer
4 | fr: Développeur junior
5 | image: '@/assets/headshots/cian-monnin.webp'
6 | description:
7 | en: Cian is currently working on implementing object storage within Open Data Capture. In the future, this will be used for new instrument modalities, such as brain imaging data.
8 | fr: Cian travaille actuellement à la mise en œuvre du stockage d'objets au sein d'Open Data Capture. À l'avenir, ce système sera utilisé pour de nouvelles modalités d'instruments, telles que les données d'imagerie cérébrale.
9 | seniority: 9
10 |
--------------------------------------------------------------------------------
/apps/outreach/src/content/team/david-roper.yaml:
--------------------------------------------------------------------------------
1 | fullName: David Roper
2 | position:
3 | en: Junior Developer
4 | fr: Développeur junior
5 | image: '@/assets/headshots/david-roper.webp'
6 | description:
7 | en: David contributes to the project by specializing in writing tests. He is also currently developing instruments for conducting animal studies using Open Data Capture, as part of a collaboration with Western University.
8 | fr: David contribue au projet en se spécialisant dans la rédaction de tests. Il développe également des instruments pour mener des études sur les animaux en utilisant Open Data Capture, dans le cadre d'une collaboration avec l'Université de Western.
9 | seniority: 8
10 |
--------------------------------------------------------------------------------
/apps/outreach/src/content/team/gabriel-devenyi.yaml:
--------------------------------------------------------------------------------
1 | fullName: Gabriel Devenyi
2 | suffix: PhD
3 | position:
4 | en: Project Lead
5 | fr: Responsable de projet
6 | image: '@/assets/headshots/gabriel-devenyi.webp'
7 | description:
8 | en: Gabriel holds a PhD in Engineering Physics and has extensive knowledge of Linux system administration. He leads the Douglas Neuroinformatics Platform, architecting its software, platforms, and infrastructure.
9 | fr: Gabriel est titulaire d'un doctorat en ingénierie et possède une connaissance considérable de l'administration des systèmes Linux. Il dirige le Douglas Neuroinformatics Platform, dont il conçoit les logiciels, les plateformes et l'infrastructure.
10 | seniority: 3
11 |
--------------------------------------------------------------------------------
/apps/outreach/src/content/team/joshua-unrau.yaml:
--------------------------------------------------------------------------------
1 | fullName: Joshua Unrau
2 | position:
3 | en: Lead Developer
4 | fr: Développeur principal
5 | image: '@/assets/headshots/joshua-unrau.webp'
6 | description:
7 | en: Joshua is an experienced TypeScript developer, specializing in React, TailwindCSS, Express and NestJS. He is also well-versed in build tooling, generic types, and the intricacies of the JavaScript ecosystem.
8 | fr: Joshua est un développeur TypeScript expérimenté, spécialisé dans React, TailwindCSS, Express et NestJS. Il connaît également bien les outils de construction, les types génériques et les subtilités de l'écosystème JavaScript.
9 | seniority: 4
10 |
--------------------------------------------------------------------------------
/apps/outreach/src/content/team/mallar-chakravarty.yaml:
--------------------------------------------------------------------------------
1 | fullName: Mallar Chakravarty
2 | suffix: PhD
3 | position:
4 | en: Program Director
5 | fr: Directeur du programme
6 | image: '@/assets/headshots/mallar-chakravarty.webp'
7 | description:
8 | en: Mallar serves as director of the Douglas Neuroinformatics Platform. He has a background in engineering and neuroscience, as well as substantial experience navigating complex regulatory environments.
9 | fr: Mallar est directeur du Douglas Neuroinformatics Platform. Il possède une formation en ingénierie et en neurosciences, ainsi qu'une expérience substantielle dans la gestion d'environnements réglementaires complexes.
10 | seniority: 1
11 |
--------------------------------------------------------------------------------
/apps/outreach/src/content/team/martin-lepage.yaml:
--------------------------------------------------------------------------------
1 | fullName: Martin Lepage
2 | suffix: PhD
3 | position:
4 | en: Program Director
5 | fr: Directeur du programme
6 | image: '@/assets/headshots/martin-lepage.webp'
7 | description:
8 | en: Martin serves as the deputy scientific director at the Douglas Research Centre, bringing extensive clinical and implementation expertise to the project. He is also a strong supporter of open science and open source initiatives.
9 | fr: Martin est directeur scientifique adjoint au Centre de recherche Douglas et apporte au projet une grande expertise clinique et de mise en œuvre. Il est également un fervent défenseur de la science ouverte et des initiatives open source.
10 | seniority: 2
11 |
--------------------------------------------------------------------------------
/apps/outreach/src/content/team/thomas-beaudry.yaml:
--------------------------------------------------------------------------------
1 | fullName: Thomas Beaudry
2 | position:
3 | en: System Administrator
4 | fr: Administrateur système
5 | image: '@/assets/headshots/thomas-beaudry.webp'
6 | description:
7 | en: Tom is responsible for the internal deployment of Open Data Capture. He offers valuable feedback on the deployment setup and the instrument development workflow.
8 | fr: Tom est responsable du déploiement interne d'Open Data Capture. Il apporte un retour d'information précieux sur la configuration du déploiement et le flux de travail du développement de l'instrument.
9 | seniority: 6
10 |
--------------------------------------------------------------------------------
/apps/outreach/src/content/team/vanessa-valiquette.yaml:
--------------------------------------------------------------------------------
1 | fullName: Vanessa Valiquette
2 | position:
3 | en: Program Coordinator
4 | fr: Coordinateur de programme
5 | image: '@/assets/headshots/vanessa-valiquette.webp'
6 | description:
7 | en: Vanessa is the program coordinator for the D3SM Initiative. She plays a key role in facilitating communication among project stakeholders and laying the groundwork for successful implementation across various settings.
8 | fr: Vanessa est la coordinatrice du programme pour l'initiative D3SM. Elle joue un rôle clé en facilitant la communication entre les parties prenantes du projet et en jetant les bases d'une mise en œuvre réussie dans différents contextes.
9 | seniority: 5
10 |
--------------------------------------------------------------------------------
/apps/outreach/src/content/team/weijie-tan.yaml:
--------------------------------------------------------------------------------
1 | fullName: Weijie Tan
2 | position:
3 | en: Junior Developer
4 | fr: Développeur junior
5 | image: '@/assets/headshots/weijie-tan.webp'
6 | description:
7 | en: Weijie is junior developer primarily working on another project. He has developed multiple instruments and contributed to shared libraries used by Open Data Capture.
8 | fr: Weijie est un développeur junior qui travaille principalement sur un autre projet. Il a développé de nombreux instruments et contribué aux bibliothèques partagées utilisées par Open Data Capture.
9 | seniority: 7
10 |
--------------------------------------------------------------------------------
/apps/outreach/src/content/testimonials/maxime-montembeault.yaml:
--------------------------------------------------------------------------------
1 | format: 'short'
2 | fullName: Maxime Montembeault
3 | suffix: PhD
4 | position:
5 | en: Neuropsychologist, Douglas Institute
6 | fr: Neuropsychologue, Institut Douglas
7 | image: '@/assets/headshots/maxime-montembeault.webp'
8 | quote:
9 | en: 'Open Data Capture is an ideal resource for my studies in the field of neuropsychology. The Douglas Neuroinformatics team is efficient, creative and responsive to my data collection needs and beyond!'
10 | fr: "Open Data Capture est une ressource idéale pour mes études dans le domaine de la neuropsychologie. L'équipe de Douglas Neuroinformatics Platform est efficace, créative et réactive à mes besoins de collecte de données et au-delà !"
11 |
--------------------------------------------------------------------------------
/apps/outreach/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
5 | interface Window {
6 | __TRANSLATIONS__?: import('./i18n').Translations;
7 | }
8 |
--------------------------------------------------------------------------------
/apps/outreach/src/i18n/translations/blog.json:
--------------------------------------------------------------------------------
1 | {
2 | "article": "Article",
3 | "caseStudy": {
4 | "en": "Case Study",
5 | "fr": "Étude de cas"
6 | },
7 | "heading": {
8 | "en": "Our Blog",
9 | "fr": "Notre blogue"
10 | },
11 | "subheading": {
12 | "en": "All the latest news on Open Data Capture and the open-source electronic data capture community",
13 | "fr": "Toutes les dernières nouvelles sur Open Data Capture et la communauté de la saisie électronique de données open-source"
14 | },
15 | "title": {
16 | "en": "Blog",
17 | "fr": "Blogue"
18 | },
19 | "video": {
20 | "en": "Video",
21 | "fr": "Vidéo"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/apps/outreach/src/i18n/translations/docs.json:
--------------------------------------------------------------------------------
1 | {
2 | "heading": "Documentation"
3 | }
4 |
--------------------------------------------------------------------------------
/apps/outreach/src/i18n/translations/faq.json:
--------------------------------------------------------------------------------
1 | {
2 | "heading": {
3 | "en": "Frequently Asked Questions",
4 | "fr": "Questions fréquemment posées"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/apps/outreach/src/i18n/translations/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Douglas Neuroinformatics Platform",
3 | "description": {
4 | "en": "An open-source, easy-to-use web application for electronic data capture",
5 | "fr": "Une application web conviviale à source ouverte pour la saisie électronique des données"
6 | },
7 | "keywords": {
8 | "en": "Data Collection, Data Capture, Electronic Data Capture, Clinical, Research",
9 | "fr": "Collecte de données, Saisie de données, Saisie électronique de données, Clinique, Recherche"
10 | },
11 | "title": "Open Data Capture"
12 | }
13 |
--------------------------------------------------------------------------------
/apps/outreach/src/i18n/translations/team.json:
--------------------------------------------------------------------------------
1 | {
2 | "heading": {
3 | "en": "Meet Our Team",
4 | "fr": "Rencontrez notre équipe"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/apps/outreach/src/modules/animate.ts:
--------------------------------------------------------------------------------
1 | export function animateUnorderedListEntry(id: string) {
2 | const ul = document.getElementById(id);
3 | if (!ul) {
4 | console.error(`Cannot find element with ID: ${id}`);
5 | return;
6 | }
7 | for (let i = 0; i < ul.children.length; i++) {
8 | const li = ul.children.item(i) as HTMLLIElement;
9 | setTimeout(() => {
10 | li.classList.replace('opacity-0', 'opacity-100');
11 | li.classList.replace('translate-y-6', 'translate-y-0');
12 | }, i * 100);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/apps/outreach/src/pages/404.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Page from '@/layouts/Page.astro';
3 | ---
4 |
5 |
6 |
7 |
8 | Please verify the URL or use the navigation menu above to find what you're looking for.
9 |
10 |
11 |
--------------------------------------------------------------------------------
/apps/outreach/src/pages/index.astro:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/apps/outreach/src/plugins/starlight-plugin-typedoc/markdown.ts:
--------------------------------------------------------------------------------
1 | export function addFrontmatter(content: string, frontmatter: { [key: string]: boolean | string }) {
2 | const entries = Object.entries(frontmatter).map(([key, value]) => `${key}: ${value}`);
3 |
4 | if (entries.length === 0) {
5 | return content;
6 | }
7 |
8 | return `---\n${entries.join('\n')}\n---\n\n${content}`;
9 | }
10 |
--------------------------------------------------------------------------------
/apps/outreach/src/scripts/theme-init.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * This script is injected inline to prevent a flash of content with the wrong theme, which
5 | * would happen if this was a module. Since this is run before all modules, it is safe to
6 | * assume that the document element will have the 'data-mode' attribute in all processed code.
7 | */
8 |
9 | let theme = window.localStorage.getItem('theme');
10 | if (!(theme === 'dark' || theme === 'light')) {
11 | theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
12 | }
13 |
14 | document.documentElement.setAttribute('data-mode', theme);
15 | window.localStorage.setItem('theme', theme);
16 |
--------------------------------------------------------------------------------
/apps/outreach/src/styles/common.css:
--------------------------------------------------------------------------------
1 | :root[data-mode='light'] {
2 | --screenshot-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
3 | }
4 |
5 | :root[data-mode='dark'] {
6 | --screenshot-shadow:
7 | rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px,
8 | rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px;
9 | }
10 |
--------------------------------------------------------------------------------
/apps/outreach/src/styles/main.css:
--------------------------------------------------------------------------------
1 | @plugin "@tailwindcss/typography";
2 | @import '@douglasneuroinformatics/libui/tailwind/globals.css';
3 | @import './common.css';
4 |
5 | @layer utilities {
6 | .container {
7 | @media (width >= theme(--breakpoint-xl)) {
8 | max-width: theme(--breakpoint-2xl);
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/apps/outreach/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx';
2 | import type { ClassValue } from 'clsx';
3 | import { twMerge } from 'tailwind-merge';
4 |
5 | export function cn(...inputs: ClassValue[]) {
6 | return twMerge(clsx(inputs));
7 | }
8 |
--------------------------------------------------------------------------------
/apps/outreach/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "baseUrl": "src",
5 | "paths": {
6 | "@/*": ["*"]
7 | }
8 | },
9 | "include": ["astro.config.ts", "src/**/*"]
10 | }
11 |
--------------------------------------------------------------------------------
/apps/playground/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Open Data Capture - Instrument Playground
8 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/apps/playground/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/playground/public/favicon.ico
--------------------------------------------------------------------------------
/apps/playground/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { NotificationHub } from '@douglasneuroinformatics/libui/components';
4 | import { ErrorPage, LoadingPage } from '@opendatacapture/react-core';
5 | import { ErrorBoundary } from 'react-error-boundary';
6 |
7 | const IndexPage = React.lazy(() => import('./pages/IndexPage'));
8 |
9 | export const App = () => {
10 | return (
11 |
14 | }
15 | >
16 |
17 |
18 |
19 |
20 |
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Editor/Editor.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { Editor } from './Editor';
4 |
5 | type Story = StoryObj;
6 |
7 | export default { component: Editor } satisfies Meta;
8 |
9 | export const Default: Story = {
10 | decorators: [
11 | (Story) => (
12 |
13 |
14 |
15 | )
16 | ],
17 | parameters: {
18 | layout: 'fullscreen'
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Editor/EditorButton.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Tooltip } from '@douglasneuroinformatics/libui/components';
4 |
5 | export type EditorButtonProps = {
6 | icon: React.ReactNode;
7 | onClick: () => void;
8 | tip: string;
9 | };
10 |
11 | export const EditorButton = ({ icon, onClick, tip }: EditorButtonProps) => (
12 |
13 |
20 | {icon}
21 |
22 |
23 | {tip}
24 |
25 |
26 | );
27 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Editor/EditorInput.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { cn } from '@douglasneuroinformatics/libui/utils';
4 |
5 | export const EditorInput = React.forwardRef<
6 | HTMLInputElement,
7 | React.DetailedHTMLProps, HTMLInputElement>
8 | >(function EditorInput({ className, ...props }, ref) {
9 | return (
10 |
18 | );
19 | });
20 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Editor/EditorPanePlaceholder.tsx:
--------------------------------------------------------------------------------
1 | import { Heading } from '@douglasneuroinformatics/libui/components';
2 |
3 | export type EditorPanePlaceholderProps = {
4 | children: string;
5 | };
6 |
7 | export const EditorPanePlaceholder = ({ children }: EditorPanePlaceholderProps) => {
8 | return (
9 |
10 |
11 | {children}
12 |
13 |
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Editor/VimStatusBar.tsx:
--------------------------------------------------------------------------------
1 | export const VimStatusBar = () => {
2 | return (
3 |
4 | --NORMAL--
5 |
6 |
7 |
8 |
9 | );
10 | };
11 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Editor/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Editor';
2 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Editor/types.ts:
--------------------------------------------------------------------------------
1 | export type MonacoType = typeof import('monaco-editor');
2 |
3 | export type MonacoEditorType = import('monaco-editor').editor.IStandaloneCodeEditor;
4 |
5 | export type MonacoModelType = import('monaco-editor').editor.IModel;
6 |
7 | export type CompletionItemProvider = import('monaco-editor').languages.CompletionItemProvider;
8 |
--------------------------------------------------------------------------------
/apps/playground/src/components/FileUploadDialog/index.ts:
--------------------------------------------------------------------------------
1 | export * from './FileUploadDialog';
2 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Header/ActionButton/ActionButton.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 | import { FileQuestionIcon } from 'lucide-react';
3 |
4 | import { ActionButton } from './ActionButton';
5 |
6 | type Story = StoryObj;
7 |
8 | export default { component: ActionButton } as Meta;
9 |
10 | export const Default: Story = {
11 | args: {
12 | icon: ,
13 | tooltip: 'Help'
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Header/ActionButton/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ActionButton';
2 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Header/ActionsDropdown/ActionsDropdown.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { ActionsDropdown } from './ActionsDropdown';
4 |
5 | type Story = StoryObj;
6 |
7 | export default { component: ActionsDropdown } as Meta;
8 |
9 | export const Default: Story = {};
10 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Header/ActionsDropdown/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ActionsDropdown';
2 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Header/CloneButton/CloneButton.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { CloneButton } from './CloneButton';
4 |
5 | type Story = StoryObj;
6 |
7 | export default { component: CloneButton } as Meta;
8 |
9 | export const Default: Story = {};
10 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Header/CloneButton/index.ts:
--------------------------------------------------------------------------------
1 | export * from './CloneButton';
2 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Header/DownloadButton/DownloadButton.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { DownloadButton } from './DownloadButton';
4 |
5 | type Story = StoryObj;
6 |
7 | export default { component: DownloadButton } as Meta;
8 |
9 | export const Default: Story = {};
10 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Header/DownloadButton/index.ts:
--------------------------------------------------------------------------------
1 | export * from './DownloadButton';
2 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Header/Header.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { Header as HeaderComponent } from './Header';
4 |
5 | type Story = StoryObj;
6 |
7 | export default { component: HeaderComponent } as Meta;
8 |
9 | export const Header: Story = {};
10 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Header/InstrumentSelector/InstrumentSelector.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { InstrumentSelector } from './InstrumentSelector';
4 |
5 | type Story = StoryObj;
6 |
7 | export default { component: InstrumentSelector } as Meta;
8 |
9 | export const Default: Story = {};
10 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Header/InstrumentSelector/index.ts:
--------------------------------------------------------------------------------
1 | export * from './InstrumentSelector';
2 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Header/RefreshButton/RefreshButton.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { RefreshButton } from './RefreshButton';
4 |
5 | type Story = StoryObj;
6 |
7 | export default { component: RefreshButton } as Meta;
8 |
9 | export const Default: Story = {};
10 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Header/RefreshButton/RefreshButton.tsx:
--------------------------------------------------------------------------------
1 | import { RefreshCwIcon } from 'lucide-react';
2 |
3 | import { useAppStore } from '@/store';
4 |
5 | import { ActionButton } from '../ActionButton';
6 |
7 | export const RefreshButton = () => {
8 | const onClick = useAppStore((store) => store.viewer.forceRefresh);
9 | return } tooltip="Refresh" onClick={onClick} />;
10 | };
11 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Header/RefreshButton/index.ts:
--------------------------------------------------------------------------------
1 | export * from './RefreshButton';
2 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Header/SaveButton/SaveButton.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { SaveButton } from './SaveButton';
4 |
5 | type Story = StoryObj;
6 |
7 | export default { component: SaveButton } as Meta;
8 |
9 | export const Default: Story = {};
10 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Header/SaveButton/index.ts:
--------------------------------------------------------------------------------
1 | export * from './SaveButton';
2 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Header/ShareButton/ShareButton.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { ShareButton } from './ShareButton';
4 |
5 | type Story = StoryObj;
6 |
7 | export default { component: ShareButton } as Meta;
8 |
9 | export const Default: Story = {};
10 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Header/ShareButton/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ShareButton';
2 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Header/UploadButton/index.ts:
--------------------------------------------------------------------------------
1 | export * from './UploadButton';
2 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Header/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Header';
2 |
--------------------------------------------------------------------------------
/apps/playground/src/components/MainContent/MainContent.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { MainContent } from './MainContent';
4 |
5 | type Story = StoryObj;
6 |
7 | export default { component: MainContent } as Meta;
8 |
9 | export const Default: Story = {
10 | decorators: [
11 | (Story) => {
12 | return (
13 |
14 |
15 |
16 | );
17 | }
18 | ],
19 | parameters: {
20 | layout: 'fullscreen'
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/apps/playground/src/components/MainContent/index.ts:
--------------------------------------------------------------------------------
1 | export * from './MainContent';
2 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Viewer/CompileErrorFallback.tsx:
--------------------------------------------------------------------------------
1 | import { ViewerErrorFallback } from './ViewerErrorFallback';
2 |
3 | import type { ViewerErrorFallbackProps } from './ViewerErrorFallback';
4 |
5 | export const CompileErrorFallback = (props: Omit) => {
6 | return ;
7 | };
8 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Viewer/Viewer.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { Viewer as ViewerComponent } from './Viewer';
4 |
5 | type Story = StoryObj;
6 |
7 | export default { component: ViewerComponent } as Meta;
8 |
9 | export const Viewer: Story = {};
10 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Viewer/ViewerErrorFallback/ErrorMessage.tsx:
--------------------------------------------------------------------------------
1 | export type ErrorMessageProps = {
2 | error: Error;
3 | };
4 |
5 | export const ErrorMessage = ({ error }: ErrorMessageProps) => {
6 | return (
7 |
8 | {error.name}:
9 | {error.message}
10 |
11 | );
12 | };
13 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Viewer/ViewerErrorFallback/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ViewerErrorFallback';
2 |
--------------------------------------------------------------------------------
/apps/playground/src/components/Viewer/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Viewer';
2 |
--------------------------------------------------------------------------------
/apps/playground/src/hooks/useFilesRef.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 |
3 | import { useAppStore } from '@/store';
4 |
5 | export function useFilesRef() {
6 | const ref = useRef(useAppStore.getState().files);
7 | useEffect(() => {
8 | useAppStore.subscribe(
9 | (store) => store.files,
10 | (files) => {
11 | ref.current = files;
12 | }
13 | );
14 | }, []);
15 | return ref;
16 | }
17 |
--------------------------------------------------------------------------------
/apps/playground/src/instruments/examples/form/Multilingual-Form-With-Dynamic-Field/translations.ts:
--------------------------------------------------------------------------------
1 | export const translations = {
2 | requiredField: {
3 | en: 'This field is required',
4 | fr: 'Ce champ est obligatoire'
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/apps/playground/src/instruments/examples/interactive/Interactive-With-CSS/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/playground/src/instruments/examples/interactive/Interactive-With-CSS/logo.png
--------------------------------------------------------------------------------
/apps/playground/src/instruments/examples/interactive/Interactive-With-Embedded-Media/cat-video.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/playground/src/instruments/examples/interactive/Interactive-With-Embedded-Media/cat-video.mp4
--------------------------------------------------------------------------------
/apps/playground/src/instruments/examples/interactive/Interactive-With-Embedded-Media/cow-moo.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/playground/src/instruments/examples/interactive/Interactive-With-Embedded-Media/cow-moo.mp3
--------------------------------------------------------------------------------
/apps/playground/src/instruments/examples/interactive/Interactive-With-Embedded-Media/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0px;
3 | padding: 0px;
4 | }
5 |
--------------------------------------------------------------------------------
/apps/playground/src/instruments/examples/interactive/Interactive-With-JSPsych/blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/playground/src/instruments/examples/interactive/Interactive-With-JSPsych/blue.png
--------------------------------------------------------------------------------
/apps/playground/src/instruments/examples/interactive/Interactive-With-JSPsych/orange.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/playground/src/instruments/examples/interactive/Interactive-With-JSPsych/orange.png
--------------------------------------------------------------------------------
/apps/playground/src/instruments/examples/interactive/Interactive-With-Legacy-Script/legacy.d.ts:
--------------------------------------------------------------------------------
1 | declare let message: 'hello';
2 |
--------------------------------------------------------------------------------
/apps/playground/src/instruments/examples/interactive/Interactive-With-Legacy-Script/legacy.js:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 |
3 | (function () {
4 | if (typeof this === 'undefined') {
5 | throw new Error('This script does not work in strict mode!');
6 | }
7 | this.message = 'Hello';
8 | })();
9 |
--------------------------------------------------------------------------------
/apps/playground/src/instruments/examples/interactive/Interactive-With-Vanilla/styles.css:
--------------------------------------------------------------------------------
1 | * {
2 | padding: 0;
3 | margin: 0;
4 | }
5 |
6 | canvas {
7 | background: #eee;
8 | display: block;
9 | margin: 0 auto;
10 | }
11 |
--------------------------------------------------------------------------------
/apps/playground/src/instruments/examples/interactive/Multilingual-Interactive/translator.ts:
--------------------------------------------------------------------------------
1 | import { Translator } from '/runtime/v1/@opendatacapture/runtime-core';
2 |
3 | export const translator = new Translator({
4 | translations: {
5 | changeLanguage: {
6 | en: 'Change Language',
7 | fr: 'Changer de langue'
8 | },
9 | greetings: {
10 | hello: {
11 | en: 'Hello',
12 | fr: 'Bonjour'
13 | }
14 | },
15 | submit: {
16 | en: 'Submit',
17 | fr: 'Soumettre'
18 | }
19 | }
20 | });
21 |
--------------------------------------------------------------------------------
/apps/playground/src/instruments/templates/form/Unilingual-Form/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable perfectionist/sort-objects */
2 |
3 | import { defineInstrument } from '/runtime/v1/@opendatacapture/runtime-core';
4 | import { z } from '/runtime/v1/zod@3.23.x';
5 |
6 | export default defineInstrument({
7 | kind: 'FORM',
8 | language: 'en',
9 | tags: [''],
10 | internal: {
11 | edition: 1,
12 | name: ''
13 | },
14 | clientDetails: {
15 | estimatedDuration: 1,
16 | instructions: ['']
17 | },
18 | content: {},
19 | details: {
20 | description: '',
21 | license: 'Apache-2.0',
22 | title: ''
23 | },
24 | measures: {},
25 | validationSchema: z.object({})
26 | });
27 |
--------------------------------------------------------------------------------
/apps/playground/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 |
4 | import { i18n } from '@douglasneuroinformatics/libui/i18n';
5 |
6 | import { App } from './App';
7 |
8 | import '@opendatacapture/react-core/globals.css';
9 |
10 | const root = document.getElementById('root')!;
11 |
12 | i18n.init();
13 |
14 | ReactDOM.createRoot(root).render(
15 |
16 |
17 |
18 | );
19 |
--------------------------------------------------------------------------------
/apps/playground/src/models/editor-file.model.ts:
--------------------------------------------------------------------------------
1 | import type { BundlerInput } from '@opendatacapture/instrument-bundler';
2 | import { z } from 'zod';
3 |
4 | export const $EditorFile = z.object({
5 | content: z.string(),
6 | name: z.string()
7 | }) satisfies z.ZodType;
8 | export type EditorFile = z.infer;
9 |
--------------------------------------------------------------------------------
/apps/playground/src/models/instrument-repository.model.ts:
--------------------------------------------------------------------------------
1 | import { $InstrumentKind } from '@opendatacapture/schemas/instrument';
2 | import { z } from 'zod';
3 |
4 | import { $EditorFile } from './editor-file.model';
5 |
6 | export const $InstrumentCategory = z.enum(['Examples', 'Saved', 'Templates']);
7 |
8 | export type InstrumentCategory = z.infer;
9 |
10 | export const $InstrumentRepository = z.object({
11 | category: $InstrumentCategory,
12 | files: z.array($EditorFile),
13 | id: z.string(),
14 | kind: $InstrumentKind.nullable(),
15 | label: z.string()
16 | });
17 | export type InstrumentRepository = z.infer;
18 |
--------------------------------------------------------------------------------
/apps/playground/src/models/settings.model.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod';
2 |
3 | export const $Settings = z.object({
4 | apiBaseUrl: z.string().optional(),
5 | enableVimMode: z.boolean().optional(),
6 | refreshInterval: z.number().positive().int()
7 | });
8 |
9 | export type Settings = z.infer;
10 |
--------------------------------------------------------------------------------
/apps/playground/src/store/slices/settings.slice.ts:
--------------------------------------------------------------------------------
1 | import { merge } from 'lodash-es';
2 |
3 | import type { Settings } from '@/models/settings.model';
4 |
5 | import type { SettingsSlice, SliceCreator } from '../types';
6 |
7 | const defaultSettings: Settings = {
8 | enableVimMode: false,
9 | refreshInterval: 2000
10 | };
11 |
12 | export const createSettingsSlice: SliceCreator = (set) => ({
13 | resetSettings: () => {
14 | set((state) => {
15 | state.settings = defaultSettings;
16 | });
17 | },
18 | settings: defaultSettings,
19 | updateSettings: (updatedSettings) => {
20 | set((state) => {
21 | merge(state.settings, updatedSettings);
22 | });
23 | }
24 | });
25 |
--------------------------------------------------------------------------------
/apps/playground/src/store/slices/transpiler.slice.ts:
--------------------------------------------------------------------------------
1 | import type { SliceCreator, TranspilerInitialState, TranspilerSlice } from '../types';
2 |
3 | const initialTranspilerState: TranspilerInitialState = {
4 | status: 'initial'
5 | };
6 |
7 | export const createTranspilerSlice: SliceCreator = (set) => ({
8 | setTranspilerState: (updatedTranspilerState) => {
9 | set((state) => {
10 | state.transpilerState = updatedTranspilerState;
11 | });
12 | },
13 | transpilerState: initialTranspilerState
14 | });
15 |
--------------------------------------------------------------------------------
/apps/playground/src/store/slices/viewer.slice.ts:
--------------------------------------------------------------------------------
1 | import type { SliceCreator, ViewerSlice } from '../types';
2 |
3 | export const createViewerSlice: SliceCreator = (set) => ({
4 | viewer: {
5 | forceRefresh: () => {
6 | set((state) => {
7 | state.viewer.key += 1;
8 | });
9 | },
10 | key: 0
11 | }
12 | });
13 |
--------------------------------------------------------------------------------
/apps/playground/src/typings/monaco-editor.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'monaco-editor/esm/vs/language/typescript/tsMode' {
2 | export class SuggestAdapter {
3 | triggerCharacters?: string[];
4 | constructor(worker: any): void;
5 | provideCompletionItems(...args: any[]): any;
6 | resolveCompletionItem?(...args: any[]): any;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/apps/playground/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | const __GITHUB_REPO_URL__: string;
2 |
--------------------------------------------------------------------------------
/apps/playground/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "allowImportingTsExtensions": true,
5 | "baseUrl": ".",
6 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
7 | "paths": {
8 | "@/*": ["./src/*"],
9 | "/runtime/v1/*": ["../../runtime/v1/dist/*"]
10 | },
11 | "types": ["@opendatacapture/runtime-v1/env", "vite/client"]
12 | },
13 | "include": ["src/**/*", "vite.config.ts"]
14 | }
15 |
--------------------------------------------------------------------------------
/apps/web/.env.public:
--------------------------------------------------------------------------------
1 | API_BASE_URL=
2 | CONTACT_EMAIL=
3 | DOCS_URL=
4 | GITHUB_REPO_URL=
5 | LICENSE_URL=
6 | GATEWAY_ENABLED=
7 | PLAUSIBLE_BASE_URL=
8 | PLAUSIBLE_WEB_DATA_DOMAIN=
9 |
--------------------------------------------------------------------------------
/apps/web/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouglasNeuroInformatics/OpenDataCapture/23db8cd6492ad3cb85544d7a5ca11d09675f698c/apps/web/public/favicon.ico
--------------------------------------------------------------------------------
/apps/web/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow: /
--------------------------------------------------------------------------------
/apps/web/src/components/Footer/Footer.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 | import { MemoryRouter } from 'react-router-dom';
3 |
4 | import { Footer } from './Footer';
5 |
6 | type Story = StoryObj;
7 |
8 | export default { component: Footer } as Meta;
9 |
10 | export const Default: Story = {
11 | decorators: [
12 | (Story) => {
13 | return (
14 |
15 |
16 |
17 | );
18 | }
19 | ]
20 | };
21 |
--------------------------------------------------------------------------------
/apps/web/src/components/Footer/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Footer';
2 |
--------------------------------------------------------------------------------
/apps/web/src/components/IdentificationForm/IdentificationForm.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { IdentificationForm } from './IdentificationForm';
4 |
5 | type Story = StoryObj;
6 |
7 | export default { component: IdentificationForm } as Meta;
8 |
9 | export const Default: Story = {
10 | args: {
11 | onSubmit(data) {
12 | alert(JSON.stringify(data, null, 2));
13 | }
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/apps/web/src/components/IdentificationForm/index.ts:
--------------------------------------------------------------------------------
1 | export * from './IdentificationForm';
2 |
--------------------------------------------------------------------------------
/apps/web/src/components/Layout/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Layout';
2 |
--------------------------------------------------------------------------------
/apps/web/src/components/LoadingFallback/LoadingFallback.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { LoadingFallback } from './LoadingFallback';
4 |
5 | type Story = StoryObj;
6 |
7 | export default { component: LoadingFallback } as Meta;
8 |
9 | export const Default: Story = {
10 | decorators: [
11 | (Story) => {
12 | return (
13 |
14 |
15 |
16 | );
17 | }
18 | ],
19 | parameters: {
20 | layout: 'fullscreen'
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/apps/web/src/components/LoadingFallback/LoadingFallback.tsx:
--------------------------------------------------------------------------------
1 | import { Spinner } from '@douglasneuroinformatics/libui/components';
2 |
3 | export const LoadingFallback = () => (
4 |
5 |
6 |
7 | );
8 |
--------------------------------------------------------------------------------
/apps/web/src/components/LoadingFallback/index.ts:
--------------------------------------------------------------------------------
1 | export * from './LoadingFallback';
2 |
--------------------------------------------------------------------------------
/apps/web/src/components/NavButton/index.ts:
--------------------------------------------------------------------------------
1 | export * from './NavButton';
2 |
--------------------------------------------------------------------------------
/apps/web/src/components/Navbar/Navbar.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 | import { MemoryRouter } from 'react-router-dom';
3 |
4 | import { Navbar } from './Navbar';
5 |
6 | type Story = StoryObj;
7 |
8 | export default { component: Navbar } as Meta;
9 |
10 | export const Default: Story = {
11 | decorators: [
12 | (Story) => {
13 | return (
14 |
15 |
16 |
17 | );
18 | }
19 | ],
20 | parameters: {
21 | layout: 'fullscreen'
22 | }
23 | };
24 |
--------------------------------------------------------------------------------
/apps/web/src/components/Navbar/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Navbar';
2 |
--------------------------------------------------------------------------------
/apps/web/src/components/PageHeader/PageHeader.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Heading } from '@douglasneuroinformatics/libui/components';
2 | import type { Meta, StoryObj } from '@storybook/react-vite';
3 |
4 | import { PageHeader } from './PageHeader';
5 | type Story = StoryObj;
6 |
7 | export default { component: PageHeader } as Meta;
8 |
9 | export const Default: Story = {
10 | args: {
11 | children: Heading
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/apps/web/src/components/PageHeader/PageHeader.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Separator } from '@douglasneuroinformatics/libui/components';
4 | import { cn } from '@douglasneuroinformatics/libui/utils';
5 |
6 | type PageHeaderProps = {
7 | children: React.ReactNode;
8 | className?: string;
9 | };
10 |
11 | export const PageHeader = ({ children, className }: PageHeaderProps) => {
12 | return (
13 |
14 | {children}
15 |
16 |
17 | );
18 | };
19 |
--------------------------------------------------------------------------------
/apps/web/src/components/PageHeader/index.ts:
--------------------------------------------------------------------------------
1 | export * from './PageHeader';
2 |
--------------------------------------------------------------------------------
/apps/web/src/components/Sidebar/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Sidebar';
2 |
--------------------------------------------------------------------------------
/apps/web/src/components/UserDropup/UserDropup.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 | import { MemoryRouter } from 'react-router-dom';
3 |
4 | import { UserDropup } from './UserDropup';
5 |
6 | type Story = StoryObj;
7 |
8 | export default { component: UserDropup } as Meta;
9 |
10 | export const Default: Story = {
11 | decorators: [
12 | (Story) => (
13 |
14 |
15 |
16 | )
17 | ],
18 | parameters: {
19 | layout: 'centered'
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/apps/web/src/components/UserDropup/index.ts:
--------------------------------------------------------------------------------
1 | export * from './UserDropup';
2 |
--------------------------------------------------------------------------------
/apps/web/src/components/UserIcon/UserIcon.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { UserIcon } from './UserIcon';
4 |
5 | type Story = StoryObj;
6 |
7 | export default { component: UserIcon } as Meta;
8 |
9 | export const Default: Story = {};
10 |
--------------------------------------------------------------------------------
/apps/web/src/components/UserIcon/index.ts:
--------------------------------------------------------------------------------
1 | export * from './UserIcon';
2 |
--------------------------------------------------------------------------------
/apps/web/src/features/about/components/InfoBlock.tsx:
--------------------------------------------------------------------------------
1 | import { TimeValue } from './TimeValue';
2 |
3 | type InfoBlockProps = {
4 | items: {
5 | [key: string]: string;
6 | };
7 | label: string;
8 | };
9 |
10 | export const InfoBlock = ({ items, label }: InfoBlockProps) => {
11 | return (
12 |
13 |
{label}
14 |
15 | {Object.entries(items).map(([key, value]) => (
16 |
17 | {key}:
18 | {value.startsWith('Uptime=') ? : {value} }
19 |
20 | ))}
21 |
22 |
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/apps/web/src/features/about/hooks/useGatewayHealthcheckQuery.ts:
--------------------------------------------------------------------------------
1 | import { $GatewayHealthcheckResult } from '@opendatacapture/schemas/gateway';
2 | import { useQuery } from '@tanstack/react-query';
3 | import axios from 'axios';
4 |
5 | export function useGatewayHealthcheckQuery({ enabled }: { enabled: boolean }) {
6 | return useQuery({
7 | enabled,
8 | queryFn: async () => {
9 | const response = await axios.get('/v1/gateway/healthcheck', {});
10 | return $GatewayHealthcheckResult.parse(response.data);
11 | },
12 | queryKey: ['gateway-healthcheck']
13 | });
14 | }
15 |
--------------------------------------------------------------------------------
/apps/web/src/features/about/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable perfectionist/sort-objects */
2 |
3 | import type { RouteObject } from 'react-router-dom';
4 |
5 | import { AboutPage } from './pages/AboutPage';
6 |
7 | export const aboutRoute: RouteObject = {
8 | path: 'about',
9 | element:
10 | };
11 |
--------------------------------------------------------------------------------
/apps/web/src/features/admin/hooks/useCreateUserMutation.ts:
--------------------------------------------------------------------------------
1 | import { useNotificationsStore } from '@douglasneuroinformatics/libui/hooks';
2 | import type { CreateUserData } from '@opendatacapture/schemas/user';
3 | import { useMutation, useQueryClient } from '@tanstack/react-query';
4 | import axios from 'axios';
5 |
6 | export function useCreateUserMutation() {
7 | const queryClient = useQueryClient();
8 | const addNotification = useNotificationsStore((store) => store.addNotification);
9 | return useMutation({
10 | mutationFn: ({ data }: { data: CreateUserData }) => axios.post('/v1/users', data),
11 | onSuccess() {
12 | addNotification({ type: 'success' });
13 | void queryClient.invalidateQueries({ queryKey: ['users'] });
14 | }
15 | });
16 | }
17 |
--------------------------------------------------------------------------------
/apps/web/src/features/admin/hooks/useDeleteGroupMutation.ts:
--------------------------------------------------------------------------------
1 | import { useNotificationsStore } from '@douglasneuroinformatics/libui/hooks';
2 | import { useMutation, useQueryClient } from '@tanstack/react-query';
3 | import axios from 'axios';
4 |
5 | export function useDeleteGroupMutation() {
6 | const queryClient = useQueryClient();
7 | const addNotification = useNotificationsStore((store) => store.addNotification);
8 | return useMutation({
9 | mutationFn: ({ id }: { id: string }) => axios.delete(`/v1/groups/${id}`),
10 | onSuccess() {
11 | addNotification({ type: 'success' });
12 | void queryClient.invalidateQueries({ queryKey: ['groups'] });
13 | }
14 | });
15 | }
16 |
--------------------------------------------------------------------------------
/apps/web/src/features/admin/hooks/useDeleteUserMutation.ts:
--------------------------------------------------------------------------------
1 | import { useNotificationsStore } from '@douglasneuroinformatics/libui/hooks';
2 | import { useMutation, useQueryClient } from '@tanstack/react-query';
3 | import axios from 'axios';
4 |
5 | export function useDeleteUserMutation() {
6 | const queryClient = useQueryClient();
7 | const addNotification = useNotificationsStore((store) => store.addNotification);
8 | return useMutation({
9 | mutationFn: ({ id }: { id: string }) => axios.delete(`/v1/users/${id}`),
10 | onSuccess() {
11 | addNotification({ type: 'success' });
12 | void queryClient.invalidateQueries({ queryKey: ['users'] });
13 | }
14 | });
15 | }
16 |
--------------------------------------------------------------------------------
/apps/web/src/features/admin/hooks/useGroupsQuery.ts:
--------------------------------------------------------------------------------
1 | import { $Group } from '@opendatacapture/schemas/group';
2 | import { useQuery } from '@tanstack/react-query';
3 | import axios from 'axios';
4 |
5 | export function useGroupsQuery() {
6 | return useQuery({
7 | queryFn: async () => {
8 | const response = await axios.get('/v1/groups');
9 | return $Group.array().parse(response.data);
10 | },
11 | queryKey: ['groups']
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/apps/web/src/features/admin/hooks/useUsersQuery.ts:
--------------------------------------------------------------------------------
1 | import { $User } from '@opendatacapture/schemas/user';
2 | import { useQuery } from '@tanstack/react-query';
3 | import axios from 'axios';
4 |
5 | export function useUsersQuery() {
6 | return useQuery({
7 | queryFn: async () => {
8 | const response = await axios.get('/v1/users');
9 | return $User.array().parse(response.data);
10 | },
11 | queryKey: ['users']
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/apps/web/src/features/auth/components/DemoBanner/DemoBanner.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { DemoBanner } from './DemoBanner';
4 |
5 | type Story = StoryObj;
6 |
7 | export default { component: DemoBanner } as Meta;
8 |
9 | export const Default: Story = {
10 | args: {
11 | onLogin(credentials) {
12 | alert(JSON.stringify(credentials, null, 2));
13 | }
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/apps/web/src/features/auth/components/DemoBanner/index.ts:
--------------------------------------------------------------------------------
1 | export * from './DemoBanner';
2 |
--------------------------------------------------------------------------------
/apps/web/src/features/auth/components/LoginForm/LoginForm.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { LoginForm } from './LoginForm';
4 |
5 | type Story = StoryObj;
6 |
7 | export default { component: LoginForm } as Meta;
8 |
9 | export const Default: Story = {};
10 |
--------------------------------------------------------------------------------
/apps/web/src/features/auth/components/LoginForm/index.ts:
--------------------------------------------------------------------------------
1 | export * from './LoginForm';
2 |
--------------------------------------------------------------------------------
/apps/web/src/features/auth/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable perfectionist/sort-objects */
2 |
3 | import { Navigate } from 'react-router-dom';
4 | import type { RouteObject } from 'react-router-dom';
5 |
6 | import { LoginPage } from './pages/LoginPage';
7 |
8 | export const authRoutes: RouteObject = {
9 | path: 'auth',
10 | children: [
11 | {
12 | path: 'login',
13 | element:
14 | },
15 | {
16 | index: true,
17 | path: '*',
18 | element:
19 | }
20 | ]
21 | };
22 |
--------------------------------------------------------------------------------
/apps/web/src/features/contact/components/ContactForm/ContactForm.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { ContactForm } from './ContactForm';
4 |
5 | type Story = StoryObj;
6 |
7 | export default { component: ContactForm } as Meta;
8 |
9 | export const Default: Story = {};
10 |
--------------------------------------------------------------------------------
/apps/web/src/features/contact/components/ContactForm/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ContactForm';
2 |
--------------------------------------------------------------------------------
/apps/web/src/features/contact/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable perfectionist/sort-objects */
2 |
3 | import type { RouteObject } from 'react-router-dom';
4 |
5 | import { ContactPage } from './pages/ContactPage';
6 |
7 | export const contactRoute: RouteObject = {
8 | path: 'contact',
9 | element:
10 | };
11 |
--------------------------------------------------------------------------------
/apps/web/src/features/dashboard/hooks/useSummaryQuery.ts:
--------------------------------------------------------------------------------
1 | import { $Summary } from '@opendatacapture/schemas/summary';
2 | import { keepPreviousData, useQuery } from '@tanstack/react-query';
3 | import axios from 'axios';
4 |
5 | type UseSummaryQueryOptions = {
6 | params?: {
7 | groupId?: string;
8 | };
9 | };
10 |
11 | export const useSummaryQuery = ({ params }: UseSummaryQueryOptions = { params: {} }) => {
12 | return useQuery({
13 | placeholderData: keepPreviousData,
14 | queryFn: async () => {
15 | const response = await axios.get('/v1/summary', {
16 | params
17 | });
18 | return $Summary.parse(response.data);
19 | },
20 | queryKey: ['summary', params?.groupId]
21 | });
22 | };
23 |
--------------------------------------------------------------------------------
/apps/web/src/features/dashboard/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable perfectionist/sort-objects */
2 |
3 | import type { RouteObject } from 'react-router-dom';
4 |
5 | import { DashboardPage } from './pages/DashboardPage';
6 |
7 | export const dashboardRoute: RouteObject = {
8 | path: 'dashboard',
9 | element:
10 | };
11 |
--------------------------------------------------------------------------------
/apps/web/src/features/datahub/components/TabLink.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx';
2 | import { NavLink } from 'react-router-dom';
3 |
4 | export const TabLink = ({ dataCy, label, pathname }: { dataCy?: string; label: string; pathname: string }) => (
5 |
8 | clsx(
9 | 'grow border-b px-1 py-3 text-center font-medium',
10 | isActive ? 'border-sky-500 text-slate-900 dark:text-slate-100' : 'border-slate-300 dark:border-slate-700'
11 | )
12 | }
13 | data-cy={dataCy}
14 | data-nav-url={pathname}
15 | data-spotlight-type="tab-link"
16 | to={pathname}
17 | >
18 | {label}
19 |
20 | );
21 |
--------------------------------------------------------------------------------
/apps/web/src/features/datahub/hooks/useSubjectsQuery.ts:
--------------------------------------------------------------------------------
1 | import { $Subject } from '@opendatacapture/schemas/subject';
2 | import { useQuery } from '@tanstack/react-query';
3 | import axios from 'axios';
4 |
5 | type UseSubjectsQueryOptions = {
6 | params: {
7 | groupId?: string;
8 | };
9 | };
10 |
11 | export function useSubjectsQuery({ params }: UseSubjectsQueryOptions) {
12 | return useQuery({
13 | queryFn: async () => {
14 | const response = await axios.get('/v1/subjects', { params });
15 | return $Subject.array().parse(response.data);
16 | },
17 | queryKey: ['subjects', params.groupId]
18 | });
19 | }
20 |
--------------------------------------------------------------------------------
/apps/web/src/features/group/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable perfectionist/sort-objects */
2 |
3 | import type { RouteObject } from 'react-router-dom';
4 |
5 | import { ManageGroupPage } from './pages/ManageGroupPage';
6 |
7 | export const groupRoute: RouteObject = {
8 | path: 'group',
9 | children: [
10 | {
11 | path: 'manage',
12 | element:
13 | }
14 | ]
15 | };
16 |
--------------------------------------------------------------------------------
/apps/web/src/features/instruments/components/InstrumentCard/InstrumentCard.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { InstrumentCard } from './InstrumentCard';
4 |
5 | type Story = StoryObj;
6 |
7 | import { unilingualFormInstrument } from '@opendatacapture/instrument-stubs/forms';
8 |
9 | export default { component: InstrumentCard } as Meta;
10 |
11 | export const Default: Story = {
12 | args: {
13 | instrument: { ...unilingualFormInstrument.instance, supportedLanguages: ['en', 'fr'] },
14 | onClick: () => alert('Click!')
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/apps/web/src/features/instruments/components/InstrumentCard/index.ts:
--------------------------------------------------------------------------------
1 | export * from './InstrumentCard';
2 |
--------------------------------------------------------------------------------
/apps/web/src/features/instruments/components/InstrumentShowcase/index.ts:
--------------------------------------------------------------------------------
1 | export * from './InstrumentShowcase';
2 |
--------------------------------------------------------------------------------
/apps/web/src/features/instruments/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable perfectionist/sort-objects */
2 |
3 | import type { RouteObject } from 'react-router-dom';
4 |
5 | import { AccessibleInstrumentsPage } from './pages/AccessibleInstrumentsPage';
6 | import { InstrumentRenderPage } from './pages/InstrumentRenderPage';
7 |
8 | export const instrumentsRoute: RouteObject = {
9 | path: 'instruments',
10 | children: [
11 | {
12 | path: 'accessible-instruments',
13 | element:
14 | },
15 | {
16 | path: 'render/:id',
17 | element:
18 | }
19 | ]
20 | };
21 |
--------------------------------------------------------------------------------
/apps/web/src/features/session/components/StartSessionForm/StartSessionForm.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { StartSessionForm } from './StartSessionForm';
4 |
5 | type Story = StoryObj;
6 |
7 | export default { component: StartSessionForm } as Meta;
8 |
9 | export const Default: Story = {
10 | args: {
11 | onSubmit(data) {
12 | alert(JSON.stringify(data, null, 2));
13 | }
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/apps/web/src/features/session/components/StartSessionForm/index.ts:
--------------------------------------------------------------------------------
1 | export * from './StartSessionForm';
2 |
--------------------------------------------------------------------------------
/apps/web/src/features/session/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable perfectionist/sort-objects */
2 |
3 | import type { RouteObject } from 'react-router-dom';
4 |
5 | import { StartSessionPage } from './pages/StartSessionPage';
6 |
7 | export const sessionRoute: RouteObject = {
8 | path: 'session',
9 | children: [
10 | {
11 | path: 'start-session',
12 | element:
13 | }
14 | ]
15 | };
16 |
--------------------------------------------------------------------------------
/apps/web/src/features/setup/hooks/useCreateSetupState.ts:
--------------------------------------------------------------------------------
1 | import { useNotificationsStore } from '@douglasneuroinformatics/libui/hooks';
2 | import type { InitAppOptions } from '@opendatacapture/schemas/setup';
3 | import { useMutation, useQueryClient } from '@tanstack/react-query';
4 | import axios from 'axios';
5 |
6 | export function useCreateSetupState() {
7 | const queryClient = useQueryClient();
8 | const addNotification = useNotificationsStore((store) => store.addNotification);
9 | return useMutation({
10 | mutationFn: (data: InitAppOptions) => axios.post('/v1/setup', data),
11 | onSuccess() {
12 | addNotification({ type: 'success' });
13 | void queryClient.invalidateQueries({ queryKey: ['setup-state'] });
14 | }
15 | });
16 | }
17 |
--------------------------------------------------------------------------------
/apps/web/src/features/setup/index.ts:
--------------------------------------------------------------------------------
1 | export * from './providers/SetupProvider';
2 |
--------------------------------------------------------------------------------
/apps/web/src/features/setup/pages/SetupLoadingPage/SetupLoadingPage.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { SetupLoadingPage } from './SetupLoadingPage';
4 |
5 | type Story = StoryObj;
6 |
7 | export default {
8 | component: SetupLoadingPage,
9 | parameters: {
10 | layout: 'fullscreen'
11 | }
12 | } as Meta;
13 |
14 | export const Default: Story = {};
15 |
--------------------------------------------------------------------------------
/apps/web/src/features/setup/pages/SetupLoadingPage/SetupLoadingPage.tsx:
--------------------------------------------------------------------------------
1 | import { useTranslation } from '@douglasneuroinformatics/libui/hooks';
2 | import { LoadingPage } from '@opendatacapture/react-core';
3 |
4 | export const SetupLoadingPage = () => {
5 | const { t } = useTranslation('setup');
6 | return ;
7 | };
8 |
--------------------------------------------------------------------------------
/apps/web/src/features/setup/pages/SetupLoadingPage/index.ts:
--------------------------------------------------------------------------------
1 | export * from './SetupLoadingPage';
2 |
--------------------------------------------------------------------------------
/apps/web/src/features/setup/pages/SetupPage/SetupPage.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { SetupPage } from './SetupPage';
4 |
5 | type Story = StoryObj;
6 |
7 | export default {
8 | component: SetupPage,
9 | parameters: {
10 | layout: 'fullscreen'
11 | }
12 | } as Meta;
13 |
14 | export const Default: Story = {};
15 |
--------------------------------------------------------------------------------
/apps/web/src/features/setup/pages/SetupPage/index.ts:
--------------------------------------------------------------------------------
1 | export * from './SetupPage';
2 |
--------------------------------------------------------------------------------
/apps/web/src/features/upload/index.tsx:
--------------------------------------------------------------------------------
1 | import type { RouteObject } from 'react-router-dom';
2 |
3 | import { UploadPage } from './pages/UploadPage';
4 | import { UploadSelectPage } from './pages/UploadSelectPage';
5 |
6 | export const uploadRoute: RouteObject = {
7 | children: [
8 | {
9 | element: ,
10 | index: true
11 | },
12 | {
13 | element: ,
14 | path: ':id' //instrument id
15 | }
16 | ],
17 | path: 'upload'
18 | };
19 |
--------------------------------------------------------------------------------
/apps/web/src/features/user/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable perfectionist/sort-objects */
2 |
3 | import type { RouteObject } from 'react-router-dom';
4 |
5 | import { UserPage } from './pages/UserPage';
6 |
7 | export const userRoute: RouteObject = {
8 | path: 'user',
9 | element:
10 | };
11 |
--------------------------------------------------------------------------------
/apps/web/src/hooks/useAssignmentsQuery.ts:
--------------------------------------------------------------------------------
1 | import { $Assignment } from '@opendatacapture/schemas/assignment';
2 | import { useQuery } from '@tanstack/react-query';
3 | import axios from 'axios';
4 |
5 | export function useAssignmentsQuery({ params }: { params?: { subjectId?: string } }) {
6 | return useQuery({
7 | queryFn: async () => {
8 | const response = await axios.get('/v1/assignments', {
9 | params: {
10 | subjectId: params?.subjectId
11 | }
12 | });
13 | return $Assignment.array().parse(response.data);
14 | },
15 | queryKey: ['assignments', params?.subjectId]
16 | });
17 | }
18 |
--------------------------------------------------------------------------------
/apps/web/src/hooks/useInstrumentBundle.ts:
--------------------------------------------------------------------------------
1 | import { $InstrumentBundleContainer } from '@opendatacapture/schemas/instrument';
2 | import { useQuery } from '@tanstack/react-query';
3 | import axios from 'axios';
4 |
5 | export function useInstrumentBundle(id: null | string) {
6 | return useQuery({
7 | enabled: Boolean(id),
8 | queryFn: async () => {
9 | const response = await axios.get(`/v1/instruments/bundle/${id}`);
10 | return $InstrumentBundleContainer.parseAsync(response.data);
11 | },
12 | queryKey: ['instrument-bundle', id]
13 | });
14 | }
15 |
--------------------------------------------------------------------------------
/apps/web/src/hooks/useInstrumentInterpreter.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 |
3 | import { InstrumentInterpreter } from '@opendatacapture/instrument-interpreter';
4 |
5 | export const useInstrumentInterpreter = () => {
6 | return useMemo(() => new InstrumentInterpreter(), []);
7 | };
8 |
--------------------------------------------------------------------------------
/apps/web/src/hooks/useIsDesktop.ts:
--------------------------------------------------------------------------------
1 | import { useMediaQuery } from '@douglasneuroinformatics/libui/hooks';
2 |
3 | export function useIsDesktop() {
4 | return useMediaQuery('(min-width: 768px)');
5 | }
6 |
--------------------------------------------------------------------------------
/apps/web/src/hooks/useSetupState.ts:
--------------------------------------------------------------------------------
1 | import { $SetupState } from '@opendatacapture/schemas/setup';
2 | import { useQuery } from '@tanstack/react-query';
3 | import axios from 'axios';
4 |
5 | export function useSetupState() {
6 | return useQuery({
7 | queryFn: async () => {
8 | const response = await axios.get('/v1/setup');
9 | return $SetupState.parseAsync(response.data);
10 | },
11 | queryKey: ['setup-state']
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/apps/web/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 |
4 | import { App } from './App';
5 |
6 | import './styles.css';
7 |
8 | const root = document.getElementById('root')!;
9 |
10 | ReactDOM.createRoot(root).render(
11 |
12 |
13 |
14 | );
15 |
--------------------------------------------------------------------------------
/apps/web/src/services/react-query.ts:
--------------------------------------------------------------------------------
1 | import { QueryClient } from '@tanstack/react-query';
2 |
3 | export const queryClient = new QueryClient({
4 | defaultOptions: {
5 | mutations: {
6 | throwOnError: true
7 | },
8 | queries: {
9 | throwOnError: true
10 | }
11 | }
12 | });
13 |
--------------------------------------------------------------------------------
/apps/web/src/services/zod.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod';
2 |
3 | import i18n from './i18n';
4 |
5 | const customErrorMap: z.ZodErrorMap = (issue, ctx) => {
6 | const isUndefined = issue.code === 'invalid_type' && issue.received === 'undefined';
7 | const isEmptyString = issue.code === 'too_small' && issue.minimum === 1 && issue.type === 'string';
8 | if (isUndefined || isEmptyString) {
9 | return { message: i18n.t('core.form.requiredField') };
10 | }
11 | return { message: ctx.defaultError };
12 | };
13 |
14 | z.setErrorMap(customErrorMap);
15 |
--------------------------------------------------------------------------------
/apps/web/src/store/slices/disclaimer.slice.ts:
--------------------------------------------------------------------------------
1 | import type { DisclaimerSlice, SliceCreator } from '../types';
2 |
3 | export const createDisclaimerSlice: SliceCreator = (set) => ({
4 | isDisclaimerAccepted: false,
5 | setIsDisclaimerAccepted: (isDisclaimerAccepted) => {
6 | set((state) => {
7 | state.isDisclaimerAccepted = isDisclaimerAccepted;
8 | });
9 | }
10 | });
11 |
--------------------------------------------------------------------------------
/apps/web/src/store/slices/session.slice.ts:
--------------------------------------------------------------------------------
1 | import type { SessionSlice, SliceCreator } from '../types';
2 |
3 | export const createSessionSlice: SliceCreator = (set) => ({
4 | currentSession: null,
5 | endSession() {
6 | set((state) => {
7 | state.currentSession = null;
8 | });
9 | },
10 | startSession(session) {
11 | set((state) => {
12 | state.currentSession = session;
13 | });
14 | }
15 | });
16 |
--------------------------------------------------------------------------------
/apps/web/src/store/slices/walkthrough.slice.ts:
--------------------------------------------------------------------------------
1 | import type { SliceCreator, WalkthroughSlice } from '../types';
2 |
3 | export const createWalkthroughSlice: SliceCreator = (set) => ({
4 | isWalkthroughComplete: false,
5 | isWalkthroughOpen: false,
6 | setIsWalkthroughComplete: (isWalkthroughComplete) => {
7 | set((state) => {
8 | state.isWalkthroughComplete = isWalkthroughComplete;
9 | });
10 | },
11 | setIsWalkthroughOpen: (isWalkthroughOpen) => {
12 | set((state) => {
13 | state.isWalkthroughOpen = isWalkthroughOpen;
14 | });
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/apps/web/src/translations/contact.json:
--------------------------------------------------------------------------------
1 | {
2 | "message": {
3 | "en": "Message",
4 | "fr": "Message"
5 | },
6 | "pageTitle": {
7 | "en": "Contact Us",
8 | "fr": "Nous contacter"
9 | },
10 | "reason": {
11 | "en": "Reason",
12 | "fr": "Raison"
13 | },
14 | "reasons": {
15 | "bug": {
16 | "en": "Bug Report",
17 | "fr": "Rapport de bug"
18 | },
19 | "feedback": {
20 | "en": "Feedback",
21 | "fr": "Retour d'information"
22 | },
23 | "other": {
24 | "en": "Other",
25 | "fr": "Autre"
26 | },
27 | "request": {
28 | "en": "Request",
29 | "fr": "Demande"
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/apps/web/src/translations/upload.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": {
3 | "en": "Instrument Title",
4 | "fr": "Titre de l'instrument"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/apps/web/src/translations/user.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/apps/web/src/utils/__tests__/excel.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 |
3 | import { downloadExcel } from '../excel';
4 |
5 | describe('downloadExcel', () => {
6 | it('should be defined', () => {
7 | expect(downloadExcel).toBeDefined();
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/apps/web/src/utils/excel.ts:
--------------------------------------------------------------------------------
1 | import type { InstrumentRecordsExport } from '@opendatacapture/schemas/instrument-records';
2 | import { utils, writeFileXLSX } from 'xlsx';
3 |
4 | export function downloadExcel(filename: string, recordsExport: InstrumentRecordsExport) {
5 | const workbook = utils.book_new();
6 | utils.book_append_sheet(workbook, utils.json_to_sheet(recordsExport), 'ULTRA_LONG');
7 | writeFileXLSX(workbook, filename);
8 | }
9 |
--------------------------------------------------------------------------------
/apps/web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "baseUrl": ".",
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "paths": {
7 | "@/*": ["./src/*"]
8 | },
9 | "types": ["vite/client"]
10 | },
11 | "include": ["src/**/*", "vite.config.ts", "vitest.config.ts"]
12 | }
13 |
--------------------------------------------------------------------------------
/apps/web/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { mergeConfig } from 'vitest/config';
2 |
3 | import baseConfig from '../../vitest.config';
4 |
5 | export default mergeConfig(baseConfig, {
6 | test: {
7 | environment: 'happy-dom',
8 | root: import.meta.dirname
9 | }
10 | });
11 |
--------------------------------------------------------------------------------
/docker-compose.dev.yaml:
--------------------------------------------------------------------------------
1 | # See https://github.com/prisma/prisma/issues/8266
2 | # docker compose -f docker-compose.dev.yaml up -d
3 | # docker compose -f docker-compose.dev.yaml exec mongo mongosh --eval "rs.initiate({_id: 'rs0', members: [{_id: 0, host: 'localhost:27017'}]});"
4 | name: open-data-capture
5 | services:
6 | mongo:
7 | image: mongo:${MONGODB_VERSION}
8 | command: --replSet rs0
9 | ports:
10 | - '27017:27017'
11 | - '28017:28017'
12 | volumes:
13 | - ./data/mongo:/data/db
14 |
--------------------------------------------------------------------------------
/docs/en/1-introduction/1.3-license.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: License
3 | slug: en/docs/introduction/license
4 | sidebar:
5 | order: 3
6 | ---
7 |
8 | Open Data Capture is free and open source software, licensed under the terms of the Apache License 2.0 This means that the [source code](https://github.com/DouglasNeuroInformatics/OpenDataCapture) is available for anyone to modify or deploy on their own private server. However, the Douglas Neuroinformatics Platform (DNP) administers an instance of Open Data Capture on our [own server](https://docs.douglasneuroinformatics.ca/en/latest/about_the_platform/index.html#hardware). Access to the platform is available to all Douglas researchers on request. Please [contact us](mailto:support@douglasneuroinformatics.ca) for more information.
9 |
--------------------------------------------------------------------------------
/docs/en/1-introduction/1.4-scope.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Scope
3 | slug: en/docs/introduction/scope
4 | sidebar:
5 | order: 4
6 | ---
7 |
8 | Open Data Capture is a comprehensive solution for safe and efficient storage of structured clinical data, including relevant quantitative information for research or performance improvement. This includes cognitive assessments (e.g., The Montreal Cognitive Assessment), symptom scales (e.g., The Scale for the Assessment of Negative Symptoms), and interactive tasks (e.g., the Stroop Test).
9 |
10 | :::note
11 | Open Data Capture is not an Electronic Health Record (EHR) and therefore it is not designed to capture or store qualitative notes.
12 | :::
13 |
--------------------------------------------------------------------------------
/docs/en/5-reference/5.2-tooling.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Tooling
3 | slug: en/docs/reference/tooling
4 | sidebar:
5 | order: 2
6 | ---
7 |
8 | ## Build System
9 |
10 | These three workspaces are managed using Turborepo. In the file `turbo.json`, various tasks are defined which may be run from the command line using scripts defined in `package.json`.
11 |
12 | ## TypeScript
13 |
14 | The entire codebase is written in Typescript, and each package has a `tsconfig.json` file that extends the `tsconfig.base.json` file located in the root of the monorepo. T
15 |
16 | ## ESLint
17 |
18 | The `eslint.config.js` file, located at the root of the monorepo, defines the linting configuration for all files.
19 |
--------------------------------------------------------------------------------
/docs/en/5-reference/5.6-docker.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Docker
3 | slug: en/docs/reference/docker
4 | sidebar:
5 | order: 6
6 | ---
7 |
8 | The application consists of four interconnected services:
9 |
10 | - A static website
11 | - A REST API
12 | - A MongoDB database
13 | - A Caddy web server
14 |
15 | These services communicate via internal networks defined in `docker-compose.yaml`. The Caddy web server acts as the entry point, exposing HTTP (port 80) and HTTPS (port 443) to the host machine. API requests (/api/\*) are forwarded to the api container, while other requests are proxied to the web container. The web container serves built static files, if available, or the single-page app otherwise.
16 |
--------------------------------------------------------------------------------
/docs/en/5-reference/5.7-runtime.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Runtime
3 | slug: en/docs/reference/runtime
4 | sidebar:
5 | order: 7
6 | ---
7 |
8 | The runtime is based on native ES modules (ESM). Since the majority of packages are written in CommonJS (CJS), we transpile them to ESM. For the greatest compatibility, CJS exports are exported both as the default export and as named exports.
9 |
10 | ### Legacy Packages
11 |
12 | Certain legacy packages, that predate either CJS or ESM, are copied directly into the Open Data Capture repository and modified for compatibility purposes.
13 |
--------------------------------------------------------------------------------
/docs/en/6-updating/v1.7.0.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: v1.7.0
3 | slug: en/docs/updating/v1.7.0
4 | sidebar:
5 | order: 0
6 | ---
7 |
8 | ### Changes
9 |
10 | - Allow users to assign series instruments for completion on the gateway
11 | - Add thank you message to series instrument
12 |
--------------------------------------------------------------------------------
/docs/fr/1-introduction/1.3-license.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Licence
3 | slug: fr/docs/introduction/license
4 | sidebar:
5 | order: 3
6 | ---
7 |
8 | Open Data Capture est un logiciel libre et gratuit, sous les termes de la Apache License 2.0. Cela signifie que le [code source](https://github.com/DouglasNeuroInformatics/OpenDataCapture) est disponible pour que chacun puisse le modifier ou le déployer sur son propre serveur privé. Cependant, le Douglas Neuroinformatics Platform (DNP)administre une instance de l'Open Data Capture sur ses propres serveurs, derrière un pare-feu. L'accès à la plateforme est disponible pour tous les chercheurs du Douglas sur demande. Veuillez [nous contacter](mailto:support@douglasneuroinformatics.ca) pour plus d'informations.
9 |
--------------------------------------------------------------------------------
/docs/fr/1-introduction/1.4-scope.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Portée du projet
3 | slug: fr/docs/introduction/scope
4 | sidebar:
5 | order: 4
6 | ---
7 |
8 | L'Open Data Capture est une solution complète pour le stockage sécurisé et efficace de données cliniques structurées, y compris des informations quantitatives pertinentes pour la recherche ou l'amélioration des performances. Il s'agit notamment d'évaluations cognitives (par exemple, l'évaluation cognitive de Montréal), d'échelles de symptômes (par exemple, un test mesurant la gravité des symptômes dépressifs) et de tâches interactives (par exemple, le test de Stroop).
9 |
10 | :::note
11 | L'Open Data Capture n'est pas un dossier médical électronique et n'est donc pas conçu pour capturer ou stocker des notes qualitatives.
12 | :::
13 |
--------------------------------------------------------------------------------
/knip.ts:
--------------------------------------------------------------------------------
1 | import type { KnipConfig } from 'knip';
2 |
3 | const config: KnipConfig = {
4 | workspaces: {
5 | 'apps/api': {
6 | entry: ['src/main.ts', 'libnest.config.ts'],
7 | ignoreDependencies: ['@opendatacapture/runtime-v1', 'prisma-json-types-generator', 'lodash-es', 'ts-pattern'],
8 | project: '**/*.{js,ts}'
9 | },
10 | 'apps/web': {
11 | ignoreDependencies: ['lodash-es', 'papaparse', 'ts-pattern']
12 | }
13 | }
14 | };
15 |
16 | export default config;
17 |
--------------------------------------------------------------------------------
/packages/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@opendatacapture/demo",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "license": "Apache-2.0",
6 | "exports": {
7 | ".": "./src/index.ts",
8 | "./assets/*": "./src/assets/*"
9 | },
10 | "scripts": {
11 | "format": "prettier --write src",
12 | "lint": "tsc && eslint --fix src"
13 | },
14 | "dependencies": {
15 | "@douglasneuroinformatics/libjs": "catalog:",
16 | "@opendatacapture/schemas": "workspace:*"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src/**/*", "*.js", "*.cjs"]
4 | }
5 |
--------------------------------------------------------------------------------
/packages/evaluate-instrument/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@opendatacapture/evaluate-instrument",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "private": true,
6 | "license": "Apache-2.0",
7 | "exports": {
8 | ".": {
9 | "types": "./src/index.d.ts",
10 | "import": "./src/index.js"
11 | },
12 | "./package.json": "./package.json"
13 | },
14 | "scripts": {
15 | "format": "prettier --write src",
16 | "lint": "tsc && eslint --fix src"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/evaluate-instrument/src/index.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Evaluates an instrument bundle stored as a string. This function does not
3 | * perform any output validation.
4 | *
5 | * **IMPORTANT: ONLY INPUT FROM TRUSTED USERS MUST BE PASSED TO THIS FUNCTION. THE ONLY EXCEPTION
6 | * TO THIS IS ON A COMPLETELY STATIC WEBSITE THAT NEVER INTERACTS WITH OUR SERVERS (I.E., THE INSTRUMENT
7 | * PLAYGROUND).**
8 | */
9 | export declare function evaluateInstrument(bundle: string): Promise;
10 |
--------------------------------------------------------------------------------
/packages/evaluate-instrument/src/index.js:
--------------------------------------------------------------------------------
1 | /** @type {import('./index.d.ts').evaluateInstrument} */
2 | export async function evaluateInstrument(bundle) {
3 | return await new Function(`return ${bundle}`)();
4 | }
5 |
--------------------------------------------------------------------------------
/packages/evaluate-instrument/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src/**/*"]
4 | }
5 |
--------------------------------------------------------------------------------
/packages/instrument-bundler/src/__tests__/preprocess.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 |
3 | import { preprocess } from '../preprocess.js';
4 |
5 | describe('preprocess', () => {
6 | it('should reject an empty array', () => {
7 | expect(() => preprocess([])).toThrowError('Received empty array for inputs');
8 | });
9 | it('should reject non-shallow names', () => {
10 | expect(() =>
11 | preprocess([
12 | { content: "export * from './utils/foo.js'", name: 'index.js' },
13 | { content: 'export const foo = null', name: 'utils/foo.js' }
14 | ])
15 | ).toThrowError(
16 | "Illegal character '/' in input name 'utils/foo.js': expected shallow relative path (e.g., './foo.js')"
17 | );
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/packages/instrument-bundler/src/__tests__/repositories/interactive/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: red;
3 | }
4 |
--------------------------------------------------------------------------------
/packages/instrument-bundler/src/__tests__/schemas.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 |
3 | import { $BundleOptions } from '../schemas.js';
4 |
5 | describe('$BundlerOptions', () => {
6 | it('should parse valid options', () => {
7 | expect($BundleOptions.safeParse({ inputs: [{ content: new Uint8Array(), name: 'style.css' }] })).toMatchObject({
8 | success: true
9 | });
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/packages/instrument-bundler/src/index.ts:
--------------------------------------------------------------------------------
1 | export { bundle } from './bundle.js';
2 | export { InstrumentBundlerError } from './error.js';
3 | export { resolveIndexInput, resolveInput } from './resolve.js';
4 | export type { BundleOptions, BundlerInput } from './schemas.js';
5 | export type * from './types.js';
6 | export { extractInputFileExtension } from './utils.js';
7 |
--------------------------------------------------------------------------------
/packages/instrument-bundler/src/types.ts:
--------------------------------------------------------------------------------
1 | export type BundlerInputFileExtension =
2 | | '.css'
3 | | '.html'
4 | | '.jpeg'
5 | | '.jpg'
6 | | '.js'
7 | | '.json'
8 | | '.jsx'
9 | | '.mp3'
10 | | '.mp4'
11 | | '.png'
12 | | '.svg'
13 | | '.ts'
14 | | '.tsx'
15 | | '.webp';
16 |
17 | export type BuildOutput = {
18 | css?: string;
19 | js: string;
20 | };
21 |
--------------------------------------------------------------------------------
/packages/instrument-bundler/src/vendor/esbuild.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-var */
2 |
3 | if (typeof window === 'undefined') {
4 | var { build, transform } = await import('esbuild');
5 | } else {
6 | var { build, transform } = await import('esbuild-wasm');
7 | }
8 |
9 | export { build, transform };
10 | export type { BuildFailure, BuildResult, Loader, Location, Message, Plugin } from 'esbuild';
11 |
--------------------------------------------------------------------------------
/packages/instrument-bundler/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
5 | "paths": {
6 | "/runtime/v1/*": ["../../runtime/v1/dist/*"]
7 | }
8 | },
9 | "include": ["src/**/*", "vitest.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/instrument-bundler/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path';
2 |
3 | import { mergeConfig } from 'vitest/config';
4 |
5 | import baseConfig from '../../vitest.config';
6 |
7 | export default mergeConfig(baseConfig, {
8 | test: {
9 | alias: {
10 | '/runtime/v1': path.resolve(import.meta.dirname, './runtime/v1/dist')
11 | },
12 | root: import.meta.dirname
13 | }
14 | });
15 |
--------------------------------------------------------------------------------
/packages/instrument-interpreter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@opendatacapture/instrument-interpreter",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "license": "Apache-2.0",
6 | "exports": {
7 | ".": "./src/index.ts"
8 | },
9 | "scripts": {
10 | "format": "prettier --write src",
11 | "lint": "tsc && eslint --fix src"
12 | },
13 | "dependencies": {
14 | "@opendatacapture/evaluate-instrument": "workspace:*",
15 | "@opendatacapture/runtime-core": "workspace:*",
16 | "@opendatacapture/schemas": "workspace:*"
17 | },
18 | "devDependencies": {
19 | "type-fest": "workspace:type-fest__4.x@*"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/instrument-interpreter/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src/**/*", "*.js", "*.cjs"]
4 | }
5 |
--------------------------------------------------------------------------------
/packages/instrument-library/src/interactive/DNP_STROOP_TASK/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: white;
3 | }
4 |
--------------------------------------------------------------------------------
/packages/instrument-library/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "allowImportingTsExtensions": true,
5 | "jsxImportSource": "/runtime/v1/react@19.x",
6 | "lib": ["ES2022", "DOM", "DOM.Iterable"],
7 | "paths": {
8 | "/runtime/v1/*": ["../../runtime/v1/dist/*"]
9 | },
10 | "types": ["@opendatacapture/runtime-v1/env", "node"]
11 | },
12 | "include": ["scripts/*", "src/**/*"]
13 | }
14 |
--------------------------------------------------------------------------------
/packages/instrument-stubs/README.md:
--------------------------------------------------------------------------------
1 | # @opendatacapture/instrument-stubs
2 |
3 | ## About
4 |
5 | This package contains example instruments that should be used exclusively for testing. Unlike `@opendatacapture/instrument-library`, this package is written in vanilla JavaScript, which is important to avoid the complications of transpilation in different environments (e.g., running esbuild in the browser vs in node).
6 |
--------------------------------------------------------------------------------
/packages/instrument-stubs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@opendatacapture/instrument-stubs",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "license": "Apache-2.0",
6 | "exports": {
7 | "./forms": "./src/forms.js",
8 | "./interactive": "./src/interactive.js",
9 | "./series": "./src/series.js"
10 | },
11 | "scripts": {
12 | "format": "prettier --write src",
13 | "lint": "eslint --fix src"
14 | },
15 | "peerDependencies": {
16 | "@opendatacapture/runtime-core": "workspace:*"
17 | },
18 | "dependencies": {
19 | "@douglasneuroinformatics/libjs": "catalog:",
20 | "zod": "workspace:zod__3.23.x@*"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/instrument-stubs/src/series.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable perfectionist/sort-objects */
2 |
3 | import { createInstrumentStub } from './utils.js';
4 |
5 | /** @type {import('./utils.js').InstrumentStub} */
6 | export const seriesInstrument = await createInstrumentStub(async () => {
7 | return {
8 | __runtimeVersion: 1,
9 | kind: 'SERIES',
10 | language: 'en',
11 | tags: ['Example', 'Useless'],
12 | content: [
13 | {
14 | name: 'HAPPINESS_QUESTIONNAIRE',
15 | edition: 1
16 | }
17 | ],
18 | details: {
19 | description: 'This is a series instrument',
20 | license: 'UNLICENSED',
21 | title: 'Series Instrument'
22 | }
23 | };
24 | });
25 |
--------------------------------------------------------------------------------
/packages/instrument-utils/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@opendatacapture/instrument-utils",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "license": "Apache-2.0",
6 | "exports": {
7 | ".": "./src/index.ts"
8 | },
9 | "scripts": {
10 | "format": "prettier --write src",
11 | "lint": "tsc && eslint --fix src"
12 | },
13 | "dependencies": {
14 | "@douglasneuroinformatics/libjs": "catalog:",
15 | "@opendatacapture/runtime-core": "workspace:*",
16 | "@opendatacapture/schemas": "workspace:*",
17 | "lodash-es": "workspace:lodash-es__4.x@*",
18 | "ts-pattern": "workspace:ts-pattern__5.x@*"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/instrument-utils/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './form.js';
2 | export * from './guards.js';
3 | export * from './measures.js';
4 | export * from './translate.js';
5 |
--------------------------------------------------------------------------------
/packages/instrument-utils/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src/**/*", "*.js", "*.cjs"]
4 | }
5 |
--------------------------------------------------------------------------------
/packages/licenses/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@opendatacapture/licenses",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "private": true,
6 | "license": "Apache-2.0",
7 | "exports": {
8 | ".": {
9 | "types": "./src/index.d.ts",
10 | "import": "./src/index.js"
11 | },
12 | "./package.json": "./package.json"
13 | },
14 | "scripts": {
15 | "format": "prettier --write src",
16 | "lint": "tsc && eslint --fix src"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/licenses/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src/**/*"]
4 | }
5 |
--------------------------------------------------------------------------------
/packages/react-core/src/components/Branding/Branding.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { Branding } from './Branding';
4 |
5 | type Story = StoryObj;
6 |
7 | export default {
8 | component: Branding,
9 | parameters: {
10 | layout: 'centered'
11 | }
12 | } satisfies Meta;
13 |
14 | export const Default: Story = {};
15 |
--------------------------------------------------------------------------------
/packages/react-core/src/components/Branding/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Branding';
2 |
--------------------------------------------------------------------------------
/packages/react-core/src/components/CopyButton/CopyButton.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { CopyButton } from './CopyButton';
4 |
5 | type Story = StoryObj;
6 |
7 | export default { component: CopyButton } as Meta;
8 |
9 | export const Default: Story = {
10 | parameters: {
11 | layout: 'centered'
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/packages/react-core/src/components/CopyButton/index.ts:
--------------------------------------------------------------------------------
1 | export * from './CopyButton';
2 |
--------------------------------------------------------------------------------
/packages/react-core/src/components/ErrorFallback/ErrorFallback.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { ErrorFallback } from './ErrorFallback';
4 |
5 | type Story = StoryObj;
6 |
7 | export default { component: ErrorFallback } as Meta;
8 |
9 | export const Default: Story = {
10 | args: {
11 | description: 'Description',
12 | subtitle: 'Subtitle',
13 | title: 'Title'
14 | },
15 | parameters: {
16 | layout: 'centered'
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/packages/react-core/src/components/ErrorFallback/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ErrorFallback';
2 |
--------------------------------------------------------------------------------
/packages/react-core/src/components/ErrorPage/ErrorPage.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { ErrorPage } from './ErrorPage';
4 |
5 | type Story = StoryObj;
6 |
7 | export default {
8 | component: ErrorPage,
9 | parameters: {
10 | layout: 'fullscreen'
11 | }
12 | } as Meta;
13 |
14 | export const Default: Story = {};
15 |
--------------------------------------------------------------------------------
/packages/react-core/src/components/ErrorPage/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ErrorPage';
2 |
--------------------------------------------------------------------------------
/packages/react-core/src/components/FormContent/index.ts:
--------------------------------------------------------------------------------
1 | export * from './FormContent';
2 |
--------------------------------------------------------------------------------
/packages/react-core/src/components/InstrumentIcon/InstrumentIcon.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { InstrumentIcon } from './InstrumentIcon';
4 |
5 | type Story = StoryObj;
6 |
7 | export default { component: InstrumentIcon } as Meta;
8 |
9 | export const Form: Story = {
10 | args: {
11 | kind: 'FORM'
12 | }
13 | };
14 |
15 | export const Interactive: Story = {
16 | args: {
17 | kind: 'INTERACTIVE'
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/packages/react-core/src/components/InstrumentIcon/index.ts:
--------------------------------------------------------------------------------
1 | export * from './InstrumentIcon';
2 |
--------------------------------------------------------------------------------
/packages/react-core/src/components/InstrumentOverview/index.ts:
--------------------------------------------------------------------------------
1 | export * from './InstrumentOverview';
2 |
--------------------------------------------------------------------------------
/packages/react-core/src/components/InstrumentRenderer/ContentPlaceholder.tsx:
--------------------------------------------------------------------------------
1 | import { Heading } from '@douglasneuroinformatics/libui/components';
2 |
3 | export type ContentPlaceholderProps = {
4 | message?: string;
5 | title: string;
6 | };
7 |
8 | export const ContentPlaceholder = ({ message, title }: ContentPlaceholderProps) => {
9 | return (
10 |
11 |
12 | {title}
13 |
14 | {message &&
{message}
}
15 |
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/packages/react-core/src/components/InstrumentRenderer/index.ts:
--------------------------------------------------------------------------------
1 | export * from './InstrumentRenderer';
2 | export * from './ScalarInstrumentRenderer';
3 | export * from './SeriesInstrumentRenderer';
4 |
--------------------------------------------------------------------------------
/packages/react-core/src/components/InstrumentSummary/index.ts:
--------------------------------------------------------------------------------
1 | export * from './InstrumentSummary';
2 |
--------------------------------------------------------------------------------
/packages/react-core/src/components/InteractiveContent/InteractiveContent.stories.tsx:
--------------------------------------------------------------------------------
1 | import { interactiveInstrument } from '@opendatacapture/instrument-stubs/interactive';
2 | import type { Meta, StoryObj } from '@storybook/react-vite';
3 |
4 | import { InteractiveContent } from './InteractiveContent';
5 |
6 | type Story = StoryObj;
7 |
8 | export default {
9 | component: InteractiveContent,
10 | decorators: [
11 | (Story) => {
12 | return (
13 |
14 |
15 |
16 | );
17 | }
18 | ]
19 | } as Meta;
20 |
21 | export const Default: Story = {
22 | args: {
23 | bundle: interactiveInstrument.bundle
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/packages/react-core/src/components/InteractiveContent/index.ts:
--------------------------------------------------------------------------------
1 | export * from './InteractiveContent';
2 |
--------------------------------------------------------------------------------
/packages/react-core/src/components/LoadingPage/LoadingPage.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { LoadingPage } from './LoadingPage';
4 |
5 | type Story = StoryObj;
6 |
7 | export default {
8 | component: LoadingPage,
9 | parameters: {
10 | layout: 'fullscreen'
11 | }
12 | } as Meta;
13 |
14 | export const Default: Story = {};
15 |
--------------------------------------------------------------------------------
/packages/react-core/src/components/LoadingPage/index.ts:
--------------------------------------------------------------------------------
1 | export * from './LoadingPage';
2 |
--------------------------------------------------------------------------------
/packages/react-core/src/components/Logo/Logo.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from '@storybook/react-vite';
2 |
3 | import { Logo } from './Logo';
4 |
5 | type Story = StoryObj;
6 |
7 | export default {
8 | component: Logo,
9 | parameters: {
10 | layout: 'centered'
11 | }
12 | } satisfies Meta;
13 |
14 | export const Default: Story = {
15 | args: {
16 | className: 'w-96'
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/packages/react-core/src/components/Logo/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Logo';
2 |
--------------------------------------------------------------------------------
/packages/react-core/src/globals.css:
--------------------------------------------------------------------------------
1 | @import '@douglasneuroinformatics/libui/tailwind/globals.css';
2 |
3 | @source "../node_modules/@douglasneuroinformatics/libui";
4 |
5 | @source "../src";
6 |
--------------------------------------------------------------------------------
/packages/react-core/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './components/Branding';
2 | export * from './components/CopyButton';
3 | export * from './components/ErrorFallback';
4 | export * from './components/ErrorPage';
5 | export * from './components/InstrumentIcon';
6 | export { InstrumentRenderer, type InstrumentRendererProps } from './components/InstrumentRenderer';
7 | export { ScalarInstrumentRenderer, type ScalarInstrumentRendererProps } from './components/InstrumentRenderer';
8 | export * from './components/LoadingPage';
9 | export * from './components/Logo';
10 | export * from './types';
11 |
--------------------------------------------------------------------------------
/packages/react-core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
5 | "paths": {
6 | "/runtime/v1/*": ["../../runtime/v1/dist/*"]
7 | },
8 | "types": ["vite/client"]
9 | },
10 | "include": ["src/**/*"]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/release-info/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@opendatacapture/release-info",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "license": "Apache-2.0",
6 | "exports": {
7 | ".": "./src/index.ts"
8 | },
9 | "scripts": {
10 | "format": "prettier --write src",
11 | "lint": "tsc && eslint --fix src",
12 | "test": "vitest"
13 | },
14 | "dependencies": {
15 | "@opendatacapture/schemas": "workspace:*",
16 | "type-fest": "workspace:type-fest__4.x@*",
17 | "zod": "workspace:zod__3.23.x@*"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/release-info/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src/**/*", "vitest.config.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/packages/release-info/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { mergeConfig } from 'vitest/config';
2 |
3 | import baseConfig from '../../vitest.config';
4 |
5 | export default mergeConfig(baseConfig, {
6 | test: {
7 | root: import.meta.dirname
8 | }
9 | });
10 |
--------------------------------------------------------------------------------
/packages/runtime-bundler/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@opendatacapture/runtime-bundler",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "license": "Apache-2.0",
6 | "exports": {
7 | ".": "./src/index.ts"
8 | },
9 | "bin": {
10 | "runtime-bundler": "./src/cli.ts"
11 | },
12 | "scripts": {
13 | "format": "prettier --write src",
14 | "lint": "tsc && eslint --fix src",
15 | "test": "vitest"
16 | },
17 | "dependencies": {
18 | "@douglasneuroinformatics/libjs": "catalog:",
19 | "esbuild": "catalog:",
20 | "zod": "workspace:zod__3.23.x@*"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/runtime-bundler/src/__tests__/schemas.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 |
3 | import { $Config } from '../schemas.js';
4 |
5 | import type { Config } from '../schemas.js';
6 |
7 | describe('$Config', () => {
8 | it('should parse an included package', () => {
9 | expect(
10 | $Config.safeParse({
11 | include: ['jquery__1.0.0'],
12 | outdir: 'dist'
13 | } satisfies Config)
14 | ).toMatchObject({ success: true });
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/packages/runtime-bundler/src/index.ts:
--------------------------------------------------------------------------------
1 | export { Bundler } from './bundler.js';
2 | export type { Config } from './schemas.js';
3 |
--------------------------------------------------------------------------------
/packages/runtime-bundler/src/schemas.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod';
2 |
3 | type Config = z.infer;
4 | const $Config = z.object({
5 | include: z.array(z.string()),
6 | mode: z.enum(['development', 'production']).optional(),
7 | outdir: z.string(),
8 | verbose: z.boolean().optional()
9 | });
10 |
11 | const $UserConfigs = z.union([$Config, z.array($Config)]);
12 |
13 | export { $Config, $UserConfigs };
14 | export type { Config };
15 |
--------------------------------------------------------------------------------
/packages/runtime-bundler/src/types.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from './schemas.js';
2 |
3 | type ExportCondition = 'default' | 'import' | 'types';
4 |
5 | type PackageExport = {
6 | [K in ExportCondition]?: string;
7 | };
8 |
9 | type ResolvedPackage = {
10 | exports: {
11 | [key: string]: PackageExport;
12 | };
13 | name: string;
14 | packageJsonPath: string;
15 | packageRoot: string;
16 | };
17 |
18 | type EntryPoint = {
19 | in: string;
20 | out: string;
21 | };
22 |
23 | export type BundlerOptions = Config & { configFilepath: string };
24 |
25 | export type { EntryPoint, ExportCondition, PackageExport, ResolvedPackage };
26 |
--------------------------------------------------------------------------------
/packages/runtime-bundler/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "lib": ["DOM", "DOM.Iterable", "ESNext"]
5 | },
6 | "include": ["src/**/*", "vitest.config.ts"]
7 | }
8 |
--------------------------------------------------------------------------------
/packages/runtime-bundler/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { mergeConfig } from 'vitest/config';
2 |
3 | import baseConfig from '../../vitest.config';
4 |
5 | export default mergeConfig(baseConfig, {
6 | test: {
7 | root: import.meta.dirname
8 | }
9 | });
10 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './define.js';
2 | export * from './i18n.js';
3 | export * from './notifications.js';
4 | export type * from './types/core.js';
5 | export type * from './types/instrument.base.js';
6 | export type * from './types/instrument.core.js';
7 | export type * from './types/instrument.form.js';
8 | export type * from './types/instrument.interactive.js';
9 | export type * from './types/instrument.series.js';
10 | export * from './utils.js';
11 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/notifications.ts:
--------------------------------------------------------------------------------
1 | /** @public */
2 | export type RuntimeNotification = {
3 | message?: string;
4 | title?: string;
5 | type: 'error' | 'info' | 'success' | 'warning';
6 | variant?: 'critical' | 'standard';
7 | };
8 |
9 | /**
10 | * @public
11 | * Display a notification in ODC for the user during an interactive instrument.
12 | * Please note this will not work with forms.
13 | */
14 | export function addNotification(notification: RuntimeNotification) {
15 | window.parent.document.dispatchEvent(new CustomEvent('addNotification', { detail: notification }));
16 | }
17 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/types/core.ts:
--------------------------------------------------------------------------------
1 | /** @public */
2 | type Language = 'en' | 'fr';
3 |
4 | /** @public */
5 | type JsonLiteral = boolean | null | number | string;
6 |
7 | /** @public */
8 | type Json = Json[] | JsonLiteral | { [key: string]: Json };
9 |
10 | export type { Json, JsonLiteral, Language };
11 |
--------------------------------------------------------------------------------
/packages/runtime-core/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { snakeCase } from 'lodash-es';
2 | import type { SnakeCasedProperties } from 'type-fest';
3 |
4 | /** @alpha */
5 | export function asSnakeCase(target: T) {
6 | const result: { [key: string]: any } = {};
7 | for (const key in target) {
8 | if (snakeCase(key) !== key) {
9 | result[snakeCase(key)] = target[key];
10 | }
11 | }
12 | return result as SnakeCasedProperties;
13 | }
14 |
--------------------------------------------------------------------------------
/packages/runtime-core/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["./tsconfig.json"],
3 | "compilerOptions": {
4 | "declaration": true,
5 | "noEmit": false,
6 | "outDir": "lib",
7 | "rootDir": "src",
8 | "tsBuildInfoFile": "./lib/.tsbuildinfo"
9 | },
10 | "include": ["src/**/*"],
11 | "exclude": ["src/**/__tests__/**"]
12 | }
13 |
--------------------------------------------------------------------------------
/packages/runtime-core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "checkJs": false,
5 | "lib": ["ESNext", "DOM", "DOM.Iterable"],
6 | "module": "Node16",
7 | "moduleResolution": "Node16"
8 | },
9 | "include": ["lib/**/*", "src/**/*"]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/schemas/src/auth/auth.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod';
2 |
3 | import type { Permissions } from '../core/core.js';
4 | import type { Group } from '../group/group.js';
5 |
6 | export type AuthPayload = {
7 | accessToken: string;
8 | };
9 |
10 | export type LoginCredentials = z.infer;
11 | export const $LoginCredentials = z.object({
12 | password: z.string(),
13 | username: z.string()
14 | });
15 |
16 | export type JwtPayload = {
17 | firstName: null | string;
18 | groups: Group[];
19 | lastName: null | string;
20 | permissions: Permissions;
21 | username: string;
22 | };
23 |
--------------------------------------------------------------------------------
/packages/schemas/src/instrument/__tests__/instrument.interactive.test.ts:
--------------------------------------------------------------------------------
1 | import { interactiveInstrument } from '@opendatacapture/instrument-stubs/interactive';
2 | import { describe, expect, it } from 'vitest';
3 |
4 | import { $InteractiveInstrument } from '../instrument.interactive.js';
5 |
6 | describe('$InteractiveInstrument', () => {
7 | it('should successfully parse valid instruments', () => {
8 | expect($InteractiveInstrument.safeParse(interactiveInstrument.instance).success).toBe(true);
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/packages/schemas/src/instrument/instrument.core.ts:
--------------------------------------------------------------------------------
1 | import type { AnyInstrument, AnyScalarInstrument } from '@opendatacapture/runtime-core';
2 | import { z } from 'zod';
3 |
4 | import { $FormInstrument } from './instrument.form.js';
5 | import { $InteractiveInstrument } from './instrument.interactive.js';
6 | import { $SeriesInstrument } from './instrument.series.js';
7 |
8 | const $AnyScalarInstrument: z.ZodType = z.union([$FormInstrument, $InteractiveInstrument]);
9 | const $AnyInstrument: z.ZodType = z.union([$FormInstrument, $InteractiveInstrument, $SeriesInstrument]);
10 |
11 | export { $AnyInstrument, $AnyScalarInstrument };
12 |
--------------------------------------------------------------------------------
/packages/schemas/src/instrument/instrument.interactive.ts:
--------------------------------------------------------------------------------
1 | import type { InteractiveInstrument } from '@opendatacapture/runtime-core';
2 | import { z } from 'zod';
3 |
4 | import { $InstrumentDetails, $ScalarInstrument } from './instrument.base.js';
5 |
6 | const $InteractiveInstrument: z.ZodType = $ScalarInstrument.extend({
7 | content: z.object({
8 | __injectHead: z
9 | .object({
10 | style: z.string().readonly()
11 | })
12 | .optional()
13 | .readonly(),
14 | render: z.function().args(z.any()).returns(z.any())
15 | }),
16 | details: $InstrumentDetails,
17 | kind: z.literal('INTERACTIVE')
18 | });
19 |
20 | export { $InteractiveInstrument };
21 |
--------------------------------------------------------------------------------
/packages/schemas/src/instrument/instrument.series.ts:
--------------------------------------------------------------------------------
1 | import type { SeriesInstrument } from '@opendatacapture/runtime-core';
2 | import { z } from 'zod';
3 |
4 | import { $BaseInstrument, $ScalarInstrumentInternal } from './instrument.base.js';
5 |
6 | const $SeriesInstrument: z.ZodType = $BaseInstrument.extend({
7 | content: z.array($ScalarInstrumentInternal),
8 | kind: z.literal('SERIES')
9 | });
10 |
11 | export { $SeriesInstrument };
12 |
--------------------------------------------------------------------------------
/packages/schemas/src/instrument/instrument.ts:
--------------------------------------------------------------------------------
1 | export * from './instrument.base.js';
2 | export * from './instrument.core.js';
3 | export * from './instrument.form.js';
4 | export * from './instrument.interactive.js';
5 | export * from './instrument.series.js';
6 |
--------------------------------------------------------------------------------
/packages/schemas/src/summary/summary.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod';
2 |
3 | export type Summary = z.infer;
4 | export const $Summary = z.object({
5 | counts: z.object({
6 | instruments: z.number().int().nonnegative(),
7 | records: z.number().int().nonnegative(),
8 | subjects: z.number().int().nonnegative(),
9 | users: z.number().int().nonnegative()
10 | })
11 | });
12 |
--------------------------------------------------------------------------------
/packages/schemas/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "jsx": "preserve",
5 | "lib": ["ES2022", "DOM", "DOM.Iterable"],
6 | "paths": {
7 | "/runtime/v1/*": ["../../runtime/v1/dist/*"]
8 | }
9 | },
10 | "include": ["src/**/*", "vitest.config.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/schemas/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { mergeConfig } from 'vitest/config';
2 |
3 | import baseConfig from '../../vitest.config';
4 |
5 | export default mergeConfig(baseConfig, {
6 | test: {
7 | root: import.meta.dirname
8 | }
9 | });
10 |
--------------------------------------------------------------------------------
/packages/subject-utils/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@opendatacapture/subject-utils",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "license": "Apache-2.0",
6 | "exports": {
7 | ".": "./src/index.ts"
8 | },
9 | "scripts": {
10 | "format": "prettier --write src",
11 | "lint": "tsc && eslint --fix src",
12 | "test": "vitest"
13 | },
14 | "dependencies": {
15 | "@douglasneuroinformatics/libcrypto": "catalog:",
16 | "@opendatacapture/schemas": "workspace:*",
17 | "transliteration": "^2.3.5"
18 | },
19 | "devDependencies": {
20 | "type-fest": "workspace:type-fest__4.x@*"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/subject-utils/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "lib": ["DOM", "DOM.Iterable", "ESNext"]
5 | },
6 | "include": ["src/**/*", "vitest.config.ts"]
7 | }
8 |
--------------------------------------------------------------------------------
/packages/subject-utils/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { mergeConfig } from 'vitest/config';
2 |
3 | import baseConfig from '../../vitest.config';
4 |
5 | export default mergeConfig(baseConfig, {
6 | test: {
7 | root: import.meta.dirname
8 | }
9 | });
10 |
--------------------------------------------------------------------------------
/packages/vite-plugin-runtime/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@opendatacapture/vite-plugin-runtime",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "license": "Apache-2.0",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "default": "./src/index.js"
10 | }
11 | },
12 | "scripts": {
13 | "format": "prettier --write src",
14 | "lint": "tsc && eslint --fix src",
15 | "test": "vitest"
16 | },
17 | "peerDependencies": {
18 | "@opendatacapture/runtime-v1": "workspace:*",
19 | "vite": "catalog:"
20 | },
21 | "devDependencies": {
22 | "@douglasneuroinformatics/libjs": "catalog:"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/vite-plugin-runtime/src/index.d.ts:
--------------------------------------------------------------------------------
1 | import type { PluginOption } from 'vite';
2 |
3 | export type RuntimeManifest = {
4 | declarations: string[];
5 | sources: string[];
6 | styles: string[];
7 | };
8 |
9 | export type RuntimeVersionInfo = {
10 | baseDir: string;
11 | importPaths: string[];
12 | manifest: RuntimeManifest;
13 | version: string;
14 | };
15 |
16 | export declare function runtime(options?: { disabled?: boolean; packageRoot?: string }): PluginOption;
17 |
--------------------------------------------------------------------------------
/packages/vite-plugin-runtime/src/runtime-middleware.js:
--------------------------------------------------------------------------------
1 | import { loadResource } from './load-resource.js';
2 |
3 | /** @type {import('vite').Connect.NextHandleFunction} */
4 | export const runtimeMiddleware = (req, res, next) => {
5 | const [version, ...paths] = req.url?.split('/').filter(Boolean) ?? [];
6 | const filepath = paths.join('/');
7 | if (!(version && filepath)) {
8 | return next();
9 | }
10 | loadResource(version, filepath)
11 | .then((resource) => {
12 | if (!resource) {
13 | return next();
14 | }
15 | res.writeHead(200, { 'Content-Type': resource.contentType });
16 | res.end(resource.content);
17 | })
18 | .catch(next);
19 | };
20 |
--------------------------------------------------------------------------------
/packages/vite-plugin-runtime/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "maxNodeModuleJsDepth": 0
7 | },
8 | "include": ["src/**/*", "vitest.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/vite-plugin-runtime/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { mergeConfig } from 'vitest/config';
2 |
3 | import baseConfig from '../../vitest.config';
4 |
5 | export default mergeConfig(baseConfig, {
6 | test: {
7 | root: import.meta.dirname
8 | }
9 | });
10 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | import { createConfig } from '@douglasneuroinformatics/prettier-config';
2 |
3 | export default createConfig({
4 | astro: true,
5 | tailwindcss: true
6 | });
7 |
--------------------------------------------------------------------------------
/runtime/v1/global.d.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
2 | declare interface OpenDataCaptureContext {
3 | isRepo: typeof import('../../package.json') extends { __isODCRepo: NonNullable } ? true : false;
4 | }
5 |
--------------------------------------------------------------------------------
/runtime/v1/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "checkJs": false,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "rootDir": "../.."
7 | },
8 | "include": ["build.ts", "src/**/*", "*.js", "*.cjs", "env.d.ts", "global.d.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/scripts/drop-database.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -euo pipefail
4 | IFS=$'\n\t'
5 |
6 | [ "${BASH_VERSINFO:-0}" -ge 5 ] || (echo "Error: Bash >= 5.0 is required for this script" >&2 && exit 1)
7 |
8 | if [ "$#" -ne 1 ] || ([ "$1" != "development" ] && [ "$1" != "test" ]); then
9 | echo "Usage: $0 {development|test}" >&2 && exit 1
10 | fi
11 |
12 | mongosh "data-capture-$1" --eval 'db.dropDatabase()'
13 |
--------------------------------------------------------------------------------
/scripts/publish.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | #
3 | # First, generate an access token on GitHub (ssh is not supported)
4 | # echo $PASSWORD | docker login ghcr.io -u USERNAME --password-stdin
5 |
6 | set -euo pipefail
7 | IFS=$'\n\t'
8 |
9 | [ "${BASH_VERSINFO:-0}" -ge 5 ] || (echo "Error: Bash >= 5.0 is required for this script" >&2 && exit 1)
10 |
11 | docker push ghcr.io/douglasneuroinformatics/open-data-capture-api:latest
12 | docker push ghcr.io/douglasneuroinformatics/open-data-capture-gateway:latest
13 | docker push ghcr.io/douglasneuroinformatics/open-data-capture-playground:latest
14 | docker push ghcr.io/douglasneuroinformatics/open-data-capture-web:latest
15 |
--------------------------------------------------------------------------------
/scripts/purge-workflow.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | #
3 | # Delete all runs of the workflow
4 |
5 | set -euo pipefail
6 | IFS=$'\n\t'
7 |
8 | [ "${BASH_VERSINFO:-0}" -ge 5 ] || (echo "Error: Bash >= 5.0 is required for this script" >&2 && exit 1)
9 |
10 | if [ -z "$1" ]; then
11 | echo "ERROR: Must specify workflow to purge (e.g., ci.yaml)"
12 | exit 1
13 | fi
14 |
15 | gh run list --limit 500 --workflow "$1" --json databaseId | jq -r '.[] | .databaseId | @sh' | xargs -I{} gh run delete "{}"
16 |
--------------------------------------------------------------------------------
/storybook/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.base.json",
3 | "compilerOptions": {
4 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
5 | "paths": {
6 | "/runtime/v1/*": ["../../runtime/v1/dist/*"]
7 | },
8 | "types": ["vite/client"]
9 | },
10 | "include": ["config/*", "vite.config.js"]
11 | }
12 |
--------------------------------------------------------------------------------
/testing/cypress/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@opendatacapture/cypress",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "private": true,
6 | "license": "Apache-2.0",
7 | "scripts": {
8 | "cy:install": "cypress install",
9 | "cy:open": "cypress open",
10 | "cy:run": "cypress run",
11 | "format": "prettier --write .",
12 | "lint": "tsc && eslint --fix .",
13 | "test": "pnpm cy:run"
14 | },
15 | "dependencies": {
16 | "@casl/ability": "catalog:",
17 | "@douglasneuroinformatics/libjs": "catalog:",
18 | "@faker-js/faker": "^9.4.0",
19 | "@opendatacapture/schemas": "workspace:",
20 | "cypress": "^13.14.2"
21 | },
22 | "devDependencies": {
23 | "type-fest": "workspace:type-fest__4.x@*"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/testing/cypress/src/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
6 |
--------------------------------------------------------------------------------
/testing/cypress/src/stubs.ts:
--------------------------------------------------------------------------------
1 | import { faker } from '@faker-js/faker';
2 | import type { SexType } from '@faker-js/faker';
3 | import type { ClinicalSubjectIdentificationData } from '@opendatacapture/schemas/subject';
4 |
5 | export const admin = Object.freeze({
6 | firstName: 'David',
7 | lastName: 'Roper',
8 | password: 'DataCapture2024',
9 | username: 'ropdav'
10 | });
11 |
12 | export const createSubjectIdentificationData = (): ClinicalSubjectIdentificationData => ({
13 | dateOfBirth: faker.date.birthdate({
14 | max: 80,
15 | min: 18,
16 | mode: 'age'
17 | }),
18 | firstName: faker.person.firstName(),
19 | lastName: faker.person.lastName(),
20 | sex: faker.person.sex().toUpperCase() as Uppercase
21 | });
22 |
--------------------------------------------------------------------------------
/testing/cypress/src/support/e2e.ts:
--------------------------------------------------------------------------------
1 | import './commands';
2 |
--------------------------------------------------------------------------------
/testing/cypress/src/utils.ts:
--------------------------------------------------------------------------------
1 | export const createDownloadsFolder = () => {
2 | const downloadsFolder = Cypress.config('downloadsFolder');
3 | cy.task('mkdir', downloadsFolder);
4 | };
5 |
6 | export const deleteDownloadsFolder = () => {
7 | const downloadsFolder = Cypress.config('downloadsFolder');
8 | cy.task('rmdir', downloadsFolder);
9 | };
10 |
--------------------------------------------------------------------------------
/testing/cypress/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@douglasneuroinformatics/tsconfig",
3 | "compilerOptions": {
4 | "module": "Preserve",
5 | "moduleResolution": "Bundler",
6 | "types": ["cypress", "node"]
7 | },
8 | "include": ["cypress.config.ts", "src/**/*"]
9 | }
10 |
--------------------------------------------------------------------------------
/testing/k6/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["scripts/build.js", "src/**/*", "*.js", "*.cjs"]
4 | }
5 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig.json",
3 | "extends": ["@douglasneuroinformatics/tsconfig"],
4 | "compilerOptions": {
5 | "allowJs": true,
6 | "allowSyntheticDefaultImports": true,
7 | "checkJs": true,
8 | "jsx": "react-jsx",
9 | "module": "ESNext",
10 | "moduleResolution": "Bundler",
11 | "resolveJsonModule": true
12 | },
13 | "include": ["knip.ts", "vitest.config.ts", "vitest.workspace.ts"],
14 | "exclude": ["docs/*", "**/dist", "**/node_modules", "vendor/**/*"]
15 | }
16 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-html-button-response@1.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jspsych/plugin-html-button-response__1.x",
3 | "type": "module",
4 | "version": "1.2.0",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "@jspsych/plugin-html-button-response": "1.2.0",
15 | "jspsych__7.x": "workspace:*"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-html-button-response@1.x/src/index.js:
--------------------------------------------------------------------------------
1 | export { default, default as HtmlButtonResponsePlugin } from '@jspsych/plugin-html-button-response';
2 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-html-button-response@2.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jspsych/plugin-html-button-response__2.x",
3 | "type": "module",
4 | "version": "2.0.0",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "@jspsych/plugin-html-button-response": "2.0.0",
15 | "jspsych__8.x": "workspace:*"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-html-button-response@2.x/src/index.js:
--------------------------------------------------------------------------------
1 | export { default, default as HtmlButtonResponsePlugin } from '@jspsych/plugin-html-button-response';
2 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-html-keyboard-response@1.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jspsych/plugin-html-keyboard-response__1.x",
3 | "type": "module",
4 | "version": "1.1.3",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "@jspsych/plugin-html-keyboard-response": "1.1.3",
15 | "jspsych__7.x": "workspace:*"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-html-keyboard-response@1.x/src/index.js:
--------------------------------------------------------------------------------
1 | export { default, default as HtmlKeyboardResponsePlugin } from '@jspsych/plugin-html-keyboard-response';
2 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-html-keyboard-response@2.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jspsych/plugin-html-keyboard-response__2.x",
3 | "type": "module",
4 | "version": "2.0.0",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "@jspsych/plugin-html-keyboard-response": "2.0.0",
15 | "jspsych__8.x": "workspace:*"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-html-keyboard-response@2.x/src/index.js:
--------------------------------------------------------------------------------
1 | export { default, default as HtmlKeyboardResponsePlugin } from '@jspsych/plugin-html-keyboard-response';
2 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-image-button-response@1.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jspsych/plugin-image-button-response__1.x",
3 | "type": "module",
4 | "version": "1.2.0",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "@jspsych/plugin-image-button-response": "1.2.0",
15 | "jspsych__7.x": "workspace:*"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-image-button-response@1.x/src/index.js:
--------------------------------------------------------------------------------
1 | export { default, default as ImageButtonResponsePlugin } from '@jspsych/plugin-image-button-response';
2 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-image-button-response@2.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jspsych/plugin-image-button-response__2.x",
3 | "type": "module",
4 | "version": "2.0.0",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "@jspsych/plugin-image-button-response": "2.0.0",
15 | "jspsych__8.x": "workspace:*"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-image-button-response@2.x/src/index.js:
--------------------------------------------------------------------------------
1 | export { default, default as ImageButtonResponsePlugin } from '@jspsych/plugin-image-button-response';
2 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-image-keyboard-response@1.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jspsych/plugin-image-keyboard-response__1.x",
3 | "type": "module",
4 | "version": "1.1.3",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "@jspsych/plugin-image-keyboard-response": "1.1.3",
15 | "jspsych__7.x": "workspace:*"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-image-keyboard-response@1.x/src/index.js:
--------------------------------------------------------------------------------
1 | export { default, default as ImageKeyboardResponsePlugin } from '@jspsych/plugin-image-keyboard-response';
2 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-image-keyboard-response@2.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jspsych/plugin-image-keyboard-response__2.x",
3 | "type": "module",
4 | "version": "2.0.0",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "@jspsych/plugin-image-keyboard-response": "2.0.0",
15 | "jspsych__8.x": "workspace:*"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-image-keyboard-response@2.x/src/index.js:
--------------------------------------------------------------------------------
1 | export { default, default as ImageKeyboardResponsePlugin } from '@jspsych/plugin-image-keyboard-response';
2 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-instructions@2.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jspsych/plugin-instructions__2.x",
3 | "type": "module",
4 | "version": "2.0.0",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "@jspsych/plugin-instructions": "2.0.0",
15 | "jspsych__8.x": "workspace:*"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-instructions@2.x/src/index.js:
--------------------------------------------------------------------------------
1 | export { default, default as InstructionsPlugin } from '@jspsych/plugin-instructions';
2 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-preload@1.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jspsych/plugin-preload__1.x",
3 | "type": "module",
4 | "version": "1.1.3",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "@jspsych/plugin-preload": "1.1.3",
15 | "jspsych__7.x": "workspace:*"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-preload@1.x/src/index.js:
--------------------------------------------------------------------------------
1 | export { default, default as PreloadPlugin } from '@jspsych/plugin-preload';
2 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-preload@2.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jspsych/plugin-preload__2.x",
3 | "type": "module",
4 | "version": "2.0.0",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "@jspsych/plugin-preload": "2.0.0",
15 | "jspsych__8.x": "workspace:*"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-preload@2.x/src/index.js:
--------------------------------------------------------------------------------
1 | export { default, default as PreloadPlugin } from '@jspsych/plugin-preload';
2 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-survey-html-form@1.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jspsych/plugin-survey-html-form__1.x",
3 | "type": "module",
4 | "version": "1.0.3",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "@jspsych/plugin-survey-html-form": "1.0.3",
15 | "jspsych__7.x": "workspace:*"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-survey-html-form@1.x/src/index.js:
--------------------------------------------------------------------------------
1 | export { default, default as SurveyHtmlFormPlugin } from '@jspsych/plugin-survey-html-form';
2 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-survey-html-form@2.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jspsych/plugin-survey-html-form__2.x",
3 | "type": "module",
4 | "version": "2.0.0",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "@jspsych/plugin-survey-html-form": "2.0.0",
15 | "jspsych__8.x": "workspace:*"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-survey-html-form@2.x/src/index.js:
--------------------------------------------------------------------------------
1 | export { default, default as SurveyHtmlFormPlugin } from '@jspsych/plugin-survey-html-form';
2 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-survey-text@1.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jspsych/plugin-survey-text__1.x",
3 | "type": "module",
4 | "version": "1.1.3",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "@jspsych/plugin-survey-text": "1.1.3",
15 | "jspsych__7.x": "workspace:*"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-survey-text@1.x/src/index.js:
--------------------------------------------------------------------------------
1 | export { default, default as SurveyTextPlugin } from '@jspsych/plugin-survey-text';
2 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-survey-text@2.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jspsych/plugin-survey-text__2.x",
3 | "type": "module",
4 | "version": "2.0.0",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "@jspsych/plugin-survey-text": "2.0.0",
15 | "jspsych__8.x": "workspace:*"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/vendor/@jspsych/plugin-survey-text@2.x/src/index.js:
--------------------------------------------------------------------------------
1 | export { default, default as SurveyTextPlugin } from '@jspsych/plugin-survey-text';
2 |
--------------------------------------------------------------------------------
/vendor/csstype@3.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "csstype__3.x",
3 | "type": "module",
4 | "version": "3.1.3",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts"
9 | },
10 | "./package.json": "./package.json"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/vendor/dompurify@3.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dompurify__3.x",
3 | "type": "module",
4 | "version": "3.1.6",
5 | "license": "Apache-2.0",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "dompurify": "3.1.6"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/vendor/dompurify@3.x/src/index.js:
--------------------------------------------------------------------------------
1 | export { default, default as DOMPurify } from 'dompurify';
2 |
--------------------------------------------------------------------------------
/vendor/jquery-ui@1.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery-ui__1.x",
3 | "type": "module",
4 | "version": "1.14.0",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "jquery-ui": "1.14.0",
15 | "jquery__3.x": "workspace:"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/vendor/jquery-ui@1.x/src/index.js:
--------------------------------------------------------------------------------
1 | import 'jquery-ui';
2 |
--------------------------------------------------------------------------------
/vendor/jquery@1.12.4/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery__1.12.4",
3 | "type": "module",
4 | "version": "1.12.4",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "jquery": "1.12.4"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/vendor/jquery@1.12.4/src/index.js:
--------------------------------------------------------------------------------
1 | import jQuery from 'jquery';
2 |
3 | export { jQuery, jQuery as $, jQuery as default };
4 |
--------------------------------------------------------------------------------
/vendor/jquery@3.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery__3.x",
3 | "type": "module",
4 | "version": "3.7.1",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "jquery": "3.7.1"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/vendor/jquery@3.x/src/index.js:
--------------------------------------------------------------------------------
1 | import jQuery from 'jquery';
2 |
3 | export { jQuery, jQuery as $, jQuery as default };
4 |
--------------------------------------------------------------------------------
/vendor/jspsych@7.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jspsych__7.x",
3 | "type": "module",
4 | "version": "7.3.4",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./css/jspsych.css": "./src/index.css",
12 | "./package.json": "./package.json"
13 | },
14 | "dependencies": {
15 | "jspsych": "7.3.4"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/vendor/jspsych@7.x/src/index.css:
--------------------------------------------------------------------------------
1 | @import 'jspsych/css/jspsych.css';
2 |
--------------------------------------------------------------------------------
/vendor/jspsych@7.x/src/index.js:
--------------------------------------------------------------------------------
1 | export * from 'jspsych';
2 |
--------------------------------------------------------------------------------
/vendor/jspsych@8.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jspsych__8.x",
3 | "type": "module",
4 | "version": "8.0.1",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./css/jspsych.css": "./src/index.css",
12 | "./package.json": "./package.json"
13 | },
14 | "dependencies": {
15 | "jspsych": "8.0.1",
16 | "type-fest__4.x": "workspace:"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/vendor/jspsych@8.x/src/index.css:
--------------------------------------------------------------------------------
1 | @import 'jspsych/css/jspsych.css';
2 |
--------------------------------------------------------------------------------
/vendor/jspsych@8.x/src/index.js:
--------------------------------------------------------------------------------
1 | export * from 'jspsych';
2 |
--------------------------------------------------------------------------------
/vendor/lodash-es@4.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lodash-es__4.x",
3 | "type": "module",
4 | "version": "4.17.21",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "lodash-es": "4.17.21"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/vendor/lodash-es@4.x/src/index.js:
--------------------------------------------------------------------------------
1 | export * from 'lodash-es';
2 |
--------------------------------------------------------------------------------
/vendor/normalize.css@8.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "normalize.css__8.x",
3 | "type": "module",
4 | "version": "8.0.1",
5 | "license": "MIT",
6 | "exports": {
7 | "./normalize.css": "./src/index.css",
8 | "./package.json": "./package.json"
9 | },
10 | "dependencies": {
11 | "normalize.css": "8.0.1"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/vendor/normalize.css@8.x/src/index.css:
--------------------------------------------------------------------------------
1 | @import 'normalize.css';
2 |
--------------------------------------------------------------------------------
/vendor/papaparse@5.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "papaparse__5.x",
3 | "type": "module",
4 | "version": "5.4.1",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "papaparse": "5.4.1"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/vendor/papaparse@5.x/src/index.js:
--------------------------------------------------------------------------------
1 | export {
2 | BAD_DELIMITERS,
3 | BYTE_ORDER_MARK,
4 | default,
5 | DefaultDelimiter,
6 | FileStreamer,
7 | LocalChunkSize,
8 | NetworkStreamer,
9 | NODE_STREAM_INPUT,
10 | parse,
11 | Parser,
12 | ParserHandle,
13 | ReadableStreamStreamer,
14 | RECORD_SEP,
15 | RemoteChunkSize,
16 | StringStreamer,
17 | UNIT_SEP,
18 | unparse,
19 | WORKERS_SUPPORTED
20 | } from 'papaparse';
21 |
--------------------------------------------------------------------------------
/vendor/preloadjs@1.0.1/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "preloadjs__1.0.1",
3 | "type": "module",
4 | "version": "1.0.1",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/vendor/prop-types@15.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "prop-types__15.x",
3 | "type": "module",
4 | "version": "15.8.1",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "prop-types": "15.8.1"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/vendor/prop-types@15.x/src/index.js:
--------------------------------------------------------------------------------
1 | export {
2 | default,
3 | array,
4 | bigint,
5 | bool,
6 | func,
7 | number,
8 | object,
9 | string,
10 | symbol,
11 | any,
12 | arrayOf,
13 | element,
14 | elementType,
15 | instanceOf,
16 | node,
17 | objectOf,
18 | oneOf,
19 | oneOfType,
20 | shape,
21 | exact,
22 | checkPropTypes,
23 | resetWarningCache,
24 | PropTypes
25 | } from 'prop-types';
26 |
--------------------------------------------------------------------------------
/vendor/psychojs@2023.1.3/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "psychojs__2023.1.3",
3 | "type": "module",
4 | "version": "2023.1.3",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./css/psychojs.css": "./src/index.css",
12 | "./package.json": "./package.json"
13 | },
14 | "dependencies": {
15 | "type-fest__4.x": "workspace:"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/vendor/pure-rand@6.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pure-rand__6.x",
3 | "type": "module",
4 | "version": "6.1.0",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "pure-rand": "6.1.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/vendor/pure-rand@6.x/src/index.js:
--------------------------------------------------------------------------------
1 | export {
2 | __commitHash,
3 | __type,
4 | __version,
5 | congruential32,
6 | default,
7 | generateN,
8 | mersenne,
9 | skipN,
10 | uniformArrayIntDistribution,
11 | uniformBigIntDistribution,
12 | uniformIntDistribution,
13 | unsafeGenerateN,
14 | unsafeSkipN,
15 | unsafeUniformArrayIntDistribution,
16 | unsafeUniformBigIntDistribution,
17 | unsafeUniformIntDistribution,
18 | xoroshiro128plus,
19 | xorshift128plus
20 | } from 'pure-rand';
21 |
--------------------------------------------------------------------------------
/vendor/react-dom@18.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-dom__18.x",
3 | "type": "module",
4 | "version": "18.3.1",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "default": "./src/index.js"
10 | },
11 | "./client": {
12 | "types": "./src/client.d.ts",
13 | "default": "./src/client.js"
14 | },
15 | "./server": {
16 | "types": "./src/server.d.ts",
17 | "default": "./src/server.js"
18 | },
19 | "./package.json": "./package.json"
20 | },
21 | "dependencies": {
22 | "react-dom": "18.3.1",
23 | "react__18.x": "workspace:"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/vendor/react-dom@18.x/src/client.js:
--------------------------------------------------------------------------------
1 | export { createRoot, default, hydrateRoot } from 'react-dom/client';
2 |
--------------------------------------------------------------------------------
/vendor/react-dom@18.x/src/index.js:
--------------------------------------------------------------------------------
1 | export {
2 | __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
3 | createPortal,
4 | createRoot,
5 | default,
6 | findDOMNode,
7 | flushSync,
8 | hydrate,
9 | hydrateRoot,
10 | render,
11 | unmountComponentAtNode,
12 | unstable_batchedUpdates,
13 | unstable_renderSubtreeIntoContainer,
14 | version
15 | } from 'react-dom';
16 |
--------------------------------------------------------------------------------
/vendor/react-dom@18.x/src/server.js:
--------------------------------------------------------------------------------
1 | export {
2 | default,
3 | renderToNodeStream,
4 | renderToPipeableStream,
5 | renderToReadableStream,
6 | renderToStaticMarkup,
7 | renderToStaticNodeStream,
8 | renderToString,
9 | version
10 | } from 'react-dom/server';
11 |
--------------------------------------------------------------------------------
/vendor/react-dom@19.x/src/client.js:
--------------------------------------------------------------------------------
1 | export { default, createRoot, hydrateRoot, version } from 'react-dom/client';
2 |
--------------------------------------------------------------------------------
/vendor/react-dom@19.x/src/index.js:
--------------------------------------------------------------------------------
1 | export {
2 | default,
3 | __DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
4 | createPortal,
5 | flushSync,
6 | preconnect,
7 | prefetchDNS,
8 | preinit,
9 | preinitModule,
10 | preload,
11 | preloadModule,
12 | requestFormReset,
13 | unstable_batchedUpdates,
14 | useFormState,
15 | useFormStatus,
16 | version
17 | } from 'react-dom';
18 |
--------------------------------------------------------------------------------
/vendor/react-dom@19.x/src/profiling.d.ts:
--------------------------------------------------------------------------------
1 | export {};
2 |
--------------------------------------------------------------------------------
/vendor/react-dom@19.x/src/profiling.js:
--------------------------------------------------------------------------------
1 | export {
2 | default,
3 | __DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
4 | createPortal,
5 | createRoot,
6 | flushSync,
7 | hydrateRoot,
8 | preconnect,
9 | prefetchDNS,
10 | preinit,
11 | preinitModule,
12 | preload,
13 | preloadModule,
14 | requestFormReset,
15 | unstable_batchedUpdates,
16 | useFormState,
17 | useFormStatus,
18 | version
19 | } from 'react-dom/profiling';
20 |
--------------------------------------------------------------------------------
/vendor/react-dom@19.x/src/server.js:
--------------------------------------------------------------------------------
1 | export { default, renderToPipeableStream, renderToStaticMarkup, renderToString, version } from 'react-dom/server';
2 |
--------------------------------------------------------------------------------
/vendor/react-dom@19.x/src/static.d.ts:
--------------------------------------------------------------------------------
1 | export {};
2 |
--------------------------------------------------------------------------------
/vendor/react-dom@19.x/src/static.js:
--------------------------------------------------------------------------------
1 | export { default, version, prerenderToNodeStream, resumeAndPrerenderToNodeStream } from 'react-dom/static';
2 |
--------------------------------------------------------------------------------
/vendor/react-dom@19.x/src/test-utils.d.ts:
--------------------------------------------------------------------------------
1 | export {};
2 |
--------------------------------------------------------------------------------
/vendor/react-dom@19.x/src/test-utils.js:
--------------------------------------------------------------------------------
1 | export { default, act } from 'react-dom/test-utils';
2 |
--------------------------------------------------------------------------------
/vendor/react@18.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react__18.x",
3 | "version": "18.3.1",
4 | "license": "MIT",
5 | "type": "module",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "default": "./src/index.js"
10 | },
11 | "./jsx-runtime": {
12 | "types": "./src/jsx-runtime.d.ts",
13 | "default": "./src/jsx-runtime.js"
14 | },
15 | "./jsx-dev-runtime": {
16 | "types": "./src/jsx-dev-runtime.d.ts",
17 | "default": "./src/jsx-dev-runtime.js"
18 | },
19 | "./package.json": "./package.json"
20 | },
21 | "dependencies": {
22 | "csstype__3.x": "workspace:*",
23 | "prop-types__15.x": "workspace:*",
24 | "react": "18.3.1"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/vendor/react@18.x/src/jsx-dev-runtime.js:
--------------------------------------------------------------------------------
1 | export { default, Fragment, jsxDEV } from 'react/jsx-dev-runtime';
2 |
--------------------------------------------------------------------------------
/vendor/react@18.x/src/jsx-runtime.js:
--------------------------------------------------------------------------------
1 | export { default, Fragment, jsx, jsxs } from 'react/jsx-runtime';
2 |
--------------------------------------------------------------------------------
/vendor/react@19.x/src/compiler-runtime.d.ts:
--------------------------------------------------------------------------------
1 | // Not meant to be used directly
2 | // Omitting all exports so that they don't appear in IDE autocomplete.
3 |
4 | export {};
5 |
--------------------------------------------------------------------------------
/vendor/react@19.x/src/compiler-runtime.js:
--------------------------------------------------------------------------------
1 | export { default, c } from 'react/compiler-runtime';
2 |
--------------------------------------------------------------------------------
/vendor/react@19.x/src/jsx-dev-runtime.js:
--------------------------------------------------------------------------------
1 | export { default, Fragment, jsxDEV } from 'react/jsx-dev-runtime';
2 |
--------------------------------------------------------------------------------
/vendor/react@19.x/src/jsx-runtime.js:
--------------------------------------------------------------------------------
1 | export { default, Fragment, jsx, jsxs } from 'react/jsx-runtime';
2 |
--------------------------------------------------------------------------------
/vendor/simple-statistics@7.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "simple-statistics__7.x",
3 | "type": "module",
4 | "version": "7.8.3",
5 | "license": "ISC",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "simple-statistics": "7.8.3"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/vendor/simple-statistics@7.x/src/index.js:
--------------------------------------------------------------------------------
1 | export * from 'simple-statistics';
2 |
--------------------------------------------------------------------------------
/vendor/ts-pattern@5.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ts-pattern__5.x",
3 | "type": "module",
4 | "version": "5.3.1",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "ts-pattern": "5.3.1"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/vendor/ts-pattern@5.x/src/index.js:
--------------------------------------------------------------------------------
1 | export * from 'ts-pattern';
2 |
--------------------------------------------------------------------------------
/vendor/type-fest@4.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "type-fest__4.x",
3 | "type": "module",
4 | "version": "4.23.0",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts"
9 | },
10 | "./package.json": "./package.json"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/vendor/zod@3.23.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "zod__3.23.x",
3 | "type": "module",
4 | "version": "3.25.36",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./package.json": "./package.json"
12 | },
13 | "dependencies": {
14 | "zod__3.x": "workspace:*"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/vendor/zod@3.23.x/src/index.d.ts:
--------------------------------------------------------------------------------
1 | export * from 'zod__3.x';
2 |
--------------------------------------------------------------------------------
/vendor/zod@3.23.x/src/index.js:
--------------------------------------------------------------------------------
1 | export * from 'zod__3.x';
2 |
--------------------------------------------------------------------------------
/vendor/zod@3.x/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "zod__3.x",
3 | "type": "module",
4 | "version": "3.25.36",
5 | "license": "MIT",
6 | "exports": {
7 | ".": {
8 | "types": "./src/index.d.ts",
9 | "import": "./src/index.js"
10 | },
11 | "./v3": {
12 | "types": "./src/v3/index.d.ts",
13 | "import": "./src/v3/index.js"
14 | },
15 | "./v4": {
16 | "types": "./src/v4/index.d.ts",
17 | "import": "./src/v4/index.js"
18 | },
19 | "./v4-mini": {
20 | "types": "./src/v4-mini/index.d.ts",
21 | "import": "./src/v4-mini/index.js"
22 | },
23 | "./package.json": "./package.json"
24 | },
25 | "dependencies": {
26 | "zod": "^3.25.36"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/vendor/zod@3.x/src/index.d.ts:
--------------------------------------------------------------------------------
1 | export * from './v3/index.d.ts';
2 |
--------------------------------------------------------------------------------
/vendor/zod@3.x/src/index.js:
--------------------------------------------------------------------------------
1 | export * from './v3/index.js';
2 |
--------------------------------------------------------------------------------
/vendor/zod@3.x/src/v3/index.js:
--------------------------------------------------------------------------------
1 | export { z } from 'zod/v3';
2 |
--------------------------------------------------------------------------------
/vendor/zod@3.x/src/v4-mini/index.d.ts:
--------------------------------------------------------------------------------
1 | export {};
2 |
--------------------------------------------------------------------------------
/vendor/zod@3.x/src/v4-mini/index.js:
--------------------------------------------------------------------------------
1 | export * from 'zod/v4-mini';
2 |
--------------------------------------------------------------------------------
/vendor/zod@3.x/src/v4/index.js:
--------------------------------------------------------------------------------
1 | export { z } from 'zod/v4';
2 |
--------------------------------------------------------------------------------
/vitest.workspace.ts:
--------------------------------------------------------------------------------
1 | import { defineWorkspace } from 'vitest/config';
2 |
3 | export default defineWorkspace(['apps/*/vitest.config.ts', 'packages/*/vitest.config.ts']);
4 |
--------------------------------------------------------------------------------