From 003cb0b1ec13a246aa4848f2c5020a937b97ac3d Mon Sep 17 00:00:00 2001 From: Gervwyk Date: Thu, 19 Aug 2021 12:40:03 +0200 Subject: [PATCH] feat(engine): Add debounce option to events. --- packages/engine/src/Events.js | 104 +++++++++++++++++++++------- packages/engine/test/Events.test.js | 24 ++++--- 2 files changed, 95 insertions(+), 33 deletions(-) diff --git a/packages/engine/src/Events.js b/packages/engine/src/Events.js index 85f4fa7cd..642806c00 100644 --- a/packages/engine/src/Events.js +++ b/packages/engine/src/Events.js @@ -18,7 +18,9 @@ import { type } from '@lowdefy/helpers'; class Events { constructor({ arrayIndices, block, context }) { + this.defaultDebounceMs = 300; this.events = {}; + this.timeouts = {}; this.arrayIndices = arrayIndices; this.block = block; this.context = context; @@ -35,6 +37,7 @@ class Events { return { actions: (type.isObject(actions) ? actions.try : actions) || [], catchActions: (type.isObject(actions) ? actions.catch : []) || [], + debounce: type.isObject(actions) ? actions.debounce : null, history: [], loading: false, }; @@ -52,37 +55,90 @@ class Events { async triggerEvent({ name, event }) { const eventDescription = this.events[name]; + let result = { + blockId: this.block.blockId, + event, + eventName: name, + responses: {}, + endTimestamp: new Date(), + startTimestamp: new Date(), + success: true, + bounced: false, + }; + // no event if (type.isUndefined(eventDescription)) { - return { - blockId: this.block.blockId, - event, - eventName: name, - responses: {}, - endTimestamp: new Date(), - startTimestamp: new Date(), - success: true, - }; + return result; } eventDescription.loading = true; this.block.update = true; this.context.update(); - const result = await this.context.Actions.callActions({ - actions: eventDescription.actions, - arrayIndices: this.arrayIndices, - block: this.block, - catchActions: eventDescription.catchActions, - event, - eventName: name, + const actionHandle = async () => { + const res = await this.context.Actions.callActions({ + actions: eventDescription.actions, + arrayIndices: this.arrayIndices, + block: this.block, + catchActions: eventDescription.catchActions, + event, + eventName: name, + }); + eventDescription.history.unshift(res); + this.context.eventLog.unshift(res); + eventDescription.loading = false; + this.block.update = true; + this.context.update(); + return res; + }; + + // no debounce + if (type.isNone(eventDescription.debounce)) { + return await actionHandle(); + } + + // leading edge: bounce + if (this.timeouts[name] && eventDescription.debounce.immediate === true) { + result.bounced = true; + eventDescription.history.unshift(result); + this.context.eventLog.unshift(result); + return result; + } + // leading edge: trigger + if (eventDescription.debounce.immediate === true) { + this.timeouts[name] = setTimeout( + () => { + clearTimeout(this.timeouts[name]); + this.timeouts[name] = null; + }, + !type.isNone(eventDescription.debounce.ms) + ? eventDescription.debounce.ms + : this.defaultDebounceMs + ); + return actionHandle(); + } + + // trailing edge + if (eventDescription.bouncer) { + eventDescription.bouncer(); + } + return new Promise((resolve) => { + const timeout = setTimeout( + async () => { + const res = await actionHandle(); + resolve(res); + }, + !type.isNone(eventDescription.debounce.ms) + ? eventDescription.debounce.ms + : this.defaultDebounceMs + ); + + eventDescription.bouncer = () => { + clearTimeout(timeout); + result.bounced = true; + eventDescription.history.unshift(result); + this.context.eventLog.unshift(result); + resolve(result); + }; }); - - eventDescription.history.unshift(result); - this.context.eventLog.unshift(result); - eventDescription.loading = false; - this.block.update = true; - this.context.update(); - - return result; } } diff --git a/packages/engine/test/Events.test.js b/packages/engine/test/Events.test.js index ab99d7db9..db329957f 100644 --- a/packages/engine/test/Events.test.js +++ b/packages/engine/test/Events.test.js @@ -105,10 +105,11 @@ test('init Events', async () => { const { button } = context.RootBlocks.map; expect(button.Events.events).toEqual({ onClick: { + actions: [{ id: 'a', type: 'SetState', params: { a: 'a' } }], + catchActions: [], + debounce: null, history: [], loading: false, - catchActions: [], - actions: [{ id: 'a', type: 'SetState', params: { a: 'a' } }], }, }); }); @@ -144,6 +145,7 @@ test('triggerEvent no event defined', async () => { const res = await promise; expect(res).toEqual({ blockId: 'button', + bounced: false, endTimestamp: { date: 0 }, event: undefined, eventName: 'onClick', @@ -185,10 +187,11 @@ test('triggerEvent x1', async () => { const promise = button.triggerEvent({ name: 'onClick', event: { x: 1 } }); expect(button.Events.events).toEqual({ onClick: { + actions: [{ id: 'a', type: 'SetState', params: { a: 'a' } }], + catchActions: [], + debounce: null, history: [], loading: true, - catchActions: [], - actions: [{ id: 'a', type: 'SetState', params: { a: 'a' } }], }, }); await promise; @@ -353,10 +356,11 @@ test('registerEvent then triggerEvent x1', async () => { }); expect(button.Events.events).toEqual({ onClick: { + actions: [{ id: 'a', type: 'SetState', params: { a: 'a' } }], + catchActions: [], + debounce: null, history: [], loading: false, - catchActions: [], - actions: [{ id: 'a', type: 'SetState', params: { a: 'a' } }], }, }); await button.triggerEvent({ name: 'onClick', event: { x: 1 } }); @@ -424,6 +428,7 @@ test('triggerEvent skip', async () => { }, ], "catchActions": Array [], + "debounce": null, "history": Array [ Object { "blockId": "button", @@ -523,6 +528,7 @@ test('triggerEvent skip tests === true', async () => { }, ], "catchActions": Array [], + "debounce": null, "history": Array [ Object { "blockId": "button", @@ -611,8 +617,8 @@ test('Actions array defaults', async () => { actions: null, }); expect(button.Events.events).toEqual({ - onClick: { actions: [], history: [], loading: false, catchActions: [] }, - registered: { actions: [], history: [], loading: false, catchActions: [] }, + onClick: { actions: [], history: [], loading: false, catchActions: [], debounce: null }, + registered: { actions: [], history: [], loading: false, catchActions: [], debounce: null }, }); }); @@ -649,7 +655,7 @@ test('Actions try catch array defaults', async () => { }); const { button } = context.RootBlocks.map; expect(button.Events.events).toEqual({ - onClick: { actions: [], history: [], loading: false, catchActions: [] }, + onClick: { actions: [], history: [], loading: false, catchActions: [], debounce: undefined }, }); });