@ -1,72 +0,0 @@ |
|||||||
function draw_stroke(stroke) { |
|
||||||
const points = stroke.points; |
|
||||||
|
|
||||||
if (points.length === 0) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// if (config.debug_print) console.debug(points)
|
|
||||||
|
|
||||||
storage.ctx0.beginPath(); |
|
||||||
storage.ctx0.moveTo(points[0].x, points[0].y); |
|
||||||
storage.ctx0.strokeStyle = color_from_u32(stroke.color); |
|
||||||
storage.ctx0.lineWidth = stroke.width; |
|
||||||
|
|
||||||
for (let i = 1; i < points.length; ++i) { |
|
||||||
const p = points[i]; |
|
||||||
storage.ctx0.lineTo(p.x, p.y); |
|
||||||
} |
|
||||||
|
|
||||||
storage.ctx0.stroke(); |
|
||||||
} |
|
||||||
|
|
||||||
function redraw_predraw() { |
|
||||||
storage.ctx1.clearRect(0, 0, storage.ctx1.canvas.width, storage.ctx1.canvas.height); |
|
||||||
} |
|
||||||
|
|
||||||
function predraw_user(user_id, event) { |
|
||||||
if (!(user_id in storage.predraw)) { |
|
||||||
storage.predraw[user_id] = []; |
|
||||||
} |
|
||||||
|
|
||||||
storage.ctx1.beginPath(); |
|
||||||
if (storage.predraw[user_id].length > 0) { |
|
||||||
const last = storage.predraw[user_id][storage.predraw[user_id].length - 1]; |
|
||||||
storage.ctx1.moveTo(last.x, last.y); |
|
||||||
storage.ctx1.lineTo(event.x, event.y); |
|
||||||
} else { |
|
||||||
storage.ctx1.moveTo(event.x, event.y); |
|
||||||
} |
|
||||||
storage.ctx1.stroke(); |
|
||||||
|
|
||||||
storage.predraw[user_id].push({ 'x': event.x, 'y': event.y }); |
|
||||||
} |
|
||||||
|
|
||||||
function redraw_region(bbox) { |
|
||||||
// const start = performance.now();
|
|
||||||
|
|
||||||
if (bbox.xmin === bbox.xmax || bbox.ymin === bbox.ymax) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
storage.ctx0.save(); |
|
||||||
storage.ctx0.clearRect(bbox.xmin, bbox.ymin, bbox.xmax - bbox.xmin, bbox.ymax - bbox.ymin); |
|
||||||
|
|
||||||
storage.ctx0.beginPath(); |
|
||||||
storage.ctx0.rect(bbox.xmin, bbox.ymin, bbox.xmax - bbox.xmin, bbox.ymax - bbox.ymin); |
|
||||||
storage.ctx0.clip(); |
|
||||||
|
|
||||||
for (const event of storage.events) { |
|
||||||
if (event.type === EVENT.STROKE && !event.deleted) { |
|
||||||
if (stroke_intersects_region(event.points, bbox)) { |
|
||||||
draw_stroke(event); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
storage.ctx0.restore(); |
|
||||||
|
|
||||||
// const end = performance.now();
|
|
||||||
|
|
||||||
// if (config.debug_print) console.debug(`Redraw took ${end - start}ms`);
|
|
||||||
} |
|
Before Width: | Height: | Size: 671 B |
Before Width: | Height: | Size: 957 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 371 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 500 B |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 691 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 643 B |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 494 B |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 457 B |
@ -1,310 +0,0 @@ |
|||||||
let ws = null; |
|
||||||
let ls = window.localStorage; |
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', main); |
|
||||||
|
|
||||||
const EVENT = Object.freeze({ |
|
||||||
PREDRAW: 10, |
|
||||||
STROKE: 20, |
|
||||||
RULER: 21, /* gets re-written with EVENT.STROKE before sending to server */ |
|
||||||
UNDO: 30, |
|
||||||
REDO: 31, |
|
||||||
IMAGE: 40, |
|
||||||
IMAGE_MOVE: 41, |
|
||||||
ERASER: 50, |
|
||||||
}); |
|
||||||
|
|
||||||
const MESSAGE = Object.freeze({ |
|
||||||
INIT: 100, |
|
||||||
SYN: 101, |
|
||||||
ACK: 102, |
|
||||||
FULL: 103, |
|
||||||
FIRE: 104, |
|
||||||
JOIN: 105, |
|
||||||
}); |
|
||||||
|
|
||||||
const config = { |
|
||||||
ws_url: 'ws://192.168.100.2/ws/', |
|
||||||
image_url: 'http://192.168.100.2/images/', |
|
||||||
sync_timeout: 1000, |
|
||||||
ws_reconnect_timeout: 2000, |
|
||||||
second_finger_timeout: 500, |
|
||||||
buffer_first_touchmoves: 5, |
|
||||||
debug_print: false, |
|
||||||
}; |
|
||||||
|
|
||||||
const storage = { |
|
||||||
'state': { |
|
||||||
'drawing': false, |
|
||||||
'moving': false, |
|
||||||
'moving_image': false, |
|
||||||
'mousedown': false, |
|
||||||
'spacedown': false, |
|
||||||
}, |
|
||||||
|
|
||||||
'moving_image_original_x': null, |
|
||||||
'moving_image_original_y': null, |
|
||||||
|
|
||||||
'touch': { |
|
||||||
'moves': 0, |
|
||||||
'drawing': false, |
|
||||||
'moving': false, |
|
||||||
'waiting_for_second_finger': false, |
|
||||||
'position': { 'x': null, 'y': null }, |
|
||||||
'screen_position': { 'x': null, 'y': null }, |
|
||||||
'finger_distance': null, |
|
||||||
'buffered': [], |
|
||||||
'ids': [], |
|
||||||
}, |
|
||||||
|
|
||||||
'tools': { |
|
||||||
'active': null, |
|
||||||
'active_element': null, |
|
||||||
}, |
|
||||||
|
|
||||||
'ruler_origin': {}, |
|
||||||
'erased': [], |
|
||||||
'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, |
|
||||||
|
|
||||||
'max_zoom': 4, |
|
||||||
'min_zoom': 0.2, |
|
||||||
|
|
||||||
'images': {}, |
|
||||||
|
|
||||||
'canvas': { |
|
||||||
'zoom': 1, |
|
||||||
'width': 1500, |
|
||||||
'height': 4000, |
|
||||||
'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, |
|
||||||
'active_image': null, |
|
||||||
}; |
|
||||||
|
|
||||||
function move_canvas() { |
|
||||||
elements.canvas0.style.transform = `translate(${-storage.canvas.offset_x}px, ${-storage.canvas.offset_y}px) scale(${storage.canvas.zoom})`; |
|
||||||
elements.canvas1.style.transform = `translate(${-storage.canvas.offset_x}px, ${-storage.canvas.offset_y}px) scale(${storage.canvas.zoom})`; |
|
||||||
elements.images.style.transform = `translate(${-storage.canvas.offset_x}px, ${-storage.canvas.offset_y}px) scale(${storage.canvas.zoom})`; |
|
||||||
} |
|
||||||
|
|
||||||
function image_at(x, y) { |
|
||||||
let image_hit = null; |
|
||||||
|
|
||||||
for (let i = storage.events.length - 1; i >= 0; --i) { |
|
||||||
if (!storage.events[i].deleted && storage.events[i].type === EVENT.IMAGE) { |
|
||||||
const event = storage.events[i]; |
|
||||||
const item = document.querySelector(`img[data-image-id="${event.image_id}"]`); |
|
||||||
if (item) { |
|
||||||
const left = storage.images[event.image_id].x; |
|
||||||
const right = left + item.width; |
|
||||||
const top = storage.images[event.image_id].y; |
|
||||||
const bottom = top + item.height; |
|
||||||
if (left <= x && x <= right && top <= y && y <= bottom) { |
|
||||||
return item; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return null; |
|
||||||
} |
|
||||||
|
|
||||||
function activate_image(item) { |
|
||||||
if (item === null) { |
|
||||||
elements.canvas1.classList.remove('disabled'); |
|
||||||
if (elements.active_image) { |
|
||||||
elements.active_image.classList.remove('activated'); |
|
||||||
elements.active_image = null; |
|
||||||
} |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
elements.canvas1.classList.add('disabled'); |
|
||||||
|
|
||||||
if (elements.active_image) { |
|
||||||
if (elements.active_image === item) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
elements.active_image.classList.remove('activated'); |
|
||||||
} |
|
||||||
|
|
||||||
elements.active_image = item; |
|
||||||
|
|
||||||
item.classList.add('activated'); |
|
||||||
} |
|
||||||
|
|
||||||
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 ruler_event(x, y) { |
|
||||||
const points = []; |
|
||||||
|
|
||||||
points.push(predraw_event(storage.ruler_origin.x, storage.ruler_origin.y)); |
|
||||||
points.push(predraw_event(x, y)); |
|
||||||
|
|
||||||
return { |
|
||||||
'type': EVENT.RULER, |
|
||||||
'points': points, |
|
||||||
'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 image_move_event(image_id, x, y) { |
|
||||||
return { |
|
||||||
'type': EVENT.IMAGE_MOVE, |
|
||||||
'image_id': image_id, |
|
||||||
'x': x, |
|
||||||
'y': y, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function eraser_events() { |
|
||||||
const result = []; |
|
||||||
|
|
||||||
for (const stroke_id of storage.erased) { |
|
||||||
result.push({ |
|
||||||
'type': EVENT.ERASER, |
|
||||||
'stroke_id': stroke_id, |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
// Generally doesn't return null
|
|
||||||
function find_stroke_backwards(stroke_id) { |
|
||||||
for (let i = storage.events.length - 1; i >= 0; --i) { |
|
||||||
const event = storage.events[i]; |
|
||||||
if (event.type === EVENT.STROKE && event.stroke_id === stroke_id) { |
|
||||||
return event; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return null; |
|
||||||
} |
|
||||||
|
|
||||||
function queue_undo() { |
|
||||||
const event = undo_event(); |
|
||||||
queue_event(event); |
|
||||||
} |
|
||||||
|
|
||||||
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(true); |
|
||||||
|
|
||||||
elements.canvas0 = document.getElementById('canvas0'); |
|
||||||
elements.canvas1 = document.getElementById('canvas1'); |
|
||||||
elements.images = document.getElementById('canvas-images'); |
|
||||||
|
|
||||||
tools_init(); |
|
||||||
|
|
||||||
// TODO: remove
|
|
||||||
elements.brush_color = document.getElementById('brush-color'); |
|
||||||
elements.brush_width = document.getElementById('brush-width'); |
|
||||||
elements.brush_preview = document.getElementById('brush-preview'); |
|
||||||
elements.toucher = document.getElementById('toucher'); |
|
||||||
|
|
||||||
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'); |
|
||||||
|
|
||||||
on_resize(); |
|
||||||
// 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; |
|
||||||
|
|
||||||
elements.toucher.addEventListener('mousedown', on_down) |
|
||||||
elements.toucher.addEventListener('mousemove', on_move) |
|
||||||
elements.toucher.addEventListener('mouseup', on_up); |
|
||||||
|
|
||||||
elements.toucher.addEventListener('keydown', on_keydown); |
|
||||||
elements.toucher.addEventListener('keyup', on_keyup); |
|
||||||
elements.toucher.addEventListener('contextmenu', cancel); |
|
||||||
elements.toucher.addEventListener('wheel', on_wheel); |
|
||||||
|
|
||||||
elements.toucher.addEventListener('touchstart', on_touchstart); |
|
||||||
elements.toucher.addEventListener('touchmove', on_touchmove); |
|
||||||
elements.toucher.addEventListener('touchend', on_touchend); |
|
||||||
elements.toucher.addEventListener('touchcancel', on_touchend); |
|
||||||
|
|
||||||
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('mouseleave', on_leave); |
|
||||||
|
|
||||||
window.addEventListener('resize', on_resize); |
|
||||||
} |
|
@ -1,44 +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"> |
|
||||||
<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,21 +0,0 @@ |
|||||||
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022/Debian) (preloaded format=pdflatex 2023.3.25) 8 APR 2023 22:14 |
|
||||||
entering extended mode |
|
||||||
restricted \write18 enabled. |
|
||||||
%&-line parsing enabled. |
|
||||||
** |
|
||||||
|
|
||||||
! Emergency stop. |
|
||||||
<*> |
|
||||||
|
|
||||||
End of file on the terminal! |
|
||||||
|
|
||||||
|
|
||||||
Here is how much of TeX's memory you used: |
|
||||||
3 strings out of 476091 |
|
||||||
111 string characters out of 5794081 |
|
||||||
1849330 words of memory out of 5000000 |
|
||||||
20488 multiletter control sequences out of 15000+600000 |
|
||||||
512287 words of font info for 32 fonts, out of 8000000 for 9000 |
|
||||||
1141 hyphenation exceptions out of 8191 |
|
||||||
0i,0n,0p,1b,6s stack positions out of 10000i,1000n,20000p,200000b,200000s |
|
||||||
! ==> Fatal error occurred, no output PDF file produced! |
|