Skip to content

Commit

Permalink
[fixed] operators to return MotionObservable rather than Observable
Browse files Browse the repository at this point in the history
Summary:
When the operator interfaces had the shape

```
interface Thingable {
  thing(): Observable<U>
}
```

TypeScript didn't know if the resulting observable had operators.  Thus, `stream.thing().otherThing()` would error, because `otherThing()` doesn't exist on `Observable`.

Reviewers: O2 Material Motion, O3 Material JavaScript platform reviewers, #material_motion, featherless

Reviewed By: O2 Material Motion, #material_motion, featherless

Tags: #material_motion

Differential Revision: http://codereview.cc/D3083
  • Loading branch information
appsforartists committed Apr 21, 2017
1 parent 0d87dd1 commit 426dd48
Show file tree
Hide file tree
Showing 24 changed files with 97 additions and 93 deletions.
6 changes: 3 additions & 3 deletions packages/core/src/operators/dedupe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,20 @@ import {
Constructor,
EqualityCheck,
NextChannel,
Observable,
ObservableWithFoundationalMotionOperators,
ObservableWithMotionOperators,
} from '../types';

export interface MotionDeduplicable<T> {
dedupe(areEqual?: EqualityCheck): Observable<T>;
dedupe(areEqual?: EqualityCheck): ObservableWithMotionOperators<T>;
}

