@adriansteffan/reactive 0.0.26 → 0.0.27
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/dist/{mod-Dqf5zajq.js → mod-CbGhKi2f.js} +11238 -10012
- package/dist/mod.d.ts +187 -49
- package/dist/reactive.es.js +26 -16
- package/dist/reactive.umd.js +37 -39
- package/dist/style.css +1 -1
- package/dist/{web-CnAMKrLX.js → web-BFGLx41c.js} +1 -1
- package/dist/{web-6wmUWZwq.js → web-DOFokKz7.js} +1 -1
- package/package.json +7 -2
- package/src/components/canvasblock.tsx +519 -0
- package/src/components/checkdevice.tsx +158 -0
- package/src/components/enterfullscreen.tsx +114 -31
- package/src/components/exitfullscreen.tsx +98 -21
- package/src/components/experimentprovider.tsx +34 -20
- package/src/components/experimentrunner.tsx +387 -0
- package/src/components/index.ts +13 -0
- package/src/components/mobilefilepermission.tsx +12 -19
- package/src/components/plaininput.tsx +7 -8
- package/src/components/prolificending.tsx +10 -4
- package/src/components/quest.tsx +27 -31
- package/src/components/settingsscreen.tsx +770 -0
- package/src/components/text.tsx +48 -3
- package/src/components/upload.tsx +218 -47
- package/src/mod.tsx +3 -12
- package/src/types/array.d.ts +6 -0
- package/src/utils/array.ts +79 -0
- package/src/utils/bytecode.ts +178 -0
- package/src/utils/common.ts +170 -39
- package/template/.env.template +2 -1
- package/template/src/{App.tsx → Experiment.tsx} +4 -4
- package/template/src/main.tsx +4 -4
- package/template/tsconfig.json +1 -0
- package/src/components/experiment.tsx +0 -371
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
export type Store = Record<string, any>;
|
|
2
|
+
|
|
3
|
+
type BaseTrialData = {
|
|
4
|
+
index: number;
|
|
5
|
+
trialNumber: number;
|
|
6
|
+
start: number;
|
|
7
|
+
end: number;
|
|
8
|
+
duration: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type ComponentResultData = BaseTrialData & {
|
|
12
|
+
type: string;
|
|
13
|
+
name: string;
|
|
14
|
+
responseData?: any;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type CanvasResultData = BaseTrialData & {
|
|
18
|
+
metadata?: Record<string, any>;
|
|
19
|
+
key: string | null;
|
|
20
|
+
reactionTime: number | null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type RefinedTrialData = ComponentResultData | CanvasResultData;
|
|
24
|
+
|
|
25
|
+
export interface MarkerItem { type: 'MARKER'; id: string; }
|
|
26
|
+
|
|
27
|
+
export type ConditionalFunction = (data?: RefinedTrialData[], store?: Store) => boolean;
|
|
28
|
+
|
|
29
|
+
export type StoreUpdateFunction = (data?: RefinedTrialData[], store?: Store) => Record<string, any>;
|
|
30
|
+
|
|
31
|
+
export interface IfGotoItem { type: 'IF_GOTO'; cond: ConditionalFunction; marker: string; }
|
|
32
|
+
export interface UpdateStoreItem { type: 'UPDATE_STORE'; fun: StoreUpdateFunction; }
|
|
33
|
+
export interface IfBlockItem { type: 'IF_BLOCK'; cond: ConditionalFunction; timeline: TimelineItem[]; }
|
|
34
|
+
export interface WhileBlockItem { type: 'WHILE_BLOCK'; cond: ConditionalFunction; timeline: TimelineItem[]; }
|
|
35
|
+
export type ControlFlowItem = MarkerItem | IfGotoItem | UpdateStoreItem | IfBlockItem | WhileBlockItem;
|
|
36
|
+
export type TimelineItem = ControlFlowItem | any;
|
|
37
|
+
|
|
38
|
+
export interface ExecuteContentInstruction { type: 'ExecuteContent'; content: any; }
|
|
39
|
+
export interface IfGotoInstruction {
|
|
40
|
+
type: 'IfGoto';
|
|
41
|
+
cond: (store: Store, data: RefinedTrialData[]) => boolean;
|
|
42
|
+
marker: string;
|
|
43
|
+
}
|
|
44
|
+
export interface UpdateStoreInstruction {
|
|
45
|
+
type: 'UpdateStore';
|
|
46
|
+
fun: (store: Store, data: RefinedTrialData[]) => Store;
|
|
47
|
+
}
|
|
48
|
+
export type UnifiedBytecodeInstruction = ExecuteContentInstruction | IfGotoInstruction | UpdateStoreInstruction;
|
|
49
|
+
|
|
50
|
+
function prefixUserMarkers(marker: string): string {
|
|
51
|
+
return `user_${marker}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function compileTimeline(
|
|
55
|
+
timeline: TimelineItem[]
|
|
56
|
+
): {
|
|
57
|
+
instructions: UnifiedBytecodeInstruction[];
|
|
58
|
+
markers: { [key: string]: number };
|
|
59
|
+
} {
|
|
60
|
+
const instructions: UnifiedBytecodeInstruction[] = [];
|
|
61
|
+
const markers: { [key: string]: number } = {};
|
|
62
|
+
let uniqueMarkerCounterForThisRun = 0;
|
|
63
|
+
|
|
64
|
+
function getUniqueMarker(prefix: string): string {
|
|
65
|
+
return `${prefix}_auto_${uniqueMarkerCounterForThisRun++}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function adaptCondition(
|
|
69
|
+
userCondition: ConditionalFunction
|
|
70
|
+
): (store: Store, data: RefinedTrialData[]) => boolean {
|
|
71
|
+
return (runtimeStore: Store, runtimeData: RefinedTrialData[]): boolean => {
|
|
72
|
+
return userCondition(runtimeData, runtimeStore);
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function adaptUpdate(
|
|
77
|
+
userUpdateFunction: StoreUpdateFunction
|
|
78
|
+
): (store: Store, data: RefinedTrialData[]) => Store {
|
|
79
|
+
return (runtimeStore: Store, runtimeData: RefinedTrialData[]): Store => {
|
|
80
|
+
const updates = userUpdateFunction(runtimeData, runtimeStore);
|
|
81
|
+
if (typeof updates === 'object' && updates !== null) {
|
|
82
|
+
return {
|
|
83
|
+
...runtimeStore,
|
|
84
|
+
...updates,
|
|
85
|
+
};
|
|
86
|
+
} else {
|
|
87
|
+
console.warn("Store update function did not return an object. Store remains unchanged.", { data: runtimeData, store: runtimeStore });
|
|
88
|
+
return runtimeStore;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function processTimeline(items: TimelineItem[]) {
|
|
94
|
+
for (const item of items) {
|
|
95
|
+
let isControlFlow = false;
|
|
96
|
+
|
|
97
|
+
if (typeof item === 'object' && item !== null && 'type' in item) {
|
|
98
|
+
const itemType = item.type;
|
|
99
|
+
|
|
100
|
+
switch (itemType) {
|
|
101
|
+
case 'MARKER': {
|
|
102
|
+
const markerItem = item as MarkerItem;
|
|
103
|
+
markers[prefixUserMarkers(markerItem.id)] = instructions.length;
|
|
104
|
+
isControlFlow = true;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case 'IF_GOTO': {
|
|
108
|
+
const ifGotoItem = item as IfGotoItem;
|
|
109
|
+
const runtimeConditionFunc = adaptCondition(ifGotoItem.cond);
|
|
110
|
+
instructions.push({
|
|
111
|
+
type: 'IfGoto',
|
|
112
|
+
cond: runtimeConditionFunc,
|
|
113
|
+
marker: prefixUserMarkers(ifGotoItem.marker),
|
|
114
|
+
});
|
|
115
|
+
isControlFlow = true;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case 'UPDATE_STORE': {
|
|
119
|
+
const updateStoreItem = item as UpdateStoreItem;
|
|
120
|
+
const runtimeUpdateFunc = adaptUpdate(updateStoreItem.fun);
|
|
121
|
+
instructions.push({
|
|
122
|
+
type: 'UpdateStore',
|
|
123
|
+
fun: runtimeUpdateFunc,
|
|
124
|
+
});
|
|
125
|
+
isControlFlow = true;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case 'IF_BLOCK': {
|
|
129
|
+
const ifBlockItem = item as IfBlockItem;
|
|
130
|
+
const endMarker = getUniqueMarker('if_end');
|
|
131
|
+
const runtimeConditionFunc = adaptCondition(ifBlockItem.cond);
|
|
132
|
+
instructions.push({
|
|
133
|
+
type: 'IfGoto',
|
|
134
|
+
cond: (store, data) => !runtimeConditionFunc(store, data),
|
|
135
|
+
marker: endMarker,
|
|
136
|
+
});
|
|
137
|
+
processTimeline(ifBlockItem.timeline);
|
|
138
|
+
markers[endMarker] = instructions.length;
|
|
139
|
+
isControlFlow = true;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
case 'WHILE_BLOCK': {
|
|
143
|
+
const whileBlockItem = item as WhileBlockItem;
|
|
144
|
+
const startMarker = getUniqueMarker('while_start');
|
|
145
|
+
const endMarker = getUniqueMarker('while_end');
|
|
146
|
+
const runtimeConditionFunc = adaptCondition(whileBlockItem.cond);
|
|
147
|
+
markers[startMarker] = instructions.length;
|
|
148
|
+
instructions.push({
|
|
149
|
+
type: 'IfGoto',
|
|
150
|
+
cond: (store, data) => !runtimeConditionFunc(store, data),
|
|
151
|
+
marker: endMarker,
|
|
152
|
+
});
|
|
153
|
+
processTimeline(whileBlockItem.timeline);
|
|
154
|
+
instructions.push({
|
|
155
|
+
type: 'IfGoto',
|
|
156
|
+
cond: () => true,
|
|
157
|
+
marker: startMarker,
|
|
158
|
+
});
|
|
159
|
+
markers[endMarker] = instructions.length;
|
|
160
|
+
isControlFlow = true;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!isControlFlow) {
|
|
167
|
+
instructions.push({
|
|
168
|
+
type: 'ExecuteContent',
|
|
169
|
+
content: item,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
processTimeline(timeline);
|
|
176
|
+
|
|
177
|
+
return { instructions, markers };
|
|
178
|
+
}
|
package/src/utils/common.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { Capacitor } from
|
|
1
|
+
import { Capacitor } from '@capacitor/core';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
export function now(){
|
|
4
5
|
return Math.round(performance.now());
|
|
5
6
|
}
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
export function isFullscreen(): boolean {
|
|
9
|
+
return !!(
|
|
10
|
+
document.fullscreenElement ||
|
|
11
|
+
(document as any).webkitFullscreenElement ||
|
|
12
|
+
(document as any).mozFullScreenElement ||
|
|
13
|
+
(document as any).msFullscreenElement
|
|
14
|
+
);
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export function isDesktop() {
|
|
@@ -20,10 +21,10 @@ export function isDesktop() {
|
|
|
20
21
|
// Generic type for all data structures
|
|
21
22
|
export interface TrialData {
|
|
22
23
|
index: number;
|
|
23
|
-
trialNumber: number
|
|
24
|
+
trialNumber: number;
|
|
24
25
|
type: string;
|
|
25
26
|
name: string;
|
|
26
|
-
|
|
27
|
+
responseData: any;
|
|
27
28
|
start: number;
|
|
28
29
|
end: number;
|
|
29
30
|
duration: number;
|
|
@@ -32,55 +33,61 @@ export interface TrialData {
|
|
|
32
33
|
export interface FileUpload {
|
|
33
34
|
filename: string;
|
|
34
35
|
content: string;
|
|
35
|
-
encoding
|
|
36
|
+
encoding?: 'base64' | 'utf8';
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
export interface ExperimentConfig {
|
|
39
40
|
showProgressBar: boolean;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
export interface Store {
|
|
43
|
+
export interface Store {
|
|
44
|
+
[key: string]: any;
|
|
45
|
+
}
|
|
43
46
|
|
|
44
47
|
export interface BaseComponentProps {
|
|
45
|
-
next: (data
|
|
48
|
+
next: (data?: object, actualStartTime?: number, actualStopTime?: number) => void;
|
|
46
49
|
data: TrialData[];
|
|
47
50
|
store?: Store;
|
|
48
51
|
updateStore: (mergeIn: Store) => void;
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
type ParamType = 'string' | 'number' | 'boolean' | 'array' | 'json';
|
|
55
|
+
|
|
52
56
|
type ParamValue<T extends ParamType> = T extends 'number'
|
|
53
|
-
? number
|
|
57
|
+
? number
|
|
54
58
|
: T extends 'boolean'
|
|
55
|
-
? boolean
|
|
59
|
+
? boolean
|
|
56
60
|
: T extends 'array' | 'json'
|
|
57
|
-
? any
|
|
58
|
-
: string
|
|
61
|
+
? any
|
|
62
|
+
: string;
|
|
63
|
+
|
|
64
|
+
const sharedRegistry: any[] = [];
|
|
59
65
|
|
|
60
66
|
export function getParam<T extends ParamType>(
|
|
61
67
|
name: string,
|
|
62
|
-
defaultValue: ParamValue<T
|
|
68
|
+
defaultValue: ParamValue<T>,
|
|
63
69
|
type: T = 'string' as T,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
description?: string,
|
|
71
|
+
): ParamValue<T> {
|
|
72
|
+
let registryEntry = sharedRegistry.find((p) => p.name === name);
|
|
73
|
+
|
|
74
|
+
if (!registryEntry) {
|
|
75
|
+
registryEntry = {
|
|
76
|
+
name,
|
|
77
|
+
defaultValue,
|
|
78
|
+
type,
|
|
79
|
+
description,
|
|
80
|
+
value: undefined,
|
|
81
|
+
};
|
|
82
|
+
sharedRegistry.push(registryEntry);
|
|
77
83
|
}
|
|
78
84
|
|
|
79
|
-
//Next, check for the parameter directly in the URL
|
|
80
|
-
// since this does not have the automatic type conversions of JSON.parse, we have to create helper functionss
|
|
81
85
|
const conversions: Record<ParamType, (v: string) => any> = {
|
|
82
86
|
string: (v) => v,
|
|
83
|
-
number: (v) =>
|
|
87
|
+
number: (v) => {
|
|
88
|
+
const num = Number(v);
|
|
89
|
+
return isNaN(num) ? defaultValue : num;
|
|
90
|
+
},
|
|
84
91
|
boolean: (v) => v.toLowerCase() === 'true',
|
|
85
92
|
array: (v) => {
|
|
86
93
|
try {
|
|
@@ -98,7 +105,7 @@ export function getParam<T extends ParamType>(
|
|
|
98
105
|
},
|
|
99
106
|
};
|
|
100
107
|
|
|
101
|
-
const convertValue = (value: any): ParamValue<T>
|
|
108
|
+
const convertValue = (value: any): ParamValue<T> => {
|
|
102
109
|
if (
|
|
103
110
|
(type === 'string' && typeof value === 'string') ||
|
|
104
111
|
(type === 'number' && typeof value === 'number') ||
|
|
@@ -110,19 +117,52 @@ export function getParam<T extends ParamType>(
|
|
|
110
117
|
}
|
|
111
118
|
|
|
112
119
|
if (typeof value === 'string') {
|
|
113
|
-
if (value.toLowerCase() === 'undefined') return
|
|
120
|
+
if (value.toLowerCase() === 'undefined') return defaultValue;
|
|
114
121
|
return conversions[type](value);
|
|
115
122
|
}
|
|
116
123
|
|
|
117
124
|
return defaultValue;
|
|
118
125
|
};
|
|
119
126
|
|
|
127
|
+
// First, check for the parameter in the base64-encoded JSON
|
|
128
|
+
const encodedJson = new URLSearchParams(window.location.search).get('_b');
|
|
129
|
+
if (encodedJson) {
|
|
130
|
+
try {
|
|
131
|
+
const jsonString = atob(encodedJson);
|
|
132
|
+
const decodedParams = JSON.parse(jsonString);
|
|
133
|
+
if (name in decodedParams) {
|
|
134
|
+
const convertedValue = convertValue(decodedParams[name]);
|
|
135
|
+
registryEntry.value = convertedValue;
|
|
136
|
+
return convertedValue;
|
|
137
|
+
}
|
|
138
|
+
} catch {
|
|
139
|
+
// Silently fail if decoding or parsing fails, fallthrough to lower case
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Next, check for the parameter directly in the URL
|
|
120
144
|
const value = new URLSearchParams(window.location.search).get(name);
|
|
121
|
-
if (value === undefined || value === null)
|
|
122
|
-
|
|
145
|
+
if (value === undefined || value === null) {
|
|
146
|
+
// If no value found, register default value
|
|
147
|
+
return defaultValue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const convertedValue = convertValue(value);
|
|
151
|
+
registryEntry.value = convertedValue;
|
|
152
|
+
return convertedValue;
|
|
123
153
|
}
|
|
124
154
|
|
|
155
|
+
const timelineRepresentation: { type: string; name?: string }[] = [];
|
|
125
156
|
|
|
157
|
+
// Param class that uses the same shared registry
|
|
158
|
+
export class Param {
|
|
159
|
+
static getRegistry() {
|
|
160
|
+
return [...sharedRegistry];
|
|
161
|
+
}
|
|
162
|
+
static getTimelineRepresentation() {
|
|
163
|
+
return [...timelineRepresentation];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
126
166
|
|
|
127
167
|
export type Platform = 'desktop' | 'mobile' | 'web';
|
|
128
168
|
|
|
@@ -135,3 +175,94 @@ export const getPlatform = (): Platform => {
|
|
|
135
175
|
return 'web';
|
|
136
176
|
}
|
|
137
177
|
};
|
|
178
|
+
|
|
179
|
+
const providedComponentParams: Record<string, any> = {};
|
|
180
|
+
|
|
181
|
+
export function registerExperimentParams(experiment: any[]) {
|
|
182
|
+
experiment.forEach((item) => {
|
|
183
|
+
const params = providedComponentParams[item.type];
|
|
184
|
+
if (params) {
|
|
185
|
+
for (const param of params) {
|
|
186
|
+
if(!item.hideSettings || (item.hideSettings !== true && !item.hideSettings.includes(param.name))){
|
|
187
|
+
sharedRegistry.push(param);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function registerComponentParams(
|
|
195
|
+
type: string,
|
|
196
|
+
params: { name: string; defaultValue: any; type: string; description?: string }[],
|
|
197
|
+
) {
|
|
198
|
+
providedComponentParams[type] = params;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function subsetExperimentByParam(experiment: any[]) {
|
|
202
|
+
registerExperimentParams(experiment);
|
|
203
|
+
|
|
204
|
+
timelineRepresentation.length = 0;
|
|
205
|
+
|
|
206
|
+
experiment.forEach((item) => {
|
|
207
|
+
timelineRepresentation.push({
|
|
208
|
+
type: item.type ?? 'NoTypeSpecified',
|
|
209
|
+
name: item.name,
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const include = getParam('includeSubset', undefined);
|
|
214
|
+
const exclude = getParam('excludeSubset', undefined);
|
|
215
|
+
|
|
216
|
+
let experimentFiltered = [...experiment];
|
|
217
|
+
|
|
218
|
+
if (include) {
|
|
219
|
+
const includeItems = include.split(',');
|
|
220
|
+
experimentFiltered = experimentFiltered.filter((item, index) => {
|
|
221
|
+
const positionMatch = includeItems.some((val: string) => {
|
|
222
|
+
const num = parseInt(val, 10);
|
|
223
|
+
return !isNaN(num) && num - 1 === index;
|
|
224
|
+
});
|
|
225
|
+
const nameMatch = item.name && includeItems.includes(item.name);
|
|
226
|
+
return positionMatch || nameMatch;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (exclude) {
|
|
231
|
+
const excludeItems = exclude.split(',');
|
|
232
|
+
experimentFiltered = experimentFiltered.filter((item, index) => {
|
|
233
|
+
const positionMatch = excludeItems.some((val: string) => {
|
|
234
|
+
const num = parseInt(val, 10);
|
|
235
|
+
return !isNaN(num) && num - 1 === index;
|
|
236
|
+
});
|
|
237
|
+
const nameMatch = item.name && excludeItems.includes(item.name);
|
|
238
|
+
return !(positionMatch || nameMatch);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return experimentFiltered;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function canvasCountdown(seconds: number) {
|
|
246
|
+
if (seconds <= 0) {
|
|
247
|
+
return [];
|
|
248
|
+
}
|
|
249
|
+
return Array.from({ length: seconds }, (_, i) => {
|
|
250
|
+
const number = seconds - i;
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
draw: (ctx: CanvasRenderingContext2D, w: number, h: number) => {
|
|
254
|
+
ctx.save();
|
|
255
|
+
|
|
256
|
+
ctx.fillStyle = 'black';
|
|
257
|
+
ctx.font = `bold ${Math.min(w, h) * 0.02 * 2}px sans-serif`; // Make countdown text larger
|
|
258
|
+
ctx.textAlign = 'center';
|
|
259
|
+
ctx.textBaseline = 'middle';
|
|
260
|
+
ctx.fillText(number.toString(), w / 2, h / 2);
|
|
261
|
+
|
|
262
|
+
ctx.restore();
|
|
263
|
+
},
|
|
264
|
+
displayDuration: 1000,
|
|
265
|
+
ignoreData: true,
|
|
266
|
+
};
|
|
267
|
+
});
|
|
268
|
+
}
|
package/template/.env.template
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
VITE_PROLIFIC_CODE=""
|
|
1
|
+
VITE_PROLIFIC_CODE=""
|
|
2
|
+
VITE_DISABLE_SETTINGS=FALSE
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import { useState } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { ExperimentRunner, BaseComponentProps, ExperimentConfig } from '@adriansteffan/reactive';
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
const config: ExperimentConfig = { showProgressBar: true };
|
|
@@ -108,13 +108,13 @@ const experiment = [
|
|
|
108
108
|
},
|
|
109
109
|
];
|
|
110
110
|
|
|
111
|
-
export default function
|
|
111
|
+
export default function Experiment() {
|
|
112
112
|
return (
|
|
113
|
-
<
|
|
113
|
+
<ExperimentRunner
|
|
114
114
|
config={config}
|
|
115
115
|
timeline={experiment}
|
|
116
116
|
components={{CustomTrial}}
|
|
117
117
|
questions={{CustomQuestion}}
|
|
118
118
|
/>
|
|
119
119
|
);
|
|
120
|
-
}
|
|
120
|
+
}
|
package/template/src/main.tsx
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import ReactDOM from "react-dom/client";
|
|
3
|
-
import
|
|
3
|
+
import Experiment from "./Experiment";
|
|
4
4
|
import "@adriansteffan/reactive/style.css";
|
|
5
5
|
import "./index.css";
|
|
6
6
|
import { ExperimentProvider } from "@adriansteffan/reactive";
|
|
7
7
|
|
|
8
8
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
9
9
|
<React.StrictMode>
|
|
10
|
-
<ExperimentProvider>
|
|
11
|
-
<
|
|
10
|
+
<ExperimentProvider disableSettings={import.meta.env.VITE_DISABLE_SETTINGS}>
|
|
11
|
+
<Experiment />
|
|
12
12
|
</ExperimentProvider>
|
|
13
13
|
</React.StrictMode>
|
|
14
|
-
);
|
|
14
|
+
);
|