@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
@@ -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,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
@@ -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
+ }