A.Olokhtonov
2 years ago
16 changed files with 793 additions and 640 deletions
@ -0,0 +1,350 @@
@@ -0,0 +1,350 @@
|
||||
function deserializer_create(buffer, dataview) { |
||||
return { |
||||
'offset': 0, |
||||
'size': buffer.byteLength, |
||||
'buffer': buffer, |
||||
'view': dataview, |
||||
'strview': new Uint8Array(buffer), |
||||
}; |
||||
} |
||||
|
||||
function des_u8(d) { |
||||
const value = d.view.getUint8(d.offset); |
||||
d.offset += 1; |
||||
return value; |
||||
} |
||||
|
||||
function des_u16(d) { |
||||
const value = d.view.getUint16(d.offset, true); |
||||
d.offset += 2; |
||||
return value; |
||||
} |
||||
|
||||
function des_u32(d) { |
||||
const value = d.view.getUint32(d.offset, true); |
||||
d.offset += 4; |
||||
return value; |
||||
} |
||||
|
||||
function des_f32(d) { |
||||
const value = d.view.getFloat32(d.offset, true); |
||||
d.offset += 4; |
||||
return value; |
||||
} |
||||
|
||||
function des_f32array(d, count) { |
||||
const result = []; |
||||
|
||||
for (let i = 0; i < count; ++i) { |
||||
const item = d.view.getFloat32(d.offset, true); |
||||
d.offset += 4; |
||||
result.push(item); |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
function des_event(d) { |
||||
const event = {}; |
||||
|
||||
event.type = des_u8(d); |
||||
event.user_id = des_u32(d); |
||||
|
||||
switch (event.type) { |
||||
case EVENT.PREDRAW: { |
||||
event.x = des_f32(d); |
||||
event.y = des_f32(d); |
||||
break; |
||||
} |
||||
|
||||
case EVENT.STROKE: { |
||||
const stroke_id = des_u32(d); |
||||
const point_count = des_u16(d); |
||||
const width = des_u16(d); |
||||
const color = des_u32(d); |
||||
const coords = des_f32array(d, point_count * 2); |
||||
|
||||
event.stroke_id = stroke_id; |
||||
event.points = []; |
||||
|
||||
for (let i = 0; i < point_count; ++i) { |
||||
const x = coords[2 * i + 0]; |
||||
const y = coords[2 * i + 1]; |
||||
event.points.push({'x': x, 'y': y}); |
||||
} |
||||
|
||||
event.color = color; |
||||
event.width = width; |
||||
|
||||
break; |
||||
} |
||||
|
||||
case EVENT.IMAGE: |
||||
case EVENT.IMAGE_MOVE: { |
||||
event.image_id = des_u32(d); |
||||
event.x = des_f32(d); |
||||
event.y = des_f32(d); |
||||
break; |
||||
} |
||||
|
||||
case EVENT.UNDO: |
||||
case EVENT.REDO: { |
||||
break; |
||||
} |
||||
|
||||
case EVENT.ERASER: { |
||||
event.stroke_id = des_u32(d); |
||||
break; |
||||
} |
||||
|
||||
default: { |
||||
console.error('fuck'); |
||||
} |
||||
} |
||||
|
||||
return event; |
||||
} |
||||
|
||||
function bitmap_bbox(event) { |
||||
const bbox = { |
||||
'xmin': event.x, |
||||
'xmax': event.x + event.bitmap.width, |
||||
'ymin': event.y, |
||||
'ymax': event.y + event.bitmap.height |
||||
}; |
||||
|
||||
return bbox; |
||||
} |
||||
|
||||
async function handle_event(state, context, event, relax = false) { |
||||
if (config.debug_print) console.debug(`event type ${event.type} from user ${event.user_id}`); |
||||
|
||||
switch (event.type) { |
||||
case EVENT.STROKE: { |
||||
if (event.user_id != state.me) { |
||||
clear_dynamic_stroke(state, context, event.user_id); |
||||
} |
||||
|
||||
add_static_stroke(state, context, event, relax); |
||||
|
||||
break; |
||||
} |
||||
|
||||
case EVENT.UNDO: { |
||||
console.error('todo'); |
||||
// for (let i = state.events.length - 1; i >=0; --i) {
|
||||
// const other_event = state.events[i];
|
||||
|
||||
// // Users can only undo their own, undeleted (not already undone) events
|
||||
// if (other_event.user_id === event.user_id && !other_event.deleted) {
|
||||
// if (other_event.type === EVENT.STROKE) {
|
||||
// other_event.deleted = true;
|
||||
// const stats = stroke_stats(other_event.points, state.cursor.width);
|
||||
// redraw_region(stats.bbox);
|
||||
// break;
|
||||
// } else if (other_event.type === EVENT.IMAGE) {
|
||||
// other_event.deleted = true;
|
||||
// const item = document.querySelector(`img[data-image-id="${other_event.image_id}"]`);
|
||||
// if (item) item.remove();
|
||||
// break;
|
||||
// } else if (other_event.type === EVENT.ERASER) {
|
||||
// other_event.deleted = true;
|
||||
// const erased = find_stroke_backwards(other_event.stroke_id);
|
||||
// if (erased) {
|
||||
// erased.deleted = false;
|
||||
// const stats = stroke_stats(erased.points, state.cursor.width);
|
||||
// redraw_region(stats.bbox);
|
||||
// }
|
||||
// break;
|
||||
// } else if (other_event.type === EVENT.IMAGE_MOVE) {
|
||||
// const item = document.querySelector(`img[data-image-id="${other_event.image_id}"]`);
|
||||
|
||||
// const ix = state.images[other_event.image_id].x -= other_event.x;
|
||||
// const iy = state.images[other_event.image_id].y -= other_event.y;
|
||||
|
||||
// item.style.transform = `translate(${ix}px, ${iy}px)`;
|
||||
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
break; |
||||
} |
||||
|
||||
case EVENT.IMAGE: { |
||||
console.error('todo'); |
||||
// const url = config.image_url + event.image_id;
|
||||
// const item = document.createElement('img');
|
||||
|
||||
// item.classList.add('floating-image');
|
||||
// item.style['z-index'] = state.events.length;
|
||||
// item.setAttribute('data-image-id', event.image_id);
|
||||
// item.setAttribute('src', url);
|
||||
// item.style.transform = `translate(${event.x}px, ${event.y}px)`;
|
||||
|
||||
// elements.images.appendChild(item);
|
||||
// state.images[event.image_id] = {
|
||||
// 'x': event.x, 'y': event.y
|
||||
// };
|
||||
// const r = await fetch(config.image_url + event.image_id);
|
||||
// const blob = await r.blob();
|
||||
// const bitmap = await createImageBitmap(blob);
|
||||
// event.bitmap = bitmap;
|
||||
// const bbox = bitmap_bbox(event);
|
||||
// state.ctx0.drawImage(bitmap, bbox.xmin, bbox.ymin);
|
||||
|
||||
break; |
||||
} |
||||
|
||||
case EVENT.IMAGE_MOVE: { |
||||
console.error('todo'); |
||||
// // Already moved due to local prediction
|
||||
// if (event.user_id !== state.me.id) {
|
||||
// const image_id = event.image_id;
|
||||
// const item = document.querySelector(`.floating-image[data-image-id="${image_id}"]`);
|
||||
|
||||
// const ix = state.images[event.image_id].x += event.x;
|
||||
// const iy = state.images[event.image_id].y += event.y;
|
||||
|
||||
// if (item) {
|
||||
// item.style.transform = `translate(${ix}px, ${iy}px)`;
|
||||
// }
|
||||
// }
|
||||
|
||||
break; |
||||
} |
||||
|
||||
case EVENT.ERASER: { |
||||
console.error('todo'); |
||||
// if (event.deleted) {
|
||||
// break;
|
||||
// }
|
||||
|
||||
// for (const other_event of state.events) {
|
||||
// if (other_event.type === EVENT.STROKE && other_event.stroke_id === event.stroke_id) {
|
||||
// // Might already be deleted because of local prediction
|
||||
// if (!other_event.deleted) {
|
||||
// other_event.deleted = true;
|
||||
// const stats = stroke_stats(other_event.points, state.cursor.width);
|
||||
// redraw_region(stats.bbox);
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
break; |
||||
} |
||||
|
||||
default: { |
||||
console.error('fuck'); |
||||
} |
||||
} |
||||
} |
||||
|
||||
async function handle_message(state, context, d) { |
||||
const message_type = des_u8(d); |
||||
let do_draw = false; |
||||
|
||||
if (config.debug_print) console.debug(message_type); |
||||
|
||||
switch (message_type) { |
||||
case MESSAGE.JOIN: |
||||
case MESSAGE.INIT: { |
||||
state.me = des_u32(d); |
||||
state.server_lsn = des_u32(d); |
||||
|
||||
if (state.server_lsn > state.lsn) { |
||||
// Server knows something that we don't
|
||||
state.lsn = state.server_lsn; |
||||
} |
||||
|
||||
if (message_type === MESSAGE.JOIN) { |
||||
localStorage.setItem('sessionId', des_u32(d)); |
||||
if (config.debug_print) console.debug('join in'); |
||||
} else { |
||||
if (config.debug_print) console.debug('init in'); |
||||
} |
||||
|
||||
const event_count = des_u32(d); |
||||
|
||||
if (config.debug_print) console.debug(`${event_count} events in init`); |
||||
|
||||
state.events.length = 0; |
||||
|
||||
for (let i = 0; i < event_count; ++i) { |
||||
const event = des_event(d); |
||||
await handle_event(state, context, event, true); |
||||
state.events.push(event); |
||||
} |
||||
|
||||
recompute_static_data(context); |
||||
|
||||
do_draw = true; |
||||
|
||||
send_ack(event_count); |
||||
|
||||
sync_queue(state); |
||||
|
||||
break; |
||||
} |
||||
|
||||
case MESSAGE.FIRE: { |
||||
const user_id = des_u32(d); |
||||
const predraw_event = des_event(d); |
||||
|
||||
update_dynamic_stroke(state, context, user_id, {'x': predraw_event.x, 'y': predraw_event.y}); |
||||
|
||||
do_draw = true; |
||||
|
||||
break; |
||||
} |
||||
|
||||
case MESSAGE.ACK: { |
||||
const lsn = des_u32(d); |
||||
|
||||
if (config.debug_print) console.debug(`ack ${lsn} in`); |
||||
|
||||
if (lsn > state.server_lsn) { |
||||
// ACKs may arrive out of order
|
||||
state.server_lsn = lsn; |
||||
} |
||||
|
||||
break; |
||||
} |
||||
|
||||
case MESSAGE.SYN: { |
||||
const sn = des_u32(d); |
||||
const count = des_u32(d); |
||||
|
||||
const we_expect = sn - state.sn; |
||||
const first = count - we_expect; |
||||
|
||||
if (config.debug_print) console.debug(`syn ${sn} in`); |
||||
|
||||
for (let i = 0; i < count; ++i) { |
||||
const event = des_event(d); |
||||
if (i >= first) { |
||||
handle_event(state, context, event); |
||||
state.events.push(event); |
||||
} |
||||
} |
||||
|
||||
do_draw = true; |
||||
|
||||
state.sn = sn; |
||||
|
||||
send_ack(sn); // await?
|
||||
|
||||
break; |
||||
} |
||||
|
||||
default: { |
||||
console.error('fuck'); |
||||
return; |
||||
} |
||||
} |
||||
|
||||
if (do_draw) { |
||||
window.requestAnimationFrame(() => draw(state, context)); |
||||
} |
||||
} |
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<title>Desk</title> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> |
||||
<link rel="shortcut icon" href="icons/favicon.svg" id="favicon"> |
||||
<link rel="stylesheet" type="text/css" href="default.css?v=6"> |
||||
<link rel="stylesheet" type="text/css" href="touch.css?v=4"> |
||||
<script type="text/javascript" src="index.js?v=10"></script> |
||||
<script type="text/javascript" src="cursor.js?v=5"></script> |
||||
<script type="text/javascript" src="touch.js?v=20"></script> |
||||
<script type="text/javascript" src="websocket.js?v=6"></script> |
||||
<script type="text/javascript" src="send.js?v=5"></script> |
||||
<script type="text/javascript" src="recv.js?v=5"></script> |
||||
<script type="text/javascript" src="math.js?v=5"></script> |
||||
<script type="text/javascript" src="draw.js?v=5"></script> |
||||
<script type="text/javascript" src="tools.js?v=6"></script> |
||||
</head> |
||||
<body> |
||||
<div class="toolbar"> |
||||
<input type="color" id="brush-color"> |
||||
<input type="number" min="1" id="brush-width"> |
||||
</div> |
||||
|
||||
<div class="tools-wrapper"> |
||||
<div class="tools"> |
||||
<div class="tool" data-tool="pencil"><img draggable="false" src="icons/draw.svg"></div> |
||||
<div class="tool" data-tool="ruler"><img draggable="false" src="icons/ruler.svg"></div> |
||||
<div class="tool" data-tool="eraser"><img draggable="false" src="icons/erase.svg"></div> |
||||
<div class="tool" data-tool="undo"><img draggable="false" src="icons/undo.svg"></div> |
||||
<!-- <div class="tool" data-tool="redo"><img draggable="false" src="icons/redo.svg"></div> --> |
||||
</div> |
||||
</div> |
||||
|
||||
<div id="brush-preview" class="dhide"></div> |
||||
|
||||
<canvas class="canvas white" id="canvas0"></canvas> |
||||
<canvas class="canvas" id="canvas1"></canvas> |
||||
<div class="canvas" id="canvas-images"></div> |
||||
|
||||
<div id="toucher"></div> |
||||
</body> |
||||
</html> |
@ -1,345 +0,0 @@
@@ -1,345 +0,0 @@
|
||||
function deserializer_create(buffer, dataview) { |
||||
return { |
||||
'offset': 0, |
||||
'size': buffer.byteLength, |
||||
'buffer': buffer, |
||||
'view': dataview, |
||||
'strview': new Uint8Array(buffer), |
||||
}; |
||||
} |
||||
|
||||
function des_u8(d) { |
||||
const value = d.view.getUint8(d.offset); |
||||
d.offset += 1; |
||||
return value; |
||||
} |
||||
|
||||
function des_u16(d) { |
||||
const value = d.view.getUint16(d.offset, true); |
||||
d.offset += 2; |
||||
return value; |
||||
} |
||||
|
||||
function des_s16(d) { |
||||
const value = d.view.getInt16(d.offset, true); |
||||
d.offset += 2; |
||||
return value; |
||||
} |
||||
|
||||
function des_u32(d) { |
||||
const value = d.view.getUint32(d.offset, true); |
||||
d.offset += 4; |
||||
return value; |
||||
} |
||||
|
||||
function des_u16array(d, count) { |
||||
const result = []; |
||||
|
||||
for (let i = 0; i < count; ++i) { |
||||
const item = d.view.getUint16(d.offset, true); |
||||
d.offset += 2; |
||||
result.push(item); |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
function des_event(d) { |
||||
const event = {}; |
||||
|
||||
event.type = des_u8(d); |
||||
event.user_id = des_u32(d); |
||||
|
||||
switch (event.type) { |
||||
case EVENT.PREDRAW: { |
||||
event.x = des_u16(d); |
||||
event.y = des_u16(d); |
||||
break; |
||||
} |
||||
|
||||
case EVENT.STROKE: { |
||||
const stroke_id = des_u32(d); |
||||
const point_count = des_u16(d); |
||||
const width = des_u16(d); |
||||
const color = des_u32(d); |
||||
const coords = des_u16array(d, point_count * 2); |
||||
|
||||
event.stroke_id = stroke_id; |
||||
event.points = []; |
||||
|
||||
for (let i = 0; i < point_count; ++i) { |
||||
const x = coords[2 * i + 0]; |
||||
const y = coords[2 * i + 1]; |
||||
event.points.push({'x': x, 'y': y}); |
||||
} |
||||
|
||||
event.color = color; |
||||
event.width = width; |
||||
|
||||
break; |
||||
} |
||||
|
||||
case EVENT.IMAGE: |
||||
case EVENT.IMAGE_MOVE: { |
||||
event.image_id = des_u32(d); |
||||
event.x = des_s16(d); // stored as u16, but actually is s16
|
||||
event.y = des_s16(d); // stored as u16, but actually is s16
|
||||
break; |
||||
} |
||||
|
||||
case EVENT.UNDO: |
||||
case EVENT.REDO: { |
||||
break; |
||||
} |
||||
|
||||
case EVENT.ERASER: { |
||||
event.stroke_id = des_u32(d); |
||||
break; |
||||
} |
||||
|
||||
default: { |
||||
console.error('fuck'); |
||||
} |
||||
} |
||||
|
||||
return event; |
||||
} |
||||
|
||||
function bitmap_bbox(event) { |
||||
const x = (event.x <= storage.canvas.width ? event.x : event.x - 65536); |
||||
const y = (event.y <= storage.canvas.height ? event.y : event.y - 65536); |
||||
|
||||
const bbox = { |
||||
'xmin': x, |
||||
'xmax': x + event.bitmap.width, |
||||
'ymin': y, |
||||
'ymax': y + event.bitmap.height |
||||
}; |
||||
|
||||
return bbox; |
||||
} |
||||
|
||||
async function handle_event(event) { |
||||
if (config.debug_print) console.debug(`event type ${event.type} from user ${event.user_id}`); |
||||
|
||||
// TODO(@speed): do not handle locally predicted events
|
||||
|
||||
switch (event.type) { |
||||
case EVENT.STROKE: { |
||||
if (event.user_id in storage.predraw || event.user_id === storage.me.id) { |
||||
storage.predraw[event.user_id] = []; |
||||
redraw_predraw(); |
||||
} |
||||
|
||||
draw_stroke(event); |
||||
|
||||
break; |
||||
} |
||||
|
||||
case EVENT.UNDO: { |
||||
for (let i = storage.events.length - 1; i >=0; --i) { |
||||
const other_event = storage.events[i]; |
||||
|
||||
// Users can only undo their own, undeleted (not already undone) events
|
||||
if (other_event.user_id === event.user_id && !other_event.deleted) { |
||||
if (other_event.type === EVENT.STROKE) { |
||||
other_event.deleted = true; |
||||
const stats = stroke_stats(other_event.points, storage.cursor.width); |
||||
redraw_region(stats.bbox); |
||||
break; |
||||
} else if (other_event.type === EVENT.IMAGE) { |
||||
other_event.deleted = true; |
||||
const item = document.querySelector(`img[data-image-id="${other_event.image_id}"]`); |
||||
if (item) item.remove(); |
||||
break; |
||||
} else if (other_event.type === EVENT.ERASER) { |
||||
other_event.deleted = true; |
||||
const erased = find_stroke_backwards(other_event.stroke_id); |
||||
if (erased) { |
||||
erased.deleted = false; |
||||
const stats = stroke_stats(erased.points, storage.cursor.width); |
||||
redraw_region(stats.bbox); |
||||
} |
||||
break; |
||||
} else if (other_event.type === EVENT.IMAGE_MOVE) { |
||||
const item = document.querySelector(`img[data-image-id="${other_event.image_id}"]`); |
||||
|
||||
const ix = storage.images[other_event.image_id].x -= other_event.x; |
||||
const iy = storage.images[other_event.image_id].y -= other_event.y; |
||||
|
||||
item.style.transform = `translate(${ix}px, ${iy}px)`; |
||||
|
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
break; |
||||
} |
||||
|
||||
case EVENT.IMAGE: { |
||||
const url = config.image_url + event.image_id; |
||||
const item = document.createElement('img'); |
||||
|
||||
item.classList.add('floating-image'); |
||||
item.style['z-index'] = storage.events.length; |
||||
item.setAttribute('data-image-id', event.image_id); |
||||
item.setAttribute('src', url); |
||||
item.style.transform = `translate(${event.x}px, ${event.y}px)`; |
||||
|
||||
elements.images.appendChild(item); |
||||
storage.images[event.image_id] = { |
||||
'x': event.x, 'y': event.y |
||||
}; |
||||
// const r = await fetch(config.image_url + event.image_id);
|
||||
// const blob = await r.blob();
|
||||
// const bitmap = await createImageBitmap(blob);
|
||||
// event.bitmap = bitmap;
|
||||
// const bbox = bitmap_bbox(event);
|
||||
// storage.ctx0.drawImage(bitmap, bbox.xmin, bbox.ymin);
|
||||
|
||||
break; |
||||
} |
||||
|
||||
case EVENT.IMAGE_MOVE: { |
||||
// Already moved due to local prediction
|
||||
if (event.user_id !== storage.me.id) { |
||||
const image_id = event.image_id; |
||||
const item = document.querySelector(`.floating-image[data-image-id="${image_id}"]`); |
||||
|
||||
const ix = storage.images[event.image_id].x += event.x; |
||||
const iy = storage.images[event.image_id].y += event.y; |
||||
|
||||
if (item) { |
||||
item.style.transform = `translate(${ix}px, ${iy}px)`; |
||||
} |
||||
} |
||||
|
||||
break; |
||||
} |
||||
|
||||
case EVENT.ERASER: { |
||||
if (event.deleted) { |
||||
break; |
||||
} |
||||
|
||||
for (const other_event of storage.events) { |
||||
if (other_event.type === EVENT.STROKE && other_event.stroke_id === event.stroke_id) { |
||||
// Might already be deleted because of local prediction
|
||||
if (!other_event.deleted) { |
||||
other_event.deleted = true; |
||||
const stats = stroke_stats(other_event.points, storage.cursor.width); |
||||
redraw_region(stats.bbox); |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
break; |
||||
} |
||||
|
||||
default: { |
||||
console.error('fuck'); |
||||
} |
||||
} |
||||
} |
||||
|
||||
async function handle_message(d) { |
||||
const message_type = des_u8(d); |
||||
|
||||
if (config.debug_print) console.debug(message_type); |
||||
|
||||
switch (message_type) { |
||||
case MESSAGE.JOIN: |
||||
case MESSAGE.INIT: { |
||||
elements.canvas0.classList.add('white'); |
||||
|
||||
storage.me.id = des_u32(d); |
||||
storage.server_lsn = des_u32(d); |
||||
|
||||
if (storage.server_lsn > storage.lsn) { |
||||
// Server knows something that we don't
|
||||
storage.lsn = storage.server_lsn; |
||||
} |
||||
|
||||
if (message_type === MESSAGE.JOIN) { |
||||
ls.setItem('sessionId', des_u32(d)); |
||||
if (config.debug_print) console.debug('join in'); |
||||
} else { |
||||
if (config.debug_print) console.debug('init in'); |
||||
} |
||||
|
||||
const event_count = des_u32(d); |
||||
|
||||
if (config.debug_print) console.debug(`${event_count} events in init`); |
||||
|
||||
storage.ctx0.clearRect(0, 0, storage.ctx0.canvas.width, storage.ctx0.canvas.height); |
||||
elements.images.innerHTML = ''; |
||||
storage.events.length = 0; |
||||
|
||||
for (let i = 0; i < event_count; ++i) { |
||||
const event = des_event(d); |
||||
await handle_event(event); |
||||
storage.events.push(event); |
||||
} |
||||
|
||||
elements.canvas0.classList.remove('white'); |
||||
|
||||
send_ack(event_count); |
||||
|
||||
sync_queue(); |
||||
|
||||
break; |
||||
} |
||||
|
||||
case MESSAGE.FIRE: { |
||||
const user_id = des_u32(d); |
||||
const predraw_event = des_event(d); |
||||
|
||||
predraw_user(user_id, predraw_event); |
||||
|
||||
break; |
||||
} |
||||
|
||||
case MESSAGE.ACK: { |
||||
const lsn = des_u32(d); |
||||
|
||||
if (config.debug_print) console.debug(`ack ${lsn} in`); |
||||
|
||||
if (lsn > storage.server_lsn) { |
||||
// ACKs may arrive out of order
|
||||
storage.server_lsn = lsn; |
||||
} |
||||
|
||||
break; |
||||
} |
||||
|
||||
case MESSAGE.SYN: { |
||||
const sn = des_u32(d); |
||||
const count = des_u32(d); |
||||
|
||||
const we_expect = sn - storage.sn; |
||||
const first = count - we_expect; |
||||
|
||||
if (config.debug_print) console.debug(`syn ${sn} in`); |
||||
|
||||
for (let i = 0; i < count; ++i) { |
||||
const event = des_event(d); |
||||
if (i >= first) { |
||||
handle_event(event); |
||||
storage.events.push(event); |
||||
} |
||||
} |
||||
|
||||
storage.sn = sn; |
||||
|
||||
await send_ack(sn); |
||||
|
||||
break; |
||||
} |
||||
|
||||
default: { |
||||
console.error('fuck'); |
||||
return; |
||||
} |
||||
} |
||||
} |
@ -1,110 +0,0 @@
@@ -1,110 +0,0 @@
|
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<title>Desk</title> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> |
||||
<link rel="shortcut icon" href="icons/favicon.svg" id="favicon"> |
||||
|
||||
<script type="text/javascript" src="math.js?v=4"></script> |
||||
<script type="text/javascript" src="aux.js?v=4"></script> |
||||
<script type="text/javascript" src="tools.js?v=4"></script> |
||||
<script type="text/javascript" src="webgl_geometry.js?v=4"></script> |
||||
<script type="text/javascript" src="webgl_shaders.js?v=4"></script> |
||||
<script type="text/javascript" src="webgl_listeners.js?v=5"></script> |
||||
<script type="text/javascript" src="webgl.js?v=4"></script> |
||||
|
||||
<style> |
||||
html, body { |
||||
margin: 0; |
||||
padding: 0; |
||||
width: 100%; |
||||
height: 100%; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
canvas { |
||||
width: 100%; |
||||
height: 100%; |
||||
display: block; |
||||
cursor: crosshair; |
||||
} |
||||
|
||||
canvas.movemode { |
||||
cursor: grab; |
||||
} |
||||
|
||||
canvas.movemode.moving { |
||||
cursor: grabbing; |
||||
} |
||||
|
||||
.tools-wrapper { |
||||
position: fixed; |
||||
bottom: 0; |
||||
width: 100%; |
||||
height: 32px; |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: end; |
||||
z-index: 10; |
||||
pointer-events: none; |
||||
} |
||||
|
||||
.tools { |
||||
pointer-events: all; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
background: #333; |
||||
border-radius: 5px; |
||||
border-bottom-right-radius: 0; |
||||
border-bottom-left-radius: 0; |
||||
height: 42px; |
||||
padding-left: 10px; |
||||
padding-right: 10px; |
||||
} |
||||
|
||||
.tool { |
||||
cursor: pointer; |
||||
padding-left: 10px; |
||||
padding-right: 10px; |
||||
height: 100%; |
||||
display: flex; |
||||
align-items: center; |
||||
background: #333; |
||||
transition: transform .1s ease-in-out; |
||||
user-select: none; |
||||
} |
||||
|
||||
.tool:hover { |
||||
background: #888; |
||||
} |
||||
|
||||
.tool.active { |
||||
transform: translateY(-10px); |
||||
border-top-right-radius: 5px; |
||||
border-top-left-radius: 5px; |
||||
background: #333; |
||||
} |
||||
|
||||
.tool img { |
||||
height: 24px; |
||||
width: 24px; |
||||
filter: invert(100%); |
||||
} |
||||
</style> |
||||
</head> |
||||
<body> |
||||
<canvas id="c"></canvas> |
||||
|
||||
<div class="tools-wrapper"> |
||||
<div class="tools"> |
||||
<div class="tool" data-tool="pencil"><img draggable="false" src="icons/draw.svg"></div> |
||||
<div class="tool" data-tool="ruler"><img draggable="false" src="icons/ruler.svg"></div> |
||||
<div class="tool" data-tool="eraser"><img draggable="false" src="icons/erase.svg"></div> |
||||
<div class="tool" data-tool="undo"><img draggable="false" src="icons/undo.svg"></div> |
||||
<!-- <div class="tool" data-tool="redo"><img draggable="false" src="icons/redo.svg"></div> --> |
||||
</div> |
||||
</div> |
||||
</body> |
||||
</html> |
Loading…
Reference in new issue