Browse Source

Colors and widths

master
A.Olokhtonov 1 year ago
parent
commit
c8365d1be0
  1. 26
      client/cursor.js
  2. 57
      client/default.css
  3. 2
      client/draw.js
  4. 6
      client/index.html
  5. 16
      client/index.js
  6. 24
      client/math.js
  7. 11
      client/recv.js
  8. 29
      client/send.js
  9. 4
      server/deserializer.js
  10. 2
      server/recv.js
  11. 2
      server/send.js
  12. 2
      server/serializer.js
  13. 15
      server/storage.js

26
client/cursor.js

@ -157,4 +157,30 @@ async function on_drop(e) { @@ -157,4 +157,30 @@ async function on_drop(e) {
function cancel(e) {
e.preventDefault();
return false;
}
function update_brush() {
elements.brush_preview.classList.remove('dhide');
const color = elements.brush_color.value;
const width = elements.brush_width.value;
storage.cursor.color = color;
storage.cursor.width = width;
const x = Math.round(storage.cursor.x - width / 2);
const y = Math.round(storage.cursor.y - width / 2);
elements.brush_preview.style.transform = `translate(${x}px, ${y}px)`;
elements.brush_preview.style.width = width + 'px';
elements.brush_preview.style.height = width + 'px';
elements.brush_preview.style.background = color;
if (storage.timers.brush_preview) {
clearTimeout(storage.timers.brush_preview);
}
storage.timers.brush_preview = setTimeout(() => {
elements.brush_preview.classList.add('dhide');
}, 1000);
}

57
client/default.css

@ -25,17 +25,6 @@ html, body { @@ -25,17 +25,6 @@ html, body {
cursor: move;
}
.cursor {
display: none;
position: absolute;
background: white;
border-radius: 50%;
box-sizing: border-box;
border: 1px solid black;
z-index: 10;
pointer-events: none;
}
#canvas0 {
z-index: 0;
}
@ -44,4 +33,50 @@ html, body { @@ -44,4 +33,50 @@ html, body {
z-index: 1;
pointer-events: none;
opacity: 0.3;
}
.toolbar {
position: fixed;
left: 20px;
top: 20px;
background: #eee;
border: 1px solid #ddd;
padding: 10px;
border-radius: 5px;
box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.1);
display: flex;
gap: 10px;
z-index: 10;
align-items: center;
}
.toolbar #brush-width {
width: 7ch;
height: 30px;
padding: 5px;
box-sizing: border-box;
border: none;
cursor: crosshair;
}
.toolbar #brush-color {
padding: 0;
height: 30px;
width: 30px;
border: none;
cursor: pointer;
}
#brush-preview {
border-radius: 50%;
width: 5px;
height: 5px;
background: black;
position: absolute;
pointer-events: none;
z-index: 11;
}
.toolbar #brush-color::-moz-color-swatch {
border: none;
}

2
client/draw.js

