@adriansteffan/reactive 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/.eslintrc.cjs +18 -0
  2. package/.prettierrc +5 -0
  3. package/Dockerfile +20 -0
  4. package/README.md +68 -0
  5. package/bin/setup.js +100 -0
  6. package/dist/mod.d.ts +102 -0
  7. package/dist/reactivepsych.es.js +71241 -0
  8. package/dist/reactivepsych.umd.js +120 -0
  9. package/dist/style.css +5 -0
  10. package/dist/tailwind.config.js +33 -0
  11. package/package.json +75 -0
  12. package/postcss.config.js +6 -0
  13. package/src/components/experiment.tsx +156 -0
  14. package/src/components/experimentprovider.tsx +28 -0
  15. package/src/components/mastermindlewrapper.tsx +662 -0
  16. package/src/components/microphonecheck.tsx +167 -0
  17. package/src/components/quest.tsx +102 -0
  18. package/src/components/text.tsx +45 -0
  19. package/src/components/upload.tsx +149 -0
  20. package/src/components/voicerecorder.tsx +346 -0
  21. package/src/index.css +74 -0
  22. package/src/mod.tsx +14 -0
  23. package/src/utils/common.ts +80 -0
  24. package/src/utils/request.ts +25 -0
  25. package/src/vite-env.d.ts +1 -0
  26. package/tailwind.config.js +33 -0
  27. package/template/.dockerignore +5 -0
  28. package/template/.eslintrc.cjs +18 -0
  29. package/template/.prettierrc +5 -0
  30. package/template/Dockerfile +25 -0
  31. package/template/README.md +102 -0
  32. package/template/backend/package-lock.json +2398 -0
  33. package/template/backend/package.json +31 -0
  34. package/template/backend/src/backend.ts +99 -0
  35. package/template/backend/tsconfig.json +110 -0
  36. package/template/docker-compose.yaml +13 -0
  37. package/template/index.html +15 -0
  38. package/template/package-lock.json +6031 -0
  39. package/template/package.json +48 -0
  40. package/template/postcss.config.js +6 -0
  41. package/template/public/Atkinson_Hyperlegible/AtkinsonHyperlegible-Bold.ttf +0 -0
  42. package/template/public/Atkinson_Hyperlegible/AtkinsonHyperlegible-BoldItalic.ttf +0 -0
  43. package/template/public/Atkinson_Hyperlegible/AtkinsonHyperlegible-Italic.ttf +0 -0
  44. package/template/public/Atkinson_Hyperlegible/AtkinsonHyperlegible-Regular.ttf +0 -0
  45. package/template/public/Atkinson_Hyperlegible/OFL.txt +93 -0
  46. package/template/src/App.tsx +116 -0
  47. package/template/src/index.css +3 -0
  48. package/template/src/main.tsx +14 -0
  49. package/template/tailwind.config.js +7 -0
  50. package/template/tsconfig.json +25 -0
  51. package/template/tsconfig.node.json +11 -0
  52. package/template/vite.config.ts +24 -0
  53. package/tsconfig.json +28 -0
  54. package/tsconfig.node.json +12 -0
  55. package/vite.config.ts +48 -0
