feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,50 @@
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
export default class CircularBuffer {
constructor(size) {
_defineProperty(this, "bufferSize", void 0);
_defineProperty(this, "buffer", void 0);
_defineProperty(this, "index", void 0);
_defineProperty(this, "actualSize", void 0);
this.bufferSize = size;
this.buffer = new Array(size);
this.index = 0;
this.actualSize = 0;
}
get size() {
return this.actualSize;
}
push(element) {
this.buffer[this.index] = element;
this.index = (this.index + 1) % this.bufferSize;
this.actualSize = Math.min(this.actualSize + 1, this.bufferSize);
}
get(at) {
if (this.actualSize === this.bufferSize) {
let index = (this.index + at) % this.bufferSize;
if (index < 0) {
index += this.bufferSize;
}
return this.buffer[index];
} else {
return this.buffer[at];
}
}
clear() {
this.buffer = new Array(this.bufferSize);
this.index = 0;
this.actualSize = 0;
}
}
//# sourceMappingURL=CircularBuffer.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["CircularBuffer.ts"],"names":["CircularBuffer","constructor","size","bufferSize","buffer","Array","index","actualSize","push","element","Math","min","get","at","clear"],"mappings":";;AAAA,eAAe,MAAMA,cAAN,CAAwB;AAMrCC,EAAAA,WAAW,CAACC,IAAD,EAAe;AAAA;;AAAA;;AAAA;;AAAA;;AACxB,SAAKC,UAAL,GAAkBD,IAAlB;AACA,SAAKE,MAAL,GAAc,IAAIC,KAAJ,CAAaH,IAAb,CAAd;AACA,SAAKI,KAAL,GAAa,CAAb;AACA,SAAKC,UAAL,GAAkB,CAAlB;AACD;;AAEc,MAAJL,IAAI,GAAW;AACxB,WAAO,KAAKK,UAAZ;AACD;;AAEMC,EAAAA,IAAI,CAACC,OAAD,EAAmB;AAC5B,SAAKL,MAAL,CAAY,KAAKE,KAAjB,IAA0BG,OAA1B;AACA,SAAKH,KAAL,GAAa,CAAC,KAAKA,KAAL,GAAa,CAAd,IAAmB,KAAKH,UAArC;AACA,SAAKI,UAAL,GAAkBG,IAAI,CAACC,GAAL,CAAS,KAAKJ,UAAL,GAAkB,CAA3B,EAA8B,KAAKJ,UAAnC,CAAlB;AACD;;AAEMS,EAAAA,GAAG,CAACC,EAAD,EAAgB;AACxB,QAAI,KAAKN,UAAL,KAAoB,KAAKJ,UAA7B,EAAyC;AACvC,UAAIG,KAAK,GAAG,CAAC,KAAKA,KAAL,GAAaO,EAAd,IAAoB,KAAKV,UAArC;;AACA,UAAIG,KAAK,GAAG,CAAZ,EAAe;AACbA,QAAAA,KAAK,IAAI,KAAKH,UAAd;AACD;;AAED,aAAO,KAAKC,MAAL,CAAYE,KAAZ,CAAP;AACD,KAPD,MAOO;AACL,aAAO,KAAKF,MAAL,CAAYS,EAAZ,CAAP;AACD;AACF;;AAEMC,EAAAA,KAAK,GAAS;AACnB,SAAKV,MAAL,GAAc,IAAIC,KAAJ,CAAa,KAAKF,UAAlB,CAAd;AACA,SAAKG,KAAL,GAAa,CAAb;AACA,SAAKC,UAAL,GAAkB,CAAlB;AACD;;AAxCoC","sourcesContent":["export default class CircularBuffer<T> {\n private bufferSize: number;\n private buffer: T[];\n private index: number;\n private actualSize: number;\n\n constructor(size: number) {\n this.bufferSize = size;\n this.buffer = new Array<T>(size);\n this.index = 0;\n this.actualSize = 0;\n }\n\n public get size(): number {\n return this.actualSize;\n }\n\n public push(element: T): void {\n this.buffer[this.index] = element;\n this.index = (this.index + 1) % this.bufferSize;\n this.actualSize = Math.min(this.actualSize + 1, this.bufferSize);\n }\n\n public get(at: number): T {\n if (this.actualSize === this.bufferSize) {\n let index = (this.index + at) % this.bufferSize;\n if (index < 0) {\n index += this.bufferSize;\n }\n\n return this.buffer[index];\n } else {\n return this.buffer[at];\n }\n }\n\n public clear(): void {\n this.buffer = new Array<T>(this.bufferSize);\n this.index = 0;\n this.actualSize = 0;\n }\n}\n"]}

View File

@@ -0,0 +1,118 @@
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
/* eslint-disable @typescript-eslint/no-empty-function */
export default class EventManager {
constructor(view) {
_defineProperty(this, "view", void 0);
_defineProperty(this, "pointersInBounds", []);
_defineProperty(this, "activePointersCounter", void 0);
this.view = view;
this.activePointersCounter = 0;
}
onPointerDown(_event) {}
onPointerAdd(_event) {}
onPointerUp(_event) {}
onPointerRemove(_event) {}
onPointerMove(_event) {}
onPointerLeave(_event) {} // called only when pointer is pressed (or touching)
onPointerEnter(_event) {} // called only when pointer is pressed (or touching)
onPointerCancel(_event) {// When pointer cancel is triggered and there are more pointers on the view, only one pointer is cancelled
// Because we want all pointers to be cancelled by that event, we are doing it manually by reseting handler and changing activePointersCounter to 0
// Events that correspond to removing the pointer (pointerup, touchend) have condition, that they don't perform any action when activePointersCounter
// is equal to 0. This prevents counter from going to negative values, when pointers are removed from view after one of them has been cancelled
}
onPointerOutOfBounds(_event) {}
onPointerMoveOver(_event) {}
onPointerMoveOut(_event) {}
setOnPointerDown(callback) {
this.onPointerDown = callback;
}
setOnPointerAdd(callback) {
this.onPointerAdd = callback;
}
setOnPointerUp(callback) {
this.onPointerUp = callback;
}
setOnPointerRemove(callback) {
this.onPointerRemove = callback;
}
setOnPointerMove(callback) {
this.onPointerMove = callback;
}
setOnPointerLeave(callback) {
this.onPointerLeave = callback;
}
setOnPointerEnter(callback) {
this.onPointerEnter = callback;
}
setOnPointerCancel(callback) {
this.onPointerCancel = callback;
}
setOnPointerOutOfBounds(callback) {
this.onPointerOutOfBounds = callback;
}
setOnPointerMoveOver(callback) {
this.onPointerMoveOver = callback;
}
setOnPointerMoveOut(callback) {
this.onPointerMoveOut = callback;
}
markAsInBounds(pointerId) {
if (this.pointersInBounds.indexOf(pointerId) >= 0) {
return;
}
this.pointersInBounds.push(pointerId);
}
markAsOutOfBounds(pointerId) {
const index = this.pointersInBounds.indexOf(pointerId);
if (index < 0) {
return;
}
this.pointersInBounds.splice(index, 1);
}
resetManager() {
// Reseting activePointersCounter is necessary to make gestures such as pinch work properly
// There are gestures that end when there is still one active pointer (like pinch/rotation)
// When these gestures end, they are reset, but they still receive events from pointer that is active
// This causes trouble, since only onPointerDown registers gesture in orchestrator, and while gestures receive
// Events from active pointer after they finished, next pointerdown event will be registered as additional pointer, not the first one
// This casues trouble like gestures getting stuck in END state, even though they should have gone to UNDETERMINED
this.activePointersCounter = 0;
this.pointersInBounds = [];
}
}
//# sourceMappingURL=EventManager.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=GestureHandlerDelegate.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","sourcesContent":[]}

View File