@ -9,6 +9,8 @@ function draw_stroke(stroke) { @@ -9,6 +9,8 @@ function draw_stroke(stroke) {
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];

6
client/index.html

@ -14,6 +14,12 @@ @@ -14,6 +14,12 @@
<script type="text/javascript" src="draw.js"></script>
</head>
<body>
<div class="toolbar">
<input type="color" id="brush-color">
<input type="number" min="1" id="brush-width">
</div>
<div id="brush-preview" class="dhide"></div>
<canvas class="canvas white" id="canvas0"></canvas>
<canvas class="canvas" id="canvas1"></canvas>
</body>

16
client/index.js

@ -57,6 +57,7 @@ const storage = { @@ -57,6 +57,7 @@ const storage = {
'cursor': {
'width': 8,
'color': 'rgb(0, 0, 0)',
'x': 0,
'y': 0,
}
@ -77,7 +78,7 @@ function event_size(event) { @@ -77,7 +78,7 @@ function event_size(event) {
}
case EVENT.STROKE: {
size += 2 + event.points.length * 2 * 2; // u16 (count) + count * (u16, u16) points
size += 2 + 2 + 4 + event.points.length * 2 * 2; // u16 (count) + u16 (width) + u32 (color + count * (u16, u16) points
break;
}
@ -111,6 +112,8 @@ function stroke_event() { @@ -111,6 +112,8 @@ function stroke_event() {
return {
'type': EVENT.STROKE,
'points': storage.current_stroke,
'width': storage.cursor.width,
'color': color_to_u32(storage.cursor.color),
};
}
@ -141,6 +144,14 @@ function main() { @@ -141,6 +144,14 @@ function main() {
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;
@ -165,6 +176,9 @@ function main() { @@ -165,6 +176,9 @@ function main() {
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);

24
client/math.js

@ -132,4 +132,28 @@ function rectangles_intersect(a, b) { @@ -132,4 +132,28 @@ function rectangles_intersect(a, b) {
function stroke_intersects_region(points, bbox) {
const stats = stroke_stats(points, storage.cursor.width);
return rectangles_intersect(stats.bbox, bbox);
}
function color_to_u32(color_str) {
const r = parseInt(color_str.substring(1, 3), 16);
const g = parseInt(color_str.substring(3, 5), 16);
const b = parseInt(color_str.substring(5, 7), 16);
return (r << 16) | (g << 8) | b;
}
function color_from_u32(color_u32) {
const r = (color_u32 >> 16) & 0xFF;
const g = (color_u32 >> 8) & 0xFF;
const b = color_u32 & 0xFF;
let r_str = r.toString(16);
let g_str = g.toString(16);
let b_str = b.toString(16);
if (r <= 0xF) r_str = '0' + r_str;
if (g <= 0xF) g_str = '0' + g_str;
if (b <= 0xF) b_str = '0' + b_str;
return '#' + r_str + g_str + b_str;
}

11
client/recv.js

@ -53,6 +53,8 @@ function des_event(d) { @@ -53,6 +53,8 @@ function des_event(d) {
case EVENT.STROKE: {
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.points = [];
@ -63,6 +65,9 @@ function des_event(d) { @@ -63,6 +65,9 @@ function des_event(d) {
event.points.push({'x': x, 'y': y});
}
event.color = color;
event.width = width;
break;
}
@ -103,6 +108,8 @@ function bitmap_bbox(event) { @@ -103,6 +108,8 @@ function bitmap_bbox(event) {
async function handle_event(event) {
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) {
@ -118,7 +125,6 @@ async function handle_event(event) { @@ -118,7 +125,6 @@ async function handle_event(event) {
case EVENT.UNDO: {
for (let i = storage.events.length - 1; i >=0; --i) {
const other_event = storage.events[i];
if (other_event.type === EVENT.STROKE && other_event.user_id === event.user_id && !other_event.deleted) {
other_event.deleted = true;
const stats = stroke_stats(other_event.points, storage.cursor.width);
@ -162,6 +168,8 @@ async function handle_message(d) { @@ -162,6 +168,8 @@ async function handle_message(d) {
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);
@ -182,6 +190,7 @@ async function handle_message(d) { @@ -182,6 +190,7 @@ async function handle_message(d) {
console.debug(`${event_count} events in init`);
storage.ctx0.clearRect(0, 0, storage.ctx0.canvas.width, storage.ctx0.canvas.height);
for (let i = 0; i < event_count; ++i) {
const event = des_event(d);
await handle_event(event);

29
client/send.js

@ -37,6 +37,8 @@ function ser_event(s, event) { @@ -37,6 +37,8 @@ function ser_event(s, event) {
case EVENT.STROKE: {
ser_u16(s, event.points.length);
ser_u16(s, event.width);
ser_u32(s, event.color);
console.debug('original', event.points);
@ -75,7 +77,11 @@ async function send_ack(sn) { @@ -75,7 +77,11 @@ async function send_ack(sn) {
console.debug(`ack ${sn} out`);
if (ws) await ws.send(s.buffer);
try {
if (ws) await ws.send(s.buffer);
} catch(e) {
ws.close();
}
}
async function sync_queue() {
@ -112,7 +118,11 @@ async function sync_queue() { @@ -112,7 +118,11 @@ async function sync_queue() {
console.debug(`syn ${storage.lsn} out`);
if (ws) await ws.send(s.buffer);
try {
if (ws) await ws.send(s.buffer);
} catch(e) {
ws.close();
}
setTimeout(sync_queue, config.sync_timeout);
}
@ -123,7 +133,14 @@ function push_event(event) { @@ -123,7 +133,14 @@ function push_event(event) {
switch (event.type) {
case EVENT.STROKE: {
const points = process_stroke(event.points);
storage.queue.push({ 'type': EVENT.STROKE, 'points': points });
storage.queue.push({
'type': EVENT.STROKE,
'points': points,
'width': event.width,
'color': event.color,
});
break;
}
@ -162,5 +179,9 @@ async function fire_event(event) { @@ -162,5 +179,9 @@ async function fire_event(event) {
ser_u8(s, MESSAGE.FIRE);
ser_event(s, event);
if (ws) await ws.send(s.buffer);
try {
if (ws) await ws.send(s.buffer);
} catch(e) {
ws.close();
}
}

4
server/deserializer.js

@ -47,6 +47,10 @@ export function event(d) { @@ -47,6 +47,10 @@ export function event(d) {
case EVENT.STROKE: {
const point_count = u16(d);
const width = u16(d);
const color = u32(d);
event.width = width;
event.color = color;
event.points = u16array(d, point_count * 2);
break;
}

2
server/recv.js

@ -20,7 +20,7 @@ function handle_event(session, event) { @@ -20,7 +20,7 @@ function handle_event(session, event) {
switch (event.type) {
case EVENT.STROKE: {
event.stroke_id = math.fast_random32();
storage.put_stroke(event.stroke_id, session.desk_id, event.points);
storage.put_stroke(event.stroke_id, session.desk_id, event.points, event.width, event.color);
storage.put_event(event);
break;
}

2
server/send.js

@ -16,7 +16,7 @@ function event_size(event) { @@ -16,7 +16,7 @@ function event_size(event) {
}
case EVENT.STROKE: {
size += 2; // point count
size += 2 + 2 + 4; // point count + width + color
size += event.points.byteLength;
break;
}

2
server/serializer.js

@ -45,6 +45,8 @@ export function event(s, event) { @@ -45,6 +45,8 @@ export function event(s, event) {
case EVENT.STROKE: {
const points_bytes = event.points;
u16(s, points_bytes.byteLength / 2 / 2); // each point is 2 u16s
u16(s, event.width);
u32(s, event.color);
bytes(s, points_bytes);
break;
}

15
server/storage.js

@ -56,6 +56,8 @@ export function startup() { @@ -56,6 +56,8 @@ export function startup() {
id INTEGER PRIMARY KEY,
desk_id INTEGER,
points BLOB,
width INTEGER,
color INTEGER,
FOREIGN KEY (desk_id)
REFERENCES desks (id)
@ -112,10 +114,10 @@ export function startup() { @@ -112,10 +114,10 @@ export function startup() {
queries.desks = db.query('SELECT id, sn FROM desks');
queries.events = db.query('SELECT * FROM events');
queries.sessions = db.query('SELECT id, lsn, user_id, desk_id FROM sessions');
queries.strokes = db.query('SELECT id, points FROM strokes');
queries.strokes = db.query('SELECT * FROM strokes');
queries.empty_desk = db.query('INSERT INTO desks (id, title, sn) VALUES (?1, ?2, 0)');
queries.desk_strokes = db.query('SELECT id, points FROM strokes WHERE desk_id = ?1');
queries.put_desk_stroke = db.query('INSERT INTO strokes (id, desk_id, points) VALUES (?1, ?2, ?3)');
queries.put_desk_stroke = db.query('INSERT INTO strokes (id, desk_id, points, width, color) VALUES (?1, ?2, ?3, ?4, ?5)');
queries.clear_desk_events = db.query('DELETE FROM events WHERE desk_id = ?1');
queries.set_desk_sn = db.query('UPDATE desks SET sn = ?1 WHERE id = ?2');
queries.save_session_lsn = db.query('UPDATE sessions SET lsn = ?1 WHERE id = ?2');
@ -153,7 +155,10 @@ export function startup() { @@ -153,7 +155,10 @@ export function startup() {
for (const event of stored_events) {
if (event.type === EVENT.STROKE) {
event.points = stroke_dict[event.stroke_id].points;
const stroke = stroke_dict[event.stroke_id];
event.points = stroke.points;
event.color = stroke.color;
event.width = stroke.width;
}
@ -191,8 +196,8 @@ export function put_event(event) { @@ -191,8 +196,8 @@ export function put_event(event) {
return queries.put_event.get(event.type, event.desk_id || 0, event.user_id || 0, event.stroke_id || 0, event.image_id || 0, event.x || 0, event.y || 0);
}
export function put_stroke(stroke_id, desk_id, points) {
return queries.put_desk_stroke.get(stroke_id, desk_id, new Uint8Array(points.buffer, points.byteOffset, points.byteLength));
export function put_stroke(stroke_id, desk_id, points, width, color) {
return queries.put_desk_stroke.get(stroke_id, desk_id, new Uint8Array(points.buffer, points.byteOffset, points.byteLength), width, color);
}
export function clear_events(desk_id) {

Loading…
Cancel
Save