export function withDedupe<T, S extends Constructor<ObservableWithFoundationalMotionOperators<T>>>(superclass: S): S & Constructor<MotionDeduplicable<T>> {
return class extends superclass implements MotionDeduplicable<T> {
/**
* Ensures that every value dispatched is different than the previous one.
*/
dedupe(areEqual: EqualityCheck = deepEqual): Observable<T> {
dedupe(areEqual: EqualityCheck = deepEqual): ObservableWithMotionOperators<T> {
let dispatched = false;
let lastValue: T;

Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/operators/delayBy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@
import {
Constructor,
Observable,
ObservableWithMotionOperators,
Observer,
} from '../types';

export interface MotionDelayable<T> {
delayBy(time: number): Observable<T>;
delayBy(time: number): ObservableWithMotionOperators<T>;
}

export function withDelayBy<T, S extends Constructor<Observable<T>>>(superclass: S): S & Constructor<MotionDelayable<T>> {
Expand All @@ -30,8 +31,8 @@ export function withDelayBy<T, S extends Constructor<Observable<T>>>(superclass:
* Buffers upstream values for the specified number of milliseconds, then
* dispatches them.
*/
delayBy(time: number): Observable<T> {
const constructor = this.constructor as Constructor<Observable<T>>;
delayBy(time: number): ObservableWithMotionOperators<T> {
const constructor = this.constructor as Constructor<ObservableWithMotionOperators<T>>;

return new constructor(
(observer: Observer<T>) => {
Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/operators/distanceFrom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import {
Constructor,
MotionMappable,
Observable,
ObservableWithMotionOperators,
Point2D,
} from '../types';

Expand All @@ -26,7 +26,7 @@ import {
} from '../typeGuards';

export interface MotionMeasurable<T> {
distanceFrom(origin: T): Observable<number>;
distanceFrom(origin: T): ObservableWithMotionOperators<number>;
}

export function withDistanceFrom<T, S extends Constructor<MotionMappable<T>>>(superclass: S): S & Constructor<MotionMeasurable<T>> {
Expand All @@ -36,13 +36,13 @@ export function withDistanceFrom<T, S extends Constructor<MotionMappable<T>>>(su
* The origin may be a number or a point, but the dispatched value will
* always be a number; distance is computed using Pythagorean theorem.
*/
distanceFrom(origin: T): Observable<number> {
distanceFrom(origin: T): ObservableWithMotionOperators<number> {
if (isPoint2D(origin)) {
return this._map(
return (this as any as ObservableWithMotionOperators<Point2D>)._map(
(value: Point2D) => Math.sqrt((origin.x - value.x) ** 2 + (origin.y - value.y) ** 2)
);
} else {
return this._map(
return (this as any as ObservableWithMotionOperators<number>)._map(
(value: number) => Math.abs(origin - value)
);
}
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/operators/foundation/_debounce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ import {
Constructor,
MotionNextOperable,
NextChannel,
Observable,
ObservableWithMotionOperators,
} from '../../types';

export interface MotionDebounceable<T> {
_debounce(): Observable<T>;
_debounce(): ObservableWithMotionOperators<T>;
}

export function withDebounce<T, S extends Constructor<MotionNextOperable<T>>>(superclass: S): S
Expand All @@ -38,7 +38,7 @@ export function withDebounce<T, S extends Constructor<MotionNextOperable<T>>>(su
* Since no rendering will happen until `requestAnimationFrame` is called,
* it should be safe to `_debounce()` without missing a frame.
*/
_debounce(): Observable<T> {
_debounce(): ObservableWithMotionOperators<T> {
let queuedFrameID: number;
let lastValue: T;

Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/operators/foundation/_filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ import {
Constructor,
MotionNextOperable,
NextChannel,
Observable,
ObservableWithMotionOperators,
Predicate,
} from '../../types';

export interface MotionFilterable<T> {
_filter(predicate: Predicate): Observable<T>;
_filter(predicate: Predicate<T>): ObservableWithMotionOperators<T>;
}

export function withFilter<T, S extends Constructor<MotionNextOperable<T>>>(superclass: S): S & Constructor<MotionFilterable<T>> {
Expand All @@ -32,7 +32,7 @@ export function withFilter<T, S extends Constructor<MotionNextOperable<T>>>(supe
* Applies `predicate` to every incoming value and synchronously passes
* values that return `true` to the observer.
*/
_filter(predicate: Predicate): Observable<T> {
_filter(predicate: Predicate<T>): ObservableWithMotionOperators<T> {
return this._nextOperator(
(value: T, dispatch: NextChannel<T>) => {
if (predicate(value)) {
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/operators/foundation/_map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ import {
Constructor,
MotionNextOperable,
NextChannel,
Observable,
ObservableWithMotionOperators,
} from '../../types';

export interface MotionMappable<T> {
_map<U>(transform: (value: T) => U): Observable<U>;
_map<U>(transform: (value: T) => U): ObservableWithMotionOperators<U>;
}

export function withMap<T, S extends Constructor<MotionNextOperable<T>>>(superclass: S): S & Constructor<MotionMappable<T>> {
Expand All @@ -31,7 +31,7 @@ export function withMap<T, S extends Constructor<MotionNextOperable<T>>>(supercl
* Applies `transform` to every incoming value and synchronously passes the
* result to the observer.
*/
_map<U>(transform: (value: T) => U): Observable<U> {
_map<U>(transform: (value: T) => U): ObservableWithMotionOperators<U> {
return this._nextOperator(
(value: T, dispatch: NextChannel<U>) => {
dispatch(transform(value));
Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/operators/foundation/_nextOperator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ import {
Constructor,
NextOperation,
Observable,
ObservableWithMotionOperators,
Observer,
} from '../../types';

export interface MotionNextOperable<T> extends Observable<T> {
_nextOperator<U>(operation: NextOperation<T, U>): Observable<U>;
_nextOperator<U>(operation: NextOperation<T, U>): ObservableWithMotionOperators<U>;
}

export function withNextOperator<T, S extends Constructor<Observable<T>>>(superclass: S): S & Constructor<MotionNextOperable<T>> {
Expand All @@ -36,8 +37,8 @@ export function withNextOperator<T, S extends Constructor<Observable<T>>>(superc
* `next` channel, transform it, and use the supplied callback to dispatch
* the result to the observer's `next` channel.
*/
_nextOperator<U>(operation: NextOperation<T, U>): Observable<U> {
const constructor = this.constructor as Constructor<Observable<U>>;
_nextOperator<U>(operation: NextOperation<T, U>): ObservableWithMotionOperators<U> {
const constructor = this.constructor as Constructor<ObservableWithMotionOperators<T>>;

return new constructor(
(observer: Observer<U>) => {
Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/operators/foundation/_remember.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ import {
Constructor,
NextOperation,
Observable,
ObservableWithMotionOperators,
Observer,
Subscription,
} from '../../types';

export interface MotionMemorable<T> extends Observable<T> {
_remember(): Observable<T>;
_remember(): ObservableWithMotionOperators<T>;
}

export function withRemember<T, S extends Constructor<Observable<T>>>(superclass: S): S & Constructor<MotionMemorable<T>> {
Expand All @@ -35,15 +36,15 @@ export function withRemember<T, S extends Constructor<Observable<T>>>(superclass
* `_remember()` is also useful for ensuring that expensive operations only
* happen once per dispatch, sharing the resulting value with all observers.
*/
_remember(): Observable<T> {
_remember(): ObservableWithMotionOperators<T> {
// Keep track of all the observers who have subscribed,
// so we can notify them when we get new values.
const observers = new Set();
let subscription: Subscription | undefined;
let lastValue: T;
let hasStarted = false;

const constructor = this.constructor as Constructor<Observable<T>>;
const constructor = this.constructor as Constructor<ObservableWithMotionOperators<T>>;

return new constructor(
(observer: Observer<T>) => {
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/operators/ignoreUntil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ import * as deepEqual from 'deep-equal';
import {
Constructor,
EqualityCheck,
Observable,
ObservableWithFoundationalMotionOperators,
ObservableWithMotionOperators,
} from '../types';

export interface MotionIgnorable<T> {
ignoreUntil(expectedValue: T, areEqual?: EqualityCheck): Observable<T>;
ignoreUntil(expectedValue: T, areEqual?: EqualityCheck): ObservableWithMotionOperators<T>;
}

export function withIgnoreUntil<T, S extends Constructor<ObservableWithFoundationalMotionOperators<T>>>(superclass: S): S & Constructor<MotionIgnorable<T>> {
Expand All @@ -34,7 +34,7 @@ export function withIgnoreUntil<T, S extends Constructor<ObservableWithFoundatio
* value. The expected value, and all subsequent values, will then be
* dispatched.
*/
ignoreUntil(expectedValue: T, areEqual: EqualityCheck = deepEqual): Observable<T> {
ignoreUntil(expectedValue: T, areEqual: EqualityCheck = deepEqual): ObservableWithMotionOperators<T> {
let ignoring = true;

return this._filter(
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/operators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ import {

export type ObservableWithMotionOperators<T> = ObservableWithFoundationalMotionOperators<T>
& MotionPluckable<T> & MotionLoggable<T> & MotionDeduplicable<T> & MotionInvertible<T>
& MotionMergeable<T> & MotionRewritable<T> & MotionRewriteToable<T> & MotionRewriteRangeable<T>
& MotionMergeable<T> & MotionRewritable<T> & MotionRewriteToable & MotionRewriteRangeable
& MotionThresholdable & MotionThresholdRangeable & MotionUpperBoundable & MotionLowerBoundable
& MotionOffsetable & MotionScalable & MotionDelayable<T> & MotionMeasurable<T>
& MotionSeedable<T> & MotionIgnorable<T>;
Expand All @@ -128,7 +128,7 @@ export function withMotionOperators<T, S extends Constructor<Observable<T>>>(sup
withMerge(withInverted(withDedupe(withLog(withUpperBound(withLowerBound(
withOffsetBy(withScaledBy(withDelayBy(withDistanceFrom(withStartWith(
withIgnoreUntil(
withPluck<T, ObservableWithFoundationalMotionOperators<T>>(
withPluck<T, Constructor<ObservableWithFoundationalMotionOperators<T>>>(
withFoundationalMotionOperators<T, Constructor<Observable<T>>>(superclass)
)
)
Expand Down
11 changes: 6 additions & 5 deletions packages/core/src/operators/inverted.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ import {
Constructor,
MotionNextOperable,
NextChannel,
Observable,
ObservableWithMotionOperators,
} from '../types';

// TODO: figure out the right way to cast T to boolean | number without
// constraining T on streams that don't support inverted. Same in rewriteRange.

export interface MotionInvertible<T> {
inverted(): Observable<T>;
inverted(): ObservableWithMotionOperators<T>;
}

export function withInverted<T, S extends Constructor<MotionNextOperable<T>>>(superclass: S): S & Constructor<MotionInvertible<T>> {
Expand All @@ -37,9 +37,10 @@ export function withInverted<T, S extends Constructor<MotionNextOperable<T>>>(su
* - 0 when it receives 1, and
* - 1 when it receives 0.
*/
inverted(): Observable<T> {
return this._nextOperator(
(value: T, dispatch: NextChannel<T>) => {
inverted(): ObservableWithMotionOperators<T> {
type U = number | boolean;
return (this as any as ObservableWithMotionOperators<U>)._nextOperator(
(value: U, dispatch: NextChannel<U>) => {
switch (value) {
case 0:
dispatch(1);
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/operators/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ import {
Constructor,
MotionNextOperable,
NextChannel,
Observable,
ObservableWithMotionOperators,
} from '../types';

import {
createPlucker,
} from './pluck';

export interface MotionLoggable<T> {
log(label: string, pluckPath: string): Observable<T>;
log(label: string, pluckPath: string): ObservableWithMotionOperators<T>;
}

export function withLog<T, S extends Constructor<MotionNextOperable<T>>>(superclass: S): S & Constructor<MotionLoggable<T>> {
Expand All @@ -47,7 +47,7 @@ export function withLog<T, S extends Constructor<MotionNextOperable<T>>>(supercl
*
* it would log `Name: banana`.
*/
log(label: string = '', pluckPath: string = ''): Observable<T> {
log(label: string = '', pluckPath: string = ''): ObservableWithMotionOperators<T> {
let plucker: (value: T) => any | undefined;

if (pluckPath) {
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/operators/lowerBound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@
import {
Constructor,
MotionMappable,
Observable,
ObservableWithMotionOperators,
} from '../types';

export interface MotionLowerBoundable {
lowerBound(limit: number): Observable<number>;
lowerBound(limit: number): ObservableWithMotionOperators<number>;
}

export function withLowerBound<T, S extends Constructor<MotionMappable<T>>>(superclass: S): S & Constructor<MotionLowerBoundable> {
return class extends superclass implements MotionLowerBoundable {
lowerBound(limit: number): Observable<number> {
return this._map(
lowerBound(limit: number): ObservableWithMotionOperators<number> {
return (this as any as ObservableWithMotionOperators<number>)._map(
(value: number) => Math.max(value, limit)
);
}
Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/operators/merge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import {
Constructor,
Observable,
ObservableWithMotionOperators,
Observer,
} from '../types';

Expand All @@ -25,7 +26,7 @@ import {
} from './pluck';

export interface MotionMergeable<T> extends Observable<T> {
merge(...otherStreams: Array<Observable<any>>): Observable<any>;
merge(...otherStreams: Array<Observable<any>>): ObservableWithMotionOperators<any>;
}

export function withMerge<T, S extends Constructor<Observable<T>>>(superclass: S): S & Constructor<MotionMergeable<T>> {
Expand All @@ -34,8 +35,8 @@ export function withMerge<T, S extends Constructor<Observable<T>>>(superclass: S
* Dispatches values as it receives them, both from upstream and from any
* streams provided as arguments.
*/
merge(...otherStreams: Array<Observable<any>>): Observable<any> {
const constructor = this.constructor as Constructor<Observable<any>>;
merge(...otherStreams: Array<Observable<any>>): ObservableWithMotionOperators<any> {
const constructor = this.constructor as Constructor<ObservableWithMotionOperators<T>>;

return new constructor(
(observer: Observer<any>) => {
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/operators/offsetBy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@
import {
Constructor,
MotionMappable,
Observable,
ObservableWithMotionOperators,
} from '../types';

export interface MotionOffsetable {
offsetBy(offset: number): Observable<number>;
offsetBy(offset: number): ObservableWithMotionOperators<number>;
}

export function withOffsetBy<T, S extends Constructor<MotionMappable<T>>>(superclass: S): S & Constructor<MotionOffsetable> {
return class extends superclass implements MotionOffsetable {
offsetBy(offset: number): Observable<number> {
return this._map(
offsetBy(offset: number): ObservableWithMotionOperators<number> {
return (this as any as MotionMappable<number>)._map(
(value: number) => value + offset
);
}
Expand Down
Loading

0 comments on commit 426dd48

Please sign in to comment.