first commit
This commit is contained in:
commit
9e4d936a96
9 changed files with 4864 additions and 0 deletions
195
script.js
Normal file
195
script.js
Normal file
|
@ -0,0 +1,195 @@
|
|||
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 = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\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<fps*duration; i++) {
|
||||
console.log('>> 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);
|
||||
}
|
||||
})
|
Loading…
Add table
Add a link
Reference in a new issue