Update examples to move types into separate files.
This commit is contained in:
Родитель
ac6b979f83
Коммит
d1b3f4a228
|
@ -2,15 +2,15 @@
|
|||
.*/src/test/.*
|
||||
.*/dist/.*
|
||||
.*/node_modules/reflex/examples/.*
|
||||
.*/node_modules/reflex-virtual-dom-renderer/lib/.*
|
||||
.*/node_modules/reflex-virtual-dom-driver/lib/.*
|
||||
.*/node_modules/reflex/lib/.*
|
||||
|
||||
[libs]
|
||||
./node_modules/reflex/interfaces/
|
||||
./node_modules/reflex-virtual-dom-renderer/interfaces/
|
||||
./node_modules/reflex-virtual-dom-driver/interfaces/
|
||||
|
||||
[include]
|
||||
|
||||
[options]
|
||||
module.name_mapper='reflex-virtual-dom-renderer' -> 'reflex-virtual-dom-renderer/src/index'
|
||||
module.name_mapper='reflex-virtual-dom-driver' -> 'reflex-virtual-dom-driver/src/index'
|
||||
module.name_mapper='reflex' -> 'reflex/src/index'
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"reflex": "latest",
|
||||
"reflex-virtual-dom-renderer": "latest"
|
||||
"reflex-virtual-dom-driver": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "11.0.1",
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/* @flow */
|
||||
|
||||
import * as Counter from "./counter";
|
||||
import {html, forward, thunk} from "reflex";
|
||||
|
||||
/*::
|
||||
import * as type from "../type/counter-list"
|
||||
*/
|
||||
|
||||
export const asAdd/*:type.asAdd*/ = () => ({type: "CounterList.Add"})
|
||||
export const asRemove/*:type.asRemove*/ = () => ({type: "CounterList.Remove"})
|
||||
export const asBy/*:type.asBy*/ = id => act =>
|
||||
({type: "CounterList.ModifyByID", id, act})
|
||||
|
||||
|
||||
export const create/*:type.create*/ = ({nextID, entries}) =>
|
||||
({type: "CounterList.Model", nextID, entries})
|
||||
|
||||
export const add/*:type.add*/ = model => create({
|
||||
nextID: model.nextID + 1,
|
||||
entries: model.entries.concat([{
|
||||
type: "CounterList.Entry",
|
||||
id: model.nextID,
|
||||
model: Counter.create({value: 0})
|
||||
}])
|
||||
})
|
||||
|
||||
export const remove/*:type.remove*/ = model => create({
|
||||
nextID: model.nextID,
|
||||
entries: model.entries.slice(1)
|
||||
})
|
||||
|
||||
export const modify/*:type.modify*/ = (model, id, action) => create({
|
||||
nextID: model.nextID,
|
||||
entries: model.entries.map(entry =>
|
||||
entry.id !== id ?
|
||||
entry :
|
||||
{type: entry.type, id: id, model: Counter.update(entry.model, action)})
|
||||
})
|
||||
|
||||
export const update/*:type.update*/ = (model, action) =>
|
||||
action.type === "CounterList.Add" ?
|
||||
add(model, action) :
|
||||
action.type === "CounterList.Remove" ?
|
||||
remove(model, action) :
|
||||
action.type === "CounterList.ModifyByID" ?
|
||||
modify(model, action.id, action.act) :
|
||||
model;
|
||||
|
||||
|
||||
// View
|
||||
const viewEntry/*:type.viewEntry*/ = ({id, model}, address) =>
|
||||
html.div({key: id}, [
|
||||
Counter.view(model, forward(address, asBy(id)))
|
||||
])
|
||||
|
||||
export const view/*:type.view*/ = (model, address) =>
|
||||
html.div({key: "CounterList"}, [
|
||||
html.div({key: "controls"}, [
|
||||
html.button({
|
||||
key: "remove",
|
||||
onClick: forward(address, asRemove)
|
||||
}, ["Remove"]),
|
||||
html.button({
|
||||
key: "add",
|
||||
onClick: forward(address, asAdd)
|
||||
}, ["Add"])
|
||||
]),
|
||||
html.div({
|
||||
key: "entries"
|
||||
}, model.entries.map(entry => thunk(String(entry.id),
|
||||
viewEntry,
|
||||
entry,
|
||||
address)))
|
||||
])
|
|
@ -1,55 +1,45 @@
|
|||
/* @flow */
|
||||
import {Record, Union} from "typed-immutable";
|
||||
import {html, forward} from "reflex";
|
||||
|
||||
/*::
|
||||
import type {Address} from "reflex/type/signal";
|
||||
import type {VirtualNode} from "reflex/type/renderer";
|
||||
|
||||
export type Model = {value:number};
|
||||
export type Increment = {$typeof: 'Increment'};
|
||||
export type Decrement = {$typeof: 'Decrement'};
|
||||
export type Action = Increment|Decrement;
|
||||
import * as type from "../type/counter"
|
||||
*/
|
||||
|
||||
export const asIncrement/*:type.asIncrement*/ = () =>
|
||||
({type: "Counter.Increment"})
|
||||
export const asDecrement/*:type.asDecrement*/ = () =>
|
||||
({type: "Counter.Decrement"})
|
||||
|
||||
const set = /*::<T>*/(record/*:T*/, field/*:string*/, value/*:any*/)/*:T*/ => {
|
||||
const result = Object.assign({}, record)
|
||||
result[field] = value
|
||||
return result
|
||||
}
|
||||
|
||||
export const create = ({value}/*:Model*/)/*:Model*/ => ({value})
|
||||
export const Inc = ()/*:Increment*/ => ({$typeof: 'Increment'})
|
||||
export const Dec = ()/*:Decrement*/ => ({$typeof: 'Decrement'})
|
||||
export const create/*:type.create*/ = ({value}) =>
|
||||
({type: "Counter.Model", value})
|
||||
|
||||
export const update = (model/*:Model*/, action/*:Action*/)/*:Model*/ =>
|
||||
action.$typeof === 'Increment' ?
|
||||
set(model, 'value', model.value + 1) :
|
||||
action.$typeof === 'Decrement' ?
|
||||
set(model, 'value', model.value - 1) :
|
||||
export const update/*:type.update*/ = (model, action) =>
|
||||
action.type === "Counter.Increment" ?
|
||||
{type:model.type, value: model.value + 1} :
|
||||
action.type === "Counter.Decrement" ?
|
||||
{type:model.type, value: model.value - 1} :
|
||||
model
|
||||
|
||||
const counterStyle = {
|
||||
value: {
|
||||
fontWeight: 'bold'
|
||||
fontWeight: "bold"
|
||||
}
|
||||
}
|
||||
|
||||
// View
|
||||
export var view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ => {
|
||||
return html.span({key: 'counter'}, [
|
||||
export const view/*:type.view*/ = (model, address) =>
|
||||
html.span({key: "counter"}, [
|
||||
html.button({
|
||||
key: 'decrement',
|
||||
onClick: forward(address, Dec)
|
||||
key: "decrement",
|
||||
onClick: forward(address, asDecrement)
|
||||
}, ["-"]),
|
||||
html.span({
|
||||
key: 'value',
|
||||
key: "value",
|
||||
style: counterStyle.value,
|
||||
}, [String(model.value)]),
|
||||
html.button({
|
||||
key: 'increment',
|
||||
onClick: forward(address, Inc)
|
||||
key: "increment",
|
||||
onClick: forward(address, asIncrement)
|
||||
}, ["+"])
|
||||
]);
|
||||
};
|
||||
])
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
/* @flow */
|
||||
|
||||
import * as CounterList from "./list"
|
||||
import * as CounterList from "./counter-list"
|
||||
import {start} from "reflex"
|
||||
import {Renderer} from "reflex-virtual-dom-renderer"
|
||||
import {Renderer} from "reflex-virtual-dom-driver"
|
||||
|
||||
var app = start({
|
||||
const app = start({
|
||||
initial: CounterList.create(window.app != null ?
|
||||
window.app.model.value :
|
||||
{nextID: 0, entries: []}),
|
||||
|
@ -13,6 +13,6 @@ var app = start({
|
|||
});
|
||||
window.app = app
|
||||
|
||||
var renderer = new Renderer({target: document.body})
|
||||
const renderer = new Renderer({target: document.body})
|
||||
|
||||
app.view.subscribe(renderer.address)
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
/* @flow */
|
||||
|
||||
import * as Counter from "./counter";
|
||||
import {html, forward, thunk} from "reflex";
|
||||
|
||||
/*::
|
||||
import type {Address} from "reflex/type/signal";
|
||||
import type {VirtualNode} from "reflex/type/renderer";
|
||||
|
||||
export type ID = number;
|
||||
export type Entry = {
|
||||
id: ID,
|
||||
model: Counter.Model
|
||||
};
|
||||
|
||||
export type Model = {
|
||||
nextID: ID,
|
||||
entries: Array<Entry>
|
||||
};
|
||||
*/
|
||||
|
||||
export const create = ({nextID, entries}/*:Model*/)/*:Model*/ =>
|
||||
({nextID, entries});
|
||||
|
||||
// Update
|
||||
|
||||
/*::
|
||||
export type Add = {$typeof: "Add"}
|
||||
export type Remove = {$typeof: "Remove"}
|
||||
export type ModifyByID = {$typeof: "ByID", id: ID, act: Counter.Action}
|
||||
|
||||
export type Action = Add|Remove|ModifyByID
|
||||
*/
|
||||
|
||||
|
||||
export const AddAction = ()/*:Add*/ => ({$typeof: "Add"})
|
||||
export const RemoveAction = ()/*:Remove*/ => ({$typeof: "Remove"})
|
||||
export const by = (id/*:ID*/)/*:(act:Counter.Action)=>ModifyByID*/ =>
|
||||
act => ({$typeof: "ByID", id, act});
|
||||
|
||||
|
||||
export const add = (model/*:Model*/)/*:Model*/ =>
|
||||
create({
|
||||
nextID: model.nextID + 1,
|
||||
entries: model.entries.concat([{
|
||||
id: model.nextID,
|
||||
model: Counter.create({value: 0})
|
||||
}])
|
||||
})
|
||||
|
||||
export const remove = (model/*:Model*/)/*:Model*/ =>
|
||||
create({
|
||||
nextID: model.nextID,
|
||||
entries: model.entries.slice(1)
|
||||
})
|
||||
|
||||
export const modify = (model/*:Model*/, id/*:ID*/, action/*:Counter.Action*/)/*:Model*/ =>
|
||||
create({
|
||||
nextID: model.nextID,
|
||||
entries: model.entries.map(entry =>
|
||||
entry.id === id
|
||||
?
|
||||
{id: id, model: Counter.update(entry.model, action)}
|
||||
:
|
||||
entry)
|
||||
})
|
||||
|
||||
export const update = (model/*:Model*/, action/*:Action*/)/*:Model*/ =>
|
||||
action.$typeof === 'Add'
|
||||
?
|
||||
add(model, action)
|
||||
:
|
||||
action.$typeof === 'Remove'
|
||||
?
|
||||
remove(model, action)
|
||||
:
|
||||
action.$typeof === 'ByID'
|
||||
?
|
||||
modify(model, action.id, action.act)
|
||||
:
|
||||
model;
|
||||
|
||||
|
||||
// View
|
||||
const viewEntry = ({id, model}/*:Entry*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
|
||||
html.div({key: id}, [
|
||||
Counter.view(model, forward(address, by(id)))
|
||||
])
|
||||
|
||||
export const view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
|
||||
html.div({key: 'CounterList'}, [
|
||||
html.div({key: 'controls'}, [
|
||||
html.button({
|
||||
key: "remove",
|
||||
onClick: forward(address, RemoveAction)
|
||||
}, ["Remove"]),
|
||||
html.button({
|
||||
key: "add",
|
||||
onClick: forward(address, AddAction)
|
||||
}, ["Add"])
|
||||
]),
|
||||
html.div({
|
||||
key: "entries"
|
||||
}, model.entries.map(entry => thunk(String(entry.id),
|
||||
viewEntry,
|
||||
entry,
|
||||
address)))
|
||||
])
|
|
@ -0,0 +1,39 @@
|
|||
/* @flow */
|
||||
|
||||
import type {Address, VirtualNode} from "reflex/type"
|
||||
import * as Counter from "./counter"
|
||||
|
||||
export type ID = number;
|
||||
export type Entry = {
|
||||
type: "CounterList.Entry",
|
||||
id: ID,
|
||||
model: Counter.Model
|
||||
};
|
||||
|
||||
export type Model = {
|
||||
type: "CounterList.Model",
|
||||
nextID: ID,
|
||||
entries: Array<Entry>
|
||||
};
|
||||
|
||||
|
||||
export type Add = {type: "CounterList.Add"}
|
||||
export type Remove = {type: "CounterList.Remove"}
|
||||
export type ModifyByID = {type: "CounterList.ModifyByID",
|
||||
id:ID,
|
||||
act:Counter.Action}
|
||||
export type Action = Add|Remove|ModifyByID
|
||||
|
||||
|
||||
export type asAdd = () => Add
|
||||
export type asRemove = () => Remove
|
||||
export type asBy = (id:ID) => (act:Counter.Action) => ModifyByID
|
||||
|
||||
export type create = (options:{nextID:ID, entries:Array<Entry>}) => Model
|
||||
export type add = (model:Model) => Model
|
||||
export type remove = (model:Model) => Model
|
||||
export type modify = (model:Model, id:ID, action:Counter.Action) => Model
|
||||
export type update = (model:Model, action:Action) => Model
|
||||
|
||||
export type viewEntry = (entry:Entry, address:Address<Action>) => VirtualNode
|
||||
export type view = (model:Model, address:Address<Action>) => VirtualNode
|
|
@ -0,0 +1,15 @@
|
|||
/* @flow */
|
||||
|
||||
import type {Address, VirtualNode} from "reflex/type"
|
||||
|
||||
export type Model = {type: "Counter.Model", value:number}
|
||||
export type Increment = {type: "Counter.Increment"}
|
||||
export type Decrement = {type: "Counter.Decrement"}
|
||||
export type Action = Increment|Decrement
|
||||
|
||||
export type asIncrement = () => Increment
|
||||
export type asDecrement = () => Decrement
|
||||
|
||||
export type create = (options:{value:number}) => Model
|
||||
export type update = (model:Model, action:Action) => Model
|
||||
export type view = (model:Model, address:Address<Action>) => VirtualNode
|
|
@ -2,15 +2,15 @@
|
|||
.*/src/test/.*
|
||||
.*/dist/.*
|
||||
.*/node_modules/reflex/examples/.*
|
||||
.*/node_modules/reflex-virtual-dom-renderer/lib/.*
|
||||
.*/node_modules/reflex-virtual-dom-driver/lib/.*
|
||||
.*/node_modules/reflex/lib/.*
|
||||
|
||||
[libs]
|
||||
./node_modules/reflex/interfaces/
|
||||
./node_modules/reflex-virtual-dom-renderer/interfaces/
|
||||
./node_modules/reflex-virtual-dom-driver/interfaces/
|
||||
|
||||
[include]
|
||||
|
||||
[options]
|
||||
module.name_mapper='reflex-virtual-dom-renderer' -> 'reflex-virtual-dom-renderer/src/index'
|
||||
module.name_mapper='reflex-virtual-dom-driver' -> 'reflex-virtual-dom-driver/src/index'
|
||||
module.name_mapper='reflex' -> 'reflex/src/index'
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"reflex": "latest",
|
||||
"reflex-virtual-dom-renderer": "latest"
|
||||
"reflex-virtual-dom-driver": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "11.0.1",
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
// @flow
|
||||
|
||||
import * as Counter from "./counter";
|
||||
import {html, forward} from "reflex";
|
||||
|
||||
/*::
|
||||
import type {VirtualNode, Address} from "reflex/type"
|
||||
import * as type from "../type/counter-pair"
|
||||
*/
|
||||
|
||||
|
||||
export const asTop/*:type.asTop*/ = act =>
|
||||
({type: "CounterPair.Top", act})
|
||||
|
||||
export const asBottom/*:type.asBottom*/ = act =>
|
||||
({type: "CounterPair.Bottom", act})
|
||||
|
||||
export const asReset/*:type.asReset*/ = () =>
|
||||
({type: "CounterPair.Reset"})
|
||||
|
||||
|
||||
export const create/*:type.create*/ = ({top, bottom}) => ({
|
||||
type: "CounterPair.Model",
|
||||
top: Counter.create(top),
|
||||
bottom: Counter.create(bottom)
|
||||
})
|
||||
|
||||
// Note last two functions are wrapped in too many parenthesis with type
|
||||
// casting comments at the end due to a bug in type checker: facebook/flow#953
|
||||
|
||||
export const update/*:type.update*/ = (model, action) =>
|
||||
action.type === "CounterPair.Top" ?
|
||||
create({top: Counter.update(model.top, action.act),
|
||||
bottom: model.bottom}) :
|
||||
action.type === "CounterPair.Bottom" ?
|
||||
create({top: model.top,
|
||||
bottom: Counter.update(model.bottom, action.act)}) :
|
||||
action.type === "CounterPair.Reset" ?
|
||||
create({top: {value: 0},
|
||||
bottom: {value: 0}}) :
|
||||
model
|
||||
|
||||
// View
|
||||
export const view/*:type.view*/ = (model, address) =>
|
||||
html.div({key: "counter-pair"}, [
|
||||
html.div({key: "top"}, [
|
||||
Counter.view(model.top, forward(address, asTop))
|
||||
]),
|
||||
html.div({key: "bottom"}, [
|
||||
Counter.view(model.bottom, forward(address, asBottom)),
|
||||
]),
|
||||
html.div({key: "controls"}, [
|
||||
html.button({
|
||||
key: "reset",
|
||||
onClick: forward(address, asReset)
|
||||
}, ["Reset"])
|
||||
])
|
||||
])
|
|
@ -1,55 +1,45 @@
|
|||
/* @flow */
|
||||
import {Record, Union} from "typed-immutable";
|
||||
import {html, forward} from "reflex";
|
||||
|
||||
/*::
|
||||
import type {Address} from "reflex/type/signal";
|
||||
import type {VirtualNode} from "reflex/type/renderer";
|
||||
|
||||
export type Model = {value:number};
|
||||
export type Increment = {$typeof: 'Increment'};
|
||||
export type Decrement = {$typeof: 'Decrement'};
|
||||
export type Action = Increment|Decrement;
|
||||
import * as type from "../type/counter"
|
||||
*/
|
||||
|
||||
export const asIncrement/*:type.asIncrement*/ = () =>
|
||||
({type: "Counter.Increment"})
|
||||
export const asDecrement/*:type.asDecrement*/ = () =>
|
||||
({type: "Counter.Decrement"})
|
||||
|
||||
const set = /*::<T>*/(record/*:T*/, field/*:string*/, value/*:any*/)/*:T*/ => {
|
||||
const result = Object.assign({}, record)
|
||||
result[field] = value
|
||||
return result
|
||||
}
|
||||
|
||||
export const create = ({value}/*:Model*/)/*:Model*/ => ({value})
|
||||
export const Inc = ()/*:Increment*/ => ({$typeof: 'Increment'})
|
||||
export const Dec = ()/*:Decrement*/ => ({$typeof: 'Decrement'})
|
||||
export const create/*:type.create*/ = ({value}) =>
|
||||
({type: "Counter.Model", value})
|
||||
|
||||
export const update = (model/*:Model*/, action/*:Action*/)/*:Model*/ =>
|
||||
action.$typeof === 'Increment' ?
|
||||
set(model, 'value', model.value + 1) :
|
||||
action.$typeof === 'Decrement' ?
|
||||
set(model, 'value', model.value - 1) :
|
||||
export const update/*:type.update*/ = (model, action) =>
|
||||
action.type === "Counter.Increment" ?
|
||||
{type:model.type, value: model.value + 1} :
|
||||
action.type === "Counter.Decrement" ?
|
||||
{type:model.type, value: model.value - 1} :
|
||||
model
|
||||
|
||||
const counterStyle = {
|
||||
value: {
|
||||
fontWeight: 'bold'
|
||||
fontWeight: "bold"
|
||||
}
|
||||
}
|
||||
|
||||
// View
|
||||
export var view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ => {
|
||||
return html.span({key: 'counter'}, [
|
||||
export const view/*:type.view*/ = (model, address) =>
|
||||
html.span({key: "counter"}, [
|
||||
html.button({
|
||||
key: 'decrement',
|
||||
onClick: forward(address, Dec)
|
||||
key: "decrement",
|
||||
onClick: forward(address, asDecrement)
|
||||
}, ["-"]),
|
||||
html.span({
|
||||
key: 'value',
|
||||
key: "value",
|
||||
style: counterStyle.value,
|
||||
}, [String(model.value)]),
|
||||
html.button({
|
||||
key: 'increment',
|
||||
onClick: forward(address, Inc)
|
||||
key: "increment",
|
||||
onClick: forward(address, asIncrement)
|
||||
}, ["+"])
|
||||
]);
|
||||
};
|
||||
])
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
/* @flow */
|
||||
|
||||
import * as Pair from "./pair"
|
||||
import * as CounterPair from "./counter-pair"
|
||||
import {start} from "reflex"
|
||||
import {Renderer} from "reflex-virtual-dom-renderer"
|
||||
import {Renderer} from "reflex-virtual-dom-driver"
|
||||
|
||||
var app = start({
|
||||
initial: Pair.create(window.app != null ?
|
||||
window.app.model.value :
|
||||
{top: {value: 0}
|
||||
,bottom: {value: 0}}),
|
||||
update: Pair.update,
|
||||
view: Pair.view
|
||||
initial: CounterPair.create(window.app != null ?
|
||||
window.app.model.value :
|
||||
{top: {value: 0},
|
||||
bottom: {value: 0}}),
|
||||
update: CounterPair.update,
|
||||
view: CounterPair.view
|
||||
});
|
||||
window.app = app
|
||||
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
/* @flow */
|
||||
import * as Counter from "./counter";
|
||||
import {html, forward} from "reflex";
|
||||
|
||||
/*::
|
||||
import type {Address} from "reflex/type/signal";
|
||||
import type {VirtualNode} from "reflex/type/renderer";
|
||||
|
||||
export type Model = {top: Counter.Model; bottom: Counter.Model};
|
||||
export type Top = {$typeof: 'Top', act: Counter.Action};
|
||||
export type Bottom = {$typeof: 'Bottom', act: Counter.Action};
|
||||
export type Reset = {$typeof: 'Reset'};
|
||||
export type Action = Reset|Top|Bottom;
|
||||
*/
|
||||
|
||||
|
||||
export const ModifyTop = (act/*:Counter.Action*/)/*:Top*/ =>
|
||||
({$typeof: "Top", act})
|
||||
|
||||
export const ModifyBottom = (act/*:Counter.Action*/)/*:Bottom*/ =>
|
||||
({$typeof: "Bottom", act})
|
||||
|
||||
export const Clear = ()/*:Reset*/ =>
|
||||
({$typeof: "Reset" })
|
||||
|
||||
|
||||
const set = /*::<T>*/(record/*:T*/, field/*:string*/, value/*:any*/)/*:T*/ => {
|
||||
const result = Object.assign({}, record)
|
||||
result[field] = value
|
||||
return result
|
||||
}
|
||||
|
||||
export const create = ({top, bottom}/*:Model*/)/*:Model*/ => ({
|
||||
top: Counter.create(top),
|
||||
bottom: Counter.create(bottom)
|
||||
})
|
||||
|
||||
export const update = (model/*:Model*/, action/*:Action*/)/*:Model*/ =>
|
||||
action.$typeof === 'Top' ?
|
||||
set(model, 'top', Counter.update(model.top, action.act)) :
|
||||
action.$typeof === 'Bottom' ?
|
||||
set(model, 'bottom', Counter.update(model.bottom, action.act)) :
|
||||
action.$typeof === 'Reset' ?
|
||||
create({top: {value: 0},
|
||||
bottom: {value: 0}}) :
|
||||
model
|
||||
|
||||
// View
|
||||
export var view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ => {
|
||||
return html.div({key: 'counter-pair'}, [
|
||||
html.div({key: 'top'}, [
|
||||
Counter.view(model.top, forward(address, ModifyTop))
|
||||
]),
|
||||
html.div({key: 'bottom'}, [
|
||||
Counter.view(model.bottom, forward(address, ModifyBottom)),
|
||||
]),
|
||||
html.div({key: 'controls'}, [
|
||||
html.button({
|
||||
key: 'reset',
|
||||
onClick: forward(address, Clear)
|
||||
}, ["Reset"])
|
||||
])
|
||||
]);
|
||||
};
|
|
@ -0,0 +1,37 @@
|
|||
// @flow
|
||||
|
||||
import type {Address, VirtualNode} from "reflex/type"
|
||||
import * as Counter from "./counter"
|
||||
|
||||
export type Model = {
|
||||
type: "CounterPair.Model",
|
||||
top: Counter.Model,
|
||||
bottom: Counter.Model
|
||||
}
|
||||
|
||||
export type Top = {
|
||||
type: "CounterPair.Top",
|
||||
act: Counter.Action
|
||||
}
|
||||
export type Bottom = {
|
||||
type: "CounterPair.Bottom",
|
||||
act: any // Workaround for facebook/flow#953
|
||||
// act: Counter.Action
|
||||
}
|
||||
export type Reset = {type: "CounterPair.Reset"}
|
||||
export type Action
|
||||
= Top
|
||||
| Bottom
|
||||
| Reset
|
||||
|
||||
export type asTop = (action:Counter.Action) => Top
|
||||
export type asBottom = (action:Counter.Action) => Bottom
|
||||
export type asReset = () => Reset
|
||||
|
||||
|
||||
export type create = (options:{top:{value:number}, bottom:{value:number}}) =>
|
||||
Model
|
||||
export type update = (model:Model, action:Action) => Model
|
||||
|
||||
|
||||
export type view = (model:Model, address:Address<Action>) => VirtualNode
|
|
@ -0,0 +1,15 @@
|
|||
/* @flow */
|
||||
|
||||
import type {Address, VirtualNode} from "reflex/type"
|
||||
|
||||
export type Model = {type: "Counter.Model", value:number}
|
||||
export type Increment = {type: "Counter.Increment"}
|
||||
export type Decrement = {type: "Counter.Decrement"}
|
||||
export type Action = Increment|Decrement
|
||||
|
||||
export type asIncrement = () => Increment
|
||||
export type asDecrement = () => Decrement
|
||||
|
||||
export type create = (options:{value:number}) => Model
|
||||
export type update = (model:Model, action:Action) => Model
|
||||
export type view = (model:Model, address:Address<Action>) => VirtualNode
|
|
@ -2,15 +2,15 @@
|
|||
.*/src/test/.*
|
||||
.*/dist/.*
|
||||
.*/node_modules/reflex/examples/.*
|
||||
.*/node_modules/reflex-virtual-dom-renderer/lib/.*
|
||||
.*/node_modules/reflex-virtual-dom-driver/lib/.*
|
||||
.*/node_modules/reflex/lib/.*
|
||||
|
||||
[libs]
|
||||
./node_modules/reflex/interfaces/
|
||||
./node_modules/reflex-virtual-dom-renderer/interfaces/
|
||||
./node_modules/reflex-virtual-dom-driver/interfaces/
|
||||
|
||||
[include]
|
||||
|
||||
[options]
|
||||
module.name_mapper='reflex-virtual-dom-renderer' -> 'reflex-virtual-dom-renderer/src/index'
|
||||
module.name_mapper='reflex-virtual-dom-driver' -> 'reflex-virtual-dom-driver/src/index'
|
||||
module.name_mapper='reflex' -> 'reflex/src/index'
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"reflex": "latest",
|
||||
"reflex-virtual-dom-renderer": "latest"
|
||||
"reflex-virtual-dom-driver": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "11.0.1",
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/* @flow */
|
||||
|
||||
import * as Counter from "./counter";
|
||||
import {html, forward, thunk} from "reflex";
|
||||
|
||||
/*::
|
||||
import * as type from "../type/counter-list"
|
||||
*/
|
||||
|
||||
export const asAdd/*:type.asAdd*/ = () => ({type: "CounterList.Add"})
|
||||
export const asRemove/*:type.asRemove*/ = () => ({type: "CounterList.Remove"})
|
||||
export const asBy/*:type.asBy*/ = id => act =>
|
||||
({type: "CounterList.ModifyByID", id, act})
|
||||
|
||||
|
||||
export const create/*:type.create*/ = ({nextID, entries}) =>
|
||||
({type: "CounterList.Model", nextID, entries})
|
||||
|
||||
export const add/*:type.add*/ = model => create({
|
||||
nextID: model.nextID + 1,
|
||||
entries: model.entries.concat([{
|
||||
type: "CounterList.Entry",
|
||||
id: model.nextID,
|
||||
model: Counter.create({value: 0})
|
||||
}])
|
||||
})
|
||||
|
||||
export const remove/*:type.remove*/ = model => create({
|
||||
nextID: model.nextID,
|
||||
entries: model.entries.slice(1)
|
||||
})
|
||||
|
||||
export const modify/*:type.modify*/ = (model, id, action) => create({
|
||||
nextID: model.nextID,
|
||||
entries: model.entries.map(entry =>
|
||||
entry.id !== id ?
|
||||
entry :
|
||||
{type: entry.type, id: id, model: Counter.update(entry.model, action)})
|
||||
})
|
||||
|
||||
export const update/*:type.update*/ = (model, action) =>
|
||||
action.type === "CounterList.Add" ?
|
||||
add(model, action) :
|
||||
action.type === "CounterList.Remove" ?
|
||||
remove(model, action) :
|
||||
action.type === "CounterList.ModifyByID" ?
|
||||
modify(model, action.id, action.act) :
|
||||
model;
|
||||
|
||||
|
||||
// View
|
||||
const viewEntry/*:type.viewEntry*/ = ({id, model}, address) =>
|
||||
html.div({key: id}, [
|
||||
Counter.view(model, forward(address, asBy(id)))
|
||||
])
|
||||
|
||||
export const view/*:type.view*/ = (model, address) =>
|
||||
html.div({key: "CounterList"}, [
|
||||
html.div({key: "controls"}, [
|
||||
html.button({
|
||||
key: "remove",
|
||||
onClick: forward(address, asRemove)
|
||||
}, ["Remove"]),
|
||||
html.button({
|
||||
key: "add",
|
||||
onClick: forward(address, asAdd)
|
||||
}, ["Add"])
|
||||
]),
|
||||
html.div({
|
||||
key: "entries"
|
||||
}, model.entries.map(entry => thunk(String(entry.id),
|
||||
viewEntry,
|
||||
entry,
|
||||
address)))
|
||||
])
|
|
@ -0,0 +1,48 @@
|
|||
/* @flow */
|
||||
|
||||
import * as CounterList from "./counter-list";
|
||||
import * as Counter from "./counter";
|
||||
import {html, forward, thunk} from "reflex";
|
||||
|
||||
/*:: import * as type from "../type/counter-set" */
|
||||
|
||||
export const create = CounterList.create
|
||||
|
||||
export const asRemoveBy/*:type.asRemoveBy*/ = id => () =>
|
||||
({type: "CounterSet.RemoveByID", id})
|
||||
|
||||
export const removeByID/*:type.removeByID*/ = (model, id) => create({
|
||||
nextID: model.nextID,
|
||||
entries: model.entries.filter(entry => entry.id != id)
|
||||
})
|
||||
|
||||
export const update/*:type.update*/ = (model, action) =>
|
||||
action.type === "CounterSet.RemoveByID" ?
|
||||
removeByID(model, action.id) :
|
||||
CounterList.update(model, action)
|
||||
|
||||
|
||||
const viewEntry/*:type.viewEntry*/ = ({id, model}, address) =>
|
||||
html.div({key: id}, [
|
||||
Counter.view(model, forward(address, CounterList.asBy(id))),
|
||||
html.button({
|
||||
key: "remove",
|
||||
onClick: forward(address, asRemoveBy(id))
|
||||
}, ["x"])
|
||||
])
|
||||
|
||||
export const view/*:type.view*/ = (model, address) =>
|
||||
html.div({key: "CounterList"}, [
|
||||
html.div({key: "controls"}, [
|
||||
html.button({
|
||||
key: "add",
|
||||
onClick: forward(address, CounterList.asAdd)
|
||||
}, ["Add"])
|
||||
]),
|
||||
html.div({
|
||||
key: "entries"
|
||||
}, model.entries.map(entry => thunk(String(entry.id),
|
||||
viewEntry,
|
||||
entry,
|
||||
address)))
|
||||
])
|
|
@ -1,55 +1,45 @@
|
|||
/* @flow */
|
||||
import {Record, Union} from "typed-immutable";
|
||||
import {html, forward} from "reflex";
|
||||
|
||||
/*::
|
||||
import type {Address} from "reflex/type/signal";
|
||||
import type {VirtualNode} from "reflex/type/renderer";
|
||||
|
||||
export type Model = {value:number};
|
||||
export type Increment = {$typeof: 'Increment'};
|
||||
export type Decrement = {$typeof: 'Decrement'};
|
||||
export type Action = Increment|Decrement;
|
||||
import * as type from "../type/counter"
|
||||
*/
|
||||
|
||||
export const asIncrement/*:type.asIncrement*/ = () =>
|
||||
({type: "Counter.Increment"})
|
||||
export const asDecrement/*:type.asDecrement*/ = () =>
|
||||
({type: "Counter.Decrement"})
|
||||
|
||||
const set = /*::<T>*/(record/*:T*/, field/*:string*/, value/*:any*/)/*:T*/ => {
|
||||
const result = Object.assign({}, record)
|
||||
result[field] = value
|
||||
return result
|
||||
}
|
||||
|
||||
export const create = ({value}/*:Model*/)/*:Model*/ => ({value})
|
||||
export const Inc = ()/*:Increment*/ => ({$typeof: 'Increment'})
|
||||
export const Dec = ()/*:Decrement*/ => ({$typeof: 'Decrement'})
|
||||
export const create/*:type.create*/ = ({value}) =>
|
||||
({type: "Counter.Model", value})
|
||||
|
||||
export const update = (model/*:Model*/, action/*:Action*/)/*:Model*/ =>
|
||||
action.$typeof === 'Increment' ?
|
||||
set(model, 'value', model.value + 1) :
|
||||
action.$typeof === 'Decrement' ?
|
||||
set(model, 'value', model.value - 1) :
|
||||
export const update/*:type.update*/ = (model, action) =>
|
||||
action.type === "Counter.Increment" ?
|
||||
{type:model.type, value: model.value + 1} :
|
||||
action.type === "Counter.Decrement" ?
|
||||
{type:model.type, value: model.value - 1} :
|
||||
model
|
||||
|
||||
const counterStyle = {
|
||||
value: {
|
||||
fontWeight: 'bold'
|
||||
fontWeight: "bold"
|
||||
}
|
||||
}
|
||||
|
||||
// View
|
||||
export var view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ => {
|
||||
return html.span({key: 'counter'}, [
|
||||
export const view/*:type.view*/ = (model, address) =>
|
||||
html.span({key: "counter"}, [
|
||||
html.button({
|
||||
key: 'decrement',
|
||||
onClick: forward(address, Dec)
|
||||
key: "decrement",
|
||||
onClick: forward(address, asDecrement)
|
||||
}, ["-"]),
|
||||
html.span({
|
||||
key: 'value',
|
||||
key: "value",
|
||||
style: counterStyle.value,
|
||||
}, [String(model.value)]),
|
||||
html.button({
|
||||
key: 'increment',
|
||||
onClick: forward(address, Inc)
|
||||
key: "increment",
|
||||
onClick: forward(address, asIncrement)
|
||||
}, ["+"])
|
||||
]);
|
||||
};
|
||||
])
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
/* @flow */
|
||||
|
||||
import * as CounterSet from "./set"
|
||||
import * as CounterSet from "./counter-set"
|
||||
import {start} from "reflex"
|
||||
import {Renderer} from "reflex-virtual-dom-renderer"
|
||||
import {Renderer} from "reflex-virtual-dom-driver"
|
||||
|
||||
var app = start({
|
||||
initial: CounterSet.create(window.app != null ?
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
/* @flow */
|
||||
|
||||
import * as Counter from "./counter";
|
||||
import {html, forward, thunk} from "reflex";
|
||||
|
||||
/*::
|
||||
import type {Address} from "reflex/type/signal";
|
||||
import type {VirtualNode} from "reflex/type/renderer";
|
||||
|
||||
export type ID = number;
|
||||
export type Entry = {
|
||||
id: ID,
|
||||
model: Counter.Model
|
||||
};
|
||||
|
||||
export type Model = {
|
||||
nextID: ID,
|
||||
entries: Array<Entry>
|
||||
};
|
||||
*/
|
||||
|
||||
export const create = ({nextID, entries}/*:Model*/)/*:Model*/ =>
|
||||
({nextID, entries});
|
||||
|
||||
// Update
|
||||
|
||||
/*::
|
||||
export type Add = {$typeof: "Add"}
|
||||
export type Remove = {$typeof: "Remove"}
|
||||
export type ModifyByID = {$typeof: "ByID", id: ID, act: Counter.Action}
|
||||
|
||||
export type Action = Add|Remove|ModifyByID
|
||||
*/
|
||||
|
||||
|
||||
export const AddAction = ()/*:Add*/ => ({$typeof: "Add"})
|
||||
export const RemoveAction = ()/*:Remove*/ => ({$typeof: "Remove"})
|
||||
export const by = (id/*:ID*/)/*:(act:Counter.Action)=>ModifyByID*/ =>
|
||||
act => ({$typeof: "ByID", id, act});
|
||||
|
||||
|
||||
export const add = (model/*:Model*/)/*:Model*/ =>
|
||||
create({
|
||||
nextID: model.nextID + 1,
|
||||
entries: model.entries.concat([{
|
||||
id: model.nextID,
|
||||
model: Counter.create({value: 0})
|
||||
}])
|
||||
})
|
||||
|
||||
export const remove = (model/*:Model*/)/*:Model*/ =>
|
||||
create({
|
||||
nextID: model.nextID,
|
||||
entries: model.entries.slice(1)
|
||||
})
|
||||
|
||||
export const modify = (model/*:Model*/, id/*:ID*/, action/*:Counter.Action*/)/*:Model*/ =>
|
||||
create({
|
||||
nextID: model.nextID,
|
||||
entries: model.entries.map(entry =>
|
||||
entry.id === id
|
||||
?
|
||||
{id: id, model: Counter.update(entry.model, action)}
|
||||
:
|
||||
entry)
|
||||
})
|
||||
|
||||
export const update = (model/*:Model*/, action/*:Action*/)/*:Model*/ =>
|
||||
action.$typeof === 'Add'
|
||||
?
|
||||
add(model, action)
|
||||
:
|
||||
action.$typeof === 'Remove'
|
||||
?
|
||||
remove(model, action)
|
||||
:
|
||||
action.$typeof === 'ByID'
|
||||
?
|
||||
modify(model, action.id, action.act)
|
||||
:
|
||||
model;
|
||||
|
||||
|
||||
// View
|
||||
const viewEntry = ({id, model}/*:Entry*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
|
||||
html.div({key: id}, [
|
||||
Counter.view(model, forward(address, by(id)))
|
||||
])
|
||||
|
||||
export const view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
|
||||
html.div({key: 'CounterList'}, [
|
||||
html.div({key: 'controls'}, [
|
||||
html.button({
|
||||
key: "remove",
|
||||
onClick: forward(address, RemoveAction)
|
||||
}, ["Remove"]),
|
||||
html.button({
|
||||
key: "add",
|
||||
onClick: forward(address, AddAction)
|
||||
}, ["Add"])
|
||||
]),
|
||||
html.div({
|
||||
key: "entries"
|
||||
}, model.entries.map(entry => thunk(String(entry.id),
|
||||
viewEntry,
|
||||
entry,
|
||||
address)))
|
||||
])
|
|
@ -1,68 +0,0 @@
|
|||
/* @flow */
|
||||
|
||||
import * as CounterList from "./list";
|
||||
import * as Counter from "./counter";
|
||||
import {html, forward, thunk} from "reflex";
|
||||
|
||||
/*::
|
||||
import type {Address} from "reflex/type/signal";
|
||||
import type {VirtualNode} from "reflex/type/renderer";
|
||||
|
||||
export type ID = number;
|
||||
export type Entry = CounterList.Entry;
|
||||
export type Model = CounterList.Model;
|
||||
*/
|
||||
|
||||
export const create = ({nextID, entries}/*:Model*/)/*:Model*/ =>
|
||||
({nextID, entries});
|
||||
|
||||
// Update
|
||||
|
||||
/*::
|
||||
export type RemoveByID = {$typeof: "RemoveByID", id: ID}
|
||||
export type Action = RemoveByID|CounterList.Action
|
||||
*/
|
||||
|
||||
|
||||
export const removeForID = (id/*:ID*/)/*:()=>RemoveByID*/ => () =>
|
||||
({$typeof: "RemoveByID", id})
|
||||
|
||||
export const removeByID = (model/*:Model*/, id/*:ID*/)/*:Model*/ =>
|
||||
create({
|
||||
nextID: model.nextID,
|
||||
entries: model.entries.filter(entry => entry.id != id)
|
||||
})
|
||||
|
||||
export const update = (model/*:Model*/, action/*:Action*/)/*:Model*/ =>
|
||||
action.$typeof === 'RemoveByID'
|
||||
?
|
||||
removeByID(model, action.id)
|
||||
:
|
||||
CounterList.update(model, action)
|
||||
|
||||
|
||||
// View
|
||||
const viewEntry = ({id, model}/*:Entry*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
|
||||
html.div({key: id}, [
|
||||
Counter.view(model, forward(address, CounterList.by(id))),
|
||||
html.button({
|
||||
key: 'remove',
|
||||
onClick: forward(address, removeForID(id))
|
||||
}, ['x'])
|
||||
])
|
||||
|
||||
export const view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
|
||||
html.div({key: 'CounterList'}, [
|
||||
html.div({key: 'controls'}, [
|
||||
html.button({
|
||||
key: "add",
|
||||
onClick: forward(address, CounterList.AddAction)
|
||||
}, ["Add"])
|
||||
]),
|
||||
html.div({
|
||||
key: "entries"
|
||||
}, model.entries.map(entry => thunk(String(entry.id),
|
||||
viewEntry,
|
||||
entry,
|
||||
address)))
|
||||
])
|
|
@ -0,0 +1,39 @@
|
|||
/* @flow */
|
||||
|
||||
import type {Address, VirtualNode} from "reflex/type"
|
||||
import * as Counter from "./counter"
|
||||
|
||||
export type ID = number;
|
||||
export type Entry = {
|
||||
type: "CounterList.Entry",
|
||||
id: ID,
|
||||
model: Counter.Model
|
||||
};
|
||||
|
||||
export type Model = {
|
||||
type: "CounterList.Model",
|
||||
nextID: ID,
|
||||
entries: Array<Entry>
|
||||
};
|
||||
|
||||
|
||||
export type Add = {type: "CounterList.Add"}
|
||||
export type Remove = {type: "CounterList.Remove"}
|
||||
export type ModifyByID = {type: "CounterList.ModifyByID",
|
||||
id:ID,
|
||||
act:Counter.Action}
|
||||
export type Action = Add|Remove|ModifyByID
|
||||
|
||||
|
||||
export type asAdd = () => Add
|
||||
export type asRemove = () => Remove
|
||||
export type asBy = (id:ID) => (act:Counter.Action) => ModifyByID
|
||||
|
||||
export type create = (options:{nextID:ID, entries:Array<Entry>}) => Model
|
||||
export type add = (model:Model) => Model
|
||||
export type remove = (model:Model) => Model
|
||||
export type modify = (model:Model, id:ID, action:Counter.Action) => Model
|
||||
export type update = (model:Model, action:Action) => Model
|
||||
|
||||
export type viewEntry = (entry:Entry, address:Address<Action>) => VirtualNode
|
||||
export type view = (model:Model, address:Address<Action>) => VirtualNode
|
|
@ -0,0 +1,23 @@
|
|||
/* @flow */
|
||||
|
||||
import * as Counter from "./counter"
|
||||
import * as CounterList from "./counter-list"
|
||||
import type {Address, VirtualNode} from "reflex/type";
|
||||
|
||||
export type ID = CounterList.ID
|
||||
export type Entry = CounterList.Entry
|
||||
export type Model = CounterList.Model
|
||||
|
||||
export type RemoveByID = {type: "CounterSet.RemoveByID", id:ID}
|
||||
export type Action
|
||||
= RemoveByID
|
||||
| CounterList.Action
|
||||
|
||||
export type asRemoveBy = (id:ID) => () => RemoveByID
|
||||
|
||||
|
||||
export type update = (model:Model, action:Action) => Model
|
||||
export type removeByID = (model:Model, id:ID) => Model
|
||||
|
||||
export type viewEntry = (entry:Entry, address:Address<Action>) => VirtualNode
|
||||
export type view = (entry:Model, address:Address<Action>) => VirtualNode
|
|
@ -0,0 +1,15 @@
|
|||
/* @flow */
|
||||
|
||||
import type {Address, VirtualNode} from "reflex/type"
|
||||
|
||||
export type Model = {type: "Counter.Model", value:number}
|
||||
export type Increment = {type: "Counter.Increment"}
|
||||
export type Decrement = {type: "Counter.Decrement"}
|
||||
export type Action = Increment|Decrement
|
||||
|
||||
export type asIncrement = () => Increment
|
||||
export type asDecrement = () => Decrement
|
||||
|
||||
export type create = (options:{value:number}) => Model
|
||||
export type update = (model:Model, action:Action) => Model
|
||||
export type view = (model:Model, address:Address<Action>) => VirtualNode
|
|
@ -2,15 +2,15 @@
|
|||
.*/src/test/.*
|
||||
.*/dist/.*
|
||||
.*/node_modules/reflex/examples/.*
|
||||
.*/node_modules/reflex-virtual-dom-renderer/lib/.*
|
||||
.*/node_modules/reflex-virtual-dom-driver/lib/.*
|
||||
.*/node_modules/reflex/lib/.*
|
||||
|
||||
[libs]
|
||||
./node_modules/reflex/interfaces/
|
||||
./node_modules/reflex-virtual-dom-renderer/interfaces/
|
||||
./node_modules/reflex-virtual-dom-driver/interfaces/
|
||||
|
||||
[include]
|
||||
|
||||
[options]
|
||||
module.name_mapper='reflex-virtual-dom-renderer' -> 'reflex-virtual-dom-renderer/src/index'
|
||||
module.name_mapper='reflex-virtual-dom-driver' -> 'reflex-virtual-dom-driver/src/index'
|
||||
module.name_mapper='reflex' -> 'reflex/src/index'
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"reflex": "latest",
|
||||
"reflex-virtual-dom-renderer": "latest"
|
||||
"reflex-virtual-dom-driver": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "11.0.1",
|
||||
|
|
|
@ -1,55 +1,45 @@
|
|||
/* @flow */
|
||||
import {Record, Union} from "typed-immutable";
|
||||
import {html, forward} from "reflex";
|
||||
|
||||
/*::
|
||||
import type {Address} from "reflex/type/signal";
|
||||
import type {VirtualNode} from "reflex/type/renderer";
|
||||
|
||||
export type Model = {value:number};
|
||||
export type Increment = {$typeof: 'Increment'};
|
||||
export type Decrement = {$typeof: 'Decrement'};
|
||||
export type Action = Increment|Decrement;
|
||||
import * as type from "../type/counter"
|
||||
*/
|
||||
|
||||
export const asIncrement/*:type.asIncrement*/ = () =>
|
||||
({type: "Counter.Increment"})
|
||||
export const asDecrement/*:type.asDecrement*/ = () =>
|
||||
({type: "Counter.Decrement"})
|
||||
|
||||
const set = /*::<T>*/(record/*:T*/, field/*:string*/, value/*:any*/)/*:T*/ => {
|
||||
const result = Object.assign({}, record)
|
||||
result[field] = value
|
||||
return result
|
||||
}
|
||||
|
||||
export const create = ({value}/*:Model*/)/*:Model*/ => ({value})
|
||||
export const Inc = ()/*:Increment*/ => ({$typeof: 'Increment'})
|
||||
export const Dec = ()/*:Decrement*/ => ({$typeof: 'Decrement'})
|
||||
export const create/*:type.create*/ = ({value}) =>
|
||||
({type: "Counter.Model", value})
|
||||
|
||||
export const update = (model/*:Model*/, action/*:Action*/)/*:Model*/ =>
|
||||
action.$typeof === 'Increment' ?
|
||||
set(model, 'value', model.value + 1) :
|
||||
action.$typeof === 'Decrement' ?
|
||||
set(model, 'value', model.value - 1) :
|
||||
export const update/*:type.update*/ = (model, action) =>
|
||||
action.type === "Counter.Increment" ?
|
||||
{type:model.type, value: model.value + 1} :
|
||||
action.type === "Counter.Decrement" ?
|
||||
{type:model.type, value: model.value - 1} :
|
||||
model
|
||||
|
||||
const counterStyle = {
|
||||
value: {
|
||||
fontWeight: 'bold'
|
||||
fontWeight: "bold"
|
||||
}
|
||||
}
|
||||
|
||||
// View
|
||||
export var view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ => {
|
||||
return html.span({key: 'counter'}, [
|
||||
export const view/*:type.view*/ = (model, address) =>
|
||||
html.span({key: "counter"}, [
|
||||
html.button({
|
||||
key: 'decrement',
|
||||
onClick: forward(address, Dec)
|
||||
key: "decrement",
|
||||
onClick: forward(address, asDecrement)
|
||||
}, ["-"]),
|
||||
html.span({
|
||||
key: 'value',
|
||||
key: "value",
|
||||
style: counterStyle.value,
|
||||
}, [String(model.value)]),
|
||||
html.button({
|
||||
key: 'increment',
|
||||
onClick: forward(address, Inc)
|
||||
key: "increment",
|
||||
onClick: forward(address, asIncrement)
|
||||
}, ["+"])
|
||||
]);
|
||||
};
|
||||
])
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
import * as Counter from "./counter"
|
||||
import {start} from "reflex"
|
||||
import {Renderer} from "reflex-virtual-dom-renderer"
|
||||
import {Renderer} from "reflex-virtual-dom-driver"
|
||||
|
||||
var app = start({
|
||||
const app = start({
|
||||
initial: Counter.create(window.app != null ?
|
||||
window.app.model.value :
|
||||
{value: 0}),
|
||||
|
@ -13,6 +13,6 @@ var app = start({
|
|||
});
|
||||
window.app = app
|
||||
|
||||
var renderer = new Renderer({target: document.body})
|
||||
const renderer = new Renderer({target: document.body})
|
||||
|
||||
app.view.subscribe(renderer.address)
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/* @flow */
|
||||
|
||||
import type {Address, VirtualNode} from "reflex/type"
|
||||
|
||||
export type Model = {type: "Counter.Model", value:number}
|
||||
export type Increment = {type: "Counter.Increment"}
|
||||
export type Decrement = {type: "Counter.Decrement"}
|
||||
export type Action = Increment|Decrement
|
||||
|
||||
export type asIncrement = () => Increment
|
||||
export type asDecrement = () => Decrement
|
||||
|
||||
export type create = (options:{value:number}) => Model
|
||||
export type update = (model:Model, action:Action) => Model
|
||||
export type view = (model:Model, address:Address<Action>) => VirtualNode
|
|
@ -1,137 +0,0 @@
|
|||
/* @flow */
|
||||
import * as RandomGif from "./random-gif";
|
||||
import {html, forward, thunk, Effects} from "reflex";
|
||||
|
||||
/*::
|
||||
import type {Address} from "reflex/type/signal"
|
||||
import type {VirtualNode} from "reflex/type/renderer"
|
||||
|
||||
export type ID = number
|
||||
|
||||
export type Entry = {
|
||||
id: ID,
|
||||
model: RandomGif.Model
|
||||
}
|
||||
|
||||
export type Model = {
|
||||
topic: string,
|
||||
entries: Array<Entry>,
|
||||
nextID: ID
|
||||
}
|
||||
*/
|
||||
|
||||
export const create = ({topic, entries, nextID}/*:Model*/)/*:Model*/ =>
|
||||
({topic, entries, nextID})
|
||||
|
||||
export const initialize = ()/*:[Model, Effects<Action>]*/ =>
|
||||
[create({topic: "", entries: [], nextID: 0}, Effects.none)]
|
||||
|
||||
|
||||
/*::
|
||||
export type Topic = {$typeof: "Topic", topic: string}
|
||||
export type Create = {$typeof: "Create"}
|
||||
export type UpdateByID = {$typeof: "UpdateByID", id: ID, act: RandomGif.Action}
|
||||
export type Action
|
||||
= Topic
|
||||
| Create
|
||||
| UpdateByID
|
||||
*/
|
||||
|
||||
|
||||
export const TopicAction = (topic/*:string*/)/*:Topic*/ =>
|
||||
({$typeof: "Topic", topic})
|
||||
|
||||
export const CreateAction = ()/*:Create*/=>
|
||||
({$typeof: "Create"})
|
||||
|
||||
export const byID = (id/*:ID*/)/*:(action:RandomGif.Action)=>UpdateByID*/=>
|
||||
action => ({$typeof: "UpdateByID", id, act: action})
|
||||
|
||||
const find = Array.prototype.find != null ? (array, p) => array.find(p) :
|
||||
(array, p) => {
|
||||
let index = 0
|
||||
while (index < array.length) {
|
||||
if (p(array[index])) {
|
||||
return array[index]
|
||||
}
|
||||
index = index + 1
|
||||
}
|
||||
}
|
||||
|
||||
export const step = (model/*:Model*/, action/*:Action*/)/*:[Model, Effects<Action>]*/ => {
|
||||
if (action.$typeof === "Topic") {
|
||||
return [
|
||||
{topic: action.topic, entries: model.entries, nextID: model.nextID},
|
||||
Effects.none
|
||||
]
|
||||
}
|
||||
|
||||
if (action.$typeof === "Create") {
|
||||
const [gif, fx] = RandomGif.initialize(model.topic)
|
||||
return [
|
||||
{
|
||||
topic: "",
|
||||
nextID: model.nextID + 1,
|
||||
entries: model.entries.concat([{id: model.nextID, model: gif}])
|
||||
},
|
||||
fx.map(byID(model.nextID))
|
||||
]
|
||||
}
|
||||
|
||||
if (action.$typeof === "UpdateByID") {
|
||||
const {id} = action
|
||||
const {entries, topic, nextID} = model
|
||||
const entry = find(entries, entry => entry.id === id)
|
||||
const index = entry != null ? entries.indexOf(entry) : -1
|
||||
if (index >= 0 && entry != null && entry.model != null && entry.id != null){
|
||||
const [gif, fx] = RandomGif.step(entry.model, action.act)
|
||||
const entries = model.entries.slice(0)
|
||||
entries[index] = {id, model: gif}
|
||||
|
||||
return [
|
||||
{topic, nextID, entries},
|
||||
fx.map(byID(id))
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
return [model, Effects.none]
|
||||
}
|
||||
|
||||
const style = {
|
||||
input: {
|
||||
width: "100%",
|
||||
height: "40px",
|
||||
padding: "10px 0",
|
||||
fontSize: "2em",
|
||||
textAlign: "center"
|
||||
},
|
||||
container: {
|
||||
display: "flex",
|
||||
flexWrap: "wrap"
|
||||
}
|
||||
}
|
||||
|
||||
// View
|
||||
const viewEntry = ({id, model}/*:Entry*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
|
||||
RandomGif.view(model, forward(address, byID(id)))
|
||||
|
||||
export const view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
|
||||
html.div({key: "random-gif-list"}, [
|
||||
html.input({
|
||||
style: style.input,
|
||||
placeholder: "What kind of gifs do you want?",
|
||||
value: model.topic,
|
||||
onChange: forward(address, event => TopicAction(event.target.value)),
|
||||
onKeyUp: event => {
|
||||
if (event.keyCode === 13) {
|
||||
address(CreateAction())
|
||||
}
|
||||
}
|
||||
}),
|
||||
html.div({key: "random-gifs-list-box", style: style.container},
|
||||
model.entries.map(entry => thunk(String(entry.id),
|
||||
viewEntry,
|
||||
entry,
|
||||
address)))
|
||||
])
|
|
@ -1,113 +0,0 @@
|
|||
/* @flow */
|
||||
import {Record, Union} from "typed-immutable";
|
||||
import {html, forward, Effects, Task} from "reflex";
|
||||
|
||||
/*::
|
||||
import type {Address} from "reflex/type/signal"
|
||||
import type {VirtualNode} from "reflex/type/renderer"
|
||||
|
||||
|
||||
export type Model = {
|
||||
topic:string,
|
||||
uri:string
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
export const create = ({topic, uri}/*:Model*/)/*:Model*/=>
|
||||
({topic, uri})
|
||||
|
||||
export const initialize = (topic/*:string*/)/*:[Model, Effects<Action>]*/ =>
|
||||
[create({topic, uri: "assets/waiting.gif"}), getRandomGif(topic)]
|
||||
|
||||
|
||||
/*::
|
||||
export type RequestMore = {$typeof: "RequestMore"}
|
||||
export type NewGif = {$typeof: "NewGif", uri: ?string}
|
||||
export type Action = RequestMore|NewGif
|
||||
*/
|
||||
|
||||
export const RequestMoreAction = ()/*:RequestMore*/ =>
|
||||
({$typeof: "RequestMore"})
|
||||
export const NewGifAction = (uri/*:?string*/)/*:NewGif*/ =>
|
||||
({$typeof: "NewGif", uri})
|
||||
|
||||
export const step = (model/*:Model*/, action/*:Action*/)/*:[Model, Effects<Action>]*/ =>
|
||||
action.$typeof === "RequestMore" ? [model, getRandomGif(model.topic)] :
|
||||
action.$typeof === "NewGif" ?
|
||||
[create({topic: model.topic,
|
||||
uri: action.uri != null ? action.uri : model.uri})
|
||||
,Effects.none] :
|
||||
[model, Effects.none]
|
||||
|
||||
const randomURI = (topic/*:string*/)/*:string*/ =>
|
||||
`http://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=${topic}`
|
||||
|
||||
/*::
|
||||
type ResponseBody = {data: ?{image_uri: ?string}}
|
||||
type Response = {json: () => Promise<ResponseBody>}
|
||||
*/
|
||||
const decodeResponseBody = (body/*:ResponseBody*/)/*:?string*/ =>
|
||||
(body.data && body.data.image_url) || null
|
||||
|
||||
const readResponseAsJSON = (response/*:Response*/) => response.json()
|
||||
|
||||
const fetch = global.fetch != null ? global.fetch :
|
||||
uri => new Promise((resolve, reject) => {
|
||||
const request = new XMLHttpRequest()
|
||||
request.open('GET', uri, true)
|
||||
request.onload = () => {
|
||||
const status = request.status === 1223 ? 204 : request.status
|
||||
if (status < 100 || status > 599) {
|
||||
reject(Error('Network request failed'))
|
||||
} else {
|
||||
resolve({
|
||||
json() {
|
||||
return new Promise(resolve => {
|
||||
resolve(JSON.parse(request.responseText))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
request.onerror = () => {
|
||||
reject(Error('Network request failed'))
|
||||
}
|
||||
request.send()
|
||||
})
|
||||
|
||||
export const getRandomGif = (topic/*:string*/)/*:Effects<Action>*/ =>
|
||||
Effects.task(Task.future(() => fetch(randomURI(topic))
|
||||
.then(readResponseAsJSON)
|
||||
.then(decodeResponseBody)
|
||||
.then(NewGifAction)))
|
||||
|
||||
const style = {
|
||||
viewer: {
|
||||
width: "200px"
|
||||
},
|
||||
header: {
|
||||
width: "200px",
|
||||
textAlign: "center"
|
||||
},
|
||||
image(uri) {
|
||||
return {
|
||||
display: "inline-block",
|
||||
width: "200px",
|
||||
height: "200px",
|
||||
backgroundPosition: "center center",
|
||||
backgroundSize: "cover",
|
||||
backgroundImage: `url('${uri}')`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
|
||||
html.div({key: "gif-viewer", style: style.viewer}, [
|
||||
html.h2({key: "header", style: style.header}, [model.topic]),
|
||||
html.div({key: "image", style: style.image(model.uri)}),
|
||||
html.button({key: "button", onClick: forward(address, RequestMoreAction)}, [
|
||||
"More please!"
|
||||
])
|
||||
])
|
|
@ -1,19 +0,0 @@
|
|||
/* @flow */
|
||||
|
||||
import * as Pair from "./pair"
|
||||
import {start, Effects} from "reflex"
|
||||
import {Renderer} from "reflex-virtual-dom-renderer"
|
||||
|
||||
var app = start({
|
||||
initial: window.app != null ?
|
||||
[Pair.create(window.app.model.value)] :
|
||||
Pair.initialize("funny cats", "funny hamsters"),
|
||||
step: Pair.step,
|
||||
view: Pair.view
|
||||
});
|
||||
window.app = app
|
||||
|
||||
var renderer = new Renderer({target: document.body})
|
||||
|
||||
app.view.subscribe(renderer.address)
|
||||
app.task.subscribe(Effects.service(app.address))
|
|
@ -1,70 +0,0 @@
|
|||
/* @flow */
|
||||
import * as RandomGif from "./random-gif";
|
||||
import {html, forward, Task, Effects} from "reflex";
|
||||
|
||||
/*::
|
||||
import type {Address} from "reflex/type/signal"
|
||||
import type {VirtualNode} from "reflex/type/renderer"
|
||||
|
||||
export type Model = {
|
||||
left: RandomGif.Model,
|
||||
right: RandomGif.Model
|
||||
};
|
||||
*/
|
||||
|
||||
export const create = ({left, right}/*:Model*/)/*:Model*/ => ({
|
||||
left: RandomGif.create(left),
|
||||
right: RandomGif.create(right)
|
||||
})
|
||||
|
||||
export const initialize = (leftTopic/*:string*/, rightTopic/*:string*/)/*:[Model, Effects<Action>]*/ => {
|
||||
const [left, leftFx] = RandomGif.initialize(leftTopic)
|
||||
const [right, rightFx] = RandomGif.initialize(rightTopic)
|
||||
return [
|
||||
{left, right},
|
||||
Effects.batch([
|
||||
leftFx.map(LeftAction),
|
||||
rightFx.map(RightAction)
|
||||
])
|
||||
]
|
||||
}
|
||||
|
||||
/*::
|
||||
export type Left = {$typeof: 'Left', act: RandomGif.Action};
|
||||
export type Right = {$typeof: 'Right', act: RandomGif.Action};
|
||||
export type Action = Left|Right;
|
||||
*/
|
||||
|
||||
|
||||
export const LeftAction = (act/*:RandomGif.Action*/)/*:Left*/ =>
|
||||
({$typeof: "Left", act})
|
||||
|
||||
export const RightAction = (act/*:RandomGif.Action*/)/*:Right*/ =>
|
||||
({$typeof: "Right", act})
|
||||
|
||||
|
||||
export const step = (model/*:Model*/, action/*:Action*/)/*:[Model, Effects<Action>]*/ => {
|
||||
if (action.$typeof === "Left") {
|
||||
const [left, fx] = RandomGif.step(model.left, action.act)
|
||||
return [create({left, right: model.right}), fx.map(LeftAction)]
|
||||
}
|
||||
if (action.$typeof === "Right") {
|
||||
const [right, fx] = RandomGif.step(model.right, action.act)
|
||||
return [create({left:model.left, right}), fx.map(RightAction)]
|
||||
}
|
||||
|
||||
return [model, Effects.none]
|
||||
}
|
||||
|
||||
// View
|
||||
export var view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ => {
|
||||
return html.div({key: "random-gif-pair",
|
||||
style: {display: "flex"}}, [
|
||||
html.div({key: 'left'}, [
|
||||
RandomGif.view(model.left, forward(address, LeftAction))
|
||||
]),
|
||||
html.div({key: 'right'}, [
|
||||
RandomGif.view(model.right, forward(address, RightAction))
|
||||
])
|
||||
]);
|
||||
};
|
|
@ -1,113 +0,0 @@
|
|||
/* @flow */
|
||||
import {Record, Union} from "typed-immutable";
|
||||
import {html, forward, Effects, Task} from "reflex";
|
||||
|
||||
/*::
|
||||
import type {Address} from "reflex/type/signal"
|
||||
import type {VirtualNode} from "reflex/type/renderer"
|
||||
|
||||
|
||||
export type Model = {
|
||||
topic:string,
|
||||
uri:string
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
export const create = ({topic, uri}/*:Model*/)/*:Model*/=>
|
||||
({topic, uri})
|
||||
|
||||
export const initialize = (topic/*:string*/)/*:[Model, Effects<Action>]*/ =>
|
||||
[create({topic, uri: "assets/waiting.gif"}), getRandomGif(topic)]
|
||||
|
||||
|
||||
/*::
|
||||
export type RequestMore = {$typeof: "RequestMore"}
|
||||
export type NewGif = {$typeof: "NewGif", uri: ?string}
|
||||
export type Action = RequestMore|NewGif
|
||||
*/
|
||||
|
||||
export const RequestMoreAction = ()/*:RequestMore*/ =>
|
||||
({$typeof: "RequestMore"})
|
||||
export const NewGifAction = (uri/*:?string*/)/*:NewGif*/ =>
|
||||
({$typeof: "NewGif", uri})
|
||||
|
||||
export const step = (model/*:Model*/, action/*:Action*/)/*:[Model, Effects<Action>]*/ =>
|
||||
action.$typeof === "RequestMore" ? [model, getRandomGif(model.topic)] :
|
||||
action.$typeof === "NewGif" ?
|
||||
[create({topic: model.topic,
|
||||
uri: action.uri != null ? action.uri : model.uri})
|
||||
,Effects.none] :
|
||||
[model, Effects.none]
|
||||
|
||||
const randomURI = (topic/*:string*/)/*:string*/ =>
|
||||
`http://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=${topic}`
|
||||
|
||||
/*::
|
||||
type ResponseBody = {data: ?{image_uri: ?string}}
|
||||
type Response = {json: () => Promise<ResponseBody>}
|
||||
*/
|
||||
const decodeResponseBody = (body/*:ResponseBody*/)/*:?string*/ =>
|
||||
(body.data && body.data.image_url) || null
|
||||
|
||||
const readResponseAsJSON = (response/*:Response*/) => response.json()
|
||||
|
||||
const fetch = global.fetch != null ? global.fetch :
|
||||
uri => new Promise((resolve, reject) => {
|
||||
const request = new XMLHttpRequest()
|
||||
request.open('GET', uri, true)
|
||||
request.onload = () => {
|
||||
const status = request.status === 1223 ? 204 : request.status
|
||||
if (status < 100 || status > 599) {
|
||||
reject(Error('Network request failed'))
|
||||
} else {
|
||||
resolve({
|
||||
json() {
|
||||
return new Promise(resolve => {
|
||||
resolve(JSON.parse(request.responseText))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
request.onerror = () => {
|
||||
reject(Error('Network request failed'))
|
||||
}
|
||||
request.send()
|
||||
})
|
||||
|
||||
export const getRandomGif = (topic/*:string*/)/*:Effects<Action>*/ =>
|
||||
Effects.task(Task.future(() => fetch(randomURI(topic))
|
||||
.then(readResponseAsJSON)
|
||||
.then(decodeResponseBody)
|
||||
.then(NewGifAction)))
|
||||
|
||||
const style = {
|
||||
viewer: {
|
||||
width: "200px"
|
||||
},
|
||||
header: {
|
||||
width: "200px",
|
||||
textAlign: "center"
|
||||
},
|
||||
image(uri) {
|
||||
return {
|
||||
display: "inline-block",
|
||||
width: "200px",
|
||||
height: "200px",
|
||||
backgroundPosition: "center center",
|
||||
backgroundSize: "cover",
|
||||
backgroundImage: `url('${uri}')`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
|
||||
html.div({key: "gif-viewer", style: style.viewer}, [
|
||||
html.h2({key: "header", style: style.header}, [model.topic]),
|
||||
html.div({key: "image", style: style.image(model.uri)}),
|
||||
html.button({key: "button", onClick: forward(address, RequestMoreAction)}, [
|
||||
"More please!"
|
||||
])
|
||||
])
|
|
@ -1,113 +0,0 @@
|
|||
/* @flow */
|
||||
import {Record, Union} from "typed-immutable";
|
||||
import {html, forward, Effects, Task} from "reflex";
|
||||
|
||||
/*::
|
||||
import type {Address} from "reflex/type/signal"
|
||||
import type {VirtualNode} from "reflex/type/renderer"
|
||||
|
||||
|
||||
export type Model = {
|
||||
topic:string,
|
||||
uri:string
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
export const create = ({topic, uri}/*:Model*/)/*:Model*/=>
|
||||
({topic, uri})
|
||||
|
||||
export const initialize = (topic/*:string*/)/*:[Model, Effects<Action>]*/ =>
|
||||
[create({topic, uri: "assets/waiting.gif"}), getRandomGif(topic)]
|
||||
|
||||
|
||||
/*::
|
||||
export type RequestMore = {$typeof: "RequestMore"}
|
||||
export type NewGif = {$typeof: "NewGif", uri: ?string}
|
||||
export type Action = RequestMore|NewGif
|
||||
*/
|
||||
|
||||
export const RequestMoreAction = ()/*:RequestMore*/ =>
|
||||
({$typeof: "RequestMore"})
|
||||
export const NewGifAction = (uri/*:?string*/)/*:NewGif*/ =>
|
||||
({$typeof: "NewGif", uri})
|
||||
|
||||
export const step = (model/*:Model*/, action/*:Action*/)/*:[Model, Effects<Action>]*/ =>
|
||||
action.$typeof === "RequestMore" ? [model, getRandomGif(model.topic)] :
|
||||
action.$typeof === "NewGif" ?
|
||||
[create({topic: model.topic,
|
||||
uri: action.uri != null ? action.uri : model.uri})
|
||||
,Effects.none] :
|
||||
[model, Effects.none]
|
||||
|
||||
const randomURI = (topic/*:string*/)/*:string*/ =>
|
||||
`http://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=${topic}`
|
||||
|
||||
/*::
|
||||
type ResponseBody = {data: ?{image_uri: ?string}}
|
||||
type Response = {json: () => Promise<ResponseBody>}
|
||||
*/
|
||||
const decodeResponseBody = (body/*:ResponseBody*/)/*:?string*/ =>
|
||||
(body.data && body.data.image_url) || null
|
||||
|
||||
const readResponseAsJSON = (response/*:Response*/) => response.json()
|
||||
|
||||
const fetch = global.fetch != null ? global.fetch :
|
||||
uri => new Promise((resolve, reject) => {
|
||||
const request = new XMLHttpRequest()
|
||||
request.open('GET', uri, true)
|
||||
request.onload = () => {
|
||||
const status = request.status === 1223 ? 204 : request.status
|
||||
if (status < 100 || status > 599) {
|
||||
reject(Error('Network request failed'))
|
||||
} else {
|
||||
resolve({
|
||||
json() {
|
||||
return new Promise(resolve => {
|
||||
resolve(JSON.parse(request.responseText))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
request.onerror = () => {
|
||||
reject(Error('Network request failed'))
|
||||
}
|
||||
request.send()
|
||||
})
|
||||
|
||||
export const getRandomGif = (topic/*:string*/)/*:Effects<Action>*/ =>
|
||||
Effects.task(Task.future(() => fetch(randomURI(topic))
|
||||
.then(readResponseAsJSON)
|
||||
.then(decodeResponseBody)
|
||||
.then(NewGifAction)))
|
||||
|
||||
const style = {
|
||||
viewer: {
|
||||
width: "200px"
|
||||
},
|
||||
header: {
|
||||
width: "200px",
|
||||
textAlign: "center"
|
||||
},
|
||||
image(uri) {
|
||||
return {
|
||||
display: "inline-block",
|
||||
width: "200px",
|
||||
height: "200px",
|
||||
backgroundPosition: "center center",
|
||||
backgroundSize: "cover",
|
||||
backgroundImage: `url('${uri}')`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
|
||||
html.div({key: "gif-viewer", style: style.viewer}, [
|
||||
html.h2({key: "header", style: style.header}, [model.topic]),
|
||||
html.div({key: "image", style: style.image(model.uri)}),
|
||||
html.button({key: "button", onClick: forward(address, RequestMoreAction)}, [
|
||||
"More please!"
|
||||
])
|
||||
])
|
|
@ -2,15 +2,15 @@
|
|||
.*/src/test/.*
|
||||
.*/dist/.*
|
||||
.*/node_modules/reflex/examples/.*
|
||||
.*/node_modules/reflex-virtual-dom-renderer/lib/.*
|
||||
.*/node_modules/reflex-virtual-dom-driver/lib/.*
|
||||
.*/node_modules/reflex/lib/.*
|
||||
|
||||
[libs]
|
||||
./node_modules/reflex/interfaces/
|
||||
./node_modules/reflex-virtual-dom-renderer/interfaces/
|
||||
./node_modules/reflex-virtual-dom-driver/interfaces/
|
||||
|
||||
[include]
|
||||
|
||||
[options]
|
||||
module.name_mapper='reflex-virtual-dom-renderer' -> 'reflex-virtual-dom-renderer/src/index'
|
||||
module.name_mapper='reflex-virtual-dom-driver' -> 'reflex-virtual-dom-driver/src/index'
|
||||
module.name_mapper='reflex' -> 'reflex/src/index'
|
До Ширина: | Высота: | Размер: 8.3 KiB После Ширина: | Высота: | Размер: 8.3 KiB |
|
@ -8,7 +8,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"reflex": "latest",
|
||||
"reflex-virtual-dom-renderer": "latest"
|
||||
"reflex-virtual-dom-driver": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "11.0.1",
|
|
@ -0,0 +1,15 @@
|
|||
/* @flow */
|
||||
|
||||
/*:: import * as type from "../type/array-find" */
|
||||
|
||||
export const find/*:type.find*/ = Array.prototype.find != null ?
|
||||
(array, p) => array.find(p) :
|
||||
(array, p) => {
|
||||
let index = 0
|
||||
while (index < array.length) {
|
||||
if (p(array[index])) {
|
||||
return array[index]
|
||||
}
|
||||
index = index + 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/* @flow */
|
||||
|
||||
/*:: import * as type from "../type/fetch" */
|
||||
|
||||
export const fetch/*:type.fetch*/ = global.fetch != null ?
|
||||
global.fetch :
|
||||
uri => new Promise((resolve, reject) => {
|
||||
const request = new XMLHttpRequest()
|
||||
request.open("GET", uri, true)
|
||||
request.onload = () => {
|
||||
const status = request.status === 1223 ? 204 : request.status
|
||||
if (status < 100 || status > 599) {
|
||||
reject(Error("Network request failed"))
|
||||
} else {
|
||||
resolve({
|
||||
status,
|
||||
statusText: request.statusText,
|
||||
json() {
|
||||
return new Promise(resolve => {
|
||||
resolve(JSON.parse(request.responseText))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
request.onerror = () => {
|
||||
reject(Error("Network request failed"))
|
||||
}
|
||||
request.send()
|
||||
})
|
|
@ -1,8 +1,8 @@
|
|||
/* @flow */
|
||||
|
||||
import * as RandemGifList from "./list"
|
||||
import * as RandemGifList from "./random-gif-list"
|
||||
import {start, Effects} from "reflex"
|
||||
import {Renderer} from "reflex-virtual-dom-renderer"
|
||||
import {Renderer} from "reflex-virtual-dom-driver"
|
||||
|
||||
var app = start({
|
||||
initial: window.app != null ?
|
|
@ -0,0 +1,111 @@
|
|||
/* @flow */
|
||||
import * as RandomGif from "./random-gif"
|
||||
import {find} from "./array-find"
|
||||
import {html, forward, thunk, Effects} from "reflex"
|
||||
|
||||
|
||||
/*:: import * as type from "../type/random-gif-list" */
|
||||
|
||||
export const create/*:type.create*/ = ({topic, entries, nextID}) =>
|
||||
({type: "RandomGifList.Model", topic, entries, nextID})
|
||||
|
||||
export const initialize/*:type.initialize*/ = () =>
|
||||
[create({topic: "", entries: [], nextID: 0}, Effects.none)]
|
||||
|
||||
export const asTopic/*:type.asTopic*/ = topic =>
|
||||
({type: "RandomGifList.Topic", topic})
|
||||
|
||||
export const asCreate/*:type.asCreate*/ = () =>
|
||||
({type: "RandomGifList.Create"})
|
||||
|
||||
export const asByID/*:type.asByID*/ = id => act =>
|
||||
({type: "RandomGifList.UpdateByID", id, act})
|
||||
|
||||
export const step/*:type.step*/ = (model, action) => {
|
||||
if (action.type === "RandomGifList.Topic") {
|
||||
return [
|
||||
create({
|
||||
topic: action.topic,
|
||||
nextID: model.nextID,
|
||||
entries: model.entries
|
||||
}),
|
||||
Effects.none
|
||||
]
|
||||
}
|
||||
|
||||
if (action.type === "RandomGifList.Create") {
|
||||
const [gif, fx] = RandomGif.initialize(model.topic)
|
||||
return [
|
||||
create({
|
||||
topic: "",
|
||||
nextID: model.nextID + 1,
|
||||
entries: model.entries.concat([{
|
||||
type: "RandomGifList.Entry",
|
||||
id: model.nextID,
|
||||
model: gif
|
||||
}])
|
||||
}),
|
||||
fx.map(asByID(model.nextID))
|
||||
]
|
||||
}
|
||||
|
||||
if (action.type === "RandomGifList.UpdateByID") {
|
||||
const {id} = action
|
||||
const {entries, topic, nextID} = model
|
||||
const entry = find(entries, entry => entry.id === id)
|
||||
const index = entry != null ? entries.indexOf(entry) : -1
|
||||
if (index >= 0 && entry != null && entry.model != null && entry.id != null){
|
||||
const [gif, fx] = RandomGif.step(entry.model, action.act)
|
||||
const entries = model.entries.slice(0)
|
||||
entries[index] = {
|
||||
type: "RandomGifList.Entry",
|
||||
id,
|
||||
model: gif
|
||||
}
|
||||
|
||||
return [
|
||||
create({topic, nextID, entries}),
|
||||
fx.map(asByID(id))
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
return [model, Effects.none]
|
||||
}
|
||||
|
||||
const style = {
|
||||
input: {
|
||||
width: "100%",
|
||||
height: "40px",
|
||||
padding: "10px 0",
|
||||
fontSize: "2em",
|
||||
textAlign: "center"
|
||||
},
|
||||
container: {
|
||||
display: "flex",
|
||||
flexWrap: "wrap"
|
||||
}
|
||||
}
|
||||
|
||||
export const viewEntry/*:type.viewEntry*/ = ({id, model}, address) =>
|
||||
RandomGif.view(model, forward(address, asByID(id)))
|
||||
|
||||
export const view/*:type.view*/ = (model, address) =>
|
||||
html.div({key: "random-gif-list"}, [
|
||||
html.input({
|
||||
style: style.input,
|
||||
placeholder: "What kind of gifs do you want?",
|
||||
value: model.topic,
|
||||
onChange: forward(address, event => asTopic(event.target.value)),
|
||||
onKeyUp: event => {
|
||||
if (event.keyCode === 13) {
|
||||
address(asCreate())
|
||||
}
|
||||
}
|
||||
}),
|
||||
html.div({key: "random-gifs-list-box", style: style.container},
|
||||
model.entries.map(entry => thunk(String(entry.id),
|
||||
viewEntry,
|
||||
entry,
|
||||
address)))
|
||||
])
|
|
@ -0,0 +1,78 @@
|
|||
/* @flow */
|
||||
|
||||
import {html, forward, Effects, Task} from "reflex"
|
||||
import {fetch} from "./fetch"
|
||||
|
||||
/*:: import * as type from "../type/random-gif" */
|
||||
|
||||
export const create/*:type.create*/ = ({topic, uri}) =>
|
||||
({type: "RandomGif.Model", topic, uri})
|
||||
|
||||
export const initialize/*:type.initialize*/ = topic =>
|
||||
[create({topic, uri: "assets/waiting.gif"}), getRandomGif(topic)]
|
||||
|
||||
|
||||
export const asRequestMore/*:type.asRequestMore*/ = () =>
|
||||
({type: "RandomGif.RequestMore"})
|
||||
|
||||
export const asReceiveNewGif/*:type.asReceiveNewGif*/ = uri =>
|
||||
({type: "RandomGif.ReceiveNewGif", uri})
|
||||
|
||||
|
||||
export const step/*:type.step*/ = (model, action) =>
|
||||
action.type === "RandomGif.RequestMore" ?
|
||||
[model, getRandomGif(model.topic)] :
|
||||
action.type === "RandomGif.ReceiveNewGif" ?
|
||||
[
|
||||
create({
|
||||
topic: model.topic,
|
||||
uri: action.uri != null ? action.uri : model.uri
|
||||
}),
|
||||
Effects.none
|
||||
] :
|
||||
[model, Effects.none]
|
||||
|
||||
const makeRandomURI = topic =>
|
||||
`http://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=${topic}`
|
||||
|
||||
const decodeResponseBody = body =>
|
||||
(body != null && body.data != null && body.data.image_url != null) ?
|
||||
String(body.data.image_url) :
|
||||
null
|
||||
|
||||
const readResponseAsJSON = response => response.json()
|
||||
|
||||
export const getRandomGif/*:type.getRandomGif*/ = topic =>
|
||||
Effects.task(Task.future(() => fetch(makeRandomURI(topic))
|
||||
.then(readResponseAsJSON)
|
||||
.then(decodeResponseBody)
|
||||
.then(asReceiveNewGif)))
|
||||
|
||||
const style = {
|
||||
viewer: {
|
||||
width: "200px"
|
||||
},
|
||||
header: {
|
||||
width: "200px",
|
||||
textAlign: "center"
|
||||
},
|
||||
image(uri) {
|
||||
return {
|
||||
display: "inline-block",
|
||||
width: "200px",
|
||||
height: "200px",
|
||||
backgroundPosition: "center center",
|
||||
backgroundSize: "cover",
|
||||
backgroundImage: `url("${uri}")`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const view/*:type.view*/ = (model, address) =>
|
||||
html.div({key: "gif-viewer", style: style.viewer}, [
|
||||
html.h2({key: "header", style: style.header}, [model.topic]),
|
||||
html.div({key: "image", style: style.image(model.uri)}),
|
||||
html.button({key: "button", onClick: forward(address, asRequestMore)}, [
|
||||
"More please!"
|
||||
])
|
||||
])
|
|
@ -0,0 +1,3 @@
|
|||
/* @flow */
|
||||
|
||||
export type find <a> = (array:Array<a>, p:(a:a) => boolean) => ?a
|
|
@ -0,0 +1,16 @@
|
|||
/* @flow */
|
||||
|
||||
export type JSONValue
|
||||
= string
|
||||
| number
|
||||
| void
|
||||
| {[key:string]: JSONValue}
|
||||
|
||||
|
||||
export type Response = {
|
||||
status: number,
|
||||
statusText: string,
|
||||
json: ()=> Promise<JSONValue>
|
||||
}
|
||||
|
||||
export type fetch = (uri:string) => Promise<Response>
|
|
@ -0,0 +1,54 @@
|
|||
/* @flow */
|
||||
|
||||
import type {Effects} from "reflex/type/effects"
|
||||
import type {Address, VirtualNode} from "reflex/type"
|
||||
import * as RandomGif from "./random-gif"
|
||||
|
||||
export type ID = number
|
||||
|
||||
export type Entry = {
|
||||
type: "RandomGifList.Entry",
|
||||
id: ID,
|
||||
model: RandomGif.Model
|
||||
}
|
||||
|
||||
export type State = {
|
||||
topic: string,
|
||||
nextID: ID,
|
||||
entries: Array<Entry>
|
||||
}
|
||||
|
||||
export type Model = {
|
||||
type: "RandomGifList.Model",
|
||||
topic: string,
|
||||
nextID: ID,
|
||||
entries: Array<Entry>
|
||||
}
|
||||
|
||||
|
||||
export type Topic = {type: "RandomGifList.Topic", topic: string}
|
||||
export type Create = {type: "RandomGifList.Create"}
|
||||
export type UpdateByID = {
|
||||
type: "RandomGifList.UpdateByID",
|
||||
id: ID,
|
||||
act: RandomGif.Action
|
||||
}
|
||||
|
||||
export type Action
|
||||
= Topic
|
||||
| Create
|
||||
| UpdateByID
|
||||
|
||||
|
||||
|
||||
export type asTopic = (topic:string) => Topic
|
||||
export type asCreate = () => Create
|
||||
export type asByID = (id:ID) => (act:RandomGif.Action) => UpdateByID
|
||||
|
||||
export type create = (options:State) => Model
|
||||
export type initialize = () => [Model, Effects<Action>]
|
||||
|
||||
export type step = (model:Model, action:Action) => [Model, Effects<Action>]
|
||||
|
||||
export type viewEntry = (entry:Entry, address:Address<Action>) => VirtualNode
|
||||
export type view = (model:Model, address:Address<Action>) => VirtualNode
|
|
@ -0,0 +1,32 @@
|
|||
/* @flow */
|
||||
|
||||
import type {Effects} from "reflex/type/effects"
|
||||
import type {Address, VirtualNode} from "reflex/type"
|
||||
|
||||
export type State = {
|
||||
topic: string,
|
||||
uri: string
|
||||
}
|
||||
|
||||
export type Model
|
||||
= {type: "RandomGif.Model"}
|
||||
& State
|
||||
|
||||
export type RequestMore = {type: "RandomGif.RequestMore"}
|
||||
export type ReceiveNewGif = {type: "RandomGif.ReceiveNewGif", uri: ?string}
|
||||
export type Action
|
||||
= RequestMore
|
||||
| ReceiveNewGif
|
||||
|
||||
export type asRequestMore = () => RequestMore
|
||||
export type asReceiveNewGif = (uri:?string) => ReceiveNewGif
|
||||
|
||||
export type create = (options:State) => Model
|
||||
|
||||
|
||||
export type initialize = (topic:string) => [Model, Effects<Action>]
|
||||
export type step = (model:Model, action:Action) => [Model, Effects<Action>]
|
||||
|
||||
export type getRandomGif = (topic:string) => Effects<Action>
|
||||
|
||||
export type view = (model:Model, address:Address<Action>) => VirtualNode
|
|
@ -2,15 +2,15 @@
|
|||
.*/src/test/.*
|
||||
.*/dist/.*
|
||||
.*/node_modules/reflex/examples/.*
|
||||
.*/node_modules/reflex-virtual-dom-renderer/lib/.*
|
||||
.*/node_modules/reflex-virtual-dom-driver/lib/.*
|
||||
.*/node_modules/reflex/lib/.*
|
||||
|
||||
[libs]
|
||||
./node_modules/reflex/interfaces/
|
||||
./node_modules/reflex-virtual-dom-renderer/interfaces/
|
||||
./node_modules/reflex-virtual-dom-driver/interfaces/
|
||||
|
||||
[include]
|
||||
|
||||
[options]
|
||||
module.name_mapper='reflex-virtual-dom-renderer' -> 'reflex-virtual-dom-renderer/src/index'
|
||||
module.name_mapper='reflex-virtual-dom-driver' -> 'reflex-virtual-dom-driver/src/index'
|
||||
module.name_mapper='reflex' -> 'reflex/src/index'
|
До Ширина: | Высота: | Размер: 8.3 KiB После Ширина: | Высота: | Размер: 8.3 KiB |
|
@ -8,7 +8,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"reflex": "latest",
|
||||
"reflex-virtual-dom-renderer": "latest"
|
||||
"reflex-virtual-dom-driver": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "11.0.1",
|
|
@ -0,0 +1,30 @@
|
|||
/* @flow */
|
||||
|
||||
/*:: import * as type from "../type/fetch" */
|
||||
|
||||
export const fetch/*:type.fetch*/ = global.fetch != null ?
|
||||
global.fetch :
|
||||
uri => new Promise((resolve, reject) => {
|
||||
const request = new XMLHttpRequest()
|
||||
request.open("GET", uri, true)
|
||||
request.onload = () => {
|
||||
const status = request.status === 1223 ? 204 : request.status
|
||||
if (status < 100 || status > 599) {
|
||||
reject(Error("Network request failed"))
|
||||
} else {
|
||||
resolve({
|
||||
status,
|
||||
statusText: request.statusText,
|
||||
json() {
|
||||
return new Promise(resolve => {
|
||||
resolve(JSON.parse(request.responseText))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
request.onerror = () => {
|
||||
reject(Error("Network request failed"))
|
||||
}
|
||||
request.send()
|
||||
})
|
|
@ -0,0 +1,19 @@
|
|||
/* @flow */
|
||||
|
||||
import * as RandomGifPair from "./random-gif-pair"
|
||||
import {start, Effects} from "reflex"
|
||||
import {Renderer} from "reflex-virtual-dom-driver"
|
||||
|
||||
var app = start({
|
||||
initial: window.app != null ?
|
||||
[RandomGifPair.create(window.app.model.value)] :
|
||||
RandomGifPair.initialize("funny cats", "funny hamsters"),
|
||||
step: RandomGifPair.step,
|
||||
view: RandomGifPair.view
|
||||
});
|
||||
window.app = app
|
||||
|
||||
var renderer = new Renderer({target: document.body})
|
||||
|
||||
app.view.subscribe(renderer.address)
|
||||
app.task.subscribe(Effects.service(app.address))
|
|
@ -0,0 +1,57 @@
|
|||
/* @flow */
|
||||
|
||||
import * as RandomGif from "./random-gif"
|
||||
import {html, forward, Task, Effects} from "reflex"
|
||||
|
||||
/*:: import * as type from "../type/random-gif-pair" */
|
||||
|
||||
export const create/*:type.create*/ = ({left, right}) => ({
|
||||
type: "RandomGifPair.Model",
|
||||
left: RandomGif.create(left),
|
||||
right: RandomGif.create(right)
|
||||
})
|
||||
|
||||
export const initialize/*:type.initialize*/ = (leftTopic, rightTopic) => {
|
||||
const [left, leftFx] = RandomGif.initialize(leftTopic)
|
||||
const [right, rightFx] = RandomGif.initialize(rightTopic)
|
||||
return [
|
||||
create({left, right}),
|
||||
Effects.batch([
|
||||
leftFx.map(asLeft),
|
||||
rightFx.map(asRight)
|
||||
])
|
||||
]
|
||||
}
|
||||
|
||||
export const asLeft/*:type.asLeft*/ = act =>
|
||||
({type: "RandomGifPair.Left", act})
|
||||
|
||||
export const asRight/*:type.asRight*/ = act =>
|
||||
({type: "RandomGifPair.Right", act})
|
||||
|
||||
|
||||
export const step/*:type.step*/ = (model, action) => {
|
||||
if (action.type === "RandomGifPair.Left") {
|
||||
const [left, fx] = RandomGif.step(model.left, action.act)
|
||||
return [create({left, right: model.right}), fx.map(asLeft)]
|
||||
}
|
||||
|
||||
if (action.type === "RandomGifPair.Right") {
|
||||
const [right, fx] = RandomGif.step(model.right, action.act)
|
||||
return [create({left:model.left, right}), fx.map(asRight)]
|
||||
}
|
||||
|
||||
return [model, Effects.none]
|
||||
}
|
||||
|
||||
export const view/*:type.view*/ = (model, address) => {
|
||||
return html.div({key: "random-gif-pair",
|
||||
style: {display: "flex"}}, [
|
||||
html.div({key: "left"}, [
|
||||
RandomGif.view(model.left, forward(address, asLeft))
|
||||
]),
|
||||
html.div({key: "right"}, [
|
||||
RandomGif.view(model.right, forward(address, asRight))
|
||||
])
|
||||
]);
|
||||
};
|
|
@ -0,0 +1,78 @@
|
|||
/* @flow */
|
||||
|
||||
import {html, forward, Effects, Task} from "reflex"
|
||||
import {fetch} from "./fetch"
|
||||
|
||||
/*:: import * as type from "../type/random-gif" */
|
||||
|
||||
export const create/*:type.create*/ = ({topic, uri}) =>
|
||||
({type: "RandomGif.Model", topic, uri})
|
||||
|
||||
export const initialize/*:type.initialize*/ = topic =>
|
||||
[create({topic, uri: "assets/waiting.gif"}), getRandomGif(topic)]
|
||||
|
||||
|
||||
export const asRequestMore/*:type.asRequestMore*/ = () =>
|
||||
({type: "RandomGif.RequestMore"})
|
||||
|
||||
export const asReceiveNewGif/*:type.asReceiveNewGif*/ = uri =>
|
||||
({type: "RandomGif.ReceiveNewGif", uri})
|
||||
|
||||
|
||||
export const step/*:type.step*/ = (model, action) =>
|
||||
action.type === "RandomGif.RequestMore" ?
|
||||
[model, getRandomGif(model.topic)] :
|
||||
action.type === "RandomGif.ReceiveNewGif" ?
|
||||
[
|
||||
create({
|
||||
topic: model.topic,
|
||||
uri: action.uri != null ? action.uri : model.uri
|
||||
}),
|
||||
Effects.none
|
||||
] :
|
||||
[model, Effects.none]
|
||||
|
||||
const makeRandomURI = topic =>
|
||||
`http://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=${topic}`
|
||||
|
||||
const decodeResponseBody = body =>
|
||||
(body != null && body.data != null && body.data.image_url != null) ?
|
||||
String(body.data.image_url) :
|
||||
null
|
||||
|
||||
const readResponseAsJSON = response => response.json()
|
||||
|
||||
export const getRandomGif/*:type.getRandomGif*/ = topic =>
|
||||
Effects.task(Task.future(() => fetch(makeRandomURI(topic))
|
||||
.then(readResponseAsJSON)
|
||||
.then(decodeResponseBody)
|
||||
.then(asReceiveNewGif)))
|
||||
|
||||
const style = {
|
||||
viewer: {
|
||||
width: "200px"
|
||||
},
|
||||
header: {
|
||||
width: "200px",
|
||||
textAlign: "center"
|
||||
},
|
||||
image(uri) {
|
||||
return {
|
||||
display: "inline-block",
|
||||
width: "200px",
|
||||
height: "200px",
|
||||
backgroundPosition: "center center",
|
||||
backgroundSize: "cover",
|
||||
backgroundImage: `url("${uri}")`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const view/*:type.view*/ = (model, address) =>
|
||||
html.div({key: "gif-viewer", style: style.viewer}, [
|
||||
html.h2({key: "header", style: style.header}, [model.topic]),
|
||||
html.div({key: "image", style: style.image(model.uri)}),
|
||||
html.button({key: "button", onClick: forward(address, asRequestMore)}, [
|
||||
"More please!"
|
||||
])
|
||||
])
|
|
@ -0,0 +1,16 @@
|
|||
/* @flow */
|
||||
|
||||
export type JSONValue
|
||||
= string
|
||||
| number
|
||||
| void
|
||||
| {[key:string]: JSONValue}
|
||||
|
||||
|
||||
export type Response = {
|
||||
status: number,
|
||||
statusText: string,
|
||||
json: ()=> Promise<JSONValue>
|
||||
}
|
||||
|
||||
export type fetch = (uri:string) => Promise<Response>
|
|
@ -0,0 +1,36 @@
|
|||
/* @flow */
|
||||
|
||||
import type {Effects} from "reflex/type/effects"
|
||||
import type {Address, VirtualNode} from "reflex/type"
|
||||
import * as RandomGif from "./random-gif"
|
||||
|
||||
export type State = {
|
||||
left: RandomGif.State,
|
||||
right: RandomGif.State
|
||||
}
|
||||
|
||||
export type Model
|
||||
= {type: "RandomGifPair.Model"}
|
||||
& {left: RandomGif.Model, right:RandomGif.Model}
|
||||
|
||||
export type Left = {
|
||||
type: "RandomGifPair.Left",
|
||||
act: RandomGif.Action
|
||||
}
|
||||
export type Right = {
|
||||
type: "RandomGifPair.Right",
|
||||
act: any // Workaround for facebook/flow#953
|
||||
// act: RandomGif.Action
|
||||
}
|
||||
export type Action = Left | Right
|
||||
|
||||
export type asLeft = (action:RandomGif.Action) => Left
|
||||
export type asRight = (action:RandomGif.Action) => Right
|
||||
|
||||
export type create = (options:State) => Model
|
||||
export type initialize = (leftTopic:string, rightTopic:string) =>
|
||||
[Model, Effects<Action>]
|
||||
|
||||
export type step = (model:Model, action:Action) => [Model, Effects<Action>]
|
||||
|
||||
export type view = (model:Model, address:Address<Action>) => VirtualNode
|
|
@ -0,0 +1,32 @@
|
|||
/* @flow */
|
||||
|
||||
import type {Effects} from "reflex/type/effects"
|
||||
import type {Address, VirtualNode} from "reflex/type"
|
||||
|
||||
export type State = {
|
||||
topic: string,
|
||||
uri: string
|
||||
}
|
||||
|
||||
export type Model
|
||||
= {type: "RandomGif.Model"}
|
||||
& State
|
||||
|
||||
export type RequestMore = {type: "RandomGif.RequestMore"}
|
||||
export type ReceiveNewGif = {type: "RandomGif.ReceiveNewGif", uri: ?string}
|
||||
export type Action
|
||||
= RequestMore
|
||||
| ReceiveNewGif
|
||||
|
||||
export type asRequestMore = () => RequestMore
|
||||
export type asReceiveNewGif = (uri:?string) => ReceiveNewGif
|
||||
|
||||
export type create = (options:State) => Model
|
||||
|
||||
|
||||
export type initialize = (topic:string) => [Model, Effects<Action>]
|
||||
export type step = (model:Model, action:Action) => [Model, Effects<Action>]
|
||||
|
||||
export type getRandomGif = (topic:string) => Effects<Action>
|
||||
|
||||
export type view = (model:Model, address:Address<Action>) => VirtualNode
|
|
@ -2,15 +2,15 @@
|
|||
.*/src/test/.*
|
||||
.*/dist/.*
|
||||
.*/node_modules/reflex/examples/.*
|
||||
.*/node_modules/reflex-virtual-dom-renderer/lib/.*
|
||||
.*/node_modules/reflex-virtual-dom-driver/lib/.*
|
||||
.*/node_modules/reflex/lib/.*
|
||||
|
||||
[libs]
|
||||
./node_modules/reflex/interfaces/
|
||||
./node_modules/reflex-virtual-dom-renderer/interfaces/
|
||||
./node_modules/reflex-virtual-dom-driver/interfaces/
|
||||
|
||||
[include]
|
||||
|
||||
[options]
|
||||
module.name_mapper='reflex-virtual-dom-renderer' -> 'reflex-virtual-dom-renderer/src/index'
|
||||
module.name_mapper='reflex-virtual-dom-driver' -> 'reflex-virtual-dom-driver/src/index'
|
||||
module.name_mapper='reflex' -> 'reflex/src/index'
|
До Ширина: | Высота: | Размер: 8.3 KiB После Ширина: | Высота: | Размер: 8.3 KiB |
|
@ -8,7 +8,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"reflex": "latest",
|
||||
"reflex-virtual-dom-renderer": "latest"
|
||||
"reflex-virtual-dom-driver": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "11.0.1",
|
|
@ -0,0 +1,30 @@
|
|||
/* @flow */
|
||||
|
||||
/*:: import * as type from "../type/fetch" */
|
||||
|
||||
export const fetch/*:type.fetch*/ = global.fetch != null ?
|
||||
global.fetch :
|
||||
uri => new Promise((resolve, reject) => {
|
||||
const request = new XMLHttpRequest()
|
||||
request.open("GET", uri, true)
|
||||
request.onload = () => {
|
||||
const status = request.status === 1223 ? 204 : request.status
|
||||
if (status < 100 || status > 599) {
|
||||
reject(Error("Network request failed"))
|
||||
} else {
|
||||
resolve({
|
||||
status,
|
||||
statusText: request.statusText,
|
||||
json() {
|
||||
return new Promise(resolve => {
|
||||
resolve(JSON.parse(request.responseText))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
request.onerror = () => {
|
||||
reject(Error("Network request failed"))
|
||||
}
|
||||
request.send()
|
||||
})
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import * as RandomGif from "./random-gif"
|
||||
import {start, Effects} from "reflex"
|
||||
import {Renderer} from "reflex-virtual-dom-renderer"
|
||||
import {Renderer} from "reflex-virtual-dom-driver"
|
||||
|
||||
var app = start({
|
||||
initial: window.app != null ?
|
|
@ -0,0 +1,78 @@
|
|||
/* @flow */
|
||||
|
||||
import {html, forward, Effects, Task} from "reflex"
|
||||
import {fetch} from "./fetch"
|
||||
|
||||
/*:: import * as type from "../type/random-gif" */
|
||||
|
||||
export const create/*:type.create*/ = ({topic, uri}) =>
|
||||
({type: "RandomGif.Model", topic, uri})
|
||||
|
||||
export const initialize/*:type.initialize*/ = topic =>
|
||||
[create({topic, uri: "assets/waiting.gif"}), getRandomGif(topic)]
|
||||
|
||||
|
||||
export const asRequestMore/*:type.asRequestMore*/ = () =>
|
||||
({type: "RandomGif.RequestMore"})
|
||||
|
||||
export const asReceiveNewGif/*:type.asReceiveNewGif*/ = uri =>
|
||||
({type: "RandomGif.ReceiveNewGif", uri})
|
||||
|
||||
|
||||
export const step/*:type.step*/ = (model, action) =>
|
||||
action.type === "RandomGif.RequestMore" ?
|
||||
[model, getRandomGif(model.topic)] :
|
||||
action.type === "RandomGif.ReceiveNewGif" ?
|
||||
[
|
||||
create({
|
||||
topic: model.topic,
|
||||
uri: action.uri != null ? action.uri : model.uri
|
||||
}),
|
||||
Effects.none
|
||||
] :
|
||||
[model, Effects.none]
|
||||
|
||||
const makeRandomURI = topic =>
|
||||
`http://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=${topic}`
|
||||
|
||||
const decodeResponseBody = body =>
|
||||
(body != null && body.data != null && body.data.image_url != null) ?
|
||||
String(body.data.image_url) :
|
||||
null
|
||||
|
||||
const readResponseAsJSON = response => response.json()
|
||||
|
||||
export const getRandomGif/*:type.getRandomGif*/ = topic =>
|
||||
Effects.task(Task.future(() => fetch(makeRandomURI(topic))
|
||||
.then(readResponseAsJSON)
|
||||
.then(decodeResponseBody)
|
||||
.then(asReceiveNewGif)))
|
||||
|
||||
const style = {
|
||||
viewer: {
|
||||
width: "200px"
|
||||
},
|
||||
header: {
|
||||
width: "200px",
|
||||
textAlign: "center"
|
||||
},
|
||||
image(uri) {
|
||||
return {
|
||||
display: "inline-block",
|
||||
width: "200px",
|
||||
height: "200px",
|
||||
backgroundPosition: "center center",
|
||||
backgroundSize: "cover",
|
||||
backgroundImage: `url("${uri}")`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const view/*:type.view*/ = (model, address) =>
|
||||
html.div({key: "gif-viewer", style: style.viewer}, [
|
||||
html.h2({key: "header", style: style.header}, [model.topic]),
|
||||
html.div({key: "image", style: style.image(model.uri)}),
|
||||
html.button({key: "button", onClick: forward(address, asRequestMore)}, [
|
||||
"More please!"
|
||||
])
|
||||
])
|
|
@ -0,0 +1,16 @@
|
|||
/* @flow */
|
||||
|
||||
export type JSONValue
|
||||
= string
|
||||
| number
|
||||
| void
|
||||
| {[key:string]: JSONValue}
|
||||
|
||||
|
||||
export type Response = {
|
||||
status: number,
|
||||
statusText: string,
|
||||
json: ()=> Promise<JSONValue>
|
||||
}
|
||||
|
||||
export type fetch = (uri:string) => Promise<Response>
|
|
@ -0,0 +1,32 @@
|
|||
/* @flow */
|
||||
|
||||
import type {Effects} from "reflex/type/effects"
|
||||
import type {Address, VirtualNode} from "reflex/type"
|
||||
|
||||
export type State = {
|
||||
topic: string,
|
||||
uri: string
|
||||
}
|
||||
|
||||
export type Model
|
||||
= {type: "RandomGif.Model"}
|
||||
& State
|
||||
|
||||
export type RequestMore = {type: "RandomGif.RequestMore"}
|
||||
export type ReceiveNewGif = {type: "RandomGif.ReceiveNewGif", uri: ?string}
|
||||
export type Action
|
||||
= RequestMore
|
||||
| ReceiveNewGif
|
||||
|
||||
export type asRequestMore = () => RequestMore
|
||||
export type asReceiveNewGif = (uri:?string) => ReceiveNewGif
|
||||
|
||||
export type create = (options:State) => Model
|
||||
|
||||
|
||||
export type initialize = (topic:string) => [Model, Effects<Action>]
|
||||
export type step = (model:Model, action:Action) => [Model, Effects<Action>]
|
||||
|
||||
export type getRandomGif = (topic:string) => Effects<Action>
|
||||
|
||||
export type view = (model:Model, address:Address<Action>) => VirtualNode
|
|
@ -2,15 +2,21 @@
|
|||
.*/src/test/.*
|
||||
.*/dist/.*
|
||||
.*/node_modules/reflex/examples/.*
|
||||
.*/node_modules/reflex-virtual-dom-renderer/lib/.*
|
||||
.*/node_modules/reflex-virtual-dom-driver/lib/.*
|
||||
.*/node_modules/reflex/lib/.*
|
||||
.*/node_modules/eased/lib/.*
|
||||
.*/node_modules/eased/dist/.*
|
||||
.*/node_modules/eased/.*/lib/.*
|
||||
.*/node_modules/eased/.*/dist/.*
|
||||
|
||||
[libs]
|
||||
./node_modules/reflex/interfaces/
|
||||
./node_modules/reflex-virtual-dom-renderer/interfaces/
|
||||
./node_modules/reflex-virtual-dom-driver/interfaces/
|
||||
|
||||
[include]
|
||||
|
||||
[options]
|
||||
module.name_mapper='reflex-virtual-dom-renderer' -> 'reflex-virtual-dom-renderer/src/index'
|
||||
module.name_mapper='reflex-virtual-dom-driver' -> 'reflex-virtual-dom-driver/src/index'
|
||||
module.name_mapper='reflex' -> 'reflex/src/index'
|
||||
module.name_mapper='eased' -> 'eased/src/index'
|
||||
module.name_mapper='color-structure' -> 'color-structure/src/index'
|
||||
|
|
|
@ -7,22 +7,20 @@
|
|||
"build": "NODE_ENV=production gulp build"
|
||||
},
|
||||
"dependencies": {
|
||||
"eased": "0.0.3",
|
||||
"reflex": "latest",
|
||||
"reflex-virtual-dom-renderer": "latest"
|
||||
"reflex-virtual-dom-driver": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "11.0.1",
|
||||
"watchify": "3.3.1",
|
||||
|
||||
"babelify": "6.1.3",
|
||||
"browserify-hmr": "0.3.0",
|
||||
"hotify": "0.0.1",
|
||||
|
||||
"babel-core": "5.8.23",
|
||||
"babel-runtime": "5.8.20",
|
||||
"ecstatic": "0.8.0",
|
||||
"flow-bin": "0.17.0",
|
||||
|
||||
"gulp": "3.9.0",
|
||||
"gulp-sequence": "0.4.1",
|
||||
"gulp-sourcemaps": "1.5.2",
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import * as SpinSquarePair from "./spin-square-pair"
|
||||
import {start, Effects} from "reflex"
|
||||
import {Renderer} from "reflex-virtual-dom-renderer"
|
||||
import {Renderer} from "reflex-virtual-dom-driver"
|
||||
|
||||
var app = start({
|
||||
initial: window.app != null ?
|
||||
|
|
|
@ -1,65 +1,52 @@
|
|||
/* @flow */
|
||||
import * as SpinSquare from "./spin-square";
|
||||
import {html, forward, thunk, Effects} from "reflex";
|
||||
|
||||
/*::
|
||||
import type {Address} from "reflex/type/signal";
|
||||
import type {VirtualNode} from "reflex/type/renderer";
|
||||
import * as SpinSquare from "./spin-square"
|
||||
import {html, forward, thunk, Effects} from "reflex"
|
||||
|
||||
export type Model = {
|
||||
left: SpinSquare.Model,
|
||||
right: SpinSquare.Model
|
||||
};
|
||||
*/
|
||||
/*:: import * as type from "../type/spin-square-pair" */
|
||||
|
||||
export const create = ({left, right}/*:Model*/)/*:Model*/ => ({
|
||||
export const create/*:type.create*/ = ({left, right}) => ({
|
||||
type: "SpinSquarePair.Model",
|
||||
left: SpinSquare.create(left),
|
||||
right: SpinSquare.create(right)
|
||||
})
|
||||
|
||||
export const initialize = ()/*:[Model, Effects<Action>]*/ => {
|
||||
export const initialize/*:type.initialize*/ = () => {
|
||||
const [left, leftFx] = SpinSquare.initialize()
|
||||
const [right, rightFx] = SpinSquare.initialize()
|
||||
return [
|
||||
{left, right},
|
||||
create({left, right}),
|
||||
Effects.batch([
|
||||
leftFx.map(LeftAction),
|
||||
rightFx.map(RightAction)
|
||||
leftFx.map(asLeft),
|
||||
rightFx.map(asRight)
|
||||
])
|
||||
]
|
||||
}
|
||||
|
||||
/*::
|
||||
export type Left = {$typeof: 'Left', act: SpinSquare.Action};
|
||||
export type Right = {$typeof: 'Right', act: SpinSquare.Action};
|
||||
export type Action = Left|Right;
|
||||
*/
|
||||
export const asLeft/*:type.asLeft*/ = act =>
|
||||
({type: "SpinSquarePair.Left", act})
|
||||
|
||||
export const asRight/*:type.asRight*/ = act =>
|
||||
({type: "SpinSquarePair.Right", act})
|
||||
|
||||
|
||||
export const LeftAction = (act/*:SpinSquare.Action*/)/*:Left*/ =>
|
||||
({$typeof: "Left", act})
|
||||
|
||||
export const RightAction = (act/*:SpinSquare.Action*/)/*:Right*/ =>
|
||||
({$typeof: "Right", act})
|
||||
|
||||
|
||||
export const step = (model/*:Model*/, action/*:Action*/)/*:[Model, Effects<Action>]*/ => {
|
||||
if (action.$typeof === "Left") {
|
||||
export const step/*:type.step*/ = (model, action) => {
|
||||
if (action.type === "SpinSquarePair.Left") {
|
||||
const [left, fx] = SpinSquare.step(model.left, action.act)
|
||||
return [create({left, right: model.right}), fx.map(LeftAction)]
|
||||
return [create({left, right: model.right}), fx.map(asLeft)]
|
||||
}
|
||||
if (action.$typeof === "Right") {
|
||||
if (action.type === "SpinSquarePair.Right") {
|
||||
const [right, fx] = SpinSquare.step(model.right, action.act)
|
||||
return [create({left:model.left, right}), fx.map(RightAction)]
|
||||
return [create({left:model.left, right}), fx.map(asRight)]
|
||||
}
|
||||
|
||||
return [model, Effects.none]
|
||||
}
|
||||
|
||||
// View
|
||||
export var view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
|
||||
|
||||
export var view/*:type.view*/ = (model, address) =>
|
||||
html.div({key: "spin-square-pair",
|
||||
style: {display: "flex"}}, [
|
||||
thunk("left", SpinSquare.view, model.left, forward(address, LeftAction)),
|
||||
thunk("right", SpinSquare.view, model.right, forward(address, RightAction))
|
||||
thunk("left", SpinSquare.view, model.left, forward(address, asLeft)),
|
||||
thunk("right", SpinSquare.view, model.right, forward(address, asRight))
|
||||
])
|
||||
|
|
|
@ -3,70 +3,48 @@ import {Record, Union} from "typed-immutable"
|
|||
import {html, forward, Effects, Task} from "reflex"
|
||||
import {ease, easeOutBounce, float} from "./easing"
|
||||
|
||||
/*::
|
||||
import type {Float, Time} from "./easing"
|
||||
import type {Address} from "reflex/type/signal"
|
||||
import type {VirtualNode} from "reflex/type/renderer"
|
||||
/*:: import * as type from "../type/spin-square" */
|
||||
|
||||
export const create/*:type.create*/ = ({angle, animationState}) =>
|
||||
({type: "SpinSquare.Model", angle, animationState})
|
||||
|
||||
export type AnimationState = {
|
||||
lastTime: Time,
|
||||
elapsedTime: Time
|
||||
}
|
||||
|
||||
export type Model = {
|
||||
angle:Float,
|
||||
animationState:?AnimationState
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
export const create = ({angle, animationState}/*:Model*/)/*:Model*/=>
|
||||
({angle, animationState})
|
||||
|
||||
export const initialize = ()/*:[Model, Effects<Action>]*/ =>
|
||||
[create({angle: 0, animationState:null}), Effects.none]
|
||||
export const initialize/*:type.initialize*/ = () => [
|
||||
create({angle: 0, animationState:null}),
|
||||
Effects.none
|
||||
]
|
||||
|
||||
const rotateStep = 90
|
||||
const ms = 1
|
||||
const second = 1000 * ms
|
||||
const duration = second
|
||||
|
||||
/*::
|
||||
export type Spin = {$typeof: "Spin"}
|
||||
export type Tick = {$typeof: "Tick", time: Time}
|
||||
export type Action = Spin|Tick
|
||||
*/
|
||||
export const asSpin/*:type.asSpin*/ = () => ({type: "SpinSquare.Spin"})
|
||||
export const asTick/*:type.asTick*/ = time => ({type: "SpinSquare.Tick", time})
|
||||
|
||||
export const SpinAction = ()/*:Spin*/ =>
|
||||
({$typeof: "Spin"})
|
||||
export const TickAction = (time/*:Time*/)/*:Tick*/ =>
|
||||
({$typeof: "Tick", time})
|
||||
|
||||
export const step = (model/*:Model*/, action/*:Action*/)/*:[Model, Effects<Action>]*/ => {
|
||||
if (action.$typeof === "Spin") {
|
||||
export const step/*:type.step*/ = (model, action) => {
|
||||
if (action.type === "SpinSquare.Spin") {
|
||||
if (model.animationState == null) {
|
||||
return [model, Effects.tick(TickAction)]
|
||||
return [model, Effects.tick(asTick)]
|
||||
} else {
|
||||
return [model, Effects.none]
|
||||
}
|
||||
}
|
||||
|
||||
if (action.$typeof === "Tick") {
|
||||
if (action.type === "SpinSquare.Tick") {
|
||||
const {animationState, angle} = model
|
||||
const elapsedTime = animationState == null ? 0 :
|
||||
animationState.elapsedTime + (action.time - animationState.lastTime)
|
||||
const elapsedTime = animationState == null ?
|
||||
0 :
|
||||
animationState.elapsedTime + (action.time - animationState.lastTime)
|
||||
|
||||
if (elapsedTime > duration) {
|
||||
return [
|
||||
{angle: angle + rotateStep, animationState: null},
|
||||
create({angle: angle + rotateStep, animationState: null}),
|
||||
Effects.none
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
{angle, animationState: {elapsedTime, lastTime: action.time}},
|
||||
Effects.tick(TickAction)
|
||||
create({angle, animationState: {elapsedTime, lastTime: action.time}}),
|
||||
Effects.tick(asTick)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -77,9 +55,11 @@ export const step = (model/*:Model*/, action/*:Action*/)/*:[Model, Effects<Actio
|
|||
|
||||
// View
|
||||
|
||||
const toOffset = (animationState/*:?AnimationState*/)/*:Float*/ =>
|
||||
animationState == null ? 0 :
|
||||
ease(easeOutBounce, float, 0, rotateStep, duration, animationState.elapsedTime)
|
||||
const toOffset = animationState =>
|
||||
animationState == null ?
|
||||
0 :
|
||||
ease(easeOutBounce, float, 0,
|
||||
rotateStep, duration, animationState.elapsedTime)
|
||||
|
||||
|
||||
const style = {
|
||||
|
@ -94,16 +74,16 @@ const style = {
|
|||
cursor: "pointer",
|
||||
borderRadius: "30px"
|
||||
},
|
||||
spin({angle, animationState}/*:Model*/)/*:Object*/{
|
||||
spin({angle, animationState}) {
|
||||
return {
|
||||
transform: `translate(100px, 100px) rotate(${angle + toOffset(animationState)}deg)`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
|
||||
export const view/*:type.view*/ = (model, address) =>
|
||||
html.figure({
|
||||
key: "spin-square",
|
||||
style: Object.assign(style.spin(model), style.square),
|
||||
onClick: forward(address, SpinAction)
|
||||
style: Object.assign({}, style.spin(model), style.square),
|
||||
onClick: forward(address, asSpin)
|
||||
}, ["Click me!"])
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/* @flow */
|
||||
|
||||
|
||||
import type {Address, VirtualNode} from "reflex/type"
|
||||
import type {Effects} from "reflex/type/effects"
|
||||
import * as SpinSquare from "./spin-square"
|
||||
|
||||
export type State = {
|
||||
left: SpinSquare.State,
|
||||
right: SpinSquare.State
|
||||
}
|
||||
|
||||
export type Model = {
|
||||
left: SpinSquare.Model,
|
||||
right: SpinSquare.Model
|
||||
}
|
||||
|
||||
|
||||
export type Left = {
|
||||
type: "SpinSquarePair.Left",
|
||||
act: SpinSquare.Action
|
||||
}
|
||||
|
||||
export type Right = {
|
||||
type: "SpinSquarePair.Right",
|
||||
act: any // Workaround for facebook/flow#953
|
||||
// act: SpinSquare.Action
|
||||
}
|
||||
|
||||
export type Action
|
||||
= Left
|
||||
| Right
|
||||
|
||||
|
||||
export type asLeft = (action:SpinSquare.Action) => Left
|
||||
export type asRight = (action:SpinSquare.Action) => Right
|
||||
|
||||
export type create = (options:State) => Model
|
||||
export type initialize = () => [Model, Effects<Action>]
|
||||
|
||||
export type step = (model:Model, action:Action) => [Model, Effects<Action>]
|
||||
|
||||
export type view = (model:Model, address:Address<Action>) => VirtualNode
|
|
@ -0,0 +1,36 @@
|
|||
/* @flow */
|
||||
|
||||
import type {Float, Time} from "eased/type"
|
||||
import type {Address, VirtualNode} from "reflex/type"
|
||||
import type {Effects} from "reflex/type/effects"
|
||||
|
||||
|
||||
export type AnimationState = {
|
||||
lastTime: Time,
|
||||
elapsedTime: Time
|
||||
}
|
||||
|
||||
export type State = {
|
||||
angle: Float,
|
||||
animationState: ?AnimationState
|
||||
}
|
||||
|
||||
export type Model
|
||||
= {type: "SpinSquare.Model"}
|
||||
& State
|
||||
|
||||
export type Spin = {type: "SpinSquare.Spin"}
|
||||
export type Tick = {type: "SpinSquare.Tick", time: Time}
|
||||
export type Action
|
||||
= Spin
|
||||
| Tick
|
||||
|
||||
export type asSpin = () => Spin
|
||||
export type asTick = (time:Time) => Tick
|
||||
|
||||
export type create = (options:State) => Model
|
||||
export type initialize = () => [Model, Effects<Action>]
|
||||
|
||||
export type step = (model:Model, action:Action) => [Model, Effects<Action>]
|
||||
|
||||
export type view = (model:Model, address:Address<Action>) => VirtualNode
|
Загрузка…
Ссылка в новой задаче