@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.
- package/.eslintrc.cjs +18 -0
- package/.prettierrc +5 -0
- package/Dockerfile +20 -0
- package/README.md +68 -0
- package/bin/setup.js +100 -0
- package/dist/mod.d.ts +102 -0
- package/dist/reactivepsych.es.js +71241 -0
- package/dist/reactivepsych.umd.js +120 -0
- package/dist/style.css +5 -0
- package/dist/tailwind.config.js +33 -0
- package/package.json +75 -0
- package/postcss.config.js +6 -0
- package/src/components/experiment.tsx +156 -0
- package/src/components/experimentprovider.tsx +28 -0
- package/src/components/mastermindlewrapper.tsx +662 -0
- package/src/components/microphonecheck.tsx +167 -0
- package/src/components/quest.tsx +102 -0
- package/src/components/text.tsx +45 -0
- package/src/components/upload.tsx +149 -0
- package/src/components/voicerecorder.tsx +346 -0
- package/src/index.css +74 -0
- package/src/mod.tsx +14 -0
- package/src/utils/common.ts +80 -0
- package/src/utils/request.ts +25 -0
- package/src/vite-env.d.ts +1 -0
- package/tailwind.config.js +33 -0
- package/template/.dockerignore +5 -0
- package/template/.eslintrc.cjs +18 -0
- package/template/.prettierrc +5 -0
- package/template/Dockerfile +25 -0
- package/template/README.md +102 -0
- package/template/backend/package-lock.json +2398 -0
- package/template/backend/package.json +31 -0
- package/template/backend/src/backend.ts +99 -0
- package/template/backend/tsconfig.json +110 -0
- package/template/docker-compose.yaml +13 -0
- package/template/index.html +15 -0
- package/template/package-lock.json +6031 -0
- package/template/package.json +48 -0
- package/template/postcss.config.js +6 -0
- package/template/public/Atkinson_Hyperlegible/AtkinsonHyperlegible-Bold.ttf +0 -0
- package/template/public/Atkinson_Hyperlegible/AtkinsonHyperlegible-BoldItalic.ttf +0 -0
- package/template/public/Atkinson_Hyperlegible/AtkinsonHyperlegible-Italic.ttf +0 -0
- package/template/public/Atkinson_Hyperlegible/AtkinsonHyperlegible-Regular.ttf +0 -0
- package/template/public/Atkinson_Hyperlegible/OFL.txt +93 -0
- package/template/src/App.tsx +116 -0
- package/template/src/index.css +3 -0
- package/template/src/main.tsx +14 -0
- package/template/tailwind.config.js +7 -0
- package/template/tsconfig.json +25 -0
- package/template/tsconfig.node.json +11 -0
- package/template/vite.config.ts +24 -0
- package/tsconfig.json +28 -0
- package/tsconfig.node.json +12 -0
- 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
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 { }
|