├── .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 | 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 |

404 - Not Found

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