diff --git a/package-lock.json b/package-lock.json index 69db57b..194b402 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,8 @@ "react-i18next": "^14.1.1", "react-router-dom": "^6.23.0", "react-scripts": "5.0.1", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "zustand": "^4.5.2" } }, "node_modules/@adobe/css-tools": { @@ -8843,6 +8844,11 @@ "node": ">= 0.6" } }, + "node_modules/fs": { + "version": "0.0.1-security", + "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", + "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==" + }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -17496,6 +17502,14 @@ "requires-port": "^1.0.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -18589,6 +18603,33 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.2.tgz", + "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==", + "dependencies": { + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 1b3283f..b0bbf52 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "react-i18next": "^14.1.1", "react-router-dom": "^6.23.0", "react-scripts": "5.0.1", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "zustand": "^4.5.2" }, "scripts": { "start": "react-scripts start", diff --git a/src/App.js b/src/App.js index 71f1c6a..5819e0a 100644 --- a/src/App.js +++ b/src/App.js @@ -1,58 +1,30 @@ -import React, {useEffect} from "react"; +import React, { useEffect } from "react"; import { Route, Routes } from "react-router-dom"; import { GeistProvider, CssBaseline } from '@geist-ui/core' - -import { SensorLogger } from "./core/log/SensorLogger"; - -import h337 from "heatmap.js"; +import RouteTracker from "./core/log/RouteTracker"; +import { createStore } from 'zustand/vanilla' +import { persist, createJSONStorage } from 'zustand/middleware' const NoPageFound = React.lazy(() => import("./pages/NoPageFound")); const TestPage = React.lazy(() => import("./pages/TestPage")); const TestQuestionnaire = React.lazy(() => import("./pages/TestQuestionnaire")); -const mouseMode = true; - -function App() { - - useEffect(() => { - /*var heatmapInstance = h337.create({ - container: document.body, - radius: 20 - }); - heatmapInstance.setDataMax(9999); +export const sensorLogState = createStore( + persist( + () => ({ + sensorLog: {mouseLog: [], clickedElements: [], visitedSites: [], playedSonifications: []} + }), + { + name: 'sensor-storage', // name of the item in the storage (must be unique) + storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used + }, + ), +) - var addData; +export const { getState, setState, subscribe, getInitialState } = sensorLogState; - if(mouseMode) { - const mouseDataFunc = function(ev) { - heatmapInstance.addData({ - x: ev.layerX, - y: ev.layerY, - value: 1 - }); - }; - - addData = mouseDataFunc; - } else { - const touchDataFunc = function(ev) { - heatmapInstance.addData({ - x: ev.layerX, - y: ev.layerY, - value: 1 - }); - }; - addData = touchDataFunc; - } - - if(mouseMode) { - 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); - }*/ - }) +function App() { // TODO FIX... /* @@ -70,18 +42,15 @@ function App() { wait(200); - var logRoutePath = (path) => { - SensorLogger.pushToVisitLog(path); - }; - return ( + - ...}>} /> - ...}>} /> - ...}>} /> - ...}>} /> + ...}>} /> + ...}>} /> + ...}>} /> + ...}>} /> diff --git a/src/components/webpage_container/StudySite.jsx b/src/components/webpage_container/StudySite.jsx index e072770..7dc65ed 100644 --- a/src/components/webpage_container/StudySite.jsx +++ b/src/components/webpage_container/StudySite.jsx @@ -18,7 +18,7 @@ export function StudySite(props) { heatmapInstance.addData({ x: ev.layerX, y: ev.layerY, - value: 1 + value: 1, }); }; diff --git a/src/core/audio/AudioHandler.jsx b/src/core/audio/AudioHandler.jsx index 1f4534e..92904cf 100644 --- a/src/core/audio/AudioHandler.jsx +++ b/src/core/audio/AudioHandler.jsx @@ -1,22 +1,27 @@ import {Component} from 'react'; import * as Tone from "tone"; +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) { diff --git a/src/core/log/RouteTracker.jsx b/src/core/log/RouteTracker.jsx new file mode 100644 index 0000000..36d962e --- /dev/null +++ b/src/core/log/RouteTracker.jsx @@ -0,0 +1,15 @@ +import React, { useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; +import { pushToVisitLog } from './SensorLogger'; + +const RouteTracker = () => { + const location = useLocation(); + + useEffect(() => { + pushToVisitLog({ pathname: location.pathname, timestamp: new Date() }); + }, [location.pathname]); + + return null; +}; + +export default RouteTracker; diff --git a/src/core/log/SensorLogger.jsx b/src/core/log/SensorLogger.jsx index b1e1614..1141bbd 100644 --- a/src/core/log/SensorLogger.jsx +++ b/src/core/log/SensorLogger.jsx @@ -1,33 +1,47 @@ -import {Component} from 'react'; +import { getState, setState } from '../../App'; -export class SensorLogger extends Component { - static sensorLog = {mouseLog: [], orientationLog: [], clickedElements: [], visitedSites: [], playedSonifications: []}; - - static pushToMouseLog(logElement) { - this.sensorLog.mouseLog.push(logElement); + export function pushToMouseLog(logElement) { + const sensorLog = getState().sensorLog; + sensorLog.mouseLog.push(logElement); + setState({ sensorLog: sensorLog }); } - static pushToOrientationLog(logElement) { - this.sensorLog.orientationLog.push(logElement); - } + export function pushToClickLog(logElement) { + // Filter out dummy body objects + if(logElement.target.localName === "body") { + return; + } - static pushToClickLog(logElement) { - this.sensorLog.clickedElements.push(logElement); + const sensorLog = getState().sensorLog; + sensorLog.clickedElements.push(logElement); + setState({ sensorLog: sensorLog }); } - static pushToVisitLog(logElement) { - this.sensorLog.visitedSites.push(logElement); + 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 }); + } } - static pushToSonificationLog(logElement) { - this.sensorLog.playedSonifications.push(logElement); + export function pushToSonificationLog(logElement) { + const sensorLog = getState().sensorLog; + sensorLog.playedSonifications.push(logElement); + setState({ sensorLog: sensorLog }); } - static getSensorLog() { - return this.sensorLog; + export function getSensorLog() { + return getState().sensorLog; } - static resetSensorLog() { - this.sensorLog = {mouseLog: [], orientationLog: [], clickedElements: [], visitedSites: [], playedSonifications: []}; - } -} \ No newline at end of file + export function resetSensorLog() { + setState({ sensorLog: {mouseLog: [], clickedElements: [], visitedSites: [], playedSonifications: []}}); + } \ No newline at end of file diff --git a/src/index.css b/src/index.css index 20bade3..74f50d2 100644 --- a/src/index.css +++ b/src/index.css @@ -16,4 +16,8 @@ code { .heatmap-canvas { pointer-events: none; +} + +canvas { + opacity: 0; } \ No newline at end of file diff --git a/src/index.js b/src/index.js index b958206..3c9757f 100644 --- a/src/index.js +++ b/src/index.js @@ -5,10 +5,11 @@ import App from './App'; import reportWebVitals from './reportWebVitals'; import { BrowserRouter } from "react-router-dom"; import "./core/i18n/i18n"; -import { SensorLogger } from './core/log/SensorLogger'; +import { pushToClickLog } from './core/log/SensorLogger'; -document.onclick = function(event) {SensorLogger.pushToClickLog(event);}; -window.addEventListener("deviceorientation", function(event) {SensorLogger.pushToOrientationLog(event);}); +document.onclick = function(event) { + pushToClickLog(event); +}; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( diff --git a/src/pages/TestPage.jsx b/src/pages/TestPage.jsx index 786ec33..a72bccd 100644 --- a/src/pages/TestPage.jsx +++ b/src/pages/TestPage.jsx @@ -1,18 +1,16 @@ import React from "react"; import { getTranslation } from "../core/i18n/I18NHandler"; import WebpageBanner from "../components/webpage_container/WebpageBanner"; -import { SensorLogger } from "../core/log/SensorLogger"; import { StudySite, getHeatmapData } from "../components/webpage_container/StudySite"; +import { pushToMouseLog, getSensorLog } from "../core/log/SensorLogger"; -export default class TestPage extends React.Component { - - - - render() { - var clickFunction = function(){ - SensorLogger.pushToMouseLog(getHeatmapData()); - console.log(SensorLogger.getSensorLog()); +function TestPage() { + var clickFunction = function() { + pushToMouseLog(getHeatmapData()); + console.log(getSensorLog()); + alert(JSON.stringify(getSensorLog())); }; - return (

{getTranslation("hello_world")}

); - } -} \ No newline at end of file + return (

{getTranslation("hello_world")}

); +} + +export default TestPage; \ No newline at end of file