@@ -0,0 +1,334 @@
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
import { PointerType } from '../../PointerType';
import { State } from '../../State';
import PointerTracker from './PointerTracker';
export default class GestureHandlerOrchestrator {
// Private beacuse of Singleton
// eslint-disable-next-line no-useless-constructor, @typescript-eslint/no-empty-function
constructor() {
_defineProperty(this, "gestureHandlers", []);
_defineProperty(this, "awaitingHandlers", []);
_defineProperty(this, "awaitingHandlersTags", new Set());
_defineProperty(this, "handlingChangeSemaphore", 0);
_defineProperty(this, "activationIndex", 0);
}
scheduleFinishedHandlersCleanup() {
if (this.handlingChangeSemaphore === 0) {
this.cleanupFinishedHandlers();
}
}
cleanHandler(handler) {
handler.reset();
handler.setActive(false);
handler.setAwaiting(false);
handler.setActivationIndex(Number.MAX_VALUE);
}
removeHandlerFromOrchestrator(handler) {
const indexInGestureHandlers = this.gestureHandlers.indexOf(handler);
const indexInAwaitingHandlers = this.awaitingHandlers.indexOf(handler);
if (indexInGestureHandlers >= 0) {
this.gestureHandlers.splice(indexInGestureHandlers, 1);
}
if (indexInAwaitingHandlers >= 0) {
this.awaitingHandlers.splice(indexInAwaitingHandlers, 1);
this.awaitingHandlersTags.delete(handler.getTag());
}
}
cleanupFinishedHandlers() {
const handlersToRemove = new Set();
for (let i = this.gestureHandlers.length - 1; i >= 0; --i) {
const handler = this.gestureHandlers[i];
if (this.isFinished(handler.getState()) && !handler.isAwaiting()) {
this.cleanHandler(handler);
handlersToRemove.add(handler);
}
}
this.gestureHandlers = this.gestureHandlers.filter(handler => !handlersToRemove.has(handler));
}
hasOtherHandlerToWaitFor(handler) {
const hasToWaitFor = otherHandler => {
return !this.isFinished(otherHandler.getState()) && this.shouldHandlerWaitForOther(handler, otherHandler);
};
return this.gestureHandlers.some(hasToWaitFor);
}
shouldBeCancelledByFinishedHandler(handler) {
const shouldBeCancelled = otherHandler => {
return this.shouldHandlerWaitForOther(handler, otherHandler) && otherHandler.getState() === State.END;
};
return this.gestureHandlers.some(shouldBeCancelled);
}
tryActivate(handler) {
if (this.shouldBeCancelledByFinishedHandler(handler)) {
handler.cancel();
return;
}
if (this.hasOtherHandlerToWaitFor(handler)) {
this.addAwaitingHandler(handler);
return;
}
const handlerState = handler.getState();
if (handlerState === State.CANCELLED || handlerState === State.FAILED) {
return;
}
if (this.shouldActivate(handler)) {
this.makeActive(handler);
return;
}
if (handlerState === State.ACTIVE) {
handler.fail();
return;
}
if (handlerState === State.BEGAN) {
handler.cancel();
}
}
shouldActivate(handler) {
const shouldBeCancelledBy = otherHandler => {
return this.shouldHandlerBeCancelledBy(handler, otherHandler);
};
return !this.gestureHandlers.some(shouldBeCancelledBy);
}
cleanupAwaitingHandlers(handler) {
const shouldWait = otherHandler => {
return !otherHandler.isAwaiting() && this.shouldHandlerWaitForOther(otherHandler, handler);
};
for (const otherHandler of this.awaitingHandlers) {
if (shouldWait(otherHandler)) {
this.cleanHandler(otherHandler);
this.awaitingHandlersTags.delete(otherHandler.getTag());
}
}
this.awaitingHandlers = this.awaitingHandlers.filter(otherHandler => this.awaitingHandlersTags.has(otherHandler.getTag()));
}
onHandlerStateChange(handler, newState, oldState, sendIfDisabled) {
if (!handler.isEnabled() && !sendIfDisabled) {
return;
}
this.handlingChangeSemaphore += 1;
if (this.isFinished(newState)) {
for (const otherHandler of this.awaitingHandlers) {
if (!this.shouldHandlerWaitForOther(otherHandler, handler) || !this.awaitingHandlersTags.has(otherHandler.getTag())) {
continue;
}
if (newState !== State.END) {
this.tryActivate(otherHandler);
continue;
}
otherHandler.cancel();
if (otherHandler.getState() === State.END) {
// Handle edge case, where discrete gestures end immediately after activation thus
// their state is set to END and when the gesture they are waiting for activates they
// should be cancelled, however `cancel` was never sent as gestures were already in the END state.
// Send synthetic BEGAN -> CANCELLED to properly handle JS logic
otherHandler.sendEvent(State.CANCELLED, State.BEGAN);
}
otherHandler.setAwaiting(false);
}
}
if (newState === State.ACTIVE) {
this.tryActivate(handler);
} else if (oldState === State.ACTIVE || oldState === State.END) {
if (handler.isActive()) {
handler.sendEvent(newState, oldState);
} else if (oldState === State.ACTIVE && (newState === State.CANCELLED || newState === State.FAILED)) {
handler.sendEvent(newState, State.BEGAN);
}
} else if (oldState !== State.UNDETERMINED || newState !== State.CANCELLED) {
handler.sendEvent(newState, oldState);
}
this.handlingChangeSemaphore -= 1;
this.scheduleFinishedHandlersCleanup();
if (!this.awaitingHandlers.includes(handler)) {
this.cleanupAwaitingHandlers(handler);
}
}
makeActive(handler) {
const currentState = handler.getState();
handler.setActive(true);
handler.setShouldResetProgress(true);
handler.setActivationIndex(this.activationIndex++);
for (let i = this.gestureHandlers.length - 1; i >= 0; --i) {
if (this.shouldHandlerBeCancelledBy(this.gestureHandlers[i], handler)) {
this.gestureHandlers[i].cancel();
}
}
for (const otherHandler of this.awaitingHandlers) {
if (this.shouldHandlerBeCancelledBy(otherHandler, handler)) {
otherHandler.setAwaiting(false);
}
}
handler.sendEvent(State.ACTIVE, State.BEGAN);
if (currentState !== State.ACTIVE) {
handler.sendEvent(State.END, State.ACTIVE);
if (currentState !== State.END) {
handler.sendEvent(State.UNDETERMINED, State.END);
}
}
if (!handler.isAwaiting()) {
return;
}
handler.setAwaiting(false);
this.awaitingHandlers = this.awaitingHandlers.filter(otherHandler => otherHandler !== handler);
}
addAwaitingHandler(handler) {
if (this.awaitingHandlers.includes(handler)) {
return;
}
this.awaitingHandlers.push(handler);
this.awaitingHandlersTags.add(handler.getTag());
handler.setAwaiting(true);
handler.setActivationIndex(this.activationIndex++);
}
recordHandlerIfNotPresent(handler) {
if (this.gestureHandlers.includes(handler)) {
return;
}
this.gestureHandlers.push(handler);
handler.setActive(false);
handler.setAwaiting(false);
handler.setActivationIndex(Number.MAX_SAFE_INTEGER);
}
shouldHandlerWaitForOther(handler, otherHandler) {
return handler !== otherHandler && (handler.shouldWaitForHandlerFailure(otherHandler) || otherHandler.shouldRequireToWaitForFailure(handler));
}
canRunSimultaneously(gh1, gh2) {
return gh1 === gh2 || gh1.shouldRecognizeSimultaneously(gh2) || gh2.shouldRecognizeSimultaneously(gh1);
}
shouldHandlerBeCancelledBy(handler, otherHandler) {
if (this.canRunSimultaneously(handler, otherHandler)) {
return false;
}
if (handler.isAwaiting() || handler.getState() === State.ACTIVE) {
// For now it always returns false
return handler.shouldBeCancelledByOther(otherHandler);
}
const handlerPointers = handler.getTrackedPointersID();
const otherPointers = otherHandler.getTrackedPointersID();
if (!PointerTracker.shareCommonPointers(handlerPointers, otherPointers) && handler.getDelegate().getView() !== otherHandler.getDelegate().getView()) {
return this.checkOverlap(handler, otherHandler);
}
return true;
}
checkOverlap(handler, otherHandler) {
// If handlers don't have common pointers, default return value is false.
// However, if at least on pointer overlaps with both handlers, we return true
// This solves issue in overlapping parents example
// TODO: Find better way to handle that issue, for example by activation order and handler cancelling
const isPointerWithinBothBounds = pointer => {
const handlerX = handler.getTracker().getLastX(pointer);
const handlerY = handler.getTracker().getLastY(pointer);
const point = {
x: handlerX,
y: handlerY
};
return handler.getDelegate().isPointerInBounds(point) && otherHandler.getDelegate().isPointerInBounds(point);
};
const handlerPointers = handler.getTrackedPointersID();
const otherPointers = otherHandler.getTrackedPointersID();
return handlerPointers.some(isPointerWithinBothBounds) || otherPointers.some(isPointerWithinBothBounds);
}
isFinished(state) {
return state === State.END || state === State.FAILED || state === State.CANCELLED;
} // This function is called when handler receives touchdown event
// If handler is using mouse or pen as a pointer and any handler receives touch event,
// mouse/pen event dissappears - it doesn't send onPointerCancel nor onPointerUp (and others)
// This became a problem because handler was left at active state without any signal to end or fail
// To handle this, when new touch event is received, we loop through active handlers and check which type of
// pointer they're using. If there are any handler with mouse/pen as a pointer, we cancel them
cancelMouseAndPenGestures(currentHandler) {
this.gestureHandlers.forEach(handler => {
if (handler.getPointerType() !== PointerType.MOUSE && handler.getPointerType() !== PointerType.STYLUS) {
return;
}
if (handler !== currentHandler) {
handler.cancel();
} else {
// Handler that received touch event should have its pointer tracker reset
// This allows handler to smoothly change from mouse/pen to touch
// The drawback is, that when we try to use mouse/pen one more time, it doesn't send onPointerDown at the first time
// so it is required to click two times to get handler to work
//
// However, handler will receive manually created onPointerEnter that is triggered in EventManager in onPointerMove method.
// There may be possibility to use that fact to make handler respond properly to first mouse click
handler.getTracker().resetTracker();
}
});
}
static getInstance() {
if (!GestureHandlerOrchestrator.instance) {
GestureHandlerOrchestrator.instance = new GestureHandlerOrchestrator();
}
return GestureHandlerOrchestrator.instance;
}
}
_defineProperty(GestureHandlerOrchestrator, "instance", void 0);
//# sourceMappingURL=GestureHandlerOrchestrator.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,141 @@
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
import { findNodeHandle } from 'react-native';
import PointerEventManager from './PointerEventManager';
import TouchEventManager from './TouchEventManager';
import { State } from '../../State';
import { isPointerInBounds } from '../utils';
import { MouseButton } from '../../handlers/gestureHandlerCommon';
export class GestureHandlerWebDelegate {
constructor() {
_defineProperty(this, "view", void 0);
_defineProperty(this, "gestureHandler", void 0);
_defineProperty(this, "eventManagers", []);
}
getView() {
return this.view;
}
init(viewRef, handler) {
var _config$touchAction;
if (!viewRef) {
throw new Error(`Cannot find HTML Element for handler ${handler.getTag()}`);
}
this.gestureHandler = handler;
this.view = findNodeHandle(viewRef);
const config = handler.getConfig();
this.addContextMenuListeners(config);
if (!config.userSelect) {
this.view.style['webkitUserSelect'] = 'none';
this.view.style['userSelect'] = 'none';
} else {
this.view.style['webkitUserSelect'] = config.userSelect;
this.view.style['userSelect'] = config.userSelect;
}
this.view.style['touchAction'] = (_config$touchAction = config.touchAction) !== null && _config$touchAction !== void 0 ? _config$touchAction : 'none'; //@ts-ignore This one disables default events on Safari
this.view.style['WebkitTouchCallout'] = 'none';
this.eventManagers.push(new PointerEventManager(this.view));
this.eventManagers.push(new TouchEventManager(this.view));
this.eventManagers.forEach(manager => this.gestureHandler.attachEventManager(manager));
}
isPointerInBounds({
x,
y
}) {
return isPointerInBounds(this.view, {
x,
y
});
}
measureView() {
const rect = this.view.getBoundingClientRect();
return {
pageX: rect.left,
pageY: rect.top,
width: rect.width,
height: rect.height
};
}
reset() {
this.eventManagers.forEach(manager => manager.resetManager());
}
tryResetCursor() {
const config = this.gestureHandler.getConfig();
if (config.activeCursor && config.activeCursor !== 'auto' && this.gestureHandler.getState() === State.ACTIVE) {
this.view.style.cursor = 'auto';
}
}
shouldDisableContextMenu(config) {
return config.enableContextMenu === undefined && this.gestureHandler.isButtonInConfig(MouseButton.RIGHT) || config.enableContextMenu === false;
}
addContextMenuListeners(config) {
if (this.shouldDisableContextMenu(config)) {
this.view.addEventListener('contextmenu', this.disableContextMenu);
} else if (config.enableContextMenu) {
this.view.addEventListener('contextmenu', this.enableContextMenu);
}
}
removeContextMenuListeners(config) {
if (this.shouldDisableContextMenu(config)) {
this.view.removeEventListener('contextmenu', this.disableContextMenu);
} else if (config.enableContextMenu) {
this.view.removeEventListener('contextmenu', this.enableContextMenu);
}
}
disableContextMenu(e) {
e.preventDefault();
}
enableContextMenu(e) {
e.stopPropagation();
}
onBegin() {// no-op for now
}
onActivate() {
const config = this.gestureHandler.getConfig();
if ((!this.view.style.cursor || this.view.style.cursor === 'auto') && config.activeCursor) {
this.view.style.cursor = config.activeCursor;
}
}
onEnd() {
this.tryResetCursor();
}
onCancel() {
this.tryResetCursor();
}
onFail() {
this.tryResetCursor();
}
destroy(config) {
this.removeContextMenuListeners(config);
this.eventManagers.forEach(manager => {
manager.unregisterListeners();
});
}
}
//# sourceMappingURL=GestureHandlerWebDelegate.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,111 @@
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
import { State } from '../../State';
export default class InteractionManager {
// Private becaues of singleton
// eslint-disable-next-line no-useless-constructor, @typescript-eslint/no-empty-function
constructor() {
_defineProperty(this, "waitForRelations", new Map());
_defineProperty(this, "simultaneousRelations", new Map());
_defineProperty(this, "blocksHandlersRelations", new Map());
}
configureInteractions(handler, config) {
this.dropRelationsForHandlerWithTag(handler.getTag());
if (config.waitFor) {
const waitFor = [];
config.waitFor.forEach(otherHandler => {
// New API reference
if (typeof otherHandler === 'number') {
waitFor.push(otherHandler);
} else {
// Old API reference
waitFor.push(otherHandler.handlerTag);
}
});
this.waitForRelations.set(handler.getTag(), waitFor);
}
if (config.simultaneousHandlers) {
const simultaneousHandlers = [];
config.simultaneousHandlers.forEach(otherHandler => {
if (typeof otherHandler === 'number') {
simultaneousHandlers.push(otherHandler);
} else {
simultaneousHandlers.push(otherHandler.handlerTag);
}
});
this.simultaneousRelations.set(handler.getTag(), simultaneousHandlers);
}
if (config.blocksHandlers) {
const blocksHandlers = [];
config.blocksHandlers.forEach(otherHandler => {
if (typeof otherHandler === 'number') {
blocksHandlers.push(otherHandler);
} else {
blocksHandlers.push(otherHandler.handlerTag);
}
});
this.blocksHandlersRelations.set(handler.getTag(), blocksHandlers);
}
}
shouldWaitForHandlerFailure(handler, otherHandler) {
const waitFor = this.waitForRelations.get(handler.getTag());
return (waitFor === null || waitFor === void 0 ? void 0 : waitFor.find(tag => {
return tag === otherHandler.getTag();
})) !== undefined;
}
shouldRecognizeSimultaneously(handler, otherHandler) {
const simultaneousHandlers = this.simultaneousRelations.get(handler.getTag());
return (simultaneousHandlers === null || simultaneousHandlers === void 0 ? void 0 : simultaneousHandlers.find(tag => {
return tag === otherHandler.getTag();
})) !== undefined;
}
shouldRequireHandlerToWaitForFailure(handler, otherHandler) {
const waitFor = this.blocksHandlersRelations.get(handler.getTag());
return (waitFor === null || waitFor === void 0 ? void 0 : waitFor.find(tag => {
return tag === otherHandler.getTag();
})) !== undefined;
}
shouldHandlerBeCancelledBy(_handler, otherHandler) {
var _otherHandler$isButto;
// We check constructor name instead of using `instanceof` in order do avoid circular dependencies
const isNativeHandler = otherHandler.constructor.name === 'NativeViewGestureHandler';
const isActive = otherHandler.getState() === State.ACTIVE;
const isButton = ((_otherHandler$isButto = otherHandler.isButton) === null || _otherHandler$isButto === void 0 ? void 0 : _otherHandler$isButto.call(otherHandler)) === true;
return isNativeHandler && isActive && !isButton;
}
dropRelationsForHandlerWithTag(handlerTag) {
this.waitForRelations.delete(handlerTag);
this.simultaneousRelations.delete(handlerTag);
this.blocksHandlersRelations.delete(handlerTag);
}
reset() {
this.waitForRelations.clear();
this.simultaneousRelations.clear();
this.blocksHandlersRelations.clear();
}
static getInstance() {
if (!this.instance) {
this.instance = new InteractionManager();
}
return this.instance;
}
}
_defineProperty(InteractionManager, "instance", void 0);
//# sourceMappingURL=InteractionManager.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,195 @@
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
// Implementation taken from Flutter's LeastSquareSolver
// https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/gestures/lsq_solver.dart
class Vector {
constructor(length) {
_defineProperty(this, "offset", void 0);
_defineProperty(this, "length", void 0);
_defineProperty(this, "elements", void 0);
this.offset = 0;
this.length = length;
this.elements = new Array(length);
}
static fromVOL(values, offset, length) {
const result = new Vector(0);
result.offset = offset;
result.length = length;
result.elements = values;
return result;
}
get(index) {
return this.elements[this.offset + index];
}
set(index, value) {
this.elements[this.offset + index] = value;
}
dot(other) {
let result = 0;
for (let i = 0; i < this.length; i++) {
result += this.get(i) * other.get(i);
}
return result;
}
norm() {
return Math.sqrt(this.dot(this));
}
}
class Matrix {
constructor(rows, columns) {
_defineProperty(this, "columns", void 0);
_defineProperty(this, "elements", void 0);
this.columns = columns;
this.elements = new Array(rows * columns);
}
get(row, column) {
return this.elements[row * this.columns + column];
}
set(row, column, value) {
this.elements[row * this.columns + column] = value;
}
getRow(row) {
return Vector.fromVOL(this.elements, row * this.columns, this.columns);
}
} /// An nth degree polynomial fit to a dataset.
class PolynomialFit {
/// The polynomial coefficients of the fit.
///
/// For each `i`, the element `coefficients[i]` is the coefficient of
/// the `i`-th power of the variable.
/// Creates a polynomial fit of the given degree.
///
/// There are n + 1 coefficients in a fit of degree n.
constructor(degree) {
_defineProperty(this, "coefficients", void 0);
this.coefficients = new Array(degree + 1);
}
}
const precisionErrorTolerance = 1e-10; /// Uses the least-squares algorithm to fit a polynomial to a set of data.
export default class LeastSquareSolver {
/// The x-coordinates of each data point.
/// The y-coordinates of each data point.
/// The weight to use for each data point.
/// Creates a least-squares solver.
///
/// The [x], [y], and [w] arguments must not be null.
constructor(x, y, w) {
_defineProperty(this, "x", void 0);
_defineProperty(this, "y", void 0);
_defineProperty(this, "w", void 0);
this.x = x;
this.y = y;
this.w = w;
} /// Fits a polynomial of the given degree to the data points.
///
/// When there is not enough data to fit a curve null is returned.
solve(degree) {
if (degree > this.x.length) {
// Not enough data to fit a curve.
return null;
}
const result = new PolynomialFit(degree); // Shorthands for the purpose of notation equivalence to original C++ code.
const m = this.x.length;
const n = degree + 1; // Expand the X vector to a matrix A, pre-multiplied by the weights.
const a = new Matrix(n, m);
for (let h = 0; h < m; h++) {
a.set(0, h, this.w[h]);
for (let i = 1; i < n; i++) {
a.set(i, h, a.get(i - 1, h) * this.x[h]);
}
} // Apply the Gram-Schmidt process to A to obtain its QR decomposition.
// Orthonormal basis, column-major ordVectorer.
const q = new Matrix(n, m); // Upper triangular matrix, row-major order.
const r = new Matrix(n, m);
for (let j = 0; j < n; j += 1) {
for (let h = 0; h < m; h += 1) {
q.set(j, h, a.get(j, h));
}
for (let i = 0; i < j; i += 1) {
const dot = q.getRow(j).dot(q.getRow(i));
for (let h = 0; h < m; h += 1) {
q.set(j, h, q.get(j, h) - dot * q.get(i, h));
}
}
const norm = q.getRow(j).norm();
if (norm < precisionErrorTolerance) {
// Vectors are linearly dependent or zero so no solution.
return null;
}
const inverseNorm = 1.0 / norm;
for (let h = 0; h < m; h += 1) {
q.set(j, h, q.get(j, h) * inverseNorm);
}
for (let i = 0; i < n; i += 1) {
r.set(j, i, i < j ? 0.0 : q.getRow(j).dot(a.getRow(i)));
}
} // Solve R B = Qt W Y to find B. This is easy because R is upper triangular.
// We just work from bottom-right to top-left calculating B's coefficients.
const wy = new Vector(m);
for (let h = 0; h < m; h += 1) {
wy.set(h, this.y[h] * this.w[h]);
}
for (let i = n - 1; i >= 0; i -= 1) {
result.coefficients[i] = q.getRow(i).dot(wy);
for (let j = n - 1; j > i; j -= 1) {
result.coefficients[i] -= r.get(i, j) * result.coefficients[j];
}
result.coefficients[i] /= r.get(i, i);
}
return result;
}
}
//# sourceMappingURL=LeastSquareSolver.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,40 @@
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export default class NodeManager {
static getHandler(tag) {
if (tag in this.gestures) {
return this.gestures[tag];
}
throw new Error(`No handler for tag ${tag}`);
}
static createGestureHandler(handlerTag, handler) {
if (handlerTag in this.gestures) {
throw new Error(`Handler with tag ${handlerTag} already exists. Please ensure that no Gesture instance is used across multiple GestureDetectors.`);
}
this.gestures[handlerTag] = handler;
this.gestures[handlerTag].setTag(handlerTag);
}
static dropGestureHandler(handlerTag) {
if (!(handlerTag in this.gestures)) {
return;
}
this.gestures[handlerTag].onDestroy(); // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete this.gestures[handlerTag];
}
static getNodes() {
return { ...this.gestures
};
}
}
_defineProperty(NodeManager, "gestures", {});
//# sourceMappingURL=NodeManager.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["NodeManager.ts"],"names":["NodeManager","getHandler","tag","gestures","Error","createGestureHandler","handlerTag","handler","setTag","dropGestureHandler","onDestroy","getNodes"],"mappings":";;AAIA;AACA,eAAe,MAAeA,WAAf,CAA2B;AAMhB,SAAVC,UAAU,CAACC,GAAD,EAA+B;AACrD,QAAIA,GAAG,IAAI,KAAKC,QAAhB,EAA0B;AACxB,aAAO,KAAKA,QAAL,CAAcD,GAAd,CAAP;AACD;;AAED,UAAM,IAAIE,KAAJ,CAAW,sBAAqBF,GAAI,EAApC,CAAN;AACD;;AAEiC,SAApBG,oBAAoB,CAChCC,UADgC,EAEhCC,OAFgC,EAG1B;AACN,QAAID,UAAU,IAAI,KAAKH,QAAvB,EAAiC;AAC/B,YAAM,IAAIC,KAAJ,CACH,oBAAmBE,UAAW,mGAD3B,CAAN;AAGD;;AAED,SAAKH,QAAL,CAAcG,UAAd,IAA4BC,OAA5B;AACA,SAAKJ,QAAL,CAAcG,UAAd,EAA0BE,MAA1B,CAAiCF,UAAjC;AACD;;AAE+B,SAAlBG,kBAAkB,CAACH,UAAD,EAA2B;AACzD,QAAI,EAAEA,UAAU,IAAI,KAAKH,QAArB,CAAJ,EAAoC;AAClC;AACD;;AAED,SAAKA,QAAL,CAAcG,UAAd,EAA0BI,SAA1B,GALyD,CAOzD;;AACA,WAAO,KAAKP,QAAL,CAAcG,UAAd,CAAP;AACD;;AAEqB,SAARK,QAAQ,GAAG;AACvB,WAAO,EAAE,GAAG,KAAKR;AAAV,KAAP;AACD;;AAzCuC;;gBAAZH,W,cAIxB,E","sourcesContent":["import { ValueOf } from '../../typeUtils';\nimport { Gestures } from '../Gestures';\nimport type IGestureHandler from '../handlers/IGestureHandler';\n\n// eslint-disable-next-line @typescript-eslint/no-extraneous-class\nexport default abstract class NodeManager {\n private static gestures: Record<\n number,\n InstanceType<ValueOf<typeof Gestures>>\n > = {};\n\n public static getHandler(tag: number): IGestureHandler {\n if (tag in this.gestures) {\n return this.gestures[tag] as IGestureHandler;\n }\n\n throw new Error(`No handler for tag ${tag}`);\n }\n\n public static createGestureHandler(\n handlerTag: number,\n handler: InstanceType<ValueOf<typeof Gestures>>\n ): void {\n if (handlerTag in this.gestures) {\n throw new Error(\n `Handler with tag ${handlerTag} already exists. Please ensure that no Gesture instance is used across multiple GestureDetectors.`\n );\n }\n\n this.gestures[handlerTag] = handler;\n this.gestures[handlerTag].setTag(handlerTag);\n }\n\n public static dropGestureHandler(handlerTag: number): void {\n if (!(handlerTag in this.gestures)) {\n return;\n }\n\n this.gestures[handlerTag].onDestroy();\n\n // eslint-disable-next-line @typescript-eslint/no-dynamic-delete\n delete this.gestures[handlerTag];\n }\n\n public static getNodes() {\n return { ...this.gestures };\n }\n}\n"]}

