From 5a25b1abcf244c13b20997c265ddd567ca49bef1 Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Thu, 22 Jun 2023 09:24:15 -0600 Subject: [PATCH 01/11] Remove unused transform() --- src/server/MessageHandler.js | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/server/MessageHandler.js b/src/server/MessageHandler.js index 3bd3d66e..48465ae4 100644 --- a/src/server/MessageHandler.js +++ b/src/server/MessageHandler.js @@ -59,29 +59,6 @@ class MessageHandler { } } - /** - * Apply a transformation event, splitting it into rotate, scale, and - * move. - * - * @param {object} event - * @param {object} data - */ - transform(event, data) { - const { delta } = data; - - if (Object.prototype.hasOwnProperty.call(delta, 'scale')) { - this.scale(event, delta); - } - - if (Object.prototype.hasOwnProperty.call(delta, 'rotation')) { - this.rotate(event, delta); - } - - if (Object.prototype.hasOwnProperty.call(delta, 'translation')) { - this.drag(event, delta); - } - } - /** * Apply a scale event * From f57f773831ff10b9bbbd7dc1fed1dc6cb99594a6 Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Thu, 22 Jun 2023 09:32:11 -0600 Subject: [PATCH 02/11] Forward as much of the original event as possible --- src/server/MessageHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/MessageHandler.js b/src/server/MessageHandler.js index 48465ae4..14b54f04 100644 --- a/src/server/MessageHandler.js +++ b/src/server/MessageHandler.js @@ -39,7 +39,7 @@ class MessageHandler { if (target != null) { const original = device.reversePoint(centroid.x, centroid.y); const { x, y } = view.transformPoint(original.x, original.y); - this[gesture]({ device, group, view, target, x, y }, data); + this[gesture]({ ...event, centroid: { x, y }, x, y, target }, data); } } From a9947a74bf7d04d45decb20cd61ebffe05416c08 Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Thu, 22 Jun 2023 09:32:49 -0600 Subject: [PATCH 03/11] Rename processSwivel -> handleSwivel --- src/server/GestureController.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/GestureController.js b/src/server/GestureController.js index ab5fdf19..b62065e1 100644 --- a/src/server/GestureController.js +++ b/src/server/GestureController.js @@ -63,7 +63,7 @@ class GestureController { const tap = new Tap(this.group, handleGesture.bind(this.messageHandler, 'click'), { applySmoothing: false, }); - const swivel = new Swivel(this.group, this.processSwivel.bind(this), { + const swivel = new Swivel(this.group, this.handleSwivel.bind(this), { applySmoothing: false, enableKeys: ['ctrlKey'], dynamicPivot: true, @@ -83,7 +83,7 @@ class GestureController { * * @param {string} event */ - processSwivel(event) { + handleSwivel(event) { event.centroid = event.pivot; this.messageHandler.handleGesture('rotate', event); } From 248d431b9a61af417a342fda10d93fe10f0d5f22 Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Thu, 22 Jun 2023 09:36:19 -0600 Subject: [PATCH 04/11] Use type='item' since that's what the switch statement looks for --- src/shared/bases.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/bases.js b/src/shared/bases.js index 81cb685a..608ec4a1 100644 --- a/src/shared/bases.js +++ b/src/shared/bases.js @@ -55,7 +55,7 @@ class Item { * @memberof module:shared.Item * @instance */ - type: 'item/polygonal', + type: 'item', /** * Whether to raise item upon interaction or lock Z position instead. @@ -109,7 +109,7 @@ class RectangularItem extends Item { */ height: 300, - type: 'item/rectangular', + type: 'item', ...values, // Assigns additional attributes to the object }); } From 392813bbcf60e3289f1e33656839cf5be612f1ef Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Thu, 22 Jun 2023 09:36:38 -0600 Subject: [PATCH 05/11] Lock on items with click listeners too --- src/server/WorkSpace.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/server/WorkSpace.js b/src/server/WorkSpace.js index 4a000c92..e737efb7 100644 --- a/src/server/WorkSpace.js +++ b/src/server/WorkSpace.js @@ -87,10 +87,12 @@ class WorkSpace { _canLock(item) { const eventNames = item.eventNames(); return ( + item.onclick || item.ondrag || item.onpinch || item.onrotate || item.onswipe || + eventNames.includes('click') || eventNames.includes('drag') || eventNames.includes('pinch') || eventNames.includes('rotate') || From 8ce75fa231cb55f04cc85d6ecf0b3d0d973c4bfb Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Thu, 22 Jun 2023 10:05:20 -0600 Subject: [PATCH 06/11] An item is considered lockable if it has _any_ event listeners attached --- src/server/WorkSpace.js | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/server/WorkSpace.js b/src/server/WorkSpace.js index e737efb7..335dd91c 100644 --- a/src/server/WorkSpace.js +++ b/src/server/WorkSpace.js @@ -85,19 +85,10 @@ class WorkSpace { } _canLock(item) { - const eventNames = item.eventNames(); - return ( - item.onclick || - item.ondrag || - item.onpinch || - item.onrotate || - item.onswipe || - eventNames.includes('click') || - eventNames.includes('drag') || - eventNames.includes('pinch') || - eventNames.includes('rotate') || - eventNames.includes('swipe') - ); + // Instead of choosing some arbitrary subset of events that allow locking, + // consider any item with any kind of event listeners as lockable. It's + // still automagical but less opinionated. + return item.eventNames().length > 0; } /** From 31912cb34e9fa8373fe87476afe3f8ccbd2c5c61 Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Thu, 22 Jun 2023 10:08:29 -0600 Subject: [PATCH 07/11] Shorten timeout before removing swipe tail --- examples/swipe-drawing.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/swipe-drawing.js b/examples/swipe-drawing.js index 8a5bd918..635169fd 100644 --- a/examples/swipe-drawing.js +++ b/examples/swipe-drawing.js @@ -38,7 +38,7 @@ function handleDrag({ x, y, dx, dy }) { }) ); // console.log('LineLayout: { x: %s, y: %s, length: %s, rotation: %s }', line.x, line.y, length, line.rotation); - setTimeout(() => app.removeItem(line), 3000); + setTimeout(() => app.removeItem(line), 500); } function handleConnect({ view }) { From 8efdf031bdb7dddcc2be67427f8eabc7b51f67c1 Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Thu, 22 Jun 2023 10:32:55 -0600 Subject: [PATCH 08/11] Optimize adding items: don't require a second packet to set sequence etc --- src/server/ServerItem.js | 3 +-- src/server/WorkSpace.js | 24 ++++++------------------ 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/src/server/ServerItem.js b/src/server/ServerItem.js index 0b5d109c..d4faa5d9 100644 --- a/src/server/ServerItem.js +++ b/src/server/ServerItem.js @@ -42,9 +42,8 @@ class ServerItem extends Identifiable(Hittable(Item)) { * Sequence of canvas instructions to be run on the client * * @type {CanvasSequence} - * @default undefined */ - this.sequence = undefined; + this.sequence = values.sequence; } /* diff --git a/src/server/WorkSpace.js b/src/server/WorkSpace.js index 335dd91c..6d88ed7f 100644 --- a/src/server/WorkSpace.js +++ b/src/server/WorkSpace.js @@ -158,13 +158,9 @@ class WorkSpace { */ spawnElement(values = {}) { const item = new ServerElement(this.namespace, values); - // Notify subscribers immediately. + this.addItem(item); this.namespace.emit(Message.ADD_ELEMENT, item); - if (values.attributes) { - // Must be called _after_ the "ADD" message is emitted - item.setAttributes(values.attributes); - } - return this.addItem(item); + return item; } /** @@ -176,13 +172,9 @@ class WorkSpace { */ spawnImage(values = {}) { const item = new ServerImage(this.namespace, values); - // Notify subscribers immediately. + this.addItem(item); this.namespace.emit(Message.ADD_IMAGE, item); - if (values.src) { - // Must be called _after_ the "ADD" message is emitted - item.setImage(values.src); - } - return this.addItem(item); + return item; } /** @@ -194,13 +186,9 @@ class WorkSpace { */ spawnItem(values = {}) { const item = new ServerItem(this.namespace, values); - // Notify subscribers immediately. + this.addItem(item); this.namespace.emit(Message.ADD_ITEM, item); - if (values.sequence) { - // Must be called _after_ the "ADD" message is emitted - item.setSequence(values.sequence); - } - return this.addItem(item); + return item; } /** From 12379775d1e869f8815d127c85d52178c1cb7dfa Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Thu, 22 Jun 2023 10:37:07 -0600 Subject: [PATCH 09/11] Send a minimal packet when asking to remove an item --- src/server/WorkSpace.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/WorkSpace.js b/src/server/WorkSpace.js index 6d88ed7f..6ad94fb2 100644 --- a/src/server/WorkSpace.js +++ b/src/server/WorkSpace.js @@ -126,7 +126,7 @@ class WorkSpace { removeItem(item) { if (removeById(this.items, item)) { item.unlock(); - this.namespace.emit(Message.RM_ITEM, item); + this.namespace.emit(Message.RM_ITEM, { id: item.id }); } } From 53f59ce73c30dcc22ac4fdef0e40f47ae668e2e8 Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Thu, 22 Jun 2023 10:55:27 -0600 Subject: [PATCH 10/11] Make more use of the shared bases --- src/client/ClientItem.js | 8 ++-- src/server/ServerElement.js | 15 ------ src/server/ServerImage.js | 17 +------ src/server/ServerItem.js | 30 ++---------- src/shared/bases.js | 93 +++++++++++++++++++++++++++++++++++++ 5 files changed, 102 insertions(+), 61 deletions(-) diff --git a/src/client/ClientItem.js b/src/client/ClientItem.js index e1dded79..b8aa15fb 100644 --- a/src/client/ClientItem.js +++ b/src/client/ClientItem.js @@ -1,17 +1,17 @@ 'use strict'; -const { Item } = require('../shared.js'); +const { CanvasItem } = require('../shared.js'); const { CanvasSequence } = require('canvas-sequencer'); /** * The ClientItem class exposes the draw() funcitonality of wams items. * - * @extends module:shared.Item + * @extends module:shared.CanvasItem * @memberof module:client * - * @param {module:shared.Item} data - The data from the server describing this item. + * @param {module:shared.CanvasItem} data - The data from the server describing this item. */ -class ClientItem extends Item { +class ClientItem extends CanvasItem { constructor(data) { super(data); diff --git a/src/server/ServerElement.js b/src/server/ServerElement.js index 0a80ec48..17c7e93a 100644 --- a/src/server/ServerElement.js +++ b/src/server/ServerElement.js @@ -24,8 +24,6 @@ const { Hittable, Identifiable } = require('../mixins.js'); * * @param {Namespace} namespace - Socket.io namespace for publishing changes. * @param {Object} values - User-supplied data detailing the elements. - * Properties on this object that line up with {@link module:shared.Element} - * members will be stored. Any other properties will be ignored. */ class ServerElement extends Identifiable(Hittable(WamsElement)) { constructor(namespace, values = {}) { @@ -67,19 +65,6 @@ class ServerElement extends Identifiable(Hittable(WamsElement)) { this.attributes = Object.assign(this.attributes, attributes); this.namespace.emit(Message.SET_ATTRS, { id: this.id, attributes }); } - - /** - * Serialize the element as a JSON object. - * - * @returns {Object} The element as a JSON object. - * @override - */ - toJSON() { - return { - ...super.toJSON(), - attributes: this.attributes, - }; - } } Object.assign(ServerElement.prototype, EventEmitter.prototype); diff --git a/src/server/ServerImage.js b/src/server/ServerImage.js index 67e826f5..ac5186c7 100644 --- a/src/server/ServerImage.js +++ b/src/server/ServerImage.js @@ -23,9 +23,7 @@ const { Hittable, Identifiable } = require('../mixins.js'); * @extends __ServerImage * * @param {Namespace} namespace - Socket.io namespace for publishing changes. - * @param {Object} values - User-supplied data detailing the image. Properties - * on this object that line up with {@link module:shared.Image} members will be - * stored. Any other properties will be ignored. + * @param {Object} values - User-supplied data detailing the image. */ class ServerImage extends Identifiable(Hittable(WamsImage)) { constructor(namespace, values = {}) { @@ -55,19 +53,6 @@ class ServerImage extends Identifiable(Hittable(WamsImage)) { this.src = path; this.namespace.emit(Message.SET_IMAGE, { id: this.id, src: path }); } - - /** - * Serialize the image as a JSON object. - * - * @returns {Object} The image as a JSON object. - * @override - */ - toJSON() { - return { - ...super.toJSON(), - src: this.src, - }; - } } Object.assign(ServerImage.prototype, EventEmitter.prototype); diff --git a/src/server/ServerItem.js b/src/server/ServerItem.js index d4faa5d9..9e3a0fbf 100644 --- a/src/server/ServerItem.js +++ b/src/server/ServerItem.js @@ -1,7 +1,7 @@ 'use strict'; const { EventEmitter } = require('node:events'); -const { Item, Message } = require('../shared.js'); +const { CanvasItem, Message } = require('../shared.js'); const { Hittable, Identifiable } = require('../mixins.js'); /** @@ -19,15 +19,13 @@ const { Hittable, Identifiable } = require('../mixins.js'); * around. * * @memberof module:server - * @extends module:shared.Item + * @extends module:shared.CanvasItem * @extends __ServerItem * * @param {Namespace} namespace - Socket.io namespace for publishing changes. - * @param {Object} values - User-supplied data detailing the item. Properties on - * this object that line up with {@link module:shared.Item} members will be - * stored. Any other properties will be ignored. + * @param {Object} values - User-supplied data detailing the item. */ -class ServerItem extends Identifiable(Hittable(Item)) { +class ServerItem extends Identifiable(Hittable(CanvasItem)) { constructor(namespace, values = {}) { super(values); @@ -37,13 +35,6 @@ class ServerItem extends Identifiable(Hittable(Item)) { * @type {Namespace} */ this.namespace = namespace; - - /** - * Sequence of canvas instructions to be run on the client - * - * @type {CanvasSequence} - */ - this.sequence = values.sequence; } /* @@ -63,19 +54,6 @@ class ServerItem extends Identifiable(Hittable(Item)) { this.sequence = sequence; this.namespace.emit(Message.SET_RENDER, { id: this.id, sequence }); } - - /** - * Serialize the item as a JSON object. - * - * @returns {Object} The item as a JSON object. - * @override - */ - toJSON() { - return { - ...super.toJSON(), - sequence: this.sequence, - }; - } } Object.assign(ServerItem.prototype, EventEmitter.prototype); diff --git a/src/shared/bases.js b/src/shared/bases.js index 608ec4a1..dee7c7d1 100644 --- a/src/shared/bases.js +++ b/src/shared/bases.js @@ -6,6 +6,7 @@ * * @class Item * @memberof module:shared + * @param {Object} values - User-supplied data detailing the item. */ class Item { constructor(values = {}) { @@ -88,6 +89,46 @@ class Item { } } +class CanvasItem extends Item { + constructor(values = {}) { + super({ + /** + * @name sequence + * @type {CanvasSequence} + * @default undefined + * @memberof module:shared.CanvasItem + * @instance + */ + sequence: undefined, + + type: 'item', + ...values, // Assigns additional attributes to the object + }); + } + + /** + * Serialize the item as a JSON object. + * + * @returns {Object} The item as a JSON object. + * @override + */ + toJSON() { + return { + ...super.toJSON(), + sequence: this.sequence, + }; + } +} + +/** + * This RectangularItem class provides a common interface between the client and + * the server by which the RectangularItems can interact safely. + * + * @class RectangularItem + * @extends module:shared.Item + * @memberof module:shared + * @param {Object} values - User-supplied data detailing the item. + */ class RectangularItem extends Item { constructor(values = {}) { super({ @@ -132,7 +173,9 @@ class RectangularItem extends Item { * server by which the elements interact safely. * * @class WamsElement + * @extends module:shared.RectangularItem * @memberof module:shared + * @param {Object} values - User-supplied data detailing the item. */ class WamsElement extends RectangularItem { constructor(values = {}) { @@ -148,6 +191,26 @@ class WamsElement extends RectangularItem { */ tagname: 'div', + /** + * Additional attributes to set on the DOM element. + * + * @name attributes + * @type {object} + * @default {} + * @memberof module:shared.WamsElement + * @instance + * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes} + * @example + * { + * class: 'my-class', + * id: 'my-id', + * style: 'background-color: red;', + * ... + * // Any other attribute you want to set on the element + * } + */ + attributes: {}, + type: 'item/element', ...values, // Assigns additional attributes to the object }); @@ -160,6 +223,7 @@ class WamsElement extends RectangularItem { return { ...super.toJSON(), tagname: this.tagname, + attributes: this.attributes, }; } } @@ -169,15 +233,41 @@ class WamsElement extends RectangularItem { * server by which the images can interact safely. * * @class WamsImage + * @extends module:shared.RectangularItem * @memberof module:shared + * @param {Object} values - User-supplied data detailing the item. */ class WamsImage extends RectangularItem { constructor(values = {}) { super({ + /** + * Source of the image. + * + * @name src + * @type {string} + * @default '' + * @memberof module:shared.WamsImage + * @instance + */ + src: '', + type: 'item/image', ...values, // Assigns additional attributes to the object }); } + + /** + * Serialize the image as a JSON object. + * + * @returns {Object} The image as a JSON object. + * @override + */ + toJSON() { + return { + ...super.toJSON(), + src: this.src, + }; + } } /** @@ -186,6 +276,8 @@ class WamsImage extends RectangularItem { * * @class View * @memberof module:shared + * @extends module:shared.RectangularItem + * @param {Object} values - User-supplied data detailing the item. */ class View extends RectangularItem { constructor(values = {}) { @@ -222,6 +314,7 @@ class View extends RectangularItem { } module.exports = { + CanvasItem, Item, View, WamsElement, From ac58353af9b056a968a6f8b66ea9b80b09a0390c Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Thu, 22 Jun 2023 10:56:57 -0600 Subject: [PATCH 11/11] Fix tests --- tests/server/ServerItem.test.js | 8 ++++---- tests/server/WorkSpace.test.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/server/ServerItem.test.js b/tests/server/ServerItem.test.js index c9bfe8e8..9508b286 100644 --- a/tests/server/ServerItem.test.js +++ b/tests/server/ServerItem.test.js @@ -17,7 +17,7 @@ describe('ServerItem', () => { y: 0, rotation: 0, scale: 1, - type: 'item/polygonal', + type: 'item', lockZ: false, }); }); @@ -122,7 +122,7 @@ describe('ServerItem', () => { y: 59, rotation: 0, scale: 1, - type: 'item/polygonal', + type: 'item', }); }); }); @@ -159,7 +159,7 @@ describe('ServerItem', () => { y: 59, rotation: 0, scale: 1, - type: 'item/polygonal', + type: 'item', }); }); test('Has no effect if parameters left out', () => { @@ -195,7 +195,7 @@ describe('ServerItem', () => { y: 9, rotation: 0, scale: 1, - type: 'item/polygonal', + type: 'item', }); }); }); diff --git a/tests/server/WorkSpace.test.js b/tests/server/WorkSpace.test.js index e5a50a2e..95347abe 100644 --- a/tests/server/WorkSpace.test.js +++ b/tests/server/WorkSpace.test.js @@ -32,7 +32,7 @@ describe('WorkSpace', () => { y: 0, rotation: 0, scale: 1, - type: 'item/polygonal', + type: 'item', lockZ: false, }); });