numbas-elm-theme/load-app.js
Christian Lawson-Perfect 24fa9a2d77 first commit
2025-02-09 20:17:33 +00:00

136 lines
No EOL
3.5 KiB
JavaScript

import show_error from './show-error.mjs';
console.clear();
(async () => {
const compilation_error = await show_error;
if(compilation_error) {
return;
}
});
class NumbasEmbedder {
constructor() {
this.exams = {};
this.numbas_promise = new Promise(resolve => {
Numbas.queueScript('go', ['start-exam', 'display'], async () => {
Numbas.display.init();
resolve(Numbas);
})
})
}
get_exam(id) {
if(!this.exams[id]) {
const exam_ref = this.exams[id] = {}
exam_ref.promise = new Promise((resolve,reject) => {
exam_ref.resolve = resolve;
exam_ref.reject = reject;
});
}
return this.exams[id];
}
async add_exam(id, source_url) {
console.log('add exam',id, source_url);
await this.numbas_promise;
console.log('numbas loaded', id);
const res = await fetch(source_url);
const exam_file = await res.text();
const exam_json = JSON.parse(exam_file.slice(exam_file.indexOf('\n')))
const exam = window.exam = Numbas.createExamFromJSON(exam_json);
exam.display_id = id;
exam.init();
exam.signals.on(['ready', 'display question list initialised'], () => {
console.info('exam ready', id);
Numbas.signals.trigger('exam ready');
exam.begin();
const exam_promise = this.get_exam(id);
console.log("LOADED", id);
exam_promise.resolve(exam);
});
}
}
const numbas_embedder = window.numbas_embedder = new NumbasEmbedder();
class ElmHTMLElement extends HTMLElement {
constructor() {
super();
this.attachShadow({mode:'open'});
}
static get observedAttributes() { return ['html'] };
attributeChangedCallback(name, oldValue, newValue) {
if(name == 'html') {
this.html = newValue;
}
}
set html(value) {
if(typeof value == 'string') {
this.shadowRoot.innerHTML = value;
} else {
this.shadowRoot.innerHTML = '';
this.shadowRoot.append(value);
}
}
}
customElements.define('elm-html', ElmHTMLElement);
class NumbasPartElement extends HTMLElement {
constructor() {
super();
this.attachShadow({mode:'open'});
const style = document.createElement('link');
style.rel = 'stylesheet';
style.href = 'numbas-part.css';
this.shadowRoot.append(style);
this.init_app();
}
async init_app() {
const exam_id = this.getAttribute('exam');
const mode = this.getAttribute('mode');
const questionNumber = parseInt(this.getAttribute('question'));
const partPath = this.getAttribute('part');
const exam = await numbas_embedder.get_exam(exam_id).promise;
const container = document.createElement('div');
this.shadowRoot.append(container);
const app = Elm.App.init({
node: container,
flags: {exam: exam, view_mode: {mode, questionNumber, partPath}}
});
const message_handlers = {
'question': ({questionNumber, msg}) => {
const question = exam.questionList[questionNumber];
question.display.handle_message(msg);
}
}
app.ports.sendMessage.subscribe(data => {
const fn = message_handlers[data.msgtype];
if(fn) {
fn(data);
}
})
exam.events.on('update app', ({type, arg}) => {
//type != 'showTiming' && console.log('update app', {type,arg});
app.ports.receiveMessage.send({type, arg});
});
}
}
customElements.define('numbas-part', NumbasPartElement);