diff --git a/src/components/ActivityCard.tsx b/src/components/ActivityCard.tsx index b416dee6..92aad428 100644 --- a/src/components/ActivityCard.tsx +++ b/src/components/ActivityCard.tsx @@ -28,42 +28,43 @@ export default function ActivityCard({ const [helpAnchor, setHelpAnchor] = useState() const [showGrid, setShowGrid] = useState(forceDefaultGrid || Boolean(freeText.length)) const { t } = useTranslation() - const selectedActivity = activity events.sort((a, b) => a.timestamp - b.timestamp) let each = Object.values( events .map((d) => - d.temporal_slices.map((t, index) => ({ - item: - activity.spec === "lamp.symbol_digit_substitution" - ? d.temporal_slices.length > index + 1 - ? "Digit " + (index + 1) + " : " + t.type - : t.type - : activity.spec === "lamp.maze_game" - ? t.level - : t.item, - [new Date(d.timestamp).toLocaleString("en-US", Date.formatStyle("medium"))]: - activity.spec === "lamp.maze_game" - ? t.duration - : activity.spec === "lamp.survey" || - activity.spec === "lamp.pop_the_bubbles" || - activity.spec === "lamp.symbol_digit_substitution" - ? typeof t.value === "string" && t.value !== null - ? typeof t.value === "string" && ["Yes", "True"].includes(t.value.replace(/\"/g, "")) - ? 1 - : typeof t.value === "string" && ["No", "False"].includes(t.value.replace(/\"/g, "")) - ? 0 - : !isNaN(Number(t.value.replace(/\"/g, ""))) - ? Number(t.value.replace(/\"/g, "")) + d.temporal_slices + .filter((t) => t.type != "manual_exit") + .map((t, index) => ({ + item: + activity.spec === "lamp.symbol_digit_substitution" + ? d.temporal_slices.length > index + 1 + ? "Digit " + (index + 1) + " : " + t.type + : t.type + : activity.spec === "lamp.maze_game" + ? t.level + : t.item, + [new Date(d.timestamp).toLocaleString("en-US", Date.formatStyle("medium"))]: + activity.spec === "lamp.maze_game" + ? t.duration + : activity.spec === "lamp.survey" || + activity.spec === "lamp.pop_the_bubbles" || + activity.spec === "lamp.symbol_digit_substitution" + ? typeof t.value === "string" && t.value !== null + ? typeof t.value === "string" && ["Yes", "True"].includes(t.value.replace(/\"/g, "")) + ? 1 + : typeof t.value === "string" && ["No", "False"].includes(t.value.replace(/\"/g, "")) + ? 0 + : !isNaN(Number(t.value.replace(/\"/g, ""))) + ? Number(t.value.replace(/\"/g, "")) + : t.value : t.value - : t.value - : activity.spec === "lamp.spin_wheel" || activity.spec === "lamp.emotion_recognition" - ? t.type - : !!t.type - ? 1 - : 0, - })) + : activity.spec === "lamp.spin_wheel" || activity.spec === "lamp.emotion_recognition" + ? t.type + : !!t.type + ? 1 + : 0, + })) ) .reduce((x, y) => x.concat(y), []) .groupBy("item") @@ -190,27 +191,29 @@ export default function ActivityCard({ value={Object.values( events .map((d) => - d.temporal_slices.map((t, index) => ({ - item: activity.spec === "lamp.maze_game" ? t.level : t.item, - [new Date(d.timestamp).toLocaleString("en-US", Date.formatStyle("medium"))]: - activity.spec === "lamp.maze_game" - ? t.duration - : activity.spec === "lamp.survey" || activity.spec === "lamp.pop_the_bubbles" - ? typeof t.value === "string" && t.value !== null - ? typeof t.value === "string" && ["Yes", "True"].includes(t.value.replace(/\"/g, "")) - ? 1 - : typeof t.value === "string" && ["No", "False"].includes(t.value.replace(/\"/g, "")) - ? 0 - : !isNaN(Number(t.value.replace(/\"/g, ""))) - ? Number(t.value.replace(/\"/g, "")) + d.temporal_slices + .filter((t) => t.type != "manual_exit") + .map((t, index) => ({ + item: activity.spec === "lamp.maze_game" ? t.level : t.item, + [new Date(d.timestamp).toLocaleString("en-US", Date.formatStyle("medium"))]: + activity.spec === "lamp.maze_game" + ? t.duration + : activity.spec === "lamp.survey" || activity.spec === "lamp.pop_the_bubbles" + ? typeof t.value === "string" && t.value !== null + ? typeof t.value === "string" && ["Yes", "True"].includes(t.value.replace(/\"/g, "")) + ? 1 + : typeof t.value === "string" && ["No", "False"].includes(t.value.replace(/\"/g, "")) + ? 0 + : !isNaN(Number(t.value.replace(/\"/g, ""))) + ? Number(t.value.replace(/\"/g, "")) + : t.value : t.value - : t.value - : activity.spec === "lamp.spin_wheel" || activity.spec === "lamp.emotion_recognition" - ? t.type - : !!t.type - ? 1 - : 0, - })) + : activity.spec === "lamp.spin_wheel" || activity.spec === "lamp.emotion_recognition" + ? t.type + : !!t.type + ? 1 + : 0, + })) ) .reduce((x, y) => x.concat(y), []) .groupBy("item") @@ -235,7 +238,7 @@ export default function ActivityCard({ activity.spec === "lamp.spin_wheel" || activity.spec === "lamp.pop_the_bubbles" || activity.spec === "lamp.maze_game" - ? d.temporal_slices + ? d.temporal_slices.filter((t) => t.type != "manual_exit") : activity.spec === "lamp.scratch_image" || activity.spec === "lamp.breathe" || activity.spec === "lamp.tips" @@ -245,7 +248,7 @@ export default function ActivityCard({ undefined ) : 0, - slice: d.temporal_slices, + slice: d.temporal_slices.filter((t) => t.type != "manual_exit"), missing: activity.spec === "lamp.survey" || activity.spec === "lamp.pop_the_bubbles" || diff --git a/src/components/EmbeddedActivity.tsx b/src/components/EmbeddedActivity.tsx index 5a9f3f55..9d327f53 100644 --- a/src/components/EmbeddedActivity.tsx +++ b/src/components/EmbeddedActivity.tsx @@ -48,7 +48,9 @@ const demoActivities = { "lamp.symbol_digit_substitution": "symbol_digit_substitution", "lamp.gyroscope": "gyroscope", "lamp.dcog": "d-cog", - "lamp.simple_memory": "funnymemory", + "lamp.funny_memory": "funnymemory", + "lamp.trails_b": "dottouch", + "lamp.voice_survey": "speech", } export default function EmbeddedActivity({ participant, activity, name, onComplete, noBack, tab, ...props }) { @@ -202,13 +204,6 @@ export default function EmbeddedActivity({ participant, activity, name, onComple const exist = localStorage.getItem("first-time-" + (participant?.id ?? participant) + "-" + currentActivity?.id) try { setSaved(false) - console.log({ - ...settings, - activity: currentActivity, - configuration: { language: i18n.language }, - autoCorrect: !(exist === "true"), - noBack: noBack, - }) setSettings({ ...settings, activity: currentActivity, diff --git a/src/components/ParticipantData.tsx b/src/components/ParticipantData.tsx index 76a6d107..6638a0b7 100644 --- a/src/components/ParticipantData.tsx +++ b/src/components/ParticipantData.tsx @@ -326,7 +326,9 @@ export default function ParticipantData({ {((activityEvents || {})[x.name] || []).slice(-1).length > 0 ? strategies["lamp.survey"]( - ((activityEvents || {})[x.name] || []).slice(-1)?.[0]?.temporal_slices, + ((activityEvents || {})[x.name] || []) + .slice(-1)?.[0] + ?.temporal_slices.filter((t) => t.type != "manual_exit"), x, undefined ) diff --git a/src/components/PreventSelectedActivities.tsx b/src/components/PreventSelectedActivities.tsx index fb275635..149ad4bb 100644 --- a/src/components/PreventSelectedActivities.tsx +++ b/src/components/PreventSelectedActivities.tsx @@ -187,34 +187,40 @@ const useStyles = makeStyles((theme: Theme) => export const strategies = { "lamp.survey": (slices, activity, scopedItem) => - (slices || []).map((x) => x.duration).reduce((prev, cur) => prev + cur, 0) / slices.length / 1000, - - // (slices ?? []) - // .filter((x, idx) => (scopedItem !== undefined ? idx === scopedItem : true)) - // .map((x, idx) => { - // console.log(slices) - - // let question = (Array.isArray(activity.settings) ? activity.settings : []).filter((y) => y.text === x.item)[0] - // if (!!question && typeof x?.value !== "undefined") - // return ["Yes", "True"].includes(x.value) ? 1 : ["No", "False"].includes(x.value) ? 0 : Number(x.value) || 0 - // else if (!!question && !!!question.options) return Math.max((question.options || []).indexOf(x.value), 0) - // else if (typeof x?.value !== "number" && typeof x?.value !== "string") { - // let sum = 0 - // Object.keys(x.value || []).map((val) => { - // if (!!x.value[val]?.value && x.value[val]?.value.length > 0) { - // sum += (x.value[val]?.value || []) - // .map((elt) => { - // // assure the value can be converted into an integer - // return !isNaN(Number(elt)) ? Number(elt) : 0 - // }) - // .reduce((sum, current) => sum + current) - // } - // }) - // return sum - // } else return Number(x?.value) || 0 - // }) - // .reduce((prev, curr) => prev + curr, 0), - + (slices ?? []) + .filter((x, idx) => (scopedItem !== undefined ? idx === scopedItem : true)) + .map((x, idx) => { + let question = (Array.isArray(activity.settings) ? activity.settings : []).filter((y) => y.text === x.item)[0] + if (!!question && typeof x?.value !== "undefined") + return ["Yes", "True"].includes(x.value) + ? 1 + : ["No", "False"].includes(x.value) + ? 0 + : Number(x.value.replace(/\"/g, "")) || 0 + else if (!!question && !!!question.options) + return Math.max((question.options || []).indexOf(x.value.replace(/\"/g, "")), 0) + else if (typeof x?.value.replace(/\"/g, "") !== "number" && typeof x?.value.replace(/\"/g, "") !== "string") { + let sum = 0 + Object.keys(x.value || []).map((val) => { + if (!!x.value[val]?.value && x.value[val]?.value.length > 0) { + sum += (x.value[val]?.value || []) + .map((elt) => { + // assure the value can be converted into an integer + return !isNaN(Number(elt)) ? Number(elt) : 0 + }) + .reduce((sum, current) => sum + current) + } + }) + return sum + } else return Number(x?.value.replace(/\"/g, "")) || 0 + }) + .reduce((prev, curr) => prev + curr, 0), + "lamp.trails_b": (slices, activity, scopedItem) => + slices.score == "NaN" + ? 0 + : (parseInt(slices.score ?? 0).toFixed(1) || 0) > 100 + ? 100 + : parseInt(slices.score ?? 0).toFixed(1) || 0, "lamp.spin_wheel": (slices, activity, scopedItem) => slices[slices.length - 1]?.type ?? 0, "lamp.jewels_a": (slices, activity, scopedItem) => slices.score == "NaN" @@ -255,7 +261,7 @@ export const strategies = { }, "lamp.cats_and_dogs": (slices, activity, scopedItem) => (slices.correct_answers / slices.total_questions) * 100, "lamp.memory_game": (slices, activity, scopedItem) => (slices.correct_answers / slices.total_questions) * 100, - "lamp.simple_memory": (slices, activity, scopedItem) => + "lamp.funny_memory": (slices, activity, scopedItem) => (slices.number_of_correct_pairs_recalled / slices.number_of_total_pairs) * 100, "lamp.scratch_image": (slices, activity, scopedItem) => ((parseInt(slices?.duration ?? 0) / 1000).toFixed(1) || 0) > 100 @@ -499,7 +505,8 @@ export default function PreventSelectedActivities({ activity.spec === "lamp.pop_the_bubbles" || activity.spec === "lamp.maze_game" || activity.spec === "lamp.emotion_recognition" - ? d?.temporal_slices ?? d["temporal_slices"] + ? d?.temporal_slices.filter((t) => t.type != "manual_exit") ?? + d["temporal_slices"].filter((t) => t.type != "manual_exit") : activity.spec === "lamp.scratch_image" || activity.spec === "lamp.breathe" || activity.spec === "lamp.tips" diff --git a/src/components/Researcher/ActivityList/Activity.tsx b/src/components/Researcher/ActivityList/Activity.tsx index 91b2a049..81aafaab 100644 --- a/src/components/Researcher/ActivityList/Activity.tsx +++ b/src/components/Researcher/ActivityList/Activity.tsx @@ -31,7 +31,9 @@ export const games = [ "lamp.symbol_digit_substitution", "lamp.gyroscope", "lamp.dcog", - "lamp.simple_memory", + "lamp.funny_memory", + "lamp.trails_b", + "lamp.voice_survey", ] const useStyles = makeStyles((theme: Theme) => diff --git a/src/components/Researcher/ActivityList/ActivityMethods.ts b/src/components/Researcher/ActivityList/ActivityMethods.ts index 64b3cce0..151555b4 100644 --- a/src/components/Researcher/ActivityList/ActivityMethods.ts +++ b/src/components/Researcher/ActivityList/ActivityMethods.ts @@ -234,7 +234,59 @@ export const SchemaList = () => { }, }, }, - "lamp.simple_memory": { + "lamp.trails_b": { + type: "object", + properties: { + settings: { + title: i18n.t("Activity Settings"), + type: "object", + required: ["level1dot_count", "level2_dot_count", "level1_timeout", "level2_timeout"], + properties: { + level1dot_count: { + title: i18n.t("Number of dots for Level 1"), + type: "number", + enum: [10, 12], + enumNames: [i18n.t("10"), i18n.t("12")], + "ui:grid": { + xs: 4, + }, + }, + level2_dot_count: { + title: i18n.t("Number of dots for Level 2"), + type: "number", + enum: [20, 24], + enumNames: [i18n.t("20"), i18n.t("24")], + "ui:grid": { + xs: 4, + }, + }, + level1_timeout: { + title: i18n.t("Timeout period for Level 1"), + description: i18n.t("Seconds"), + type: "number", + enum: [60, 90, 120], + enumNames: [i18n.t("60"), i18n.t("90"), i18n.t("120")], + default: 2, + "ui:grid": { + xs: 4, + }, + }, + level2_timeout: { + title: i18n.t("Timeout period for Level 2"), + description: i18n.t("Seconds"), + type: "number", + enum: [90, 120, 180], + enumNames: [i18n.t("90"), i18n.t("120"), i18n.t("180")], + default: 2, + "ui:grid": { + xs: 4, + }, + }, + }, + }, + }, + }, + "lamp.funny_memory": { type: "object", properties: { settings: { @@ -1033,6 +1085,7 @@ export const SchemaList = () => { "ui:widget": "file", "ui:options": { accept: ".gif,.jpg,.png,.svg", + maxSize: 4000, }, }, emotionText: { @@ -1050,6 +1103,26 @@ export const SchemaList = () => { }, }, }, + "lamp.voice_survey": { + type: "object", + properties: { + settings: { + title: i18n.t("Activity settings"), + type: "array", + items: { + required: ["question"], + type: "object", + properties: { + question: { + title: i18n.t("Question"), + type: "string", + default: "", + }, + }, + }, + }, + }, + }, "lamp.dbt_diary_card": { type: "object", properties: { diff --git a/src/components/Researcher/ActivityList/AddActivity.tsx b/src/components/Researcher/ActivityList/AddActivity.tsx index 62acde5d..428ffd9e 100644 --- a/src/components/Researcher/ActivityList/AddActivity.tsx +++ b/src/components/Researcher/ActivityList/AddActivity.tsx @@ -158,7 +158,9 @@ export default function AddActivity({ "lamp.emotion_recognition": `${t("Emotion Recognition")}`, "lamp.symbol_digit_substitution": `${t("Symbol-digit Substitution")}`, "lamp.dcog": `${t("D-Cog")}`, - "lamp.simple_memory": `${t("Funny Memory Game")}`, + "lamp.funny_memory": `${t("Funny Memory Game")}`, + "lamp.trails_b": `${t("Trails B")}`, + "lamp.voice_survey": `${t("Speech Recording")}`, } const getActivitySpec = async (id) => { diff --git a/src/components/Researcher/ActivityList/Index.tsx b/src/components/Researcher/ActivityList/Index.tsx index 6c9b79bf..3db95064 100644 --- a/src/components/Researcher/ActivityList/Index.tsx +++ b/src/components/Researcher/ActivityList/Index.tsx @@ -60,7 +60,9 @@ export const availableActivitySpecs = [ "lamp.maze_game", "lamp.emotion_recognition", "lamp.symbol_digit_substitution", - "lamp.simple_memory", + "lamp.funny_memory", + "lamp.trails_b", + "lamp.voice_survey", ] export const games = [ "lamp.jewels_a", @@ -75,7 +77,9 @@ export const games = [ "lamp.symbol_digit_substitution", "lamp.gyroscope", "lamp.dcog", - "lamp.simple_memory", + "lamp.funny_memory", + "lamp.trails_b", + "lamp.voice_survey", ] export default function ActivityList({ researcherId, diff --git a/src/components/Survey.tsx b/src/components/Survey.tsx index dd156f1d..f9ddc29c 100644 --- a/src/components/Survey.tsx +++ b/src/components/Survey.tsx @@ -33,7 +33,9 @@ export const games = [ "lamp.symbol_digit_substitution", "lamp.gyroscope", "lamp.dcog", - "lamp.simple_memory", + "lamp.funny_memory", + "lamp.trails_b", + "lamp.voice_survey", ] export default function Survey({ participant, activities, showStreak, ...props }) { diff --git a/src/components/shared/CustomFileWidget.tsx b/src/components/shared/CustomFileWidget.tsx index 31a6df35..199e6b85 100644 --- a/src/components/shared/CustomFileWidget.tsx +++ b/src/components/shared/CustomFileWidget.tsx @@ -34,6 +34,7 @@ const useStyles = makeStyles((theme) => ({ }, imgBox: { padding: "20px 0 0 0" }, closeIcon: { color: "red", cursor: "pointer" }, + errorText: { color: "red" }, })) function processFile(files) { @@ -50,6 +51,7 @@ export default function CustomFileWidget(props) { const classes = useStyles() const ref = React.useRef(props.value) const { t } = useTranslation() + const [error, setError] = React.useState("") const onClick = () => { ref.current.value = "" @@ -76,9 +78,20 @@ export default function CustomFileWidget(props) { name="file" style={{ display: "none" }} onChange={(event) => { - processFile(event.target.files).then(props.onChange) + if (event.target.files[0].size / 1024 > props.options.maxSize) { + setError("Maximum file size should be 4MB") + ref.current.value = "" + props.onChange("") + } else { + setError("") + processFile(event.target.files).then(props.onChange) + } }} /> + +
+ {error} +
{props?.value && (