Created
February 22, 2017 16:46
-
-
Save diestrin/6cec6fa951b28603325a304b37065d28 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // lib imports | |
| import { ReplaySubject } from 'rxjs/ReplaySubject'; | |
| import { BehaviorSubject } from 'rxjs/BehaviorSubject'; | |
| import 'rxjs/add/operator/scan'; | |
| // external imports | |
| import { mergeDeep, isFunction } from '@wu/utils'; | |
| export interface IAction<Actions> { | |
| type: Actions; | |
| payload?: any; | |
| } | |
| export const ReducerMethod = (action: number) => { | |
| return (target: Reducer<any, any>|ReducerSimple<any, any>, | |
| key: string, | |
| descriptor: TypedPropertyDescriptor<(state: any, action: any) => any> | |
| ) => { | |
| if (!('cases' in target)) { | |
| (target as any).cases = {}; | |
| } | |
| (target as any).cases[action] = target[key]; | |
| return descriptor; | |
| }; | |
| }; | |
| type reducer = <State, Action>(state: State, action: Action) => State; | |
| type reducerOrSubReducer = reducer|{stateKey: string, reducer: reducer}; | |
| export abstract class ReducerSimple<State, Action extends IAction<number>> { | |
| public state$: BehaviorSubject<State>; | |
| public dispatcher: ReplaySubject<Action>; | |
| public dispatch: (value: Action) => void; | |
| public dispatchNext: (value: Action) => void; | |
| public cases: {[key: number]: reducer}; | |
| public get currentState(): State { | |
| return this.state$.getValue(); | |
| } | |
| } | |
| export abstract class Reducer<State, Action extends IAction<number>> { | |
| public readonly dispatcher = new ReplaySubject<Action>(); | |
| public readonly state$ = new BehaviorSubject(this.intialState); | |
| public get currentState(): State { | |
| return this.state$.getValue(); | |
| } | |
| private cases: {[key: number]: reducerOrSubReducer}; | |
| constructor(private intialState: State) { | |
| if (!this.cases) { | |
| this.cases = {}; | |
| } else { | |
| Object.keys(this.cases) | |
| .map(num => { | |
| this.cases[num] = this.cases[num].bind(this); | |
| }); | |
| } | |
| this.dispatcher | |
| .scan((state, action) => this.scan(state, action), this.intialState) | |
| .subscribe(state => this.state$.next(state)); | |
| } | |
| public dispatch(action: Action): void { | |
| this.dispatcher.next(action); | |
| } | |
| public dispatchNext(action: Action): void { | |
| setTimeout(() => this.dispatcher.next(action), 0); | |
| } | |
| protected mergeReducer( | |
| stateKey: string, | |
| reducer: ReducerSimple<State, Action> | |
| ): void { | |
| reducer.state$ = this.state$; | |
| reducer.dispatcher = this.dispatcher; | |
| reducer.dispatch = this.dispatch; | |
| reducer.dispatchNext = this.dispatchNext; | |
| Object.keys(reducer.cases) | |
| .map(num => { | |
| this.cases[num] = { | |
| stateKey, | |
| reducer: reducer.cases[num].bind(reducer) | |
| }; | |
| }); | |
| } | |
| private scan(state: State, action: Action): State { | |
| if (action.type in this.cases) { | |
| const match = this.cases[action.type]; | |
| if (isFunction(match)) { | |
| return match(state, action); | |
| } else { | |
| const newState = mergeDeep(state); | |
| newState[match.stateKey] = match.reducer(state[match.stateKey], action); | |
| return newState; | |
| } | |
| } else { | |
| return state; | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // lib imports | |
| import { Component } from '@angular/core'; | |
| import 'rxjs/add/operator/map'; | |
| import 'rxjs/add/operator/do'; | |
| // external imports | |
| import { VideoReducer, VideoActions } from '@wu/common/video.reducer'; | |
| @Component({ | |
| moduleId: __filename, | |
| selector: 'video-thumbnails', | |
| templateUrl: `./templates/video-thumbnails.html` | |
| }) | |
| export class VideoThumbnails { | |
| // comes from API | |
| public videos: any[]; | |
| public state = this.videoReducer.currentState; | |
| constructor(public videoReducer: VideoReducer) { } | |
| public play(index: number): void { | |
| if (index in this.videos) { | |
| this.videoReducer.dispatch({ | |
| type: VideoActions.PLAY_VIDEO, | |
| payload: { | |
| index, | |
| video: this.videos[index] | |
| } | |
| }); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <div id="playlist"> | |
| <div id="playlist-topstories"> | |
| <ul class="row small-up-2 medium-up-3 large-up-4"> | |
| <li class="column" *ngFor="let video of videos; let i = index"> | |
| <div class="thumb-div shadow-box" | |
| (click)="play(i)" | |
| [attr.id]="video.source_guid" | |
| [class.selected]="i === (state | async)?.index"> | |
| <img class="thumb-image" | |
| [attr.src]="video.mediaUrl" | |
| [attr.alt]="video.title" /> | |
| <div class="thumb-title">{{ video.title }} </div> | |
| </div> | |
| </li> | |
| </ul> | |
| </div> | |
| </div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // lib imports | |
| import { Injectable } from '@angular/core'; | |
| import { Observable } from 'rxjs/Observable'; | |
| import 'rxjs/add/observable/of'; | |
| import 'rxjs/add/operator/do'; | |
| import 'rxjs/add/operator/scan'; | |
| import 'rxjs/add/operator/share'; | |
| import 'rxjs/add/operator/startWith'; | |
| // external imports | |
| import { Reducer, IAction, ReducerMethod } from '@wu/common/reducer'; | |
| export class VideoState { | |
| public video: any = {}; | |
| public ended = false; | |
| public index = -1; | |
| } | |
| export const enum VideoActions { | |
| /** | |
| * Payload sould contain the video and index | |
| */ | |
| PLAY_VIDEO, | |
| /** | |
| * Payload can be empty | |
| */ | |
| END_VIDEO | |
| } | |
| type State = VideoState | |
| type Action = IAction<VideoActions> | |
| @Injectable() | |
| export class VideoReducer extends Reducer<State, Action> { | |
| constructor() { | |
| super(new VideoState()); | |
| } | |
| @ReducerMethod(VideoActions.PLAY_VIDEO) | |
| protected playVideo(state: State, action: Action): State { | |
| return Object.assign({}, state, { | |
| video: action.payload.video, | |
| ended: false, | |
| index: action.payload.index | |
| }); | |
| } | |
| @ReducerMethod(WuVideoActions.END_VIDEO) | |
| protected endVideo(state: State, action: Action): State { | |
| return Object.assign({}, state, {ended: true}); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment