feat(engine): Add debounce option to events.

This commit is contained in:
Gervwyk 2021-08-19 12:40:03 +02:00
parent aa250fc057
commit 003cb0b1ec
2 changed files with 95 additions and 33 deletions

View File

@ -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;
}
}

View File

@ -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 },
});
});