|
|
|
let ws = null;
|
|
|
|
let ls = window.localStorage;
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', main);
|
|
|
|
|
|
|
|
const EVENT = Object.freeze({
|
|
|
|
PREDRAW: 10,
|
|
|
|
STROKE: 20,
|
|
|
|
UNDO: 30,
|
|
|
|
REDO: 31,
|
|
|
|
IMAGE: 40,
|
|
|
|
});
|
|
|
|
|
|
|
|
const MESSAGE = Object.freeze({
|
|
|
|
INIT: 100,
|
|
|
|
SYN: 101,
|
|
|
|
ACK: 102,
|
|
|
|
FULL: 103,
|
|
|
|
FIRE: 104,
|
|
|
|
JOIN: 105,
|
|
|
|
});
|
|
|
|
|
|
|
|
const config = {
|
|
|
|
ws_url: 'wss://desk.local/ws/',
|
|
|
|
image_url: 'https://desk.local/images/',
|
|
|
|
sync_timeout: 1000,
|
|
|
|
ws_reconnect_timeout: 2000,
|
|
|
|
};
|
|
|
|
|
|
|
|
const storage = {
|
|
|
|
'state': {
|
|
|
|
'drawing': false,
|
|
|
|
'moving': false,
|
|
|
|
'mousedown': false,
|
|
|
|
'spacedown': false,
|
|
|
|
},
|
|
|
|
|
|
|
|
'predraw': {},
|
|
|
|
'timers': {},
|
|
|
|
'me': {},
|
|
|
|
|
|
|
|
'sn': 0, // what WE think SERVER SN is (we tell this to the server, it uses to decide how much stuff to SYN to us)
|
|
|
|
'server_lsn': 0, // what SERVER said LSN is (used to decide how much stuff to SYN)
|
|
|
|
'lsn': 0, // what actual LSN is (can't just use length of local queue because it gets cleared)
|
|
|
|
'queue': [], // to server
|
|
|
|
'events': [], // from server
|
|
|
|
'current_stroke': [],
|
|
|
|
|
|
|
|
'desk_id': 123,
|
|
|
|
|
|
|
|
'canvas': {
|
|
|
|
'width': 4096,
|
|
|
|
'height': 4096,
|
|
|
|
'offset_x': 0,
|
|
|
|
'offset_y': 0,
|
|
|
|
},
|
|
|
|
|
|
|
|
'cursor': {
|
|
|
|
'width': 8,
|
|
|
|
'color': 'rgb(0, 0, 0)',
|
|
|
|
'x': 0,
|
|
|
|
'y': 0,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
const elements = {
|
|
|
|
'cursor': null,
|
|
|
|
'canvas0': null,
|
|
|
|
'canvas1': null,
|
|
|
|
};
|
|
|
|
|
|
|
|
function event_size(event) {
|
|
|
|
let size = 1 + 1; // type + padding
|
|
|
|
|
|
|
|
switch (event.type) {
|
|
|
|
case EVENT.PREDRAW: {
|
|
|
|
size += 2 * 2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.STROKE: {
|
|
|
|
size += 2 + 2 + 4 + event.points.length * 2 * 2; // u16 (count) + u16 (width) + u32 (color + count * (u16, u16) points
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.UNDO:
|
|
|
|
case EVENT.REDO: {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.IMAGE: {
|
|
|
|
size += 4 + 2 + 2; // file id + x + y
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default: {
|
|
|
|
console.error('fuck');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
function predraw_event(x, y) {
|
|
|
|
return {
|
|
|
|
'type': EVENT.PREDRAW,
|
|
|
|
'x': x,
|
|
|
|
'y': y
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function stroke_event() {
|
|
|
|
return {
|
|
|
|
'type': EVENT.STROKE,
|
|
|
|
'points': storage.current_stroke,
|
|
|
|
'width': storage.cursor.width,
|
|
|
|
'color': color_to_u32(storage.cursor.color),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function undo_event() {
|
|
|
|
return { 'type': EVENT.UNDO };
|
|
|
|
}
|
|
|
|
|
|
|
|
function redo_event() {
|
|
|
|
return { 'type': EVENT.REDO };
|
|
|
|
}
|
|
|
|
|
|
|
|
function image_event(image_id, x, y) {
|
|
|
|
return {
|
|
|
|
'type': EVENT.IMAGE,
|
|
|
|
'image_id': image_id,
|
|
|
|
'x': x,
|
|
|
|
'y': y,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function main() {
|
|
|
|
const url = new URL(window.location.href);
|
|
|
|
const parts = url.pathname.split('/');
|
|
|
|
|
|
|
|
storage.desk_id = parts.length > 0 ? parts[parts.length - 1] : 0;
|
|
|
|
|
|
|
|
ws_connect();
|
|
|
|
|
|
|
|
elements.canvas0 = document.getElementById('canvas0');
|
|
|
|
elements.canvas1 = document.getElementById('canvas1');
|
|
|
|
elements.brush_color = document.getElementById('brush-color');
|
|
|
|
elements.brush_width = document.getElementById('brush-width');
|
|
|
|
elements.brush_preview = document.getElementById('brush-preview');
|
|
|
|
|
|
|
|
elements.brush_color.value = storage.cursor.color;
|
|
|
|
elements.brush_width.value = storage.cursor.width;
|
|
|
|
|
|
|
|
update_brush();
|
|
|
|
|
|
|
|
storage.canvas.offset_x = window.scrollX;
|
|
|
|
storage.canvas.offset_y = window.scrollY;
|
|
|
|
|
|
|
|
storage.canvas.max_scroll_x = storage.canvas.width - window.innerWidth;
|
|
|
|
storage.canvas.max_scroll_y = storage.canvas.height - window.innerHeight;
|
|
|
|
|
|
|
|
storage.ctx0 = elements.canvas0.getContext('2d');
|
|
|
|
storage.ctx1 = elements.canvas1.getContext('2d');
|
|
|
|
storage.ctx1.canvas.width = storage.ctx0.canvas.width = storage.canvas.width;
|
|
|
|
storage.ctx1.canvas.height = storage.ctx0.canvas.height = storage.canvas.height;
|
|
|
|
|
|
|
|
storage.ctx1.lineJoin = storage.ctx1.lineCap = storage.ctx0.lineJoin = storage.ctx0.lineCap = 'round';
|
|
|
|
storage.ctx1.lineWidth = storage.ctx0.lineWidth = storage.cursor.width;
|
|
|
|
|
|
|
|
window.addEventListener('pointerdown', on_down)
|
|
|
|
window.addEventListener('pointermove', on_move)
|
|
|
|
window.addEventListener('pointerup', on_up);
|
|
|
|
window.addEventListener('pointercancel', on_up);
|
|
|
|
window.addEventListener('touchstart', (e) => e.preventDefault());
|
|
|
|
window.addEventListener('keydown', on_keydown);
|
|
|
|
window.addEventListener('keyup', on_keyup);
|
|
|
|
window.addEventListener('resize', on_resize);
|
|
|
|
|
|
|
|
elements.brush_color.addEventListener('input', update_brush);
|
|
|
|
elements.brush_width.addEventListener('input', update_brush);
|
|
|
|
|
|
|
|
elements.canvas0.addEventListener('dragover', on_move);
|
|
|
|
elements.canvas0.addEventListener('drop', on_drop);
|
|
|
|
elements.canvas0.addEventListener('pointerleave', on_leave);
|
|
|
|
}
|