Browse Source

Images!

master
A.Olokhtonov 1 year ago
parent
commit
5ea55d9ab0
  1. 1
      .gitignore
  2. 5
      Caddyfile
  3. 88
      client/cursor.js
  4. 1
      client/default.css
  5. 30
      client/index.js
  6. 22
      client/recv.js
  7. 14
      client/send.js
  8. 3
      server/config.js
  9. BIN
      server/data/db.sqlite
  10. 7
      server/deserializer.js
  11. 1
      server/enums.js
  12. 24
      server/http.js
  13. 1
      server/recv.js
  14. 5
      server/send.js
  15. 7
      server/serializer.js
  16. 34
      server/storage.js

1
.gitignore vendored

@ -0,0 +1 @@
server/images

5
Caddyfile

@ -2,6 +2,11 @@ desk.local {
redir /ws /ws/ redir /ws /ws/
redir /desk /desk/ redir /desk /desk/
handle_path /images/* {
root * /code/desk2/server/images
file_server
}
handle /ws/* { handle /ws/* {
reverse_proxy 127.0.0.1:3003 reverse_proxy 127.0.0.1:3003
} }

88
client/cursor.js

@ -1,7 +1,8 @@
function on_down(e) { function on_down(e) {
if (e.button === 1) { if (e.button === 1) {
const event = undo_event(); elements.cursor.classList.add('dhide');
queue_event(event); elements.canvas0.classList.add('moving');
storage.state.moving = true;
return; return;
} }
@ -9,6 +10,10 @@ function on_down(e) {
return; return;
} }
if (storage.state.moving) {
return;
}
const x = Math.round(e.clientX + window.scrollX); const x = Math.round(e.clientX + window.scrollX);
const y = Math.round(e.clientY + window.scrollY); const y = Math.round(e.clientY + window.scrollY);
@ -33,11 +38,10 @@ function on_move(e) {
const x = storage.cursor.x = Math.round(e.clientX + window.scrollX); const x = storage.cursor.x = Math.round(e.clientX + window.scrollX);
const y = storage.cursor.y = Math.round(e.clientY + window.scrollY); const y = storage.cursor.y = Math.round(e.clientY + window.scrollY);
const width = storage.cursor.width;
elements.cursor.style.transform = `translate3D(${Math.round(x - width / 2)}px, ${Math.round(y - width / 2)}px, 0)`;
if (storage.state.drawing) { if (storage.state.drawing) {
const width = storage.cursor.width;
storage.ctx1.beginPath(); storage.ctx1.beginPath();
storage.ctx1.moveTo(last_x, last_y); storage.ctx1.moveTo(last_x, last_y);
@ -49,19 +53,36 @@ function on_move(e) {
storage.current_stroke.push(predraw); storage.current_stroke.push(predraw);
fire_event(predraw); fire_event(predraw);
} else if (storage.state.moving) {
storage.canvas.offset_x -= e.movementX;
storage.canvas.offset_y -= e.movementY;
if (window.scrollX !== storage.canvas.offset_x || window.scrollY !== storage.canvas.offset_y) {
window.scrollTo(storage.canvas.offset_x, storage.canvas.offset_y);
}
if (storage.canvas.offset_x > storage.canvas.max_scroll_x) storage.canvas.offset_x = storage.canvas.max_scroll_x;
if (storage.canvas.offset_x < 0) storage.canvas.offset_x = 0;
if (storage.canvas.offset_y > storage.canvas.max_scroll_y) storage.canvas.offset_y = storage.canvas.max_scroll_y;
if (storage.canvas.offset_y < 0) storage.canvas.offset_y = 0;
} }
e.preventDefault();
} }
async function on_up(e) { async function on_up(e) {
if (e.button != 0) { if (storage.state.moving && e.button === 1) {
return; elements.cursor.classList.remove('dhide');
elements.canvas0.classList.remove('moving');
storage.state.moving = false;
} }
storage.state.drawing = false; if (storage.state.drawing && e.button === 0) {
storage.state.drawing = false;
const event = stroke_event(); const event = stroke_event();
storage.current_stroke = []; storage.current_stroke = [];
await queue_event(event); await queue_event(event);
}
} }
function on_keydown(e) { function on_keydown(e) {
@ -81,16 +102,47 @@ function on_keyup(e) {
} }
function on_leave(e) { function on_leave(e) {
if (storage.state.drawing) {
on_up(e);
storage.state.drawing = false;
return;
}
if (storage.state.moving) { if (storage.state.moving) {
elements.cursor.classList.remove('dhide'); elements.cursor.classList.remove('dhide');
elements.canvas0.classList.remove('moving'); elements.canvas0.classList.remove('moving');
storage.state.moving = false; storage.state.moving = false;
return; return;
} }
}
function on_resize(e) {
storage.canvas.max_scroll_x = storage.canvas.width - window.innerWidth;
storage.canvas.max_scroll_y = storage.canvas.height - window.innerHeight;
}
async function on_drop(e) {
e.preventDefault();
const file = e.dataTransfer.files[0];
const bitmap = await createImageBitmap(file);
const x = storage.cursor.x - Math.round(bitmap.width / 2);
const y = storage.cursor.y - Math.round(bitmap.height / 2);
// storage.ctx0.drawImage(bitmap, x, y);
const form_data = new FormData();
form_data.append('file', file);
const resp = await fetch(`/api/image?deskId=${storage.desk_id}`, {
method: 'post',
body: form_data,
})
if (resp.ok) {
const image_id = await resp.text();
const event = image_event(image_id, x, y);
await queue_event(event);
}
return false;
}
function cancel(e) {
e.preventDefault();
return false;
} }

1
client/default.css

@ -1,6 +1,7 @@
html, body { html, body {
margin: 0; margin: 0;
padding: 0; padding: 0;
overflow: hidden;
} }
.dhide { .dhide {

30
client/index.js

@ -8,7 +8,9 @@ const EVENT = Object.freeze({
STROKE: 20, STROKE: 20,
UNDO: 30, UNDO: 30,
REDO: 31, REDO: 31,
IMAGE: 40,
}); });
const MESSAGE = Object.freeze({ const MESSAGE = Object.freeze({
INIT: 100, INIT: 100,
SYN: 101, SYN: 101,
@ -20,6 +22,7 @@ const MESSAGE = Object.freeze({
const config = { const config = {
ws_url: 'wss://desk.local/ws/', ws_url: 'wss://desk.local/ws/',
image_url: 'https://desk.local/images/',
sync_timeout: 1000, sync_timeout: 1000,
ws_reconnect_timeout: 2000, ws_reconnect_timeout: 2000,
}; };
@ -81,9 +84,13 @@ function event_size(event) {
break; break;
} }
case EVENT.IMAGE: {
size += 4 + 2 + 2; // file id + x + y
break;
}
default: { default: {
console.error('fuck'); console.error('fuck');
process.exit(1);
} }
} }
@ -113,6 +120,15 @@ function redo_event() {
return { 'type': EVENT.REDO }; 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() { function main() {
const url = new URL(window.location.href); const url = new URL(window.location.href);
const parts = url.pathname.split('/'); const parts = url.pathname.split('/');
@ -127,7 +143,11 @@ function main() {
elements.cursor.style.width = storage.cursor.width + 'px'; elements.cursor.style.width = storage.cursor.width + 'px';
elements.cursor.style.height = storage.cursor.width + 'px'; elements.cursor.style.height = storage.cursor.width + 'px';
storage.canvas.rect = elements.canvas0.getBoundingClientRect(); 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.ctx0 = elements.canvas0.getContext('2d');
storage.ctx1 = elements.canvas1.getContext('2d'); storage.ctx1 = elements.canvas1.getContext('2d');
@ -144,5 +164,9 @@ function main() {
window.addEventListener('touchstart', (e) => e.preventDefault()); window.addEventListener('touchstart', (e) => e.preventDefault());
window.addEventListener('keydown', on_keydown); window.addEventListener('keydown', on_keydown);
window.addEventListener('keyup', on_keyup); window.addEventListener('keyup', on_keyup);
// window.addEventListener('pointerleave', on_leave); window.addEventListener('resize', on_resize);
elements.canvas0.addEventListener('dragover', on_move);
elements.canvas0.addEventListener('drop', on_drop);
elements.canvas0.addEventListener('pointerleave', on_leave);
} }

22
client/recv.js

@ -66,6 +66,13 @@ function des_event(d) {
break; break;
} }
case EVENT.IMAGE: {
event.image_id = des_u32(d);
event.x = des_u16(d);
event.y = des_u16(d);
break;
}
case EVENT.UNDO: case EVENT.UNDO:
case EVENT.REDO: { case EVENT.REDO: {
break; break;
@ -98,7 +105,7 @@ function redraw_region(bbox) {
storage.ctx0.restore(); storage.ctx0.restore();
} }
function handle_event(event) { async function handle_event(event) {
console.debug(`event type ${event.type} from user ${event.user_id}`); console.debug(`event type ${event.type} from user ${event.user_id}`);
switch (event.type) { switch (event.type) {
@ -127,6 +134,19 @@ function handle_event(event) {
break; break;
} }
case EVENT.IMAGE: {
const r = await fetch(config.image_url + event.image_id);
const blob = await r.blob();
const bitmap = await createImageBitmap(blob);
const x = (event.x <= storage.canvas.width ? event.x : event.x - 65536);
const y = (event.y <= storage.canvas.height ? event.y : event.y - 65536);
storage.ctx0.drawImage(bitmap, x, y);
break;
}
default: { default: {
console.error('fuck'); console.error('fuck');
} }

14
client/send.js

@ -48,6 +48,14 @@ function ser_event(s, event) {
break; break;
} }
case EVENT.IMAGE: {
const image_id = parseInt(event.image_id);
ser_u32(s, image_id);
ser_u16(s, event.x);
ser_u16(s, event.y);
break;
}
case EVENT.UNDO: case EVENT.UNDO:
case EVENT.REDO: { case EVENT.REDO: {
break; break;
@ -55,7 +63,6 @@ function ser_event(s, event) {
default: { default: {
console.error('fuck'); console.error('fuck');
process.exit(1);
} }
} }
} }
@ -120,11 +127,16 @@ function push_event(event) {
break; break;
} }
case EVENT.IMAGE:
case EVENT.UNDO: case EVENT.UNDO:
case EVENT.REDO: { case EVENT.REDO: {
storage.queue.push(event); storage.queue.push(event);
break; break;
} }
default: {
console.error('fuck');
}
} }
} }

3
server/config.js

@ -1,4 +1,5 @@
export const HOST = '127.0.0.1'; export const HOST = '127.0.0.1';
export const PORT = 3003; export const PORT = 3003;
export const DATADIR = 'data'; export const DATADIR = 'data';
export const SYNC_TIMEOUT = 1000; export const SYNC_TIMEOUT = 1000;
export const IMAGEDIR = 'images';

BIN
server/data/db.sqlite

Binary file not shown.

7
server/deserializer.js

@ -51,6 +51,13 @@ export function event(d) {
break; break;
} }
case EVENT.IMAGE: {
event.image_id = u32(d);
event.x = u16(d);
event.y = u16(d);
break;
}
case EVENT.UNDO: case EVENT.UNDO:
case EVENT.REDO: { case EVENT.REDO: {
break; break;

1
server/enums.js

@ -9,6 +9,7 @@ export const EVENT = Object.freeze({
STROKE: 20, STROKE: 20,
UNDO: 30, UNDO: 30,
REDO: 31, REDO: 31,
IMAGE: 40,
}); });
export const MESSAGE = Object.freeze({ export const MESSAGE = Object.freeze({

24
server/http.js

@ -1,4 +1,22 @@
export function route(req) { import * as config from './config';
console.log('HTTP:', req.url); import * as math from './math';
return new Response(req.url); import * as storage from './storage';
export async function route(req) {
const url = new URL(req.url);
console.log(url.pathname);
if (url.pathname === '/api/image') {
const desk_id = url.searchParams.get('deskId') || '0';
const formData = await req.formData();
const file = formData.get('file');
const image_id = math.fast_random32();
await Bun.write(config.IMAGEDIR + '/' + image_id, file);
storage.put_image(image_id, desk_id);
return new Response(image_id);
}
} }

1
server/recv.js

@ -25,6 +25,7 @@ function handle_event(session, event) {
break; break;
} }
case EVENT.IMAGE:
case EVENT.UNDO: { case EVENT.UNDO: {
storage.put_event(event); storage.put_event(event);
break; break;

5
server/send.js

@ -21,6 +21,11 @@ function event_size(event) {
break; break;
} }
case EVENT.IMAGE: {
size += 4 + 2 + 2; // file id + x + y
break;
}
case EVENT.UNDO: case EVENT.UNDO:
case EVENT.REDO: { case EVENT.REDO: {
break; break;

7
server/serializer.js

@ -49,6 +49,13 @@ export function event(s, event) {
break; break;
} }
case EVENT.IMAGE: {
u32(s, event.image_id);
u16(s, event.x);
u16(s, event.y);
break;
}
case EVENT.UNDO: case EVENT.UNDO:
case EVENT.REDO: { case EVENT.REDO: {
break; break;

34
server/storage.js

@ -42,6 +42,16 @@ export function startup() {
ON UPDATE NO ACTION ON UPDATE NO ACTION
);`).run(); );`).run();
db.query(`CREATE TABLE IF NOT EXISTS images (
id INTEGER PRIMARY KEY,
desk_id INTEGER,
FOREIGN KEY (desk_id)
REFERENCES desks (id)
ON DELETE CASCADE
ON UPDATE NO ACTION
);`).run();
db.query(`CREATE TABLE IF NOT EXISTS strokes ( db.query(`CREATE TABLE IF NOT EXISTS strokes (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
desk_id INTEGER, desk_id INTEGER,
@ -59,13 +69,14 @@ export function startup() {
desk_id INTEGER, desk_id INTEGER,
user_id INTEGER, user_id INTEGER,
stroke_id INTEGER, stroke_id INTEGER,
image_id INTEGER,
x INTEGER, x INTEGER,
y INTEGER, y INTEGER,
FOREIGN KEY (desk_id) FOREIGN KEY (desk_id)
REFERENCES desks (id) REFERENCES desks (id)
ON DELETE CASCADE ON DELETE CASCADE
ON UPDATE NO ACTION, ON UPDATE NO ACTION
FOREIGN KEY (user_id) FOREIGN KEY (user_id)
REFERENCES users (id) REFERENCES users (id)
@ -76,6 +87,11 @@ export function startup() {
REFERENCES strokes (id) REFERENCES strokes (id)
ON DELETE CASCADE ON DELETE CASCADE
ON UPDATE NO ACTION ON UPDATE NO ACTION
FOREIGN KEY (image_id)
REFERENCES images (id)
ON DELETE CASCADE
ON UPDATE NO ACTION
);`).run(); );`).run();
db.query(`CREATE INDEX IF NOT EXISTS idx_events_desk_id db.query(`CREATE INDEX IF NOT EXISTS idx_events_desk_id
@ -91,9 +107,11 @@ export function startup() {
const res3 = db.query('SELECT COUNT(id) as count FROM strokes').get(); const res3 = db.query('SELECT COUNT(id) as count FROM strokes').get();
const res4 = db.query('SELECT COUNT(id) as count FROM users').get(); const res4 = db.query('SELECT COUNT(id) as count FROM users').get();
const res5 = db.query('SELECT COUNT(id) as count FROM sessions').get(); const res5 = db.query('SELECT COUNT(id) as count FROM sessions').get();
const res6 = db.query('SELECT COUNT(id) as count FROM images').get();
queries.desks = db.query('SELECT id, sn FROM desks'); queries.desks = db.query('SELECT id, sn FROM desks');
queries.events = db.query('SELECT id, desk_id, user_id, stroke_id, type, x, y FROM events'); queries.events = db.query('SELECT * FROM events');
queries.sessions = db.query('SELECT id, lsn, user_id, desk_id FROM sessions'); 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 id, points FROM strokes');
queries.empty_desk = db.query('INSERT INTO desks (id, title, sn) VALUES (?1, ?2, 0)'); queries.empty_desk = db.query('INSERT INTO desks (id, title, sn) VALUES (?1, ?2, 0)');
@ -104,7 +122,8 @@ export function startup() {
queries.save_session_lsn = db.query('UPDATE sessions SET lsn = ?1 WHERE id = ?2'); queries.save_session_lsn = db.query('UPDATE sessions SET lsn = ?1 WHERE id = ?2');
queries.create_session = db.query('INSERT INTO sessions (id, lsn, user_id, desk_id) VALUES (?1, 0, ?2, ?3)'); queries.create_session = db.query('INSERT INTO sessions (id, lsn, user_id, desk_id) VALUES (?1, 0, ?2, ?3)');
queries.create_user = db.query('INSERT INTO users (id, login) VALUES (?1, ?2)'); queries.create_user = db.query('INSERT INTO users (id, login) VALUES (?1, ?2)');
queries.put_event = db.query('INSERT INTO events (type, desk_id, user_id, stroke_id, x, y) VALUES (?1, ?2, ?3, ?4, ?5, ?6)'); queries.put_event = db.query('INSERT INTO events (type, desk_id, user_id, stroke_id, image_id, x, y) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)');
queries.put_image = db.query('INSERT INTO images (id, desk_id) VALUES (?1, ?2)');
console.log(`Storing data in ${path}`); console.log(`Storing data in ${path}`);
console.log(`Entity count at startup: console.log(`Entity count at startup:
@ -112,7 +131,8 @@ export function startup() {
${res2.count} events ${res2.count} events
${res3.count} strokes ${res3.count} strokes
${res4.count} users ${res4.count} users
${res5.count} sessions` ${res5.count} sessions
${res6.count} images`
); );
const stored_desks = get_desks(); const stored_desks = get_desks();
@ -169,7 +189,7 @@ export function get_desk_strokes(desk_id) {
} }
export function put_event(event) { 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.x || 0, event.y || 0); 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) { export function put_stroke(stroke_id, desk_id, points) {
@ -198,4 +218,8 @@ export function create_user(user) {
export function save_session_lsn(session_id, lsn) { export function save_session_lsn(session_id, lsn) {
return queries.save_session_lsn.get(lsn, session_id); return queries.save_session_lsn.get(lsn, session_id);
}
export function put_image(image_id, desk_id) {
return queries.put_image.get(image_id, desk_id);
} }
Loading…
Cancel
Save