import './jme-runtime.js'; import './locales.js'; import * as euk from './eukleides.mjs'; import './extension.js'; import './code-editor.mjs'; window.eukleides = euk; function toBinary(string) { const codeUnits = new Uint16Array(string.length); for (let i = 0; i < codeUnits.length; i++) { codeUnits[i] = string.charCodeAt(i); } return btoa(String.fromCharCode(...new Uint8Array(codeUnits.buffer))); } class Editor { constructor() { this.files = []; this.scope = new Numbas.jme.Scope([Numbas.jme.builtinScope, Numbas.extensions.eukleides.scope]) this.files = JSON.parse(localStorage.getItem('files') || '{"drawing":""}'); this.code_editor = document.getElementById('code'); this.display = document.getElementById('display'); this.time_input = document.getElementById('time'); this.time_display = document.querySelector('output[for="time"]'); this.fps_input = document.getElementById('fps'); this.width_input = document.getElementById('width'); this.download_still_link = document.getElementById('download-still'); this.make_animation_button = document.getElementById('make-animation'); this.download_animation_link = document.getElementById('download-animation'); this.code_editor.addEventListener('change', e => { const code = this.code_editor.value; this.save_file(code); this.render(); }) this.time_input.addEventListener('input', e => { this.redraw(); }); this.save_as_form = document.getElementById('save-as-form'); this.save_as_form.addEventListener('submit', e => { e.preventDefault(); this.filename = this.save_as_form.elements['save-as'].value; this.save_file(this.code_editor.value); this.make_files_list(); }) this.download_still_link.addEventListener('click', (e) => this.download_still()); this.make_animation_button.addEventListener('click', (e) => this.make_animation()); this.load_file('drawing'); this.render(); } make_files_list() { const ul = document.querySelector('#files ul'); ul.innerHTML = ''; for(let name of Object.keys(this.files)) { const li = document.createElement('li'); const button = document.createElement('button'); li.append(button); if(name == this.filename) { li.ariaCurrent = 'page'; } button.type = 'button'; button.addEventListener('click', (e) => { this.load_file(name); }); button.textContent = name; ul.append(li); } } load_file(name) { this.filename = name; this.code_editor.value = this.files[this.filename] || ''; this.make_files_list(); document.getElementById('current-file').textContent = this.filename; } save_file(text) { this.files[this.filename] = text; localStorage.setItem('files', JSON.stringify(this.files)); } render() { const code = this.code_editor.value; try { const v = this.scope.evaluate(code); const h = Numbas.jme.castToType(v, 'html'); this.drawing = h; h.ctx.get_time = () => this.time_input.valueAsNumber; h.ctx.draw(); this.display.innerHTML = ''; this.display.append(...h.value); } catch(e) { this.drawing = null; this.display.innerHTML = e.message; console.clear(); console.error(e); return; } } redraw() { if(!this.drawing) { return; } this.time_display.textContent = this.time_input.value; this.drawing.ctx.draw(); } async to_canvas() { console.log('to canvas'); const {min_x, min_y, max_x, max_y} = this.drawing.ctx.drawer; const vw = max_x - min_x; const vh = max_y - min_y; const width = this.width_input.valueAsNumber; const height = vh/vw * width; const img = document.createElement('img'); document.body.appendChild(img); const canvas = new OffscreenCanvas(width, height); return new Promise((resolve) => { // set it as the source of the img element img.onload = async function() { console.log('loaded'); // draw the image onto the canvas const ctx = canvas.getContext('2d'); ctx.fillStyle = 'white'; ctx.fillRect(0,0,width,height); ctx.drawImage(img, 0, 0, width, height); const blob = await canvas.convertToBlob(); const reader = new FileReader(); reader.readAsDataURL(blob); reader.onloadend = function() { const base64data = reader.result; resolve(base64data); img.parentElement.removeChild(img); } } console.log('setting src'); img.src = this.to_blob_url(); }) } to_blob_url() { const content = '\n' + this.display.innerHTML; const blob = new Blob([content], {type: 'image/svg+xml'}); const url = URL.createObjectURL(blob); return url } async download_still() { this.download_still_link.download = this.filename+'.svg'; this.download_still_link.href = this.to_blob_url(); } async make_animation() { this.download_animation_link.href = ''; const duration = 10; const fps = this.fps_input.valueAsNumber; const frames = []; for(let i=0; i> frame',i); this.drawing.ctx.get_time = () => i/fps; this.drawing.ctx.draw(); frames.push(await this.to_canvas()) } const blob = new Blob([JSON.stringify({fps, duration, frames})], {type: 'application/json'}); const url = URL.createObjectURL(blob); this.download_animation_link.download = this.filename+'.json'; this.download_animation_link.href = url; } } Numbas.queueScript('base', [], function () {}); Numbas.runImmediately(['jme'], function () {}); Numbas.queueScript('base', [], function () {}); Numbas.queueScript('demo', ['extensions/eukleides/eukleides.js'], function () { Numbas.activateExtension('eukleides'); try { window.editor = new Editor(); } catch(e) { console.error(e); } })