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);