// vim: set ts=2 sw=2 et tw=80: class App { static get BRUSHES() { return { "PenBrush": new PenBrush(), "DiscBrush": new DiscBrush(), "StarBrush": new StarBrush(), }; } constructor(conf) { if (!(typeof conf === 'object' && conf)) { throw new Error('Argument conf different from specification'); } this.canvas = document.getElementById(conf.canvas); if (!this.canvas || this.canvas.tagName !== 'CANVAS') { throw new Error(`canvas is not a canvas`); } this.ctx = this.canvas.getContext('2d'); this.favourites = document.querySelector('div#favourites'); if (typeof conf.buttons === 'object' && conf.buttons) { this.buttons = {} for (const b of ['clear', 'undo', 'camera']) this.buttons[b] = document.getElementById(conf.buttons[b]); if (this.buttons.clear) this.buttons.clear.addEventListener('click', () => { this.erase(); this.background = null; history.clear(); }); if (this.buttons.undo) this.buttons.undo.addEventListener('click', () => history.pop(this)); if (this.buttons.camera) this.buttons.camera.addEventListener('click', () => { player.srcObject.getVideoTracks().forEach(track => track.stop()); const base64 = this.canvas.toDataURL(); const img = document.createElement('img'); img.src = base64; const span = document.createElement('span'); span.contentEditable = true; this.favourites.appendChild(img); this.favourites.appendChild(span); }); const player = document.createElement('video'); player.addEventListener('loadeddata', () => { const imgCanvas = document.createElement('canvas'); imgCanvas.width = this.canvas.width; imgCanvas.height = this.canvas.height; const imgCtx = imgCanvas.getContext('2d'); imgCtx.drawImage(player, 0, 0, canvas.width, canvas.height); const img = document.createElement('img'); img.src = imgCanvas.toDataURL(); img.addEventListener('load', () => { this.background = img; this.redrawAll(); }); player.srcObject.getVideoTracks().forEach(track => track.stop()); }); const button = document.createElement('button'); button.type = 'button'; button.innerHTML = 'Photo'; button.addEventListener('click', () => { navigator.mediaDevices.getUserMedia({ video: true }) .then((stream) => { player.srcObject = stream; }); }); document.getElementById('left-toolbar').appendChild(button); } this.ctx.lineWidth = 1; this.strokeStyle = this.constructor.defaultStrokeStyle; this.brush = "PenBrush"; const brushToolbar = document.querySelector('#brush-toolbar'); for (const name in App.BRUSHES) { const b = document.createElement('button'); b.innerText = name; b.addEventListener('click', () => this.brush = name); brushToolbar.appendChild(b); } const label = document.createElement('label'); label.setAttribute('for', 'color'); const color = document.createElement('input'); color.type = 'color'; color.name = 'color'; color.addEventListener('change', () => this.strokeStyle = color.value); brushToolbar.appendChild(label); brushToolbar.appendChild(color); this.canvas.addEventListener('mousedown', this.startPath.bind(this)); this.canvas.addEventListener('mousemove', this.draw.bind(this)); this.canvas.addEventListener('mouseup', this.endPath.bind(this)); this.canvas.addEventListener('mouseout', this.endPath.bind(this)); } static get defaultStrokeStyle() { return 'black'; } get strokeStyle() { return this.ctx.strokeStyle; } set strokeStyle(style) { if (typeof style !== 'string') { throw new Error('style is not a string'); } this.ctx.strokeStyle = style; } get brush() { return this._brush.name; } set brush(brushName) { this._brush = App.BRUSHES[brushName]; } erase() { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); } startPath(e, record = true) { this.ctx.beginPath(); this.ctx.moveTo(e.offsetX, e.offsetY); if (record) { history.initializeNewPath(); history.push(new Stroke(this.brush, this.strokeStyle, e.offsetX, e.offsetY)); } this.mousedown = true; } draw(e, beginNew = true, record = true) { if (this.mousedown) { this._brush.draw(this.ctx, this.strokeStyle, e.offsetX, e.offsetY); if (record) { history.push(new Stroke(this.brush, this.strokeStyle, e.offsetX, e.offsetY)); } if (beginNew) { this.ctx.beginPath(); this.ctx.moveTo(e.offsetX, e.offsetY); } else { this.mousedown = false; } } } endPath(e, record = true) { this.draw(e, false, record); } drawPath(path) { const last = path.length - 1; const lastBrush = this.brush; const lastStyle = this.strokeStyle; for (let i = 0; i <= last; i++) { this.brush = path[i].brushName; this.strokeStyle = path[i].strokeStyle; switch(i) { case 0: this.startPath(path[i], false); break; case last: this.endPath(path[i], false); break; default: this.draw(path[i], true, false); } } this.brush = lastBrush; this.strokeStyle = lastStyle; } redrawAll() { this.erase(); if (this.background) { this.ctx.drawImage(this.background, 0, 0, this.canvas.width, this.canvas.height); } for (const path of history.paths) { this.drawPath(path); } } }