View File

@@ -0,0 +1,241 @@
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
import EventManager from './EventManager';
import { MouseButton } from '../../handlers/gestureHandlerCommon';
import { EventTypes } from '../interfaces';
import { PointerTypeMapping, isPointerInBounds } from '../utils';
import { PointerType } from '../../PointerType';
const POINTER_CAPTURE_EXCLUDE_LIST = new Set(['SELECT', 'INPUT']);
const PointerTypes = {
Touch: 'touch',
Stylus: 'pen'
};
export default class PointerEventManager extends EventManager {
constructor(view) {
super(view);
_defineProperty(this, "trackedPointers", new Set());
_defineProperty(this, "mouseButtonsMapper", new Map());
_defineProperty(this, "lastPosition", void 0);
_defineProperty(this, "pointerDownCallback", event => {
if (event.pointerType === PointerTypes.Touch) {
return;
}
if (!isPointerInBounds(this.view, {
x: event.clientX,
y: event.clientY
})) {
return;
}
const adaptedEvent = this.mapEvent(event, EventTypes.DOWN);
const target = event.target;
if (!POINTER_CAPTURE_EXCLUDE_LIST.has(target.tagName)) {
target.setPointerCapture(adaptedEvent.pointerId);
}
this.markAsInBounds(adaptedEvent.pointerId);
this.trackedPointers.add(adaptedEvent.pointerId);
if (++this.activePointersCounter > 1) {
adaptedEvent.eventType = EventTypes.ADDITIONAL_POINTER_DOWN;
this.onPointerAdd(adaptedEvent);
} else {
this.onPointerDown(adaptedEvent);
}
});
_defineProperty(this, "pointerUpCallback", event => {
if (event.pointerType === PointerTypes.Touch) {
return;
} // When we call reset on gesture handlers, it also resets their event managers
// In some handlers (like RotationGestureHandler) reset is called before all pointers leave view
// This means, that activePointersCounter will be set to 0, while there are still remaining pointers on view
// Removing them will end in activePointersCounter going below 0, therefore handlers won't behave properly
if (this.activePointersCounter === 0) {
return;
}
const adaptedEvent = this.mapEvent(event, EventTypes.UP);
const target = event.target;
if (!POINTER_CAPTURE_EXCLUDE_LIST.has(target.tagName)) {
target.releasePointerCapture(adaptedEvent.pointerId);
}
this.markAsOutOfBounds(adaptedEvent.pointerId);
this.trackedPointers.delete(adaptedEvent.pointerId);
if (--this.activePointersCounter > 0) {
adaptedEvent.eventType = EventTypes.ADDITIONAL_POINTER_UP;
this.onPointerRemove(adaptedEvent);
} else {
this.onPointerUp(adaptedEvent);
}
});
_defineProperty(this, "pointerMoveCallback", event => {
if (event.pointerType === PointerTypes.Touch) {
return;
} // Stylus triggers `pointermove` event when it detects changes in pressure. Since it is very sensitive to those changes,
// it constantly sends events, even though there was no change in position. To fix that we check whether
// pointer has actually moved and if not, we do not send event.
if (event.pointerType === PointerTypes.Stylus && event.x === this.lastPosition.x && event.y === this.lastPosition.y) {
return;
}
const adaptedEvent = this.mapEvent(event, EventTypes.MOVE);
const target = event.target; // You may be wondering why are we setting pointer capture here, when we
// already set it in `pointerdown` handler. Well, that's a great question,
// for which I don't have an answer. Specification (https://www.w3.org/TR/pointerevents2/#dom-element-setpointercapture)
// says that the requirement for `setPointerCapture` to work is that pointer
// must be in 'active buttons state`, otherwise it will fail silently, which
// is lovely. Obviously, when `pointerdown` is fired, one of the buttons
// (when using mouse) is pressed, but that doesn't mean that `setPointerCapture`
// will succeed, for some reason. Since it fails silently, we don't actually know
// if it worked or not (there's `gotpointercapture` event, but the complexity of
// incorporating it here seems stupid), so we just call it again here, every time
// pointer moves until it succeeds.
// God, I do love web development.
if (!target.hasPointerCapture(event.pointerId) && !POINTER_CAPTURE_EXCLUDE_LIST.has(target.tagName)) {
target.setPointerCapture(event.pointerId);
}
const inBounds = isPointerInBounds(this.view, {
x: adaptedEvent.x,
y: adaptedEvent.y
});
const pointerIndex = this.pointersInBounds.indexOf(adaptedEvent.pointerId);
if (inBounds) {
if (pointerIndex < 0) {
adaptedEvent.eventType = EventTypes.ENTER;
this.onPointerEnter(adaptedEvent);
this.markAsInBounds(adaptedEvent.pointerId);
} else {
this.onPointerMove(adaptedEvent);
}
} else {
if (pointerIndex >= 0) {
adaptedEvent.eventType = EventTypes.LEAVE;
this.onPointerLeave(adaptedEvent);
this.markAsOutOfBounds(adaptedEvent.pointerId);
} else {
this.onPointerOutOfBounds(adaptedEvent);
}
}
this.lastPosition.x = event.x;
this.lastPosition.y = event.y;
});
_defineProperty(this, "pointerCancelCallback", event => {
if (event.pointerType === PointerTypes.Touch) {
return;
}
const adaptedEvent = this.mapEvent(event, EventTypes.CANCEL);
this.onPointerCancel(adaptedEvent);
this.markAsOutOfBounds(adaptedEvent.pointerId);
this.activePointersCounter = 0;
this.trackedPointers.clear();
});
_defineProperty(this, "pointerEnterCallback", event => {
if (event.pointerType === PointerTypes.Touch) {
return;
}
const adaptedEvent = this.mapEvent(event, EventTypes.ENTER);
this.onPointerMoveOver(adaptedEvent);
});
_defineProperty(this, "pointerLeaveCallback", event => {
if (event.pointerType === PointerTypes.Touch) {
return;
}
const adaptedEvent = this.mapEvent(event, EventTypes.LEAVE);
this.onPointerMoveOut(adaptedEvent);
});
_defineProperty(this, "lostPointerCaptureCallback", event => {
const adaptedEvent = this.mapEvent(event, EventTypes.CANCEL);
if (this.trackedPointers.has(adaptedEvent.pointerId)) {
// in some cases the `pointerup` event is not fired, but `lostpointercapture` is
// we simulate the `pointercancel` event here to make sure the gesture handler stops tracking it
this.onPointerCancel(adaptedEvent);
this.activePointersCounter = 0;
this.trackedPointers.clear();
}
});
this.mouseButtonsMapper.set(0, MouseButton.LEFT);
this.mouseButtonsMapper.set(1, MouseButton.MIDDLE);
this.mouseButtonsMapper.set(2, MouseButton.RIGHT);
this.mouseButtonsMapper.set(3, MouseButton.BUTTON_4);
this.mouseButtonsMapper.set(4, MouseButton.BUTTON_5);
this.lastPosition = {
x: -Infinity,
y: -Infinity
};
}
registerListeners() {
this.view.addEventListener('pointerdown', this.pointerDownCallback);
this.view.addEventListener('pointerup', this.pointerUpCallback);
this.view.addEventListener('pointermove', this.pointerMoveCallback);
this.view.addEventListener('pointercancel', this.pointerCancelCallback); // onPointerEnter and onPointerLeave are triggered by a custom logic responsible for
// handling shouldCancelWhenOutside flag, and are unreliable unless the pointer is down.
// We therefore use pointerenter and pointerleave events to handle the hover gesture,
// mapping them to onPointerMoveOver and onPointerMoveOut respectively.
this.view.addEventListener('pointerenter', this.pointerEnterCallback);
this.view.addEventListener('pointerleave', this.pointerLeaveCallback);
this.view.addEventListener('lostpointercapture', this.lostPointerCaptureCallback);
}
unregisterListeners() {
this.view.removeEventListener('pointerdown', this.pointerDownCallback);
this.view.removeEventListener('pointerup', this.pointerUpCallback);
this.view.removeEventListener('pointermove', this.pointerMoveCallback);
this.view.removeEventListener('pointercancel', this.pointerCancelCallback);
this.view.removeEventListener('pointerenter', this.pointerEnterCallback);
this.view.removeEventListener('pointerleave', this.pointerLeaveCallback);
this.view.removeEventListener('lostpointercapture', this.lostPointerCaptureCallback);
}
mapEvent(event, eventType) {
var _PointerTypeMapping$g;
return {
x: event.clientX,
y: event.clientY,
offsetX: event.offsetX,
offsetY: event.offsetY,
pointerId: event.pointerId,
eventType: eventType,
pointerType: (_PointerTypeMapping$g = PointerTypeMapping.get(event.pointerType)) !== null && _PointerTypeMapping$g !== void 0 ? _PointerTypeMapping$g : PointerType.OTHER,
button: this.mouseButtonsMapper.get(event.button),
time: event.timeStamp
};
}
resetManager() {
super.resetManager();
this.trackedPointers.clear();
}
}
//# sourceMappingURL=PointerEventManager.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,213 @@
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
import VelocityTracker from './VelocityTracker';
const MAX_POINTERS = 20;
export default class PointerTracker {
constructor() {
_defineProperty(this, "velocityTracker", new VelocityTracker());
_defineProperty(this, "trackedPointers", new Map());
_defineProperty(this, "touchEventsIds", new Map());
_defineProperty(this, "lastMovedPointerId", void 0);
_defineProperty(this, "cachedAverages", {
x: 0,
y: 0
});
this.lastMovedPointerId = NaN;
for (let i = 0; i < MAX_POINTERS; ++i) {
this.touchEventsIds.set(i, NaN);
}
}
addToTracker(event) {
if (this.trackedPointers.has(event.pointerId)) {
return;
}
this.lastMovedPointerId = event.pointerId;
const newElement = {
lastX: event.x,
lastY: event.y,
timeStamp: event.time,
velocityX: 0,
velocityY: 0
};
this.trackedPointers.set(event.pointerId, newElement);
this.mapTouchEventId(event.pointerId);
this.cachedAverages = {
x: this.getLastAvgX(),
y: this.getLastAvgY()
};
}
removeFromTracker(pointerId) {
this.trackedPointers.delete(pointerId);
this.removeMappedTouchId(pointerId);
}
track(event) {
const element = this.trackedPointers.get(event.pointerId);
if (!element) {
return;
}
this.lastMovedPointerId = event.pointerId;
this.velocityTracker.add(event);
const [velocityX, velocityY] = this.velocityTracker.getVelocity();
element.velocityX = velocityX;
element.velocityY = velocityY;
element.lastX = event.x;
element.lastY = event.y;
this.trackedPointers.set(event.pointerId, element);
const avgX = this.getLastAvgX();
const avgY = this.getLastAvgY();
this.cachedAverages = {
x: avgX,
y: avgY
};
} //Mapping TouchEvents ID
mapTouchEventId(id) {
for (const [mappedId, touchId] of this.touchEventsIds) {
if (isNaN(touchId)) {
this.touchEventsIds.set(mappedId, id);
break;
}
}
}
removeMappedTouchId(id) {
const mappedId = this.getMappedTouchEventId(id);
if (!isNaN(mappedId)) {
this.touchEventsIds.set(mappedId, NaN);
}
}
getMappedTouchEventId(touchEventId) {
for (const [key, value] of this.touchEventsIds.entries()) {
if (value === touchEventId) {
return key;
}
}
return NaN;
}
getVelocityX(pointerId) {
var _this$trackedPointers;
return (_this$trackedPointers = this.trackedPointers.get(pointerId)) === null || _this$trackedPointers === void 0 ? void 0 : _this$trackedPointers.velocityX;
}
getVelocityY(pointerId) {
var _this$trackedPointers2;
return (_this$trackedPointers2 = this.trackedPointers.get(pointerId)) === null || _this$trackedPointers2 === void 0 ? void 0 : _this$trackedPointers2.velocityY;
}
/**
* Returns X coordinate of last moved pointer
*/
getLastX(pointerId) {
if (pointerId !== undefined) {
var _this$trackedPointers3;
return (_this$trackedPointers3 = this.trackedPointers.get(pointerId)) === null || _this$trackedPointers3 === void 0 ? void 0 : _this$trackedPointers3.lastX;
} else {
var _this$trackedPointers4;
return (_this$trackedPointers4 = this.trackedPointers.get(this.lastMovedPointerId)) === null || _this$trackedPointers4 === void 0 ? void 0 : _this$trackedPointers4.lastX;
}
}
/**
* Returns Y coordinate of last moved pointer
*/
getLastY(pointerId) {
if (pointerId !== undefined) {
var _this$trackedPointers5;
return (_this$trackedPointers5 = this.trackedPointers.get(pointerId)) === null || _this$trackedPointers5 === void 0 ? void 0 : _this$trackedPointers5.lastY;
} else {
var _this$trackedPointers6;
return (_this$trackedPointers6 = this.trackedPointers.get(this.lastMovedPointerId)) === null || _this$trackedPointers6 === void 0 ? void 0 : _this$trackedPointers6.lastY;
}
} // Some handlers use these methods to send average values in native event.
// This may happen when pointers have already been removed from tracker (i.e. pointerup event).
// In situation when NaN would be sent as a response, we return cached value.
// That prevents handlers from crashing
getLastAvgX() {
const avgX = this.getSumX() / this.trackedPointers.size;
return isNaN(avgX) ? this.cachedAverages.x : avgX;
}
getLastAvgY() {
const avgY = this.getSumY() / this.trackedPointers.size;
return isNaN(avgY) ? this.cachedAverages.y : avgY;
}
getSumX(ignoredPointer) {
let sumX = 0;
this.trackedPointers.forEach((value, key) => {
if (key !== ignoredPointer) {
sumX += value.lastX;
}
});
return sumX;
}
getSumY(ignoredPointer) {
let sumY = 0;
this.trackedPointers.forEach((value, key) => {
if (key !== ignoredPointer) {
sumY += value.lastY;
}
});
return sumY;
}
getTrackedPointersCount() {
return this.trackedPointers.size;
}
getTrackedPointersID() {
const keys = [];
this.trackedPointers.forEach((_value, key) => {
keys.push(key);
});
return keys;
}
getData() {
return this.trackedPointers;
}
resetTracker() {
this.velocityTracker.reset();
this.trackedPointers.clear();
this.lastMovedPointerId = NaN;
for (let i = 0; i < MAX_POINTERS; ++i) {
this.touchEventsIds.set(i, NaN);
}
}
static shareCommonPointers(stPointers, ndPointers) {
return stPointers.some(pointerId => ndPointers.includes(pointerId));
}
}
//# sourceMappingURL=PointerTracker.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,145 @@
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
import { EventTypes, TouchEventType } from '../interfaces';
import EventManager from './EventManager';
import { isPointerInBounds } from '../utils';
import { PointerType } from '../../PointerType';
export default class TouchEventManager extends EventManager {
constructor(...args) {
super(...args);
_defineProperty(this, "touchStartCallback", event => {
for (let i = 0; i < event.changedTouches.length; ++i) {
const adaptedEvent = this.mapEvent(event, EventTypes.DOWN, i, TouchEventType.DOWN); // Here we skip stylus, because in case of anything different than touch we want to handle it by using PointerEvents
// If we leave stylus to send touch events, handlers will receive every action twice
if (!isPointerInBounds(this.view, {
x: adaptedEvent.x,
y: adaptedEvent.y
}) || //@ts-ignore touchType field does exist
event.changedTouches[i].touchType === 'stylus') {
continue;
}
this.markAsInBounds(adaptedEvent.pointerId);
if (++this.activePointersCounter > 1) {
adaptedEvent.eventType = EventTypes.ADDITIONAL_POINTER_DOWN;
this.onPointerAdd(adaptedEvent);
} else {
this.onPointerDown(adaptedEvent);
}
}
});
_defineProperty(this, "touchMoveCallback", event => {
for (let i = 0; i < event.changedTouches.length; ++i) {
const adaptedEvent = this.mapEvent(event, EventTypes.MOVE, i, TouchEventType.MOVE); //@ts-ignore touchType field does exist
if (event.changedTouches[i].touchType === 'stylus') {
continue;
}
const inBounds = isPointerInBounds(this.view, {
x: adaptedEvent.x,
y: adaptedEvent.y
});
const pointerIndex = this.pointersInBounds.indexOf(adaptedEvent.pointerId);
if (inBounds) {
if (pointerIndex < 0) {
adaptedEvent.eventType = EventTypes.ENTER;
this.onPointerEnter(adaptedEvent);
this.markAsInBounds(adaptedEvent.pointerId);
} else {
this.onPointerMove(adaptedEvent);
}
} else {
if (pointerIndex >= 0) {
adaptedEvent.eventType = EventTypes.LEAVE;
this.onPointerLeave(adaptedEvent);
this.markAsOutOfBounds(adaptedEvent.pointerId);
} else {
this.onPointerOutOfBounds(adaptedEvent);
}
}
}
});
_defineProperty(this, "touchEndCallback", event => {
for (let i = 0; i < event.changedTouches.length; ++i) {
// When we call reset on gesture handlers, it also resets their event managers
// In some handlers (like RotationGestureHandler) reset is called before all pointers leave view
// This means, that activePointersCounter will be set to 0, while there are still remaining pointers on view
// Removing them will end in activePointersCounter going below 0, therefore handlers won't behave properly
if (this.activePointersCounter === 0) {
break;
} //@ts-ignore touchType field does exist
if (event.changedTouches[i].touchType === 'stylus') {
continue;
}
const adaptedEvent = this.mapEvent(event, EventTypes.UP, i, TouchEventType.UP);
this.markAsOutOfBounds(adaptedEvent.pointerId);
if (--this.activePointersCounter > 0) {
adaptedEvent.eventType = EventTypes.ADDITIONAL_POINTER_UP;
this.onPointerRemove(adaptedEvent);
} else {
this.onPointerUp(adaptedEvent);
}
}
});
_defineProperty(this, "touchCancelCallback", event => {
for (let i = 0; i < event.changedTouches.length; ++i) {
const adaptedEvent = this.mapEvent(event, EventTypes.CANCEL, i, TouchEventType.CANCELLED); //@ts-ignore touchType field does exist
if (event.changedTouches[i].touchType === 'stylus') {
continue;
}
this.onPointerCancel(adaptedEvent);
this.markAsOutOfBounds(adaptedEvent.pointerId);
this.activePointersCounter = 0;
}
});
}
registerListeners() {
this.view.addEventListener('touchstart', this.touchStartCallback);
this.view.addEventListener('touchmove', this.touchMoveCallback);
this.view.addEventListener('touchend', this.touchEndCallback);
this.view.addEventListener('touchcancel', this.touchCancelCallback);
}
unregisterListeners() {
this.view.removeEventListener('touchstart', this.touchStartCallback);
this.view.removeEventListener('touchmove', this.touchMoveCallback);
this.view.removeEventListener('touchend', this.touchEndCallback);
this.view.removeEventListener('touchcancel', this.touchCancelCallback);
}
mapEvent(event, eventType, index, touchEventType) {
const rect = this.view.getBoundingClientRect();
const clientX = event.changedTouches[index].clientX;
const clientY = event.changedTouches[index].clientY;
return {
x: clientX,
y: clientY,
offsetX: clientX - rect.left,
offsetY: clientY - rect.top,
pointerId: event.changedTouches[index].identifier,
eventType: eventType,
pointerType: PointerType.TOUCH,
time: event.timeStamp,
allTouches: event.touches,
changedTouches: event.changedTouches,
touchEventType: touchEventType
};
}
}
//# sourceMappingURL=TouchEventManager.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,47 @@
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
import { DiagonalDirections, Directions } from '../../Directions';
import { MINIMAL_FLING_VELOCITY } from '../constants';
export default class Vector {
constructor(x, y) {
_defineProperty(this, "x", void 0);
_defineProperty(this, "y", void 0);
_defineProperty(this, "unitX", void 0);
_defineProperty(this, "unitY", void 0);
_defineProperty(this, "_magnitude", void 0);
this.x = x;
this.y = y;
this._magnitude = Math.hypot(this.x, this.y);
const isMagnitudeSufficient = this._magnitude > MINIMAL_FLING_VELOCITY;
this.unitX = isMagnitudeSufficient ? this.x / this._magnitude : 0;
this.unitY = isMagnitudeSufficient ? this.y / this._magnitude : 0;
}
static fromDirection(direction) {
return DirectionToVectorMappings.get(direction);
}
static fromVelocity(tracker, pointerId) {
return new Vector(tracker.getVelocityX(pointerId), tracker.getVelocityY(pointerId));
}
get magnitude() {
return this._magnitude;
}
computeSimilarity(vector) {
return this.unitX * vector.unitX + this.unitY * vector.unitY;
}
isSimilar(vector, threshold) {
return this.computeSimilarity(vector) > threshold;
}
}
const DirectionToVectorMappings = new Map([[Directions.LEFT, new Vector(-1, 0)], [Directions.RIGHT, new Vector(1, 0)], [Directions.UP, new Vector(0, -1)], [Directions.DOWN, new Vector(0, 1)], [DiagonalDirections.UP_RIGHT, new Vector(1, -1)], [DiagonalDirections.DOWN_RIGHT, new Vector(1, 1)], [DiagonalDirections.UP_LEFT, new Vector(-1, -1)], [DiagonalDirections.DOWN_LEFT, new Vector(-1, 1)]]);
//# sourceMappingURL=Vector.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["Vector.ts"],"names":["DiagonalDirections","Directions","MINIMAL_FLING_VELOCITY","Vector","constructor","x","y","_magnitude","Math","hypot","isMagnitudeSufficient","unitX","unitY","fromDirection","direction","DirectionToVectorMappings","get","fromVelocity","tracker","pointerId","getVelocityX","getVelocityY","magnitude","computeSimilarity","vector","isSimilar","threshold","Map","LEFT","RIGHT","UP","DOWN","UP_RIGHT","DOWN_RIGHT","UP_LEFT","DOWN_LEFT"],"mappings":";;AAAA,SAASA,kBAAT,EAA6BC,UAA7B,QAA+C,kBAA/C;AACA,SAASC,sBAAT,QAAuC,cAAvC;AAGA,eAAe,MAAMC,MAAN,CAAa;AAO1BC,EAAAA,WAAW,CAACC,CAAD,EAAYC,CAAZ,EAAuB;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAChC,SAAKD,CAAL,GAASA,CAAT;AACA,SAAKC,CAAL,GAASA,CAAT;AAEA,SAAKC,UAAL,GAAkBC,IAAI,CAACC,KAAL,CAAW,KAAKJ,CAAhB,EAAmB,KAAKC,CAAxB,CAAlB;AACA,UAAMI,qBAAqB,GAAG,KAAKH,UAAL,GAAkBL,sBAAhD;AAEA,SAAKS,KAAL,GAAaD,qBAAqB,GAAG,KAAKL,CAAL,GAAS,KAAKE,UAAjB,GAA8B,CAAhE;AACA,SAAKK,KAAL,GAAaF,qBAAqB,GAAG,KAAKJ,CAAL,GAAS,KAAKC,UAAjB,GAA8B,CAAhE;AACD;;AAEmB,SAAbM,aAAa,CAACC,SAAD,EAAqD;AACvE,WAAOC,yBAAyB,CAACC,GAA1B,CAA8BF,SAA9B,CAAP;AACD;;AAEkB,SAAZG,YAAY,CAACC,OAAD,EAA0BC,SAA1B,EAA6C;AAC9D,WAAO,IAAIhB,MAAJ,CACLe,OAAO,CAACE,YAAR,CAAqBD,SAArB,CADK,EAELD,OAAO,CAACG,YAAR,CAAqBF,SAArB,CAFK,CAAP;AAID;;AAEY,MAATG,SAAS,GAAG;AACd,WAAO,KAAKf,UAAZ;AACD;;AAEDgB,EAAAA,iBAAiB,CAACC,MAAD,EAAiB;AAChC,WAAO,KAAKb,KAAL,GAAaa,MAAM,CAACb,KAApB,GAA4B,KAAKC,KAAL,GAAaY,MAAM,CAACZ,KAAvD;AACD;;AAEDa,EAAAA,SAAS,CAACD,MAAD,EAAiBE,SAAjB,EAAoC;AAC3C,WAAO,KAAKH,iBAAL,CAAuBC,MAAvB,IAAiCE,SAAxC;AACD;;AAvCyB;AA0C5B,MAAMX,yBAAyB,GAAG,IAAIY,GAAJ,CAGhC,CACA,CAAC1B,UAAU,CAAC2B,IAAZ,EAAkB,IAAIzB,MAAJ,CAAW,CAAC,CAAZ,EAAe,CAAf,CAAlB,CADA,EAEA,CAACF,UAAU,CAAC4B,KAAZ,EAAmB,IAAI1B,MAAJ,CAAW,CAAX,EAAc,CAAd,CAAnB,CAFA,EAGA,CAACF,UAAU,CAAC6B,EAAZ,EAAgB,IAAI3B,MAAJ,CAAW,CAAX,EAAc,CAAC,CAAf,CAAhB,CAHA,EAIA,CAACF,UAAU,CAAC8B,IAAZ,EAAkB,IAAI5B,MAAJ,CAAW,CAAX,EAAc,CAAd,CAAlB,CAJA,EAMA,CAACH,kBAAkB,CAACgC,QAApB,EAA8B,IAAI7B,MAAJ,CAAW,CAAX,EAAc,CAAC,CAAf,CAA9B,CANA,EAOA,CAACH,kBAAkB,CAACiC,UAApB,EAAgC,IAAI9B,MAAJ,CAAW,CAAX,EAAc,CAAd,CAAhC,CAPA,EAQA,CAACH,kBAAkB,CAACkC,OAApB,EAA6B,IAAI/B,MAAJ,CAAW,CAAC,CAAZ,EAAe,CAAC,CAAhB,CAA7B,CARA,EASA,CAACH,kBAAkB,CAACmC,SAApB,EAA+B,IAAIhC,MAAJ,CAAW,CAAC,CAAZ,EAAe,CAAf,CAA/B,CATA,CAHgC,CAAlC","sourcesContent":["import { DiagonalDirections, Directions } from '../../Directions';\nimport { MINIMAL_FLING_VELOCITY } from '../constants';\nimport PointerTracker from './PointerTracker';\n\nexport default class Vector {\n private readonly x;\n private readonly y;\n private readonly unitX;\n private readonly unitY;\n private readonly _magnitude;\n\n constructor(x: number, y: number) {\n this.x = x;\n this.y = y;\n\n this._magnitude = Math.hypot(this.x, this.y);\n const isMagnitudeSufficient = this._magnitude > MINIMAL_FLING_VELOCITY;\n\n this.unitX = isMagnitudeSufficient ? this.x / this._magnitude : 0;\n this.unitY = isMagnitudeSufficient ? this.y / this._magnitude : 0;\n }\n\n static fromDirection(direction: Directions | DiagonalDirections): Vector {\n return DirectionToVectorMappings.get(direction)!;\n }\n\n static fromVelocity(tracker: PointerTracker, pointerId: number) {\n return new Vector(\n tracker.getVelocityX(pointerId),\n tracker.getVelocityY(pointerId)\n );\n }\n\n get magnitude() {\n return this._magnitude;\n }\n\n computeSimilarity(vector: Vector) {\n return this.unitX * vector.unitX + this.unitY * vector.unitY;\n }\n\n isSimilar(vector: Vector, threshold: number) {\n return this.computeSimilarity(vector) > threshold;\n }\n}\n\nconst DirectionToVectorMappings = new Map<\n Directions | DiagonalDirections,\n Vector\n>([\n [Directions.LEFT, new Vector(-1, 0)],\n [Directions.RIGHT, new Vector(1, 0)],\n [Directions.UP, new Vector(0, -1)],\n [Directions.DOWN, new Vector(0, 1)],\n\n [DiagonalDirections.UP_RIGHT, new Vector(1, -1)],\n [DiagonalDirections.DOWN_RIGHT, new Vector(1, 1)],\n [DiagonalDirections.UP_LEFT, new Vector(-1, -1)],\n [DiagonalDirections.DOWN_LEFT, new Vector(-1, 1)],\n]);\n"]}

