let space; let scale = 1; // todo: make zooming work let editing; let dragging; let moved; let movedFrom; let offset; document.onmousemove = event => { moved = true; if (!dragging) return; let left = (event.clientX - space.offsetLeft) * scale - offset.x; let top = (event.clientY - space.offsetTop) * scale - offset.y; dragging.style.left = `${left < 0 ? 0 : left}px`; dragging.style.top = `${top < 0 ? 0 : top}px`; save(dragging); }; let saving; let queue; function save(span) { if (saving) { queue = span; return; } saving = true; window.currentInstance.emit('save_span', { thread: window.currentThread.id, id: span.id ? span.id.slice(4) : '', content: span.innerText, x: span.style.left.slice(0, -2), y: span.style.top.slice(0, -2), scale: span.scale }, msg => { if (!msg.success) console.log('span save failed: ' + msg.message); if (!span.id) span.id = 'span' + msg.id; saving = false; if (queue) { save(queue); queue = null; } }); } function add(s) { let span = document.createElement('span'); span.classList.add('span'); if (s.id) span.id = 'span' + s.id; span.innerText = s.content; span.contentEditable = true; span.spellcheck = false; span.scale = s.scale; span.style.left = `${s.x}px`; span.style.top = `${s.y}px`; span.style.transform = `translate(-50%, -50%) scale(${s.scale})`; span.onkeydown = function(event) { if (event.key === 'Enter' && !event.getModifierState('Shift')) { event.preventDefault(); editing = null; span.blur(); } }; span.oninput = function(event) { save(this); }; span.onblur = function(event) { if (this.innerText) return; this.remove(); if (this.id) save(this); }; span.onwheel = function(event) { event.preventDefault(); if (event.deltaY < 0 && this.scale >= 200) return; this.scale *= 1 - event.deltaY * .001; this.style.transform = `translate(-50%, -50%) scale(${this.scale})`; save(this); }; span.onmousedown = function(event) { if (dragging || editing === this) return; dragging = this; event.preventDefault(); offset = { x: event.clientX - (space.offsetLeft + this.offsetLeft), y: event.clientY - (space.offsetTop + this.offsetTop) }; }; span.onmouseup = function(event) { event.stopPropagation(); dragging = null; if (moved) return; this.focus(); editing = this; }; space.append(span); return span; } export default function loadSpace(callback) { let instance = window.currentInstance; if (!instance.spaceid) { space = document.getElementById('space'); space.onmousedown = event => { moved = false; movedFrom = { x: event.offsetX, y: event.offsetY }; }; space.onmouseup = event => { if (dragging) { dragging.onmouseup(event); return; } if (editing) { if (event.target !== editing) editing = null; return; } if (moved && (event.offsetX - movedFrom.x) * (event.offsetX - movedFrom.x) + (event.offsetY - movedFrom.y) * (event.offsetY - movedFrom.y) > 100) return; editing = add({ x: event.offsetX + space.scrollLeft, y: event.offsetY + space.scrollTop, scale: 1, content: '' }); editing.focus(); }; instance.socket.on('span', msg => { if (msg.thread !== instance.spaceid || window.currentInstance !== instance) return; let span = document.getElementById('span' + msg.id); if (span) { span.innerText = msg.content; span.style.left = `${msg.x}px`; span.style.top = `${msg.y}px`; span.scale = msg.scale; span.style.transform = `translate(-50%, -50%) scale(${msg.scale})`; } else add(msg); }); } else if (instance.spaceid === window.currentThread.id) return; instance.spaceid = window.currentThread.id; space.innerHTML = ''; instance.emit('get_space', { thread: window.currentThread.id }, msg => { if (!msg.success) { console.log('get space failed: ' + msg.message); return; } callback && callback(msg.spans); msg.spans.forEach(add); }); };