gazeplotter
Version:
Gazeplotter is a Svelte application for visualizing eye-tracking data.
133 lines (132 loc) • 4.99 kB
JavaScript
import ModalContentTobiiParsingInput from '../../components/Modal/ModalContent/ModalContentTobiiParsingInput.svelte';
import { modalStore } from '../../stores/modalStore.ts';
import { addErrorToast, addInfoToast, toastStore, } from '../../stores/toastStore.ts';
/**
* Creates a worker to handle whole eyefiles processing.
* It is a separate file to avoid blocking the main thread.
*
* Workers must be instantiated in a specific way to work with TypeScript modules in Vite:
* new Worker(new URL('path/to/typescriptWorker.ts', import.meta.url), { type: 'module' })
*/
export class EyeWorkerService {
worker;
onData;
constructor(onData) {
this.worker = new Worker(new URL('$lib/worker/eyePipelineWorker.ts', import.meta.url), {
type: 'module',
});
this.worker.onmessage = this.handleMessage.bind(this);
this.worker.onerror = (event) => this.handleError(event);
this.onData = onData;
}
/**
* Sends the given files to the worker.
* @param files - The files to send.
*/
sendFiles(files) {
const fileNames = [];
// check extension of first file
const extension = files[0].name.split('.').pop();
if (extension === 'json')
return this.processJsonWorkspace(files[0]);
for (let index = 0; index < files.length; index++) {
fileNames.push(files[index].name);
}
this.worker.postMessage({ type: 'file-names', data: fileNames });
if (this.isStreamTransferable()) {
this.processDataAsStream(files);
}
else {
void this.processDataAsArrayBuffer(files);
}
}
/**
* Makes a test postMessage call to the worker to check if the browser supports transferable streams.
* If the browser does not support transferable streams, runtimes will throw an error.
* This method catches the error and returns false.
*
* @returns {boolean} - Whether the browser supports transferable streams.
*/
isStreamTransferable() {
const stream = new ReadableStream({
start(controller) {
controller.enqueue(new Uint8Array([]));
controller.close();
},
});
try {
this.worker.postMessage({ type: 'test-stream', data: stream }, [stream]);
return true;
}
catch (error) {
return false;
}
}
processDataAsStream(files) {
for (let index = 0; index < files.length; index++) {
const stream = files[index].stream();
this.worker.postMessage({ type: 'stream', data: stream }, [stream]);
}
}
async processDataAsArrayBuffer(files) {
for (let index = 0; index < files.length; index++) {
const buffer = await files[index].arrayBuffer();
this.worker.postMessage({ type: 'buffer', data: buffer }, [buffer]);
}
}
processJsonWorkspace(file) {
addInfoToast('Loading workspace from JSON file. GazePlotter accepts only JSON files exported from its environment');
addInfoToast('Only the first file will be loaded');
const reader = new FileReader();
reader.onload = () => {
const json = JSON.parse(reader.result);
this.onData(json);
};
reader.readAsText(file);
}
handleMessage(event) {
switch (event.data.type) {
case 'done':
this.onData(event.data.data);
break;
case 'fail':
this.handleError(event.data.data);
break;
case 'request-user-input':
this.handleUserInputProcess();
break;
default:
console.error('EyeWorkerService.handleMessage() - event:', event);
}
}
handleError(event) {
addErrorToast('Could not process the file');
console.error(event.error);
console.error('EyeWorkerService.handleError() - event:', event);
}
handleUserInputProcess() {
this.requestUserInput()
.then(userInput => {
this.worker.postMessage({ type: 'user-input', data: userInput });
modalStore.close();
})
.catch(() => {
addInfoToast('User input was not provided. The file will be processed as Tobii without events');
this.worker.postMessage({ type: 'user-input', data: '' });
});
}
/**
* Requests user input for further processing,
* to determine how to parse stimuli in the file.
*
* The user input is then sent to the worker which resumes processing.
*/
requestUserInput() {
return new Promise((resolve, reject) => {
modalStore.open(ModalContentTobiiParsingInput, 'Tobii Parsing Input', {
valuePromiseResolve: resolve,
valuePromiseReject: reject,
});
});
}
}