View File

@@ -0,0 +1,98 @@
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
import CircularBuffer from './CircularBuffer';
import LeastSquareSolver from './LeastSquareSolver';
export default class VelocityTracker {
constructor() {
_defineProperty(this, "assumePointerMoveStoppedMilliseconds", 40);
_defineProperty(this, "historySize", 20);
_defineProperty(this, "horizonMilliseconds", 300);
_defineProperty(this, "minSampleSize", 3);
_defineProperty(this, "samples", void 0);
this.samples = new CircularBuffer(this.historySize);
}
add(event) {
this.samples.push(event);
} /// Returns an estimate of the velocity of the object being tracked by the
/// tracker given the current information available to the tracker.
///
/// Information is added using [addPosition].
///
/// Returns null if there is no data on which to base an estimate.
getVelocityEstimate() {
const x = [];
const y = [];
const w = [];
const time = [];
let sampleCount = 0;
let index = this.samples.size - 1;
const newestSample = this.samples.get(index);
if (!newestSample) {
return null;
}
let previousSample = newestSample; // Starting with the most recent PointAtTime sample, iterate backwards while
// the samples represent continuous motion.
while (sampleCount < this.samples.size) {
const sample = this.samples.get(index);
const age = newestSample.time - sample.time;
const delta = Math.abs(sample.time - previousSample.time);
previousSample = sample;
if (age > this.horizonMilliseconds || delta > this.assumePointerMoveStoppedMilliseconds) {
break;
}
x.push(sample.x);
y.push(sample.y);
w.push(1);
time.push(-age);
sampleCount++;
index--;
}
if (sampleCount >= this.minSampleSize) {
const xSolver = new LeastSquareSolver(time, x, w);
const xFit = xSolver.solve(2);
if (xFit !== null) {
const ySolver = new LeastSquareSolver(time, y, w);
const yFit = ySolver.solve(2);
if (yFit !== null) {
const xVelocity = xFit.coefficients[1] * 1000;
const yVelocity = yFit.coefficients[1] * 1000;
return [xVelocity, yVelocity];
}
}
}
return null;
}
getVelocity() {
const estimate = this.getVelocityEstimate();
if (estimate !== null) {
return estimate;
}
return [0, 0];
}
reset() {
this.samples.clear();
}
}
//# sourceMappingURL=VelocityTracker.js.map

File diff suppressed because one or more lines are too long