Denis Thiessen
6 months ago
27 changed files with 841 additions and 547 deletions
-
211src/App.js
-
6src/App.test.js
-
57src/components/DownloadButton.jsx
-
30src/components/InfoPageComponent.jsx
-
35src/components/RandomIDComponent.jsx
-
125src/components/questionnaire/QuestionComponent.jsx
-
66src/components/questionnaire/QuestionData.jsx
-
44src/components/questionnaire/QuestionnaireData.jsx
-
99src/components/webpage_container/StudySite.jsx
-
29src/components/webpage_container/WebpageBanner.jsx
-
63src/core/audio/AudioHandler.jsx
-
30src/core/audio/ChordHelper.jsx
-
2src/core/i18n/I18NHandler.jsx
-
10src/core/i18n/i18n.js
-
6src/core/log/RouteTracker.jsx
-
102src/core/log/SensorLogger.jsx
-
23src/index.js
-
16src/interfaces/DataComponent.jsx
-
6src/interfaces/ResetableComponent.jsx
-
7src/pages/NoPageFound.jsx
-
10src/pages/TestEndPage.jsx
-
22src/pages/TestInfoPage.jsx
-
20src/pages/TestInfoPage2.jsx
-
32src/pages/TestPage.jsx
-
31src/pages/TestPage2.jsx
-
40src/pages/TestQuestionnaire.jsx
-
212src/pages/study_site_1/StartPage1.jsx
@ -1,7 +1,7 @@ |
|||
import { render, screen } from '@testing-library/react'; |
|||
import App from './App'; |
|||
import { render, screen } from "@testing-library/react"; |
|||
import App from "./App"; |
|||
|
|||
test('renders learn react link', () => { |
|||
test("renders learn react link", () => { |
|||
render(<App />); |
|||
const linkElement = screen.getByText(/learn react/i); |
|||
expect(linkElement).toBeInTheDocument(); |
|||
|
@ -1,42 +1,41 @@ |
|||
import React from "react"; |
|||
import { getTranslation } from "../core/i18n/I18NHandler"; |
|||
import { getSensorLog } from "../core/log/SensorLogger"; |
|||
import { Button } from '@geist-ui/core'; |
|||
import { Button } from "@geist-ui/core"; |
|||
import { getUserID } from "./RandomIDComponent"; |
|||
import { PARTICIPANT_NUMBER } from "../core/Constants"; |
|||
|
|||
function DownloadButton() { |
|||
const downloadFile = ({ data, fileName, fileType }) => { |
|||
const blob = new Blob([data], { type: fileType }); |
|||
const a = document.createElement('a'); |
|||
a.download = fileName; |
|||
a.href = window.URL.createObjectURL(blob); |
|||
const clickEvt = new MouseEvent('click', { |
|||
view: window, |
|||
bubbles: true, |
|||
cancelable: true, |
|||
}); |
|||
a.dispatchEvent(clickEvt); |
|||
a.remove(); |
|||
} |
|||
const downloadFile = ({ data, fileName, fileType }) => { |
|||
const blob = new Blob([data], { type: fileType }); |
|||
const a = document.createElement("a"); |
|||
a.download = fileName; |
|||
a.href = window.URL.createObjectURL(blob); |
|||
const clickEvt = new MouseEvent("click", { |
|||
view: window, |
|||
bubbles: true, |
|||
cancelable: true, |
|||
}); |
|||
a.dispatchEvent(clickEvt); |
|||
a.remove(); |
|||
}; |
|||
|
|||
const userData = { |
|||
participantId: getUserID(), |
|||
sensorLog: getSensorLog(), |
|||
participantNumber: PARTICIPANT_NUMBER, |
|||
}; |
|||
|
|||
const userData = { |
|||
participantId: getUserID(), |
|||
sensorLog: getSensorLog(), |
|||
participantNumber: PARTICIPANT_NUMBER |
|||
} |
|||
const exportToJson = (e) => { |
|||
e.preventDefault(); |
|||
downloadFile({ |
|||
data: JSON.stringify(userData), |
|||
fileName: "sensorData.json", |
|||
fileType: "text/json", |
|||
}); |
|||
}; |
|||
|
|||
const exportToJson = e => { |
|||
e.preventDefault(); |
|||
downloadFile({ |
|||
data: JSON.stringify(userData), |
|||
fileName: 'sensorData.json', |
|||
fileType: 'text/json', |
|||
}) |
|||
} |
|||
|
|||
return (<Button onClick={exportToJson}>{getTranslation("download")}</Button>); |
|||
return <Button onClick={exportToJson}>{getTranslation("download")}</Button>; |
|||
} |
|||
|
|||
export default DownloadButton; |
@ -1,20 +1,26 @@ |
|||
import React from "react"; |
|||
import { getTranslation } from "../core/i18n/I18NHandler"; |
|||
import { Button } from '@geist-ui/core'; |
|||
import { Button } from "@geist-ui/core"; |
|||
|
|||
const redirectToPage = (redirectLoc) => { |
|||
window.location.href = "./" + redirectLoc; |
|||
} |
|||
window.location.href = "./" + redirectLoc; |
|||
}; |
|||
|
|||
function InfoPageComponent({children, redirectLoc}) { |
|||
return ( |
|||
<div> |
|||
{children} |
|||
<div> |
|||
<Button onClick={() => {redirectToPage(redirectLoc)}}>{getTranslation("continue")}</Button> |
|||
</div> |
|||
</div> |
|||
); |
|||
function InfoPageComponent({ children, redirectLoc }) { |
|||
return ( |
|||
<div> |
|||
{children} |
|||
<div> |
|||
<Button |
|||
onClick={() => { |
|||
redirectToPage(redirectLoc); |
|||
}} |
|||
> |
|||
{getTranslation("continue")} |
|||
</Button> |
|||
</div> |
|||
</div> |
|||
); |
|||
} |
|||
|
|||
export default InfoPageComponent; |
@ -1,34 +1,39 @@ |
|||
import React from "react"; |
|||
import { createStore } from 'zustand/vanilla' |
|||
import { persist, createJSONStorage } from 'zustand/middleware' |
|||
import { createStore } from "zustand/vanilla"; |
|||
import { persist, createJSONStorage } from "zustand/middleware"; |
|||
import { getTranslation } from "../core/i18n/I18NHandler"; |
|||
|
|||
var randomId = 0; |
|||
|
|||
// State for saving participant ID globally. |
|||
export const participantIdLogState = createStore( |
|||
persist( |
|||
() => ({ |
|||
participant_id: "" |
|||
}), |
|||
{ |
|||
name: 'participant-id-storage', // name of the item in the storage (must be unique) |
|||
storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used |
|||
}, |
|||
), |
|||
persist( |
|||
() => ({ |
|||
participant_id: "", |
|||
}), |
|||
{ |
|||
name: "participant-id-storage", // name of the item in the storage (must be unique) |
|||
storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used |
|||
} |
|||
) |
|||
); |
|||
|
|||
const { getState, setState } = participantIdLogState; |
|||
|
|||
// Can only be called once. :) |
|||
function RandomIDComponent() { |
|||
randomId = Math.floor(Math.random() * 89999) + 10000; |
|||
setState({ participant_id: "" + randomId }) |
|||
return (<div><h3>Your ID:</h3><p>{randomId}</p></div>); |
|||
randomId = Math.floor(Math.random() * 89999) + 10000; |
|||
setState({ participant_id: "" + randomId }); |
|||
return ( |
|||
<div> |
|||
<h3>Your ID:</h3> |
|||
<p>{randomId}</p> |
|||
</div> |
|||
); |
|||
} |
|||
|
|||
export function getUserID() { |
|||
return getState().participant_id; |
|||
return getState().participant_id; |
|||
} |
|||
|
|||
export default RandomIDComponent; |
@ -1,63 +1,80 @@ |
|||
// QuestionComponent.js |
|||
import React, { useEffect } from "react"; |
|||
import { Slider, Input, Radio, Checkbox, Link } from '@geist-ui/core'; |
|||
import { Slider, Input, Radio, Checkbox, Link } from "@geist-ui/core"; |
|||
|
|||
export default function QuestionComponent(props) { |
|||
const { questionData, onUpdateAnswerData } = props; |
|||
const { questionData, onUpdateAnswerData } = props; |
|||
|
|||
useEffect(() => { |
|||
// Update questionData when component mounts or props change |
|||
questionData.sData({ "id": props.id, "answerValue": questionData.answerValue }); |
|||
}, [props.id, questionData.answerValue]); |
|||
useEffect(() => { |
|||
// Update questionData when component mounts or props change |
|||
questionData.sData({ id: props.id, answerValue: questionData.answerValue }); |
|||
}, [props.id, questionData.answerValue]); |
|||
|
|||
// Define a function to handle answer data change |
|||
const handleAnswerChange = (val) => { |
|||
questionData.sAnswerData(val); |
|||
onUpdateAnswerData(questionData.gAnswerData()); // Update answerData in parent component |
|||
}; |
|||
// Define a function to handle answer data change |
|||
const handleAnswerChange = (val) => { |
|||
questionData.sAnswerData(val); |
|||
onUpdateAnswerData(questionData.gAnswerData()); // Update answerData in parent component |
|||
}; |
|||
|
|||
// Determine and render answer element based on props |
|||
var element; |
|||
if (props.answerType === "slider") { |
|||
element = ( |
|||
<Slider |
|||
onChange={handleAnswerChange} |
|||
max={props.maxElement} |
|||
initialValue={questionData.gAnswerData()} |
|||
showMarkers |
|||
width="75%" |
|||
/> |
|||
); |
|||
} else if (props.answerType === "radio") { |
|||
const btnIndexes = Array.from({ length: props.maxElement }, (_, i) => i + 1); |
|||
element = ( |
|||
<Radio.Group onChange={handleAnswerChange}> |
|||
{btnIndexes.map((x) => <Radio key={x} value={x}>{x}</Radio>)} |
|||
</Radio.Group> |
|||
); |
|||
} else if (props.answerType === "checkbox") { |
|||
const btnIndexes = Array.from({ length: props.maxElement }, (_, i) => i + 1); |
|||
element = ( |
|||
<Checkbox.Group onChange={handleAnswerChange}> |
|||
{btnIndexes.map((x) => <Checkbox key={x} value={x}>{x}</Checkbox>)} |
|||
</Checkbox.Group> |
|||
); |
|||
} else { |
|||
element = ( |
|||
<Input |
|||
onChange={e => handleAnswerChange(e.target.value)} |
|||
clearable |
|||
/> |
|||
); |
|||
} |
|||
|
|||
// Render the component |
|||
return ( |
|||
<div> |
|||
<p>{props.text}</p> |
|||
{element} |
|||
{props.referBack && <Link href={props.referBack} block>←</Link>} |
|||
<Link href={props.referTo} block>→</Link> |
|||
</div> |
|||
// Determine and render answer element based on props |
|||
var element; |
|||
if (props.answerType === "slider") { |
|||
element = ( |
|||
<Slider |
|||
onChange={handleAnswerChange} |
|||
max={props.maxElement} |
|||
initialValue={questionData.gAnswerData()} |
|||
showMarkers |
|||
width="75%" |
|||
/> |
|||
); |
|||
} else if (props.answerType === "radio") { |
|||
const btnIndexes = Array.from( |
|||
{ length: props.maxElement }, |
|||
(_, i) => i + 1 |
|||
); |
|||
element = ( |
|||
<Radio.Group onChange={handleAnswerChange}> |
|||
{btnIndexes.map((x) => ( |
|||
<Radio key={x} value={x}> |
|||
{x} |
|||
</Radio> |
|||
))} |
|||
</Radio.Group> |
|||
); |
|||
} else if (props.answerType === "checkbox") { |
|||
const btnIndexes = Array.from( |
|||
{ length: props.maxElement }, |
|||
(_, i) => i + 1 |
|||
); |
|||
element = ( |
|||
<Checkbox.Group onChange={handleAnswerChange}> |
|||
{btnIndexes.map((x) => ( |
|||
<Checkbox key={x} value={x}> |
|||
{x} |
|||
</Checkbox> |
|||
))} |
|||
</Checkbox.Group> |
|||
); |
|||
} else { |
|||
element = ( |
|||
<Input onChange={(e) => handleAnswerChange(e.target.value)} clearable /> |
|||
); |
|||
} |
|||
|
|||
// Render the component |
|||
return ( |
|||
<div> |
|||
<p>{props.text}</p> |
|||
{element} |
|||
{props.referBack && ( |
|||
<Link href={props.referBack} block> |
|||
← |
|||
</Link> |
|||
)} |
|||
<Link href={props.referTo} block> |
|||
→ |
|||
</Link> |
|||
</div> |
|||
); |
|||
} |
@ -1,36 +1,36 @@ |
|||
import {useState} from "react"; |
|||
import { useState } from "react"; |
|||
|
|||
export function useQuestionData() { |
|||
const [data, setData] = useState({}); |
|||
|
|||
function setQuestionData(questionData) { |
|||
setData(questionData); |
|||
}; |
|||
|
|||
function getQuestionData() { |
|||
return data; |
|||
} |
|||
|
|||
function setAnswerValue(val) { |
|||
data.answerValue = val; |
|||
setData(data); |
|||
} |
|||
|
|||
function getAnswerValue() { |
|||
return data.answerValue; |
|||
} |
|||
|
|||
function reset() { |
|||
setData({}); |
|||
}; |
|||
|
|||
const inputProps = { |
|||
sData: setQuestionData, |
|||
gData: getQuestionData, |
|||
sAnswerData: setAnswerValue, |
|||
gAnswerData: getAnswerValue, |
|||
r: reset |
|||
}; |
|||
|
|||
return inputProps; |
|||
const [data, setData] = useState({}); |
|||
|
|||
function setQuestionData(questionData) { |
|||
setData(questionData); |
|||
} |
|||
|
|||
function getQuestionData() { |
|||
return data; |
|||
} |
|||
|
|||
function setAnswerValue(val) { |
|||
data.answerValue = val; |
|||
setData(data); |
|||
} |
|||
|
|||
function getAnswerValue() { |
|||
return data.answerValue; |
|||
} |
|||
|
|||
function reset() { |
|||
setData({}); |
|||
} |
|||
|
|||
const inputProps = { |
|||
sData: setQuestionData, |
|||
gData: getQuestionData, |
|||
sAnswerData: setAnswerValue, |
|||
gAnswerData: getAnswerValue, |
|||
r: reset, |
|||
}; |
|||
|
|||
return inputProps; |
|||
} |
@ -1,69 +1,68 @@ |
|||
import React, {useEffect} from "react"; |
|||
import React, { useEffect } from "react"; |
|||
import h337 from "heatmap.js"; |
|||
import { MOUSE_MODE, HEATMAP_MAX } from "../../core/Constants"; |
|||
|
|||
var stopInput = false; |
|||
|
|||
var heatmapInstance = h337.create({ |
|||
container: document.body, |
|||
radius: 20 |
|||
container: document.body, |
|||
radius: 20, |
|||
}); |
|||
|
|||
const visualiseMouseData = ((mouseData) => { |
|||
mouseData.max = mouseData.data.length + 1; |
|||
heatmapInstance.setData(mouseData); |
|||
document.getElementsByTagName("canvas")[0].style.opacity = 1; |
|||
stopInput = true; |
|||
}); |
|||
const visualiseMouseData = (mouseData) => { |
|||
mouseData.max = mouseData.data.length + 1; |
|||
heatmapInstance.setData(mouseData); |
|||
document.getElementsByTagName("canvas")[0].style.opacity = 1; |
|||
stopInput = true; |
|||
}; |
|||
|
|||
export function StudySite(props) { |
|||
useEffect(() => { |
|||
window.visualiseMouseData = visualiseMouseData; |
|||
heatmapInstance.setDataMax(HEATMAP_MAX); |
|||
var addData; |
|||
|
|||
useEffect(() => { |
|||
window.visualiseMouseData = visualiseMouseData; |
|||
heatmapInstance.setDataMax(HEATMAP_MAX); |
|||
var addData; |
|||
|
|||
if(MOUSE_MODE) { |
|||
const mouseDataFunc = function(ev) { |
|||
if(!stopInput) { |
|||
heatmapInstance.addData({ |
|||
x: ev.x, |
|||
y: ev.y, |
|||
value: 1, |
|||
}); |
|||
} |
|||
}; |
|||
if (MOUSE_MODE) { |
|||
const mouseDataFunc = function (ev) { |
|||
if (!stopInput) { |
|||
heatmapInstance.addData({ |
|||
x: ev.x, |
|||
y: ev.y, |
|||
value: 1, |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
addData = mouseDataFunc; |
|||
} else { |
|||
const touchDataFunc = function(ev) { |
|||
if(!stopInput) { |
|||
heatmapInstance.addData({ |
|||
x: ev.layerX, |
|||
y: ev.layerY, |
|||
value: 1 |
|||
}); |
|||
} |
|||
}; |
|||
addData = touchDataFunc; |
|||
} |
|||
addData = mouseDataFunc; |
|||
} else { |
|||
const touchDataFunc = function (ev) { |
|||
if (!stopInput) { |
|||
heatmapInstance.addData({ |
|||
x: ev.layerX, |
|||
y: ev.layerY, |
|||
value: 1, |
|||
}); |
|||
} |
|||
}; |
|||
addData = touchDataFunc; |
|||
} |
|||
|
|||
if(MOUSE_MODE) { |
|||
document.body.addEventListener("mousemove", addData, false); |
|||
} else { |
|||
document.body.addEventListener("touchmove", addData, false); |
|||
document.body.addEventListener("touchstart", addData, false); |
|||
document.body.addEventListener("touchend", addData, false); |
|||
} |
|||
if (MOUSE_MODE) { |
|||
document.body.addEventListener("mousemove", addData, false); |
|||
} else { |
|||
document.body.addEventListener("touchmove", addData, false); |
|||
document.body.addEventListener("touchstart", addData, false); |
|||
document.body.addEventListener("touchend", addData, false); |
|||
} |
|||
|
|||
return () => { |
|||
delete window.visualiseMouseData; |
|||
}; |
|||
}); |
|||
return () => { |
|||
delete window.visualiseMouseData; |
|||
}; |
|||
}); |
|||
|
|||
return (<>{props.children}</>); |
|||
return <>{props.children}</>; |
|||
} |
|||
|
|||
export function getHeatmapData() { |
|||
return heatmapInstance.getData(); |
|||
return heatmapInstance.getData(); |
|||
} |
@ -1,23 +1,24 @@ |
|||
import { Component } from "react"; |
|||
import { getTranslation } from "../../core/i18n/I18NHandler"; |
|||
import { Note } from '@geist-ui/core'; |
|||
import { Note } from "@geist-ui/core"; |
|||
|
|||
var bannerContent = ""; |
|||
|
|||
export default class WebpageBanner extends Component { |
|||
constructor(props) { |
|||
super(props); |
|||
bannerContent = getTranslation(props.translationKey); |
|||
} |
|||
|
|||
constructor(props) { |
|||
super(props); |
|||
bannerContent = getTranslation(props.translationKey); |
|||
} |
|||
setBannerContent(content) { |
|||
bannerContent = content; |
|||
} |
|||
|
|||
setBannerContent(content) { |
|||
bannerContent = content; |
|||
} |
|||
|
|||
render() { |
|||
return ( |
|||
<Note type="warning" label={false}>{bannerContent}</Note> |
|||
); |
|||
} |
|||
render() { |
|||
return ( |
|||
<Note type="warning" label={false}> |
|||
{bannerContent} |
|||
</Note> |
|||
); |
|||
} |
|||
} |
@ -1,38 +1,33 @@ |
|||
import {Component} from 'react'; |
|||
import { Component } from "react"; |
|||
import * as Tone from "tone"; |
|||
import { pushToSonificationLog } from '../log/SensorLogger'; |
|||
import { pushToSonificationLog } from "../log/SensorLogger"; |
|||
|
|||
export default class AudioHandler extends Component { |
|||
|
|||
playTabEarconSonification() { |
|||
|
|||
pushToSonificationLog("tab_earcon"); |
|||
} |
|||
|
|||
playTabModelSonification() { |
|||
|
|||
pushToSonificationLog("tab_model"); |
|||
} |
|||
|
|||
playDetailEarconSonification() { |
|||
|
|||
pushToSonificationLog("detail_earcon"); |
|||
} |
|||
|
|||
playDetailModelSonification() { |
|||
|
|||
pushToSonificationLog("detail_model"); |
|||
} |
|||
|
|||
setRotation(angle) { |
|||
Tone.Listener.forwardX.value = Math.sin(angle); |
|||
Tone.Listener.forwardY.value = 0; |
|||
Tone.Listener.forwardZ.value = -Math.cos(angle); |
|||
} |
|||
|
|||
setRotationRamp(panner, angle, rampTime) { |
|||
panner.positionX.rampTo(Math.sin(angle), rampTime); |
|||
panner.positionY.rampTo(0, rampTime); |
|||
panner.positionZ.rampTo(-Math.cos(angle), rampTime); |
|||
} |
|||
playTabEarconSonification() { |
|||
pushToSonificationLog("tab_earcon"); |
|||
} |
|||
|
|||
playTabModelSonification() { |
|||
pushToSonificationLog("tab_model"); |
|||
} |
|||
|
|||
playDetailEarconSonification() { |
|||
pushToSonificationLog("detail_earcon"); |
|||
} |
|||
|
|||
playDetailModelSonification() { |
|||
pushToSonificationLog("detail_model"); |
|||
} |
|||
|
|||
setRotation(angle) { |
|||
Tone.Listener.forwardX.value = Math.sin(angle); |
|||
Tone.Listener.forwardY.value = 0; |
|||
Tone.Listener.forwardZ.value = -Math.cos(angle); |
|||
} |
|||
|
|||
setRotationRamp(panner, angle, rampTime) { |
|||
panner.positionX.rampTo(Math.sin(angle), rampTime); |
|||
panner.positionY.rampTo(0, rampTime); |
|||
panner.positionZ.rampTo(-Math.cos(angle), rampTime); |
|||
} |
|||
} |
@ -1,26 +1,28 @@ |
|||
import * as Tone from "tone"; |
|||
|
|||
export function buildChord(notes, octave) { |
|||
return notes.map(note => {return (note + octave);}) |
|||
return notes.map((note) => { |
|||
return note + octave; |
|||
}); |
|||
} |
|||
|
|||
export function buildNote(note, octave) { |
|||
return (note + octave); |
|||
return note + octave; |
|||
} |
|||
|
|||
export function generateChord(rootNote, chordType) { |
|||
// Define intervals for different chord types |
|||
const chordIntervals = { |
|||
'major': [0, 4, 7], |
|||
'minor': [0, 3, 7], |
|||
'7th': [0, 4, 7, 10], |
|||
'diminished': [0, 3, 6], |
|||
'augmented': [0, 4, 8], |
|||
'sus2': [0, 2, 7], |
|||
'sus4': [0, 5, 7] |
|||
}; |
|||
// Define intervals for different chord types |
|||
const chordIntervals = { |
|||
major: [0, 4, 7], |
|||
minor: [0, 3, 7], |
|||
"7th": [0, 4, 7, 10], |
|||
diminished: [0, 3, 6], |
|||
augmented: [0, 4, 8], |
|||
sus2: [0, 2, 7], |
|||
sus4: [0, 5, 7], |
|||
}; |
|||
|
|||
const intervals = chordIntervals[chordType]; |
|||
const intervals = chordIntervals[chordType]; |
|||
|
|||
return Tone.Frequency(rootNote).harmonize(intervals); |
|||
return Tone.Frequency(rootNote).harmonize(intervals); |
|||
} |
@ -1,5 +1,5 @@ |
|||
import i18n from "./i18n"; |
|||
|
|||
export function getTranslation(translationKey) { |
|||
return i18n.t(translationKey); |
|||
return i18n.t(translationKey); |
|||
} |
@ -1,47 +1,57 @@ |
|||
import { getState, setState } from '../../App'; |
|||
|
|||
export function pushToMouseLog(logElement) { |
|||
const sensorLog = getState().sensorLog; |
|||
sensorLog.mouseLog.push(logElement); |
|||
setState({ sensorLog: sensorLog }); |
|||
} |
|||
|
|||
export function pushToClickLog(logElement) { |
|||
// Filter out dummy body objects |
|||
if(logElement.target.localName === "body") { |
|||
return; |
|||
} |
|||
|
|||
const sensorLog = getState().sensorLog; |
|||
sensorLog.clickedElements.push(logElement); |
|||
setState({ sensorLog: sensorLog }); |
|||
} |
|||
|
|||
export function pushToVisitLog(logElement) { |
|||
const sensorLog = getState().sensorLog; |
|||
|
|||
// Filter out double visit pushes... |
|||
const visitedSitesAmount = sensorLog.visitedSites.length; |
|||
if(visitedSitesAmount > 0) { |
|||
const lastVisitedElement = sensorLog.visitedSites[visitedSitesAmount - 1]; |
|||
if(lastVisitedElement.path === logElement.path && logElement.timestamp - lastVisitedElement.timestamp < 500) { |
|||
return; |
|||
} |
|||
sensorLog.visitedSites.push(logElement); |
|||
setState({ sensorLog: sensorLog }); |
|||
} |
|||
} |
|||
|
|||
export function pushToSonificationLog(logElement) { |
|||
const sensorLog = getState().sensorLog; |
|||
sensorLog.playedSonifications.push(logElement); |
|||
setState({ sensorLog: sensorLog }); |
|||
} |
|||
|
|||
export function getSensorLog() { |
|||
return getState().sensorLog; |
|||
} |
|||
|
|||
export function resetSensorLog() { |
|||
setState({ sensorLog: {mouseLog: [], clickedElements: [], visitedSites: [], playedSonifications: []}}); |
|||
import { getState, setState } from "../../App"; |
|||
|
|||
export function pushToMouseLog(logElement) { |
|||
const sensorLog = getState().sensorLog; |
|||
sensorLog.mouseLog.push(logElement); |
|||
setState({ sensorLog: sensorLog }); |
|||
} |
|||
|
|||
export function pushToClickLog(logElement) { |
|||
// Filter out dummy body objects |
|||
if (logElement.target.localName === "body") { |
|||
return; |
|||
} |
|||
|
|||
const sensorLog = getState().sensorLog; |
|||
sensorLog.clickedElements.push(logElement); |
|||
setState({ sensorLog: sensorLog }); |
|||
} |
|||
|
|||
export function pushToVisitLog(logElement) { |
|||
const sensorLog = getState().sensorLog; |
|||
|
|||
// Filter out double visit pushes... |
|||
const visitedSitesAmount = sensorLog.visitedSites.length; |
|||
if (visitedSitesAmount > 0) { |
|||
const lastVisitedElement = sensorLog.visitedSites[visitedSitesAmount - 1]; |
|||
if ( |
|||
lastVisitedElement.path === logElement.path && |
|||
logElement.timestamp - lastVisitedElement.timestamp < 500 |
|||
) { |
|||
return; |
|||
} |
|||
sensorLog.visitedSites.push(logElement); |
|||
setState({ sensorLog: sensorLog }); |
|||
} |
|||
} |
|||
|
|||
export function pushToSonificationLog(logElement) { |
|||
const sensorLog = getState().sensorLog; |
|||
sensorLog.playedSonifications.push(logElement); |
|||
setState({ sensorLog: sensorLog }); |
|||
} |
|||
|
|||
export function getSensorLog() { |
|||
return getState().sensorLog; |
|||
} |
|||
|
|||
export function resetSensorLog() { |
|||
setState({ |
|||
sensorLog: { |
|||
mouseLog: [], |
|||
clickedElements: [], |
|||
visitedSites: [], |
|||
playedSonifications: [], |
|||
}, |
|||
}); |
|||
} |
@ -1,14 +1,14 @@ |
|||
import ResetableComponent from "./ResetableComponent.jsx"; |
|||
|
|||
export default class DataComponent extends ResetableComponent { |
|||
constructor(componentId) { |
|||
super(); |
|||
this.compId = componentId; |
|||
} |
|||
|
|||
constructor(componentId) { |
|||
super(); |
|||
this.compId = componentId; |
|||
} |
|||
|
|||
exportData() {} |
|||
|
|||
getId() { return this.compId; } |
|||
exportData() {} |
|||
|
|||
getId() { |
|||
return this.compId; |
|||
} |
|||
} |
@ -1,7 +1,5 @@ |
|||
import React from 'react'; |
|||
import React from "react"; |
|||
|
|||
export default class ResetableComponent extends React.Component { |
|||
|
|||
reset() {} |
|||
|
|||
reset() {} |
|||
} |
@ -1,8 +1,7 @@ |
|||
import React from "react"; |
|||
|
|||
export default class NoPageFound extends React.Component { |
|||
|
|||
render() { |
|||
return (<p>No Page Found</p>); |
|||
} |
|||
render() { |
|||
return <p>No Page Found</p>; |
|||
} |
|||
} |
@ -1,10 +1,22 @@ |
|||
import React from "react"; |
|||
import InfoPageComponent from "../components/InfoPageComponent"; |
|||
|
|||
function TestInfoPage2({redirectLoc}) { |
|||
return (<InfoPageComponent redirectLoc={redirectLoc}> |
|||
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p> |
|||
</InfoPageComponent>); |
|||
function TestInfoPage2({ redirectLoc }) { |
|||
return ( |
|||
<InfoPageComponent redirectLoc={redirectLoc}> |
|||
<p> |
|||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy |
|||
eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam |
|||
voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet |
|||
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit |
|||
amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam |
|||
nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, |
|||
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea |
|||
rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem |
|||
ipsum dolor sit amet. |
|||
</p> |
|||
</InfoPageComponent> |
|||
); |
|||
} |
|||
|
|||
export default TestInfoPage2; |
@ -1,17 +1,31 @@ |
|||
import React from "react"; |
|||
import { getTranslation } from "../core/i18n/I18NHandler"; |
|||
import WebpageBanner from "../components/webpage_container/WebpageBanner"; |
|||
import { StudySite, getHeatmapData } from "../components/webpage_container/StudySite"; |
|||
import { |
|||
StudySite, |
|||
getHeatmapData, |
|||
} from "../components/webpage_container/StudySite"; |
|||
import { pushToMouseLog, getSensorLog } from "../core/log/SensorLogger"; |
|||
import { Button } from '@geist-ui/core'; |
|||
import { Button } from "@geist-ui/core"; |
|||
|
|||
|
|||
function TestPage({redirectLoc}) { |
|||
var clickFunction = function() { |
|||
pushToMouseLog(getHeatmapData()); |
|||
window.location.href = "./" + redirectLoc; |
|||
}; |
|||
return (<StudySite><WebpageBanner translationKey="hello_world" /><p onClick={() => {clickFunction()}}>{getTranslation("hello_world")}</p><Button onClick={clickFunction}>Ye</Button></StudySite>); |
|||
function TestPage({ redirectLoc }) { |
|||
var clickFunction = function () { |
|||
pushToMouseLog(getHeatmapData()); |
|||
window.location.href = "./" + redirectLoc; |
|||
}; |
|||
return ( |
|||
<StudySite> |
|||
<WebpageBanner translationKey="hello_world" /> |
|||
<p |
|||
onClick={() => { |
|||
clickFunction(); |
|||
}} |
|||
> |
|||
{getTranslation("hello_world")} |
|||
</p> |
|||
<Button onClick={clickFunction}>Ye</Button> |
|||
</StudySite> |
|||
); |
|||
} |
|||
|
|||
export default TestPage; |
@ -1,16 +1,31 @@ |
|||
import React from "react"; |
|||
import { getTranslation } from "../core/i18n/I18NHandler"; |
|||
import WebpageBanner from "../components/webpage_container/WebpageBanner"; |
|||
import { StudySite, getHeatmapData } from "../components/webpage_container/StudySite"; |
|||
import { |
|||
StudySite, |
|||
getHeatmapData, |
|||
} from "../components/webpage_container/StudySite"; |
|||
import { pushToMouseLog } from "../core/log/SensorLogger"; |
|||
import { Button } from '@geist-ui/core'; |
|||
import { Button } from "@geist-ui/core"; |
|||
|
|||
function TestPage2({redirectLoc}) { |
|||
var clickFunction = function() { |
|||
pushToMouseLog(getHeatmapData()); |
|||
window.location.href = "./" + redirectLoc; |
|||
}; |
|||
return (<StudySite><WebpageBanner translationKey="hello_world" /><p>{getTranslation("hello_world")}</p><Button onClick={() => {clickFunction()}}>Go on...</Button></StudySite>); |
|||
function TestPage2({ redirectLoc }) { |
|||
var clickFunction = function () { |
|||
pushToMouseLog(getHeatmapData()); |
|||
window.location.href = "./" + redirectLoc; |
|||
}; |
|||
return ( |
|||
<StudySite> |
|||
<WebpageBanner translationKey="hello_world" /> |
|||
<p>{getTranslation("hello_world")}</p> |
|||
<Button |
|||
onClick={() => { |
|||
clickFunction(); |
|||
}} |
|||
> |
|||
Go on... |
|||
</Button> |
|||
</StudySite> |
|||
); |
|||
} |
|||
|
|||
export default TestPage2; |
@ -1,22 +1,36 @@ |
|||
import React from "react"; |
|||
import { useParams } from 'react-router-dom'; |
|||
import { useParams } from "react-router-dom"; |
|||
import QuestionComponent from "../components/questionnaire/QuestionComponent"; |
|||
import { useQuestionComponent } from "../components/questionnaire/QuestionnaireData"; |
|||
|
|||
|
|||
export default function TestQuestionnaire() { |
|||
const { id } = useParams() |
|||
const { id } = useParams(); |
|||
|
|||
const q1Props = useQuestionComponent("checkbox", 4, "Lorem Ipsum", "/info", "./q2"); |
|||
const q2Props = useQuestionComponent("slider", 8, "Lorem Ipsum Ye", "./q1", "/info"); |
|||
const q1Props = useQuestionComponent( |
|||
"checkbox", |
|||
4, |
|||
"Lorem Ipsum", |
|||
"/info", |
|||
"./q2" |
|||
); |
|||
const q2Props = useQuestionComponent( |
|||
"slider", |
|||
8, |
|||
"Lorem Ipsum Ye", |
|||
"./q1", |
|||
"/info" |
|||
); |
|||
|
|||
const questionProperties = {"q1": q1Props, "q2": q2Props}; |
|||
const questionProperty = questionProperties[id]; |
|||
const questionProperties = { q1: q1Props, q2: q2Props }; |
|||
const questionProperty = questionProperties[id]; |
|||
|
|||
return ( |
|||
<div> |
|||
<QuestionComponent {...questionProperty} onUpdateAnswerData={questionProperty.handleUpdateAnswerData} /> |
|||
<p>Current Answer Data: {JSON.stringify(questionProperty.answerData)}</p> |
|||
</div> |
|||
); |
|||
return ( |
|||
<div> |
|||
<QuestionComponent |
|||
{...questionProperty} |
|||
onUpdateAnswerData={questionProperty.handleUpdateAnswerData} |
|||
/> |
|||
<p>Current Answer Data: {JSON.stringify(questionProperty.answerData)}</p> |
|||
</div> |
|||
); |
|||
} |
@ -1,81 +1,155 @@ |
|||
import React from "react"; |
|||
import { getTranslation } from "../../core/i18n/I18NHandler"; |
|||
import WebpageBanner from "../../components/webpage_container/WebpageBanner"; |
|||
import { StudySite, getHeatmapData } from "../../components/webpage_container/StudySite"; |
|||
import { |
|||
StudySite, |
|||
getHeatmapData, |
|||
} from "../../components/webpage_container/StudySite"; |
|||
import { pushToMouseLog, getSensorLog } from "../../core/log/SensorLogger"; |
|||
import { Collapse, Text, Spacer } from "@geist-ui/core"; |
|||
//import {logo} from "/public/images/budget_bird_logo.png"; |
|||
|
|||
function StartPage1({redirectLoc}) { |
|||
var saveMouseLog = function() { |
|||
pushToMouseLog(getHeatmapData()); |
|||
//window.location.href = "./" + redirectLoc; |
|||
}; |
|||
function StartPage1({ redirectLoc }) { |
|||
var saveMouseLog = function () { |
|||
pushToMouseLog(getHeatmapData()); |
|||
//window.location.href = "./" + redirectLoc; |
|||
}; |
|||
|
|||
// TODO THINK IF I WANT TO JUST USE AN OVERLAY FOR OTHER DUMMY LINKS, WHICH ARENT CORRECT??? |
|||
// IS THAT OKAY??? |
|||
// TODO THINK IF I WANT TO JUST USE AN OVERLAY FOR OTHER DUMMY LINKS, WHICH ARENT CORRECT??? |
|||
// IS THAT OKAY??? |
|||
|
|||
return (<StudySite> |
|||
<WebpageBanner translationKey="task_1_info" /> |
|||
<Spacer h={2}/> |
|||
<img src="/images/budget_bird_logo.png" width="150px" alt="BudgetBird Airlines Logo" /> |
|||
<Spacer h={1}/> |
|||
<h3>Frequently Asked Questions</h3> |
|||
<Spacer h={2}/> |
|||
<Collapse.Group> |
|||
<Collapse title="Under what conditions can I rebook?"> |
|||
<Text>You can change your flight bookings providing the fare conditions of your ticket permit this. |
|||
You can find out whether your ticket can be rebooked and whether there is a fee for this on your booking confirmation, or contact your travel agency or the Service Centre. |
|||
Alternatively, you can also rebook your flight details under <a href="./">My Bookings.</a></Text> |
|||
</Collapse> |
|||
<Collapse title="Can I make a name change after I have booked?"> |
|||
<Text>It is not possible to make a retroactive name change for a booking. Lufthansa shall provide the transport service to the passenger named in the ticket only. |
|||
The BudgetBird Service Team will be happy to help you if you require further information.</Text> |
|||
</Collapse> |
|||
<Collapse title="What are the upper weight limits for free baggage?"> |
|||
<Text>The maximum weight limit for your flight can be checked </Text> |
|||
<Text> |
|||
The maximum size per piece of baggage is 158 cm (width X + height Y + depth Z). |
|||
</Text> |
|||
<Text>Larger, heavier or additional bags are carried as excess baggage. You can find the prices in our baggage calculator.</Text> |
|||
</Collapse> |
|||
<Collapse title="What are the passenger rights you have in case of rescheduling or cancellation?"> |
|||
<Text>Since BudgetBird Airlines offers no flights outside the European Union the <a href="https://europa.eu/youreurope/citizens/travel/passenger-rights/air/index_en.htm">European Air Passenger Rights</a> will apply.</Text> |
|||
</Collapse> |
|||
<Collapse title="What costs will incur for rebooking my ticket?"> |
|||
<Text>You can change your flight bookings provided that the fare conditions of your ticket permit this. |
|||
You can find out whether your ticket can be rebooked and whether there is a fee for this on your booking confirmation, or contact your travel agency or the Service Centre. |
|||
Alternatively, you can also make a change to your flight details in the <a href="./">My Bookings</a> section. Depending on the alternative flight you choose, there may be surcharges on a higher fare. The amount of the surcharge depends on the availability of the selected booking class and will be communicated to you during the rebooking process.</Text> |
|||
</Collapse> |
|||
<Collapse title="How do I get my money returned to me after a refund?"> |
|||
<Text>In principle, the refund is paid using the same method that you originally used to pay for the ticket, in other words, if you paid for your ticket by credit card, the refund will be credited to the same credit card account.</Text> |
|||
</Collapse> |
|||
<Collapse title="How do I make a reservation?"> |
|||
<Text>To make a reservation, please contact us in the following way: </Text> |
|||
<Text>Contact Details: </Text> |
|||
<Text><a href="tel:+31 99 999 9999">+31 99 999 9999</a> |
|||
<Text>Monday - Sunday open for 24 hours.</Text> |
|||
<Text>(Standard phone charges apply.)</Text></Text> |
|||
<Text><a href="mailto:reservation.budgetbird@example.com">reservation.budgetbird@example.com</a></Text> |
|||
</Collapse> |
|||
<Collapse title="My flight was cancelled. How do I get my refund?"> |
|||
<Text>When your ticket has not been used, the ticket price including all taxes and fees will be refunded to the original method of payment. |
|||
If you have already used one or more segments of the journey, the unused portion of the fare value, taxes and fees will be calculated and the ticket refunded accordingly on a pro rata basis.</Text> |
|||
</Collapse> |
|||
<Collapse title="How can I contact the Customer Support?"> |
|||
<Text>Depending on your concern, you can contact Customer Support in the following ways:</Text> |
|||
<ul> |
|||
<li><a href="./">Flight Problems</a></li> |
|||
<li><a href="./">Rebooking</a></li> |
|||
<li><a href={redirectLoc} onclick={saveMouseLog()}>Delay/Cancellation Refunds</a></li> |
|||
<li><a href="./">General Concerns</a></li> |
|||
</ul> |
|||
</Collapse> |
|||
<Collapse title="How does the BudgetBird Miles program work?"> |
|||
<Text>For every 100 kilometers travelled on any flight with BudgetBird Airlines, you will gain one BudgetBird Mile. These BudgetBird Miles can be used within the BudgetBird Miles Store, in exchange for goodies, discounts or free BudgetBird flight tickets.</Text> |
|||
</Collapse> |
|||
</Collapse.Group> |
|||
</StudySite>); |
|||
return ( |
|||
<StudySite> |
|||
<WebpageBanner translationKey="task_1_info" /> |
|||
<Spacer h={2} /> |
|||
<img |
|||
src="/images/budget_bird_logo.png" |
|||
width="150px" |
|||
alt="BudgetBird Airlines Logo" |
|||
/> |
|||
<Spacer h={1} /> |
|||
<h3>Frequently Asked Questions</h3> |
|||
<Spacer h={2} /> |
|||
<Collapse.Group> |
|||
<Collapse title="Under what conditions can I rebook?"> |
|||
<Text> |
|||
You can change your flight bookings providing the fare conditions of |
|||
your ticket permit this. You can find out whether your ticket can be |
|||
rebooked and whether there is a fee for this on your booking |
|||
confirmation, or contact your travel agency or the Service Centre. |
|||
Alternatively, you can also rebook your flight details under{" "} |
|||
<a href="./">My Bookings.</a> |
|||
</Text> |
|||
</Collapse> |
|||
<Collapse title="Can I make a name change after I have booked?"> |
|||
<Text> |
|||
It is not possible to make a retroactive name change for a booking. |
|||
Lufthansa shall provide the transport service to the passenger named |
|||
in the ticket only. The BudgetBird Service Team will be happy to |
|||
help you if you require further information. |
|||
</Text> |
|||
</Collapse> |
|||
<Collapse title="What are the upper weight limits for free baggage?"> |
|||
<Text>The maximum weight limit for your flight can be checked </Text> |
|||
<Text> |
|||
The maximum size per piece of baggage is 158 cm (width X + height Y |
|||
+ depth Z). |
|||
</Text> |
|||
<Text> |
|||
Larger, heavier or additional bags are carried as excess baggage. |
|||
You can find the prices in our baggage calculator. |
|||
</Text> |
|||
</Collapse> |
|||
<Collapse title="What are the passenger rights you have in case of rescheduling or cancellation?"> |
|||
<Text> |
|||
Since BudgetBird Airlines offers no flights outside the European |
|||
Union the{" "} |
|||
<a href="https://europa.eu/youreurope/citizens/travel/passenger-rights/air/index_en.htm"> |
|||
European Air Passenger Rights |
|||
</a>{" "} |
|||
will apply. |
|||
</Text> |
|||
</Collapse> |
|||
<Collapse title="What costs will incur for rebooking my ticket?"> |
|||
<Text> |
|||
You can change your flight bookings provided that the fare |
|||
conditions of your ticket permit this. You can find out whether your |
|||
ticket can be rebooked and whether there is a fee for this on your |
|||
booking confirmation, or contact your travel agency or the Service |
|||
Centre. Alternatively, you can also make a change to your flight |
|||
details in the <a href="./">My Bookings</a> section. Depending on |
|||
the alternative flight you choose, there may be surcharges on a |
|||
higher fare. The amount of the surcharge depends on the availability |
|||
of the selected booking class and will be communicated to you during |
|||
the rebooking process. |
|||
</Text> |
|||
</Collapse> |
|||
<Collapse title="How do I get my money returned to me after a refund?"> |
|||
<Text> |
|||
In principle, the refund is paid using the same method that you |
|||
originally used to pay for the ticket, in other words, if you paid |
|||
for your ticket by credit card, the refund will be credited to the |
|||
same credit card account. |
|||
</Text> |
|||
</Collapse> |
|||
<Collapse title="How do I make a reservation?"> |
|||
<Text> |
|||
To make a reservation, please contact us in the following way:{" "} |
|||
</Text> |
|||
<Text>Contact Details: </Text> |
|||
<Text> |
|||
<a href="tel:+31 99 999 9999">+31 99 999 9999</a> |
|||
<Text>Monday - Sunday open for 24 hours.</Text> |
|||
<Text>(Standard phone charges apply.)</Text> |
|||
</Text> |
|||
<Text> |
|||
<a href="mailto:reservation.budgetbird@example.com"> |
|||
reservation.budgetbird@example.com |
|||
</a> |
|||
</Text> |
|||
</Collapse> |
|||
<Collapse title="My flight was cancelled. How do I get my refund?"> |
|||
<Text> |
|||
When your ticket has not been used, the ticket price including all |
|||
taxes and fees will be refunded to the original method of payment. |
|||
If you have already used one or more segments of the journey, the |
|||
unused portion of the fare value, taxes and fees will be calculated |
|||
and the ticket refunded accordingly on a pro rata basis. |
|||
</Text> |
|||
</Collapse> |
|||
<Collapse title="How can I contact the Customer Support?"> |
|||
<Text> |
|||
Depending on your concern, you can contact Customer Support in the |
|||
following ways: |
|||
</Text> |
|||
<ul> |
|||
<li> |
|||
<a href="./">Flight Problems</a> |
|||
</li> |
|||
<li> |
|||
<a href="./">Rebooking</a> |
|||
</li> |
|||
<li> |
|||
<a href={redirectLoc} onclick={saveMouseLog()}> |
|||
Delay/Cancellation Refunds |
|||
</a> |
|||
</li> |
|||
<li> |
|||
<a href="./">General Concerns</a> |
|||
</li> |
|||
</ul> |
|||
</Collapse> |
|||
<Collapse title="How does the BudgetBird Miles program work?"> |
|||
<Text> |
|||
For every 100 kilometers travelled on any flight with BudgetBird |
|||
Airlines, you will gain one BudgetBird Mile. These BudgetBird Miles |
|||
can be used within the BudgetBird Miles Store, in exchange for |
|||
goodies, discounts or free BudgetBird flight tickets. |
|||
</Text> |
|||
</Collapse> |
|||
</Collapse.Group> |
|||
</StudySite> |
|||
); |
|||
} |
|||
|
|||
export default StartPage1; |
Write
Preview
Loading…
Cancel
Save
Reference in new issue