mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-12 00:37:19 +00:00
* Splitting main part of app out of the main bundle. Also improving the transition from intro to compressor. * Showing error if app fails to load. * lol these aren't async * Please don't tell anyone I did this * Spinner if user selects a file before the app has loaded. (#208)
256 lines
8.0 KiB
TypeScript
256 lines
8.0 KiB
TypeScript
import { bind } from '../../lib/initial-util';
|
|
const enum Button { Left }
|
|
|
|
export class Pointer {
|
|
/** x offset from the top of the document */
|
|
pageX: number;
|
|
/** y offset from the top of the document */
|
|
pageY: number;
|
|
/** x offset from the top of the viewport */
|
|
clientX: number;
|
|
/** y offset from the top of the viewport */
|
|
clientY: number;
|
|
/** ID for this pointer */
|
|
id: number = -1;
|
|
/** The platform object used to create this Pointer */
|
|
nativePointer: Touch | PointerEvent | MouseEvent;
|
|
|
|
constructor (nativePointer: Touch | PointerEvent | MouseEvent) {
|
|
this.nativePointer = nativePointer;
|
|
this.pageX = nativePointer.pageX;
|
|
this.pageY = nativePointer.pageY;
|
|
this.clientX = nativePointer.clientX;
|
|
this.clientY = nativePointer.clientY;
|
|
|
|
if (self.Touch && nativePointer instanceof Touch) {
|
|
this.id = nativePointer.identifier;
|
|
} else if (isPointerEvent(nativePointer)) { // is PointerEvent
|
|
this.id = nativePointer.pointerId;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns an expanded set of Pointers for high-resolution inputs.
|
|
*/
|
|
getCoalesced(): Pointer[] {
|
|
if ('getCoalescedEvents' in this.nativePointer) {
|
|
return this.nativePointer.getCoalescedEvents().map(p => new Pointer(p));
|
|
}
|
|
return [this];
|
|
}
|
|
}
|
|
|
|
const isPointerEvent = (event: any): event is PointerEvent =>
|
|
self.PointerEvent && event instanceof PointerEvent;
|
|
|
|
const noop = () => {};
|
|
|
|
export type InputEvent = TouchEvent | PointerEvent | MouseEvent;
|
|
type StartCallback = (pointer: Pointer, event: InputEvent) => boolean;
|
|
type MoveCallback = (
|
|
previousPointers: Pointer[],
|
|
changedPointers: Pointer[],
|
|
event: InputEvent,
|
|
) => void;
|
|
type EndCallback = (pointer: Pointer, event: InputEvent) => void;
|
|
|
|
interface PointerTrackerCallbacks {
|
|
/**
|
|
* Called when a pointer is pressed/touched within the element.
|
|
*
|
|
* @param pointer The new pointer.
|
|
* This pointer isn't included in this.currentPointers or this.startPointers yet.
|
|
* @param event The event related to this pointer.
|
|
*
|
|
* @returns Whether you want to track this pointer as it moves.
|
|
*/
|
|
start?: StartCallback;
|
|
/**
|
|
* Called when pointers have moved.
|
|
*
|
|
* @param previousPointers The state of the pointers before this event.
|
|
* This contains the same number of pointers, in the same order, as
|
|
* this.currentPointers and this.startPointers.
|
|
* @param changedPointers The pointers that have changed since the last move callback.
|
|
* @param event The event related to the pointer changes.
|
|
*/
|
|
move?: MoveCallback;
|
|
/**
|
|
* Called when a pointer is released.
|
|
*
|
|
* @param pointer The final state of the pointer that ended. This
|
|
* pointer is now absent from this.currentPointers and
|
|
* this.startPointers.
|
|
* @param event The event related to this pointer.
|
|
*/
|
|
end?: EndCallback;
|
|
}
|
|
|
|
/**
|
|
* Track pointers across a particular element
|
|
*/
|
|
export class PointerTracker {
|
|
/**
|
|
* State of the tracked pointers when they were pressed/touched.
|
|
*/
|
|
readonly startPointers: Pointer[] = [];
|
|
/**
|
|
* Latest state of the tracked pointers. Contains the same number
|
|
* of pointers, and in the same order as this.startPointers.
|
|
*/
|
|
readonly currentPointers: Pointer[] = [];
|
|
|
|
private _startCallback: StartCallback;
|
|
private _moveCallback: MoveCallback;
|
|
private _endCallback: EndCallback;
|
|
|
|
/**
|
|
* Track pointers across a particular element
|
|
*
|
|
* @param element Element to monitor.
|
|
* @param callbacks
|
|
*/
|
|
constructor (private _element: HTMLElement, callbacks: PointerTrackerCallbacks) {
|
|
const {
|
|
start = () => true,
|
|
move = noop,
|
|
end = noop,
|
|
} = callbacks;
|
|
|
|
this._startCallback = start;
|
|
this._moveCallback = move;
|
|
this._endCallback = end;
|
|
|
|
// Add listeners
|
|
if (self.PointerEvent) {
|
|
this._element.addEventListener('pointerdown', this._pointerStart);
|
|
} else {
|
|
this._element.addEventListener('mousedown', this._pointerStart);
|
|
this._element.addEventListener('touchstart', this._touchStart);
|
|
this._element.addEventListener('touchmove', this._move);
|
|
this._element.addEventListener('touchend', this._touchEnd);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Call the start callback for this pointer, and track it if the user wants.
|
|
*
|
|
* @param pointer Pointer
|
|
* @param event Related event
|
|
* @returns Whether the pointer is being tracked.
|
|
*/
|
|
private _triggerPointerStart (pointer: Pointer, event: InputEvent): boolean {
|
|
if (!this._startCallback(pointer, event)) return false;
|
|
this.currentPointers.push(pointer);
|
|
this.startPointers.push(pointer);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Listener for mouse/pointer starts. Bound to the class in the constructor.
|
|
*
|
|
* @param event This will only be a MouseEvent if the browser doesn't support
|
|
* pointer events.
|
|
*/
|
|
@bind
|
|
private _pointerStart (event: PointerEvent | MouseEvent) {
|
|
if (event.button !== Button.Left) return;
|
|
if (!this._triggerPointerStart(new Pointer(event), event)) return;
|
|
|
|
// Add listeners for additional events.
|
|
// The listeners may already exist, but no harm in adding them again.
|
|
if (isPointerEvent(event)) {
|
|
this._element.setPointerCapture(event.pointerId);
|
|
this._element.addEventListener('pointermove', this._move);
|
|
this._element.addEventListener('pointerup', this._pointerEnd);
|
|
} else { // MouseEvent
|
|
window.addEventListener('mousemove', this._move);
|
|
window.addEventListener('mouseup', this._pointerEnd);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Listener for touchstart. Bound to the class in the constructor.
|
|
* Only used if the browser doesn't support pointer events.
|
|
*/
|
|
@bind
|
|
private _touchStart (event: TouchEvent) {
|
|
for (const touch of Array.from(event.changedTouches)) {
|
|
this._triggerPointerStart(new Pointer(touch), event);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Listener for pointer/mouse/touch move events.
|
|
* Bound to the class in the constructor.
|
|
*/
|
|
@bind
|
|
private _move (event: PointerEvent | MouseEvent | TouchEvent) {
|
|
const previousPointers = this.currentPointers.slice();
|
|
const changedPointers = ('changedTouches' in event) ? // Shortcut for 'is touch event'.
|
|
Array.from(event.changedTouches).map(t => new Pointer(t)) :
|
|
[new Pointer(event)];
|
|
const trackedChangedPointers = [];
|
|
|
|
for (const pointer of changedPointers) {
|
|
const index = this.currentPointers.findIndex(p => p.id === pointer.id);
|
|
if (index === -1) continue; // Not a pointer we're tracking
|
|
trackedChangedPointers.push(pointer);
|
|
this.currentPointers[index] = pointer;
|
|
}
|
|
|
|
if (trackedChangedPointers.length === 0) return;
|
|
|
|
this._moveCallback(previousPointers, trackedChangedPointers, event);
|
|
}
|
|
|
|
/**
|
|
* Call the end callback for this pointer.
|
|
*
|
|
* @param pointer Pointer
|
|
* @param event Related event
|
|
*/
|
|
@bind
|
|
private _triggerPointerEnd (pointer: Pointer, event: InputEvent): boolean {
|
|
const index = this.currentPointers.findIndex(p => p.id === pointer.id);
|
|
// Not a pointer we're interested in?
|
|
if (index === -1) return false;
|
|
|
|
this.currentPointers.splice(index, 1);
|
|
this.startPointers.splice(index, 1);
|
|
|
|
this._endCallback(pointer, event);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Listener for mouse/pointer ends. Bound to the class in the constructor.
|
|
* @param event This will only be a MouseEvent if the browser doesn't support
|
|
* pointer events.
|
|
*/
|
|
@bind
|
|
private _pointerEnd (event: PointerEvent | MouseEvent) {
|
|
if (!this._triggerPointerEnd(new Pointer(event), event)) return;
|
|
|
|
if (isPointerEvent(event)) {
|
|
if (this.currentPointers.length) return;
|
|
this._element.removeEventListener('pointermove', this._move);
|
|
this._element.removeEventListener('pointerup', this._pointerEnd);
|
|
} else { // MouseEvent
|
|
window.removeEventListener('mousemove', this._move);
|
|
window.removeEventListener('mouseup', this._pointerEnd);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Listener for touchend. Bound to the class in the constructor.
|
|
* Only used if the browser doesn't support pointer events.
|
|
*/
|
|
@bind
|
|
private _touchEnd (event: TouchEvent) {
|
|
for (const touch of Array.from(event.changedTouches)) {
|
|
this._triggerPointerEnd(new Pointer(touch), event);
|
|
}
|
|
}
|
|
}
|