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 @@ @@ -0,0 +1 @@
server/images

5
Caddyfile

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

88
client/cursor.js

@ -1,7 +1,8 @@ @@ -1,7 +1,8 @@
function on_down(e) {
if (e.button === 1) {
const event = undo_event();
queue_event(event);
elements.cursor.classList.add('dhide');
elements.canvas0.classList.add('moving');
storage.state.moving = true;
return;
}
@ -9,6 +10,10 @@ function on_down(e) { @@ -9,6 +10,10 @@ function on_down(e) {
return;
}
if (storage.state.moving) {
return;
}
const x = Math.round(e.clientX + window.scrollX);
const y = Math.round(e.clientY + window.scrollY);
@ -33,11 +38,10 @@ function on_move(e) { @@ -33,11 +38,10 @@ function on_move(e) {
const x = storage.cursor.x = Math.round(e.clientX + window.scrollX);
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) {
const width = storage.cursor.width;
storage.ctx1.beginPath();
storage.ctx1.moveTo(last_x, last_y);
@ -49,19 +53,36 @@ function on_move(e) { @@ -49,19 +53,36 @@ function on_move(e) {
storage.current_stroke.push(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) {
if (e.button != 0) {
return;
if (storage.state.moving && e.button === 1) {
elements.cursor.classList.remove('dhide');
elements.canvas0.classList.remove('moving');
storage.state.moving = false;
}
storage.state.drawing = false;
const event = stroke_event();
storage.current_stroke = [];
await queue_event(event);
if (storage.state.drawing && e.button === 0) {
storage.state.drawing = false;
const event = stroke_event();
storage.current_stroke = [];
await queue_event(event);
}
}
function on_keydown(e) {
@ -81,16 +102,47 @@ function on_keyup(e) { @@ -81,16 +102,47 @@ function on_keyup(e) {
}
function on_leave(e) {
if (storage.state.drawing) {
on_up(e);
storage.state.drawing = false;
return;
}
if (storage.state.moving) {
elements.cursor.classList.remove('dhide');
elements.canvas0.classList.remove('moving');
storage.state.moving = false;
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 @@ @@ -1,6 +1,7 @@
html, body {
margin: 0;
padding: 0;
overflow: hidden;
}
.dhide {

30
client/index.js

@ -8,7 +8,9 @@ const EVENT = Object.freeze({ @@ -8,7 +8,9 @@ const EVENT = Object.freeze({
STROKE: 20,
UNDO: 30,
REDO: 31,
IMAGE: 40,
});
const MESSAGE = Object.freeze({
INIT: 100,
SYN: 101,
@ -20,6 +22,7 @@ const MESSAGE = Object.freeze({ @@ -20,6 +22,7 @@ const MESSAGE = Object.freeze({
const config = {
ws_url: 'wss://desk.local/ws/',
image_url: 'https://desk.local/images/',
sync_timeout: 1000,
ws_reconnect_timeout: 2000,
};
@ -81,9 +84,13 @@ function event_size(event) { @@ -81,9 +84,13 @@ function event_size(event) {
break;
}
case EVENT.IMAGE: {
size += 4 + 2 + 2; // file id + x + y
break;
}
default: {
console.error('fuck');
process.exit(1);
}
}
@ -113,6 +120,15 @@ function redo_event() { @@ -113,6 +120,15 @@ 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('/');
@ -127,7 +143,11 @@ function main() { @@ -127,7 +143,11 @@ function main() {
elements.cursor.style.width = 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.ctx1 = elements.canvas1.getContext('2d');
@ -144,5 +164,9 @@ function main() { @@ -144,5 +164,9 @@ function main() {
window.addEventListener('touchstart', (e) => e.preventDefault());
window.addEventListener('keydown', on_keydown);
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) { @@ -66,6 +66,13 @@ function des_event(d) {
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.REDO: {
break;
@ -98,7 +105,7 @@ function redraw_region(bbox) { @@ -98,7 +105,7 @@ function redraw_region(bbox) {
storage.ctx0.restore();
}
function handle_event(event) {
async function handle_event(event) {
console.debug(`event type ${event.type} from user ${event.user_id}`);
switch (event.type) {
@ -127,6 +134,19 @@ function handle_event(event) { @@ -127,6 +134,19 @@ function handle_event(event) {
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: {
console.error('fuck');
}

14
client/send.js

@ -48,6 +48,14 @@ function ser_event(s, event) { @@ -48,6 +48,14 @@ function ser_event(s, event) {
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.REDO: {
break;
@ -55,7 +63,6 @@ function ser_event(s, event) { @@ -55,7 +63,6 @@ function ser_event(s, event) {
default: {
console.error('fuck');
process.exit(1);
}
}
}
@ -120,11 +127,16 @@ function push_event(event) { @@ -120,11 +127,16 @@ function push_event(event) {
break;
}
case EVENT.IMAGE:
case EVENT.UNDO:
case EVENT.REDO: {
storage.queue.push(event);
break;
}
default: {
console.error('fuck');
}
}
}

3
server/config.js

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
export const HOST = '127.0.0.1';
export const PORT = 3003;
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) { @@ -51,6 +51,13 @@ export function event(d) {
break;
}
case EVENT.IMAGE: {
event.image_id = u32(d);
event.x = u16(d);
event.y = u16(d);
break;
}
case EVENT.UNDO:
case EVENT.REDO: {
break;

1
server/enums.js

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

24
server/http.js

@ -1,4 +1,22 @@ @@ -1,4 +1,22 @@
export function route(req) {
console.log('HTTP:', req.url);
return new Response(req.url);
import * as config from './config';
import * as math from './math';
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) { @@ -25,6 +25,7 @@ function handle_event(session, event) {
break;
}
case EVENT.IMAGE:
case EVENT.UNDO: {
storage.put_event(event);
break;

5
server/send.js

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

7
server/serializer.js

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

34
server/storage.js

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