forked from BioPhoton/rxjs-operating-heavily-dynamic-uis
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path03-5_interval-process.ts
109 lines (91 loc) · 4.3 KB
/
03-5_interval-process.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import {Counter, CountDownState, ConterStateKeys, PartialCountDownState} from './counter'
import { Observable, Observer, NEVER, Subject, pipe, timer, combineLatest, merge} from 'rxjs';
import { map, mapTo, withLatestFrom,tap, distinctUntilChanged, shareReplay,distinctUntilKeyChanged, startWith, scan, pluck, switchMap} from 'rxjs/operators';
// EXERCISE DESCRIPTION ==============================
/**
* Use `ConterStateKeys` for property names.
* Explort the counterUI API by typing `counterUI.` somewhere. ;)
*
* Implement all features of the counter:
* 1. Start, pause the counter. Then restart the counter with 0 (+)
* 2. Start it again from paused number (++)
* 3. If Set to button is clicked set counter value to input value while counting (+++)
* 4. Reset to initial state if reset button is clicked (+)
* 5. If count up button is clicked count up, if count down button is clicked count down (+)
* 6. Change interval if input tickSpeed input changes (++)
* 7. Change count up if input countDiff changes (++)
* 8. Take care of rendering execution and other performance optimisations as well as refactoring (+)
*/
// ==================================================================
// == CONSTANTS ===========================================================
// Setup conutDown state
const initialConterState: CountDownState = {
isTicking: false,
count: 0,
countUp: true,
tickSpeed: 200,
countDiff:1
};
// Init CountDown counterUI
const counterUI = new Counter(
document.body,
{
initialSetTo: initialConterState.count + 10,
initialTickSpeed: initialConterState.tickSpeed,
initialCountDiff: initialConterState.countDiff,
}
);
// = BASE OBSERVABLES ====================================================
// == SOURCE OBSERVABLES ==================================================
// All our source observables are extracted into Counter class
// === STATE OBSERVABLES ==================================================
const programmaticCommandSubject = new Subject<PartialCountDownState>();
const counterCommands$ = merge(
counterUI.btnStart$.pipe(mapTo({isTicking: true})),
counterUI.btnPause$.pipe(mapTo({isTicking: false})),
counterUI.btnSetTo$.pipe(map(n => ({count: n}))),
counterUI.btnUp$.pipe(mapTo({countUp: true})),
counterUI.btnDown$.pipe(mapTo({countUp: false})),
counterUI.btnReset$.pipe(mapTo({...initialConterState})),
counterUI.inputTickSpeed$.pipe(map ( n => ({tickSpeed: n}))),
counterUI.inputCountDiff$.pipe(map ( n => ({countDiff: n}))),
programmaticCommandSubject.asObservable()
);
const counterState$: Observable<CountDownState> = counterCommands$
.pipe(
startWith(initialConterState),
scan( (counterState: CountDownState, command): CountDownState => ( {...counterState, ...command} ) ),
shareReplay(1)
);
// === INTERACTION OBSERVABLES ============================================
// == INTERMEDIATE OBSERVABLES ============================================
const count$ = counterState$.pipe(pluck<CountDownState, number>(ConterStateKeys.count));
const isTicking$ = counterState$.pipe(pluck(ConterStateKeys.isTicking), distinctUntilChanged<boolean>());
const intervalTick$ = isTicking$
.pipe(
switchMap(isTicking => isTicking ? timer(0, initialConterState.tickSpeed) : NEVER)
);
// = SIDE EFFECTS =========================================================
// == UI INPUTS ===========================================================
const renderCountChange$ = count$
.pipe(
tap(n => counterUI.renderCounterValue(n))
);
// == UI OUTPUTS ==========================================================
const commandFromTick$ = intervalTick$
.pipe(
withLatestFrom(count$, (_, count) => count),
tap(count => programmaticCommandSubject.next({count: ++count}))
);
// == SUBSCRIPTION ========================================================
merge(
// Input side effect
renderCountChange$,
// Outputs side effect
commandFromTick$
)
.subscribe();
// = HELPER ===============================================================
// = CUSTOM OPERATORS =====================================================
// == CREATION METHODS ====================================================
// == OPERATORS ===========================================================