@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
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** @type {import('tailwindcss').Config} */
|
|
2
|
+
export default {
|
|
3
|
+
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}', './dist/**/*.{js,ts,jsx,tsx}'],
|
|
4
|
+
theme: {
|
|
5
|
+
fontFamily: {
|
|
6
|
+
sans: ['Atkinson Hyperlegible', 'sans-serif'],
|
|
7
|
+
atkinson: ['Atkinson Hyperlegible', 'sans-serif'],
|
|
8
|
+
},
|
|
9
|
+
extend: {
|
|
10
|
+
keyframes: {
|
|
11
|
+
slideDown: {
|
|
12
|
+
'0%': {
|
|
13
|
+
transform: 'translateY(-10px)',
|
|
14
|
+
opacity: '0',
|
|
15
|
+
},
|
|
16
|
+
'100%': {
|
|
17
|
+
transform: 'translateY(0)',
|
|
18
|
+
opacity: '1',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
fadeIn: {
|
|
22
|
+
'0%': { opacity: '0' },
|
|
23
|
+
'100%': { opacity: '1' },
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
animation: {
|
|
27
|
+
slideDown: 'slideDown 0.8s ease-out forwards',
|
|
28
|
+
fadeIn: 'fadeIn 0.5s ease-out forwards',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
plugins: [],
|
|
33
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@adriansteffan/reactive",
|
|
3
|
+
"version": "0.0.9",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "vite",
|
|
7
|
+
"build": "tsc && vite build",
|
|
8
|
+
"test": "vitest --silent=false",
|
|
9
|
+
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
|
10
|
+
"preview": "vite preview"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"create-reactive-experiment": "bin/setup.js",
|
|
14
|
+
"@adriansteffan/reactive": "bin/setup.js"
|
|
15
|
+
},
|
|
16
|
+
"main": "./dist/reactive.umd.js",
|
|
17
|
+
"module": "./dist/reactive.es.js",
|
|
18
|
+
"types": "./dist/mod.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"import": "./dist/reactive.es.js",
|
|
22
|
+
"require": "./dist/reactive.umd.js",
|
|
23
|
+
"types": "./dist/mod.d.ts"
|
|
24
|
+
},
|
|
25
|
+
"./style.css": "./dist/style.css",
|
|
26
|
+
"./tailwind.config.js": "./dist/tailwind.config.js"
|
|
27
|
+
},
|
|
28
|
+
"author": "Adrian Steffan",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/adriansteffan/reactive/issues"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@tanstack/react-query": "^5.61.3",
|
|
35
|
+
"@tanstack/react-query-devtools": "^5.61.3",
|
|
36
|
+
"@zip.js/zip.js": "^2.7.53",
|
|
37
|
+
"react": "^18.2.0",
|
|
38
|
+
"react-dom": "^18.2.0",
|
|
39
|
+
"react-icons": "^5.1.0",
|
|
40
|
+
"react-toastify": "^10.0.6",
|
|
41
|
+
"survey-react-ui": "^1.12.9",
|
|
42
|
+
"uuid": "^11.0.3"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"tailwindcss": "^3.4.3"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@eslint/config-array": "^0.19.0",
|
|
49
|
+
"@eslint/object-schema": "^2.1.4",
|
|
50
|
+
"@types/howler": "^2.2.11",
|
|
51
|
+
"@types/node": "^22.9.2",
|
|
52
|
+
"@types/react": "^18.2.66",
|
|
53
|
+
"@types/react-dom": "^18.2.22",
|
|
54
|
+
"@types/uuid": "^10.0.0",
|
|
55
|
+
"@typescript-eslint/eslint-plugin": "^8.16.0",
|
|
56
|
+
"@typescript-eslint/parser": "^8.16.0",
|
|
57
|
+
"@vitejs/plugin-react": "^4.2.1",
|
|
58
|
+
"autoprefixer": "^10.4.19",
|
|
59
|
+
"concurrently": "^9.1.0",
|
|
60
|
+
"eslint": "^9.0.0",
|
|
61
|
+
"eslint-plugin-react-hooks": "^5.0.0",
|
|
62
|
+
"eslint-plugin-react-refresh": "^0.4.14",
|
|
63
|
+
"glob": "^10.3.10",
|
|
64
|
+
"lru-cache": "^10.0.1",
|
|
65
|
+
"postcss": "^8.4.38",
|
|
66
|
+
"prettier": "3.2.5",
|
|
67
|
+
"rimraf": "^5.0.5",
|
|
68
|
+
"tailwindcss": "^3.4.3",
|
|
69
|
+
"typescript": "^5.2.2",
|
|
70
|
+
"vite": "^5.2.0",
|
|
71
|
+
"vite-plugin-dts": "^4.3.0",
|
|
72
|
+
"vite-plugin-static-copy": "^2.1.0",
|
|
73
|
+
"vitest": "^2.1.5"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
|
|
3
|
+
import { ExperimentConfig, now } from '../utils/common';
|
|
4
|
+
import { useEffect, useRef, useState } from 'react';
|
|
5
|
+
import { ComponentType } from 'react';
|
|
6
|
+
|
|
7
|
+
// Default components
|
|
8
|
+
import Upload from './upload';
|
|
9
|
+
import Text from './text';
|
|
10
|
+
import Quest from './quest';
|
|
11
|
+
import MasterMindleWrapper from './mastermindlewrapper';
|
|
12
|
+
import MicrophoneCheck from './microphonecheck';
|
|
13
|
+
|
|
14
|
+
// Default Custom Questions
|
|
15
|
+
import VoicerecorderQuestionComponent from './voicerecorder';
|
|
16
|
+
|
|
17
|
+
type ComponentsMap = {
|
|
18
|
+
[key: string]: ComponentType<any>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Default components map
|
|
22
|
+
const defaultComponents: ComponentsMap = {
|
|
23
|
+
Text,
|
|
24
|
+
Quest,
|
|
25
|
+
Upload,
|
|
26
|
+
MicrophoneCheck,
|
|
27
|
+
MasterMindleWrapper,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const defaultCustomQuestions = {
|
|
31
|
+
voicerecorder: VoicerecorderQuestionComponent,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
interface ExperimentTrial {
|
|
35
|
+
name: string;
|
|
36
|
+
type: string;
|
|
37
|
+
props?: Record<string, any>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface TrialData {
|
|
41
|
+
index: number;
|
|
42
|
+
type: string;
|
|
43
|
+
name: string;
|
|
44
|
+
data: object | undefined;
|
|
45
|
+
start: number;
|
|
46
|
+
end: number;
|
|
47
|
+
duration: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Function to transform experiment definition into components
|
|
51
|
+
const transformExperiment = (
|
|
52
|
+
experimentDef: ExperimentTrial[],
|
|
53
|
+
index: number,
|
|
54
|
+
next: (data: any) => void,
|
|
55
|
+
data: any,
|
|
56
|
+
componentsMap: ComponentsMap,
|
|
57
|
+
customQuestions: ComponentsMap,
|
|
58
|
+
) => {
|
|
59
|
+
if (index >= experimentDef.length) {
|
|
60
|
+
return <></>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const def = experimentDef[index];
|
|
64
|
+
|
|
65
|
+
const Component = componentsMap[def.type];
|
|
66
|
+
|
|
67
|
+
if (!Component) {
|
|
68
|
+
throw new Error(`No component found for type: ${def.type}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<Component
|
|
73
|
+
next={next}
|
|
74
|
+
key={index}
|
|
75
|
+
data={data}
|
|
76
|
+
{...(def.type === 'Quest' ? { customQuestions: customQuestions } : {})}
|
|
77
|
+
{...def.props}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export default function Experiment({
|
|
83
|
+
timeline,
|
|
84
|
+
config = {
|
|
85
|
+
showProgressBar: true,
|
|
86
|
+
},
|
|
87
|
+
components = {},
|
|
88
|
+
questions = {},
|
|
89
|
+
}: {
|
|
90
|
+
timeline: ExperimentTrial[];
|
|
91
|
+
config?: ExperimentConfig;
|
|
92
|
+
components?: ComponentsMap;
|
|
93
|
+
questions?: ComponentsMap;
|
|
94
|
+
}) {
|
|
95
|
+
const [trialCounter, setTrialCounter] = useState(0);
|
|
96
|
+
const [data, setData] = useState<TrialData[]>([]);
|
|
97
|
+
const trialStartTimeRef = useRef(now());
|
|
98
|
+
|
|
99
|
+
const componentsMap = { ...defaultComponents, ...components };
|
|
100
|
+
const customQuestions: ComponentsMap = { ...defaultCustomQuestions, ...questions };
|
|
101
|
+
|
|
102
|
+
const progress = trialCounter / (timeline.length - 1);
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
window.scrollTo(0, 0);
|
|
106
|
+
}, [trialCounter]);
|
|
107
|
+
|
|
108
|
+
function next(newData?: object): void {
|
|
109
|
+
const currentTime = now();
|
|
110
|
+
const currentTrial = timeline[trialCounter];
|
|
111
|
+
|
|
112
|
+
if (currentTrial && data) {
|
|
113
|
+
const trialData: TrialData = {
|
|
114
|
+
index: trialCounter,
|
|
115
|
+
type: currentTrial.type,
|
|
116
|
+
name: currentTrial.name,
|
|
117
|
+
data: newData,
|
|
118
|
+
start: trialStartTimeRef.current,
|
|
119
|
+
end: currentTime,
|
|
120
|
+
duration: currentTime - trialStartTimeRef.current,
|
|
121
|
+
};
|
|
122
|
+
setData([...data, trialData]);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
trialStartTimeRef.current = currentTime;
|
|
126
|
+
setTrialCounter(trialCounter + 1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<div className='px-4 w-screen'>
|
|
131
|
+
<div
|
|
132
|
+
className={` ${
|
|
133
|
+
config.showProgressBar ? '' : 'hidden '
|
|
134
|
+
} mt-4 sm:mt-12 max-w-2xl mx-auto flex-1 h-6 bg-gray-200 rounded-full overflow-hidden`}
|
|
135
|
+
>
|
|
136
|
+
<div
|
|
137
|
+
className={`h-full bg-gray-200 rounded-full duration-300 ${
|
|
138
|
+
progress > 0 ? ' border-black border-2' : ''
|
|
139
|
+
}`}
|
|
140
|
+
style={{
|
|
141
|
+
width: `${progress * 100}%`,
|
|
142
|
+
backgroundImage: `repeating-linear-gradient(
|
|
143
|
+
-45deg,
|
|
144
|
+
#E5E7EB,
|
|
145
|
+
#E5E7EB 10px,
|
|
146
|
+
#D1D5DB 10px,
|
|
147
|
+
#D1D5DB 20px
|
|
148
|
+
)`,
|
|
149
|
+
transition: 'width 300ms',
|
|
150
|
+
}}
|
|
151
|
+
/>
|
|
152
|
+
</div>
|
|
153
|
+
{transformExperiment(timeline, trialCounter, next, data, componentsMap, customQuestions)}
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import 'react-toastify/dist/ReactToastify.min.css';
|
|
2
|
+
import { ToastContainer } from 'react-toastify';
|
|
3
|
+
|
|
4
|
+
const queryClient = new QueryClient();
|
|
5
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
6
|
+
import { ReactNode } from 'react';
|
|
7
|
+
|
|
8
|
+
export default function ExperimentProvider({children}: {children: ReactNode}){
|
|
9
|
+
return(<QueryClientProvider client={queryClient}>
|
|
10
|
+
{children}
|
|
11
|
+
<ToastContainer
|
|
12
|
+
position='top-center'
|
|
13
|
+
autoClose={3000}
|
|
14
|
+
hideProgressBar
|
|
15
|
+
newestOnTop={false}
|
|
16
|
+
closeOnClick
|
|
17
|
+
rtl={false}
|
|
18
|
+
pauseOnFocusLoss
|
|
19
|
+
draggable={false}
|
|
20
|
+
pauseOnHover
|
|
21
|
+
theme='light'
|
|
22
|
+
toastClassName={() =>
|
|
23
|
+
'relative flex p-4 min-h-10 rounded-none justify-between overflow-hidden cursor-pointer border-2 border-black shadow-[2px_2px_0px_rgba(0,0,0,1)] bg-white'
|
|
24
|
+
}
|
|
25
|
+
bodyClassName="text-black font-sans"
|
|
26
|
+
/>
|
|
27
|
+
</QueryClientProvider>)
|
|
28
|
+
}
|