package/.eslintrc.cjs ADDED
@@ -0,0 +1,18 @@
1
+ module.exports = {
2
+ root: true,
3
+ env: { browser: true, es2020: true, node: true, commonjs: true },
4
+ extends: [
5
+ 'eslint:recommended',
6
+ 'plugin:@typescript-eslint/recommended',
7
+ 'plugin:react-hooks/recommended',
8
+ ],
9
+ ignorePatterns: ['dist', '.eslintrc.cjs'],
10
+ parser: '@typescript-eslint/parser',
11
+ plugins: ['react-refresh'],
12
+ rules: {
13
+ 'react-refresh/only-export-components': [
14
+ 'warn',
15
+ { allowConstantExport: true },
16
+ ],
17
+ },
18
+ }
package/.prettierrc ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "printWidth": 100,
3
+ "jsxSingleQuote": true,
4
+ "singleQuote": true
5
+ }
package/Dockerfile ADDED
@@ -0,0 +1,20 @@
1
+ FROM node:21-bookworm-slim
2
+
3
+ RUN apt-get update
4
+ RUN apt-get upgrade -y
5
+ RUN apt-get install -y git curl software-properties-common apache2
6
+ WORKDIR /srv/frontend
7
+
8
+ # this assumes build context one directory up
9
+ COPY ./frontend .
10
+ COPY .env .env
11
+
12
+ RUN npm install
13
+ RUN a2enmod rewrite
14
+ RUN sed -i 's%/var/www/html%/srv/frontend/dist%g' /etc/apache2/sites-available/000-default.conf && sed -i 's%ServerTokens OS%ServerTokens Prod%g' /etc/apache2/conf-available/security.conf && sed -i 's%ServerSignature On%ServerSignature Off%g' /etc/apache2/conf-available/security.conf && sed -i 's%<Directory /var/www/>%<Directory /srv/frontend/dist/>%g' /etc/apache2/apache2.conf && sed -i 's|AllowOverride None|AllowOverride All\nOptions -MultiViews\nRewriteEngine On\nRewriteCond %{REQUEST_FILENAME} !-f\nRewriteRule ^ index.html [QSA,L]|g' /etc/apache2/apache2.conf
15
+ RUN npm run build
16
+ RUN rm .env
17
+
18
+ EXPOSE 80
19
+
20
+ CMD ["apachectl","-D","FOREGROUND"]
package/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # ReactivePsych
2
+
3
+ A framework for quickly building interactive online experiments using Typescript, React, and TailwindCSS. Comes with a template project that has all batteries included (build tools, docker deployment setup, node server for upload etc.)
4
+ The project is very early stage, so many of the abstractions are still very leaky and the documentation is largely unfinished.
5
+
6
+
7
+ ## Prerequisites
8
+
9
+ You will need a current version of [node.js](https://nodejs.org/en/download/) installed on your system.
10
+
11
+ ## Using the package
12
+
13
+ ### Create a template project
14
+
15
+ ```
16
+ npx @adriansteffan/reactive
17
+ ```
18
+
19
+ Then follow the instructions shown there and in the created `README.md`
20
+
21
+
22
+ ### Usage
23
+
24
+ For now, refer to the `App.tsx` in the template project to find out how to define an experiment, and add custom trials and questions!
25
+
26
+ Premade components available so far:
27
+
28
+ * Text: A simple display of Text and a Button
29
+ * MicCheck: used to test the voice recording feature and choose a preferred microphone to use
30
+ * Quest: SurveyJS questionnaires
31
+ * ... all questiontypes supported by SurveyJS can be used
32
+ * voicerecorder: a custom question type that allows participants to record voice
33
+ * MasterMindleWrapper: (weird name atm, needs splitting up) Participants play a game similar to Mastermind with varying difficulties
34
+ * Upload: Uploads the collected data on a button press by the participant
35
+
36
+
37
+
38
+ ## Development
39
+
40
+
41
+ Run this to in the root of the repo to build the project locally (also needs to be run after every change):
42
+
43
+ ```
44
+ npm run build
45
+ ```
46
+
47
+ Then create a global link (only needs to run once during setup);
48
+ ```
49
+ npm link
50
+ ```
51
+
52
+ Then set up a local testing project:
53
+
54
+ ```
55
+ npx @adriansteffan/reactive
56
+ npm uninstall @adriansteffan/reactive && npm link @adriansteffan/reactive
57
+ ```
58
+
59
+
60
+ Manually publishing to npm (until we figure out a better ci/cd process):
61
+ ```
62
+ npm publish
63
+ ```
64
+
65
+
66
+ ## Authors
67
+
68
+ * **Adrian Steffan** - [adriansteffan](https://github.com/adriansteffan)
package/bin/setup.js ADDED
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { mkdirSync, readdirSync, copyFileSync, existsSync, writeFileSync, readFileSync } from 'fs';
4
+ import { join, dirname } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import readline from 'readline';
7
+ import { createRequire } from 'module';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+
12
+ const version = createRequire(import.meta.url)(join(__dirname, '../package.json')).version;
13
+ console.log(version)
14
+
15
+ const rl = readline.createInterface({
16
+ input: process.stdin,
17
+ output: process.stdout
18
+ });
19
+
20
+
21
+ const question = (query) => new Promise((resolve) => rl.question(query, resolve));
22
+
23
+
24
+ function copyDir(src, dest, ignoreFiles = []) {
25
+ mkdirSync(dest, { recursive: true });
26
+
27
+ const entries = readdirSync(src, { withFileTypes: true });
28
+
29
+ for (let entry of entries) {
30
+ const srcPath = join(src, entry.name);
31
+ const destPath = join(dest, entry.name);
32
+
33
+ // Check if the current file path matches any in the ignore list
34
+ const shouldIgnore = ignoreFiles.some(ignorePath =>
35
+ join(src, entry.name) === join(src, ignorePath)
36
+ );
37
+
38
+ if (entry.isDirectory()) {
39
+ // only ignore at top level
40
+ copyDir(srcPath, destPath);
41
+ } else if (shouldIgnore) {
42
+ continue;
43
+ } else {
44
+ copyFileSync(srcPath, destPath);
45
+ }
46
+ }
47
+ }
48
+
49
+ async function main() {
50
+ try {
51
+
52
+ const projectName = await question('Please enter your project name: ');
53
+
54
+ if (!projectName.trim()) {
55
+ console.error('Project name cannot be empty');
56
+ process.exit(1);
57
+ }
58
+
59
+ const projectPath = join(process.cwd(), projectName);
60
+ mkdirSync(projectPath, { recursive: true });
61
+
62
+ const templatePath = join(__dirname, '../template');
63
+
64
+ if (!existsSync(templatePath)) {
65
+ console.error('Template directory not found');
66
+ process.exit(1);
67
+ }
68
+
69
+ copyDir(templatePath, projectPath, ['package.json']);
70
+
71
+ const templatePackageJsonPath = join(templatePath, 'package.json');
72
+ if (!existsSync(templatePackageJsonPath)) {
73
+ console.error('Template package.json not found');
74
+ process.exit(1);
75
+ }
76
+
77
+ let packageJsonContent = readFileSync(templatePackageJsonPath, 'utf8');
78
+ packageJsonContent = packageJsonContent.replace(/PROJECT_NAME/g, projectName).replace(/RP_VERSION/g, version);
79
+
80
+ writeFileSync(join(projectPath, 'package.json'), packageJsonContent);
81
+
82
+ console.log(`
83
+ Project ${projectName} created successfully!
84
+
85
+ To get started:
86
+ cd ${projectName}
87
+ npm i && npm i --prefix backend
88
+ npm run dev:all
89
+ `);
90
+
91
+ } catch (error) {
92
+ console.error('Error creating project:', error);
93
+ console.error('Error details:', error.message);
94
+ process.exit(1);
95
+ } finally {
96
+ rl.close();
97
+ }
98
+ }
99
+
100
+ main();
package/dist/mod.d.ts ADDED
@@ -0,0 +1,102 @@
1
+ import { ComponentType } from 'react';
2
+ import { JSX as JSX_2 } from 'react/jsx-runtime';
3
+ import { ReactNode } from 'react';
4
+
5
+ export declare interface BaseComponentProps {
6
+ next: (data: object) => void;
7
+ data?: object;
8
+ metaData?: object;
9
+ }
10
+
11
+ declare type ComponentsMap = {
12
+ [key: string]: ComponentType<any>;
13
+ };
14
+
15
+ declare type ComponentsMap_2 = {
16
+ [key: string]: ComponentType<any>;
17
+ };
18
+
19
+ export declare function Experiment({ timeline, config, components, questions, }: {
20
+ timeline: ExperimentTrial[];
21
+ config?: ExperimentConfig;
22
+ components?: ComponentsMap_2;
23
+ questions?: ComponentsMap_2;
24
+ }): JSX_2.Element;
25
+
26
+ export declare interface ExperimentConfig {
27
+ showProgressBar: boolean;
28
+ }
29
+
30
+ export declare function ExperimentProvider({ children }: {
31
+ children: ReactNode;
32
+ }): JSX_2.Element;
33
+
34
+ declare interface ExperimentTrial {
35
+ name: string;
36
+ type: string;
37
+ props?: Record<string, any>;
38
+ }
39
+
40
+ export declare interface FileUpload {
41
+ filename: string;
42
+ content: string;
43
+ encoding: 'base64' | 'utf8';
44
+ }
45
+
46
+ export declare function getParam<T extends ParamType>(name: string, defaultValue: ParamValue<T> | undefined, type?: T): ParamValue<T> | undefined;
47
+
48
+ export declare function MasterMindleWrapper({ next, blockIndex, feedback, timeLimit, maxGuesses, }: {
49
+ next: (data: object) => void;
50
+ blockIndex: number;
51
+ feedback: 1 | 2 | 3 | 4 | 5;
52
+ timeLimit: number;
53
+ maxGuesses: number;
54
+ }): JSX_2.Element;
55
+
56
+ export declare const MicCheck: ({ next }: {
57
+ next: (data: object) => void;
58
+ }) => JSX_2.Element;
59
+
60
+ export declare function now(): number;
61
+
62
+ declare type ParamType = 'string' | 'number' | 'boolean' | 'array' | 'json';
63
+
64
+ declare type ParamValue<T extends ParamType> = T extends 'number' ? number | undefined : T extends 'boolean' ? boolean | undefined : T extends 'array' | 'json' ? any | undefined : string | undefined;
65
+
66
+ export declare function Quest({ next, surveyJson, customQuestions }: {
67
+ next: (data: object) => void;
68
+ surveyJson: object;
69
+ customQuestions?: ComponentsMap;
70
+ }): JSX_2.Element;
71
+
72
+ export declare function shuffle(array: any[]): any[];
73
+
74
+ export declare interface StudyEvent {
75
+ index: number;
76
+ type: string;
77
+ name: string;
78
+ data: any;
79
+ start: number;
80
+ end: number;
81
+ duration: number;
82
+ }
83
+
84
+ declare function Text_2({ content, buttonText, className, next, animate, }: {
85
+ content: React.ReactNode;
86
+ buttonText?: string;
87
+ onButtonClick?: () => void;
88
+ className?: string;
89
+ next: (newData: object) => void;
90
+ animate?: boolean;
91
+ }): JSX_2.Element;
92
+ export { Text_2 as Text }
93
+
94
+ export declare function Upload({ data, next, sessionID, generateFiles, uploadRaw, }: {
95
+ data: StudyEvent[];
96
+ next: () => void;
97
+ sessionID?: string | null;
98
+ generateFiles: (sessionID: string, data: StudyEvent[]) => FileUpload[];
99
+ uploadRaw: boolean;
100
+ }): JSX_2.Element;
101
+
102
+ export { }