Last active
November 5, 2015 07:23
-
-
Save zerkalica/19d7fe14a489586cbce7 to your computer and use it in GitHub Desktop.
This file contains 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
/* @flow */ | |
type BaseAction = { | |
type: string | |
} | |
type ReduxReducer<TState, TAction> = (state: TState, action: TAction & BaseAction) => TState | |
class Reducer<TState, TAction> { | |
reduce: ReduxReducer<TState, TAction>; | |
constructor() { | |
this.reduce = this._reduce.bind(this) | |
} | |
_reduce(state: TState, action: BaseAction & TAction): TState { | |
// $FlowFixMe | |
const method = this[action.type] | |
return method ? method.call(this, state, action) : state | |
} | |
} | |
class Store<K, I, R, S> { | |
items: {[id: K]: I}; | |
constructor(items: {[id: K]: I}) { | |
this.items = items | |
} | |
/* eslint-disable no-unused-vars */ | |
clone(items: {[id: K]: I}): S { | |
throw new Error('abstract method') | |
} | |
createItem(rec: R, v?: I): I { | |
throw new Error('abstract method') | |
} | |
getItemKey(v: I): K { | |
throw new Error('abstract method') | |
} | |
/* eslint-enable no-unused-vars */ | |
set(k: K, v: I): S { | |
const newItems: {[id: K]: I} = {...this.items} | |
newItems[k] = v | |
return this.clone(newItems) | |
} | |
has(k: K): boolean { | |
return this.items[k] !== undefined | |
} | |
get(k: K): I { | |
return this.items[k] | |
} | |
delete(k: K): S { | |
const obj: {[k: K]: any} = {} | |
obj[k] = undefined | |
const newItems: {[id: K]: I} = {...this.items, ...obj} | |
return this.clone(newItems) | |
} | |
replace(k: K, rec: R): S { | |
const item = this.createItem(rec, this.items[k]) | |
return this.set(k, item) | |
} | |
filter(fn: (v: I) => boolean): S { | |
const newItems = {} | |
Object.keys(this.items).forEach(key => { | |
const item = this.items[key] | |
if (fn(item)) { | |
newItems[key] = item | |
} | |
}) | |
return this.clone(newItems) | |
} | |
map(fn: (v: I) => I): S { | |
const newItems: {[id: K]: I} = {} | |
Object.keys(this.items).forEach(key => { | |
newItems[key] = fn(this.items[key]) | |
}) | |
return this.clone(newItems) | |
} | |
fromArray(recs: Array<R>): { | |
keys: Array<K>, | |
store: S | |
} { | |
const items: {[id: K]: I} = {} | |
const keys: Array<K> = [] | |
recs.forEach((rec: R) => { | |
const item = this.createItem(rec) | |
const key = this.getItemKey(item) | |
keys.push(key) | |
items[key] = item | |
}) | |
return { | |
keys: keys, | |
store: this.clone(items) | |
} | |
} | |
} | |
function createId(): string { | |
return Date.now() + String(Math.round(Math.random() * 1000)) | |
} | |
type TodoRec = { | |
id?: string, | |
complete?: boolean, | |
text?: string | |
} | |
class Todo { | |
id: string; | |
complete: boolean; | |
text: string; | |
constructor(rec: TodoRec) { | |
this.id = rec.id || createId() | |
this.complete = rec.complete || false | |
this.text = rec.text || '' | |
} | |
} | |
class TodoStore extends Store<string, Todo, TodoRec, TodoStore> { | |
clone(newItems: {[id: string]: Todo}): TodoStore { | |
return new TodoStore(newItems) | |
} | |
getItemKey(todo: Todo): string { | |
return todo.id | |
} | |
createItem(rec: TodoRec, oldTodo?: Todo): Todo { | |
return new Todo({...oldTodo || {}, ...rec}) | |
} | |
} | |
type TodosRec = { | |
store?: TodoStore, | |
keys?: Array<string>, | |
selectedTodo?: ?string, | |
serverError?: ?string | |
} | |
class Todos { | |
store: TodoStore; | |
keys: Array<string>; | |
selectedTodo: ?string; | |
serverError: ?string; | |
constructor(rec: TodosRec) { | |
this.store = rec.store || new TodoStore({}) | |
this.keys = rec.keys || [] | |
this.selectedTodo = rec.selectedTodo || null | |
this.serverError = rec.serverError || null | |
} | |
clone(rec: TodosRec): Todos { | |
return new Todos({...this, ...rec}) | |
} | |
} | |
class TodoAddAction { | |
type: string = 'todoAdd'; | |
text: string; | |
constructor(text: string) { | |
this.text = text | |
} | |
} | |
class TodoCompleteAction { | |
type: string = 'todoComplete'; | |
id: string; | |
constructor(id: string) { | |
this.id = id | |
} | |
} | |
class TodoDestroyAction { | |
type: string = 'todoDestroy'; | |
id: string; | |
constructor(id: string) { | |
this.id = id | |
} | |
} | |
class TodoAddMultipleAction { | |
type: string = 'todoAddMultiple'; | |
items: Array<TodoRec>; | |
} | |
class TodoAddErrorAction { | |
type: string = 'todoAddError'; | |
error: Error; | |
contructor(error: Error) { | |
this.error = error | |
} | |
} | |
class TodoAddMultipleErrorAction { | |
type: string = 'todoAddMultipleError'; | |
error: Error; | |
contructor(error: Error) { | |
this.error = error | |
} | |
} | |
class TodoCompleteErrorAction { | |
type: string = 'todoCompleteError'; | |
error: Error; | |
contructor(error: Error) { | |
this.error = error | |
} | |
} | |
class TodoDestroyErrorAction { | |
type: string = 'todoDestroyError'; | |
error: Error; | |
contructor(error: Error) { | |
this.error = error | |
} | |
} | |
class TodoClearErrorsAction { | |
type: string = 'todoClearErrors'; | |
} | |
type TodoReducerActions = TodoAddAction | |
| TodoCompleteAction | |
| TodoDestroyAction | |
| TodoAddMultipleAction | |
| TodoAddErrorAction | |
| TodoAddMultipleErrorAction | |
| TodoCompleteErrorAction | |
| TodoDestroyErrorAction | |
| TodoClearErrorsAction | |
class TodoReducer extends Reducer<Todos, TodoReducerActions> { | |
todoAddMultiple(todos: Todos, action: TodoAddMultipleAction): Todos { | |
const {store, keys} = todos.store.fromArray(action.items) | |
return todos.clone({ | |
store, | |
keys, | |
selectedTodo: null | |
}) | |
} | |
todoAddMultipleError(todos: Todos, {error}: TodoAddMultipleErrorAction): Todos { | |
return this._todoAnyError(todos, error) | |
} | |
todoAddError(todos: Todos, {error}: TodoAddErrorAction): Todos { | |
return this._todoAnyError(todos, error) | |
} | |
todoCompleteError(todos: Todos, {error}: TodoCompleteErrorAction): Todos { | |
return this._todoAnyError(todos, error) | |
} | |
todoDestroyError(todos: Todos, {error}: TodoDestroyErrorAction): Todos { | |
return this._todoAnyError(todos, error) | |
} | |
_todoAnyError(todos: Todos, error: Error): Todos { | |
return todos.clone({ | |
serverError: error.message | |
}) | |
} | |
/* eslint-disable no-unused-vars */ | |
todoClearErrors(todos: Todos, action: TodoClearErrorsAction): Todos { | |
return todos.clone({ | |
serverError: null | |
}) | |
} | |
/* eslint-enable no-unused-vars */ | |
todoAdd(todos: Todos, {text}: TodoAddAction): Todos { | |
const todo = todos.store.createItem({text}) | |
return todos.clone({ | |
store: todos.store.set(todo.id, todo) | |
}) | |
} | |
todoComplete(todos: Todos, {id}: TodoCompleteAction): Todos { | |
return todos.clone({ | |
store: todos.store.replace(id, {complete: true}) | |
}) | |
} | |
todoDestroy(todos: Todos, {id}: TodoDestroyAction): Todos { | |
return todos.clone({ | |
store: todos.store.delete(id) | |
}) | |
} | |
} | |
type AppAction = BaseAction & ( | |
TodoReducerActions | |
) | |
type StateRec = { | |
todos?: Todos | |
} | |
class AppState { | |
todos: Todos; | |
constructor(rec: ?StateRec) { | |
const r: StateRec = rec || {} | |
this.todos = r.todos || new Todos({}) | |
} | |
clone(rec: StateRec): AppState { | |
return new AppState({...this, ...rec}) | |
} | |
} | |
class AppReducers { | |
todos: ReduxReducer; | |
constructor() { | |
this.todos = (new TodoReducer()).reduce | |
} | |
} | |
const initialState: AppState = new AppState() | |
const reducers: AppReducers = new AppReducers() | |
// register reducers and initialState in redux |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment