Merge pull request #3 from Gozala/flowtypes

Update to new reflex & flow versions
This commit is contained in:
Irakli Gozalishvili 2016-03-19 01:05:57 -07:00
Родитель 7b24f16465 be68551fe4
Коммит c0248855bc
152 изменённых файлов: 4140 добавлений и 2771 удалений

12
.babelrc Normal file
Просмотреть файл

@ -0,0 +1,12 @@
{
"sourceMaps": "inline",
"comments": false,
"presets": [
"es2015"
],
"plugins": [
"syntax-flow",
"transform-flow-strip-types",
"remove-comments"
]
}

Просмотреть файл

@ -1,15 +1,15 @@
[ignore]
.*/dist/.*
.*/node_modules/babel.*
.*/node_modules/tap/.*
.*/node_modules/flow-bin/.*
.*/examples/.*
.*/src/test/.*
.*/node_modules/reflex/examples/.*
.*/node_modules/reflex/lib/.*
.*/node_modules/reflex/dist/.*
.*/reflex-virtual-dom-driver/lib/.*
.*/reflex-virtual-dom-driver/dist/.*
[libs]
./node_modules/reflex/interfaces/
[include]
[options]
suppress_comment= \\(.\\|\n\\)*\\@FlowIssue
suppress_comment= \\(.\\|\n\\)*\\@FlowIgnore

Просмотреть файл

@ -0,0 +1,12 @@
{
"sourceMaps": "inline",
"comments": false,
"presets": [
"es2015"
],
"plugins": [
"syntax-flow",
"transform-flow-strip-types",
"remove-comments"
]
}

Просмотреть файл

@ -1,16 +1,14 @@
[ignore]
.*/src/test/.*
.*/dist/.*
.*/node_modules/reflex/examples/.*
.*/node_modules/reflex-virtual-dom-driver/lib/.*
.*/node_modules/reflex/lib/.*
.*/node_modules/babel.*
.*/node_modules/tap/.*
.*/node_modules/reflex-virtual-dom-driver/examlpes/.*
[libs]
./node_modules/reflex/interfaces/
./node_modules/reflex-virtual-dom-driver/interfaces/
[include]
[options]
module.name_mapper='reflex-virtual-dom-driver' -> 'reflex-virtual-dom-driver/src/index'
module.name_mapper='reflex' -> 'reflex/src/index'
suppress_comment= \\(.\\|\n\\)*\\@FlowIssue
suppress_comment= \\(.\\|\n\\)*\\@FlowIgnore

Просмотреть файл

@ -0,0 +1,14 @@
# Counter
### Demo
```
npm start
```
Navigate to [demo][]
[demo]:http://localhost:6061/

Просмотреть файл

@ -1,5 +1,3 @@
'use strict';
import browserify from 'browserify';
import gulp from 'gulp';
import source from 'vinyl-source-stream';
@ -16,120 +14,119 @@ import sequencial from 'gulp-sequence';
import ecstatic from 'ecstatic';
import hmr from 'browserify-hmr';
import hotify from 'hotify';
import manifest from './package.json';
var settings = {
port: process.env.DEV_PORT || '6061',
cache: {},
plugin: [],
transform: [
babelify.configure({
"optional": [
"spec.protoToAssign",
"runtime"
],
"blacklist": []
})
],
debug: true,
watch: false,
compression: null
};
class Bundler {
constructor(options) {
this.options = options
var Bundler = function(entry) {
this.entry = entry
this.compression = settings.compression
this.build = this.build.bind(this);
this.build = this.build.bind(this);
this.plugin = []
this.transform = []
this.cache = {}
this.entry = path.join
( options.input
, options.main
)
this.bundler = browserify({
entries: ['./src/' + entry],
debug: settings.debug,
cache: {},
transform: settings.transform,
plugin: settings.plugin
});
this.watcher = settings.watch &&
watchify(this.bundler)
.on('update', this.build);
}
Bundler.prototype.bundle = function() {
gutil.log(`Begin bundling: '${this.entry}'`);
return this.watcher ? this.watcher.bundle() : this.bundler.bundle();
if (options.babel != null) {
this.transform.push(babelify)
}
if (options.hotreload != null) {
this.plugin.push(hmr)
this.transform.push(hotify)
}
this.bundler = browserify
( { entries: [this.entry]
, debug:
( options.sourceMaps == null
? false
: options.sourceMaps === false
? false
: true
)
, cache: this.cache
, transform: this.transform
, plugin: this.plugin
}
)
if (options.watch) {
this.watcher =
watchify(this.bundler)
.on('update', this.build)
}
}
bundle() {
gutil.log(`Begin bundling: '${this.entry}'`);
const output =
( this.options.watch
? this.watcher.bundle()
: this.bundler.bundle()
)
return output
}
build() {
const transforms =
[ source(this.options.main)
, buffer()
, ( this.options.sourceMaps == null
? null
: sourcemaps.init({loadMaps: true})
)
, ( this.options.compression == null
? null
: uglify(this.options.compression)
)
, ( this.options.sourceMaps == null
? null
: sourcemaps.write(this.options.sourceMaps.output)
)
, gulp.dest(this.options.output)
]
const output =
transforms.reduce
( (input, transform) =>
( transform != null
? input.pipe(transform)
: input
)
, this
.bundle()
.on('error', (error) => {
gutil.beep();
console.error(`Failed to browserify: '${this.entry}'`, error.message)
})
)
return output.on('end', () => gutil.log(`Completed bundling: '${this.entry}'`))
}
}
Bundler.prototype.build = function() {
var bundle = this
.bundle()
.on('error', (error) => {
gutil.beep();
console.error(`Failed to browserify: '${this.entry}'`, error.message);
})
.pipe(source(this.entry + '.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true}))
.on('error', (error) => {
gutil.beep();
console.error(`Failed to make source maps for: '${this.entry}'`,
error.message);
});
return (this.compression ? bundle.pipe(uglify(this.compression)) : bundle)
.on('error', (error) => {
gutil.beep();
console.error(`Failed to bundle: '${this.entry}'`,
error.message);
})
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('./dist/'))
.on('end', () => {
gutil.log(`Completed bundling: '${this.entry}'`);
});
}
var bundler = function(entry) {
return gulp.task(entry, function() {
return new Bundler(entry).build();
});
}
// Starts a static http server that serves browser.html directory.
gulp.task('server', function() {
var server = config => () => {
var server = http.createServer(ecstatic({
root: path.join(module.filename, '../'),
cache: 0
root: path.join(module.filename, config.root),
cache: config.cache
}));
server.listen(settings.port);
});
server.listen(config.port);
gutil.log(`Navigate to http://localhost:${config.port}/`)
}
var bundler = config => () => new Bundler(config).build()
gulp.task('compressor', function() {
settings.compression = {
mangle: true,
compress: true,
acorn: true
};
});
gulp.task('watcher', function() {
settings.watch = true
});
Object.keys(manifest.builds).forEach(name => {
const config = manifest.builds[name]
gulp.task('hotreload', function() {
settings.plugin.push(hmr);
settings.transform.push(hotify);
});
bundler('index');
gulp.task('build', [
'compressor',
'index'
]);
gulp.task('watch', [
'watcher',
'index'
]);
gulp.task('develop', sequencial('watch', 'server'));
gulp.task('live', ['hotreload', 'develop']);
gulp.task('default', ['live']);
if (config.server) {
gulp.task(`serve ${name}`, server(config.server))
gulp.task(`build ${name}`, bundler(config))
gulp.task(name, sequencial(`build ${name}`, `serve ${name}`))
}
else {
gulp.task(name, bundler(config))
}
})

Просмотреть файл

@ -1,10 +1,11 @@
<html>
<head>
<title>Sample App</title>
<title></title>
</head>
<body>
<div id='root'>
</div>
<script src="./dist/index.js"></script>
<main>
</main>
<script src="./dist/virtual-dom-driver.js"></script>
<scrit src="./dist/reflex-driver.js"></script>
</body>
</html>

Просмотреть файл

@ -1,34 +1,58 @@
{
"name": "counter-list",
"description": "Reflex counter list example",
"version": "0.0.0",
"scripts": {
"test": "flow check",
"start": "gulp live",
"build": "NODE_ENV=production gulp build"
"start": "gulp"
},
"dependencies": {
"reflex": "latest",
"reflex-virtual-dom-driver": "latest"
"reflex": "0.2.0",
"reflex-virtual-dom-driver": "0.2.0"
},
"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",
"babel-core": "6.7.2",
"babel-plugin-remove-comments": "2.0.0",
"babel-plugin-syntax-flow": "6.3.13",
"babel-plugin-transform-es2015-modules-umd": "6.4.3",
"babel-plugin-transform-flow-strip-types": "6.4.0",
"babel-preset-es2015": "6.3.13",
"babelify": "7.2.0",
"browserify": "13.0.0",
"browserify-hmr": "0.3.1",
"ecstatic": "0.8.0",
"flow-bin": "0.17.0",
"gulp": "3.9.0",
"gulp-sequence": "0.4.1",
"gulp-sourcemaps": "1.5.2",
"gulp-uglify": "^1.2.0",
"gulp-util": "^3.0.6",
"flow-bin": "0.22.1",
"gulp": "3.9.1",
"gulp-sequence": "0.4.5",
"gulp-sourcemaps": "1.6.0",
"gulp-uglify": "1.5.3",
"gulp-util": "3.0.7",
"hotify": "0.1.0",
"vinyl-buffer": "1.0.0",
"vinyl-source-stream": "1.1.0"
"vinyl-source-stream": "1.1.0",
"watchify": "3.7.0"
},
"builds": {
"default": {
"//compression": {
"mangle": true,
"compress": true,
"acorn": true
},
"babel": true,
"hotreload": true,
"watch": true,
"server": {
"port": 6061,
"cache": 0,
"root": "../"
},
"sourceMaps": {
"output": "./"
},
"main": "./virtual-dom-driver.js",
"input": "./src/",
"output": "./dist/"
}
}
}

Просмотреть файл

@ -4,72 +4,134 @@ import * as Counter from "./counter";
import {html, forward, thunk} from "reflex";
/*::
import * as type from "../type/counter-list"
import type {ID, Model, Action} from "./counter-list"
import type {Address, DOM} from "reflex";
*/
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})
const Add =
() =>
( { type: "Add"
}
)
const Remove =
() =>
( { type: "Remove"
}
)
export const create/*:type.create*/ = ({nextID, entries}) =>
({type: "CounterList.Model", nextID, entries})
const Modify =
(name/*:ID*/) =>
(action/*:Counter.Action*/)/*:Action*/ =>
( { type: "Modify"
, source: {name, action}
}
)
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 init =
()/*:Model*/ =>
( { nextID: 1
, counters: []
}
)
export const remove/*:type.remove*/ = model => create({
nextID: model.nextID,
entries: model.entries.slice(1)
})
const add =
model =>
( { nextID: model.nextID + 1
, counters:
[ ...model.counters
, { name: model.nextID
, counter: Counter.init(0)
}
]
}
)
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)})
})
const remove =
model =>
( { nextID: model.nextID
, counters: model.counters.slice(1)
}
)
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;
const modify =
(model, {name, action}) =>
( { nextID: model.nextID
, counters:
model.counters.map
( ( named ) =>
( named.name === name
? { name
, counter: Counter.update(named.counter, action)
}
: named
)
)
}
)
export const update =
(model/*:Model*/, action/*:Action*/)/*:Model*/ =>
( action.type === "Add"
? add(model)
: action.type === "Remove"
? remove(model)
: action.type === "Modify"
? modify(model, action.source)
: model
);
// View
const viewEntry/*:type.viewEntry*/ = ({id, model}, address) =>
html.div({key: id}, [
Counter.view(model, forward(address, asBy(id)))
])
const viewNamed =
(model, address) =>
thunk
( String(model.name)
, renderNamed
, model
, address
)
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)))
])
const renderNamed =
(model, address) =>
html.div
( { key: model.name
}
, [ Counter.view
( model.counter
, forward(address, Modify(model.name))
)
]
)
export const view =
(model/*:Model*/, address/*:Address<Action>*/)/*:DOM*/ =>
html.div
( { key: "CounterList"
}
, [ html.div
( { key: "controls"
}
, [ html.button
( { key: "add"
, onClick: forward(address, Add)
}
, ["Add"]
)
, html.button
( { key: "remove"
, onClick: forward(address, Remove)
}
, ["Remove"]
)
]
)
, html.div
( { key: "counters"
}
, model.counters.map
( named => viewNamed(named, address)
)
)
]
)

Просмотреть файл

@ -0,0 +1,41 @@
/* @flow */
import type {Address, DOM} from "reflex"
import * as Counter from "./counter"
export type ID = number
export type NamedCounter =
{ name: ID
, counter: Counter.Model
}
export type Model =
{ nextID: ID
, counters: Array<NamedCounter>
}
export type Action
= { type: "Add" }
| { type: "Remove" }
| { type: "Modify"
, source:
{ name: ID
, action: Counter.Action
}
}
declare export function Modify
( name:ID
, action:Counter.Action
):Action
declare export function init (): Model
declare export function update
(model:Model, action:Action):
Model
declare export function view
(model:Model, address:Address<Action>):
DOM

Просмотреть файл

@ -1,45 +1,70 @@
/* @flow */
import {html, forward} from "reflex";
import {html, forward} from "reflex"
/*::
import * as type from "../type/counter"
import type {Model, Action} from "./counter"
import type {Address, DOM} from "reflex"
*/
export const asIncrement/*:type.asIncrement*/ = () =>
({type: "Counter.Increment"})
export const asDecrement/*:type.asDecrement*/ = () =>
({type: "Counter.Decrement"})
const Increment =
() =>
( { type: "Increment"
}
)
const Decrement =
() =>
( { type: "Decrement"
}
)
export const create/*:type.create*/ = ({value}) =>
({type: "Counter.Model", value})
export const init =
(value/*:number*/)/*:Model*/ =>
({ value })
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
export const update =
( model/*:Model*/
, action/*:Action*/
)/*:Model*/ =>
( action.type === "Increment"
? { value: model.value + 1 }
: action.type === "Decrement"
? { value: model.value - 1 }
: model
)
const counterStyle = {
value: {
fontWeight: "bold"
const counterStyle =
{ value:
{ fontWeight: "bold"
}
}
}
// View
export const view/*:type.view*/ = (model, address) =>
html.span({key: "counter"}, [
html.button({
key: "decrement",
onClick: forward(address, asDecrement)
}, ["-"]),
html.span({
key: "value",
style: counterStyle.value,
}, [String(model.value)]),
html.button({
key: "increment",
onClick: forward(address, asIncrement)
}, ["+"])
])
export const view =
( model/*:Model*/
, address/*:Address<Action>*/
)/*:DOM*/ =>
html.span
( { key: "counter"
}
, [ html.button
( { key: "decrement"
, onClick: forward(address, Decrement)
}
, ["-"]
)
, html.span
( { key: "value"
, style: counterStyle.value
}
, [ `${model.value}` ]
)
, html.button
( { key: "increment"
, onClick: forward(address, Increment)
}
, ["+"]
)
]
)

Просмотреть файл

@ -0,0 +1,21 @@
/* @flow */
import type {Address, DOM} from "reflex"
export type Model =
{ value: number
}
export type Action
= {type: "Increment"}
| {type: "Decrement"}
declare export function init (value:number):
Model
declare export function update (model:Model, action:Action):
Model
declare export function view (model:Model, address:Address<Action>):
DOM

Просмотреть файл

@ -1,18 +0,0 @@
/* @flow */
import * as CounterList from "./counter-list"
import {start} from "reflex"
import {Renderer} from "reflex-virtual-dom-driver"
const app = start({
initial: CounterList.create(window.app != null ?
window.app.model.value :
{nextID: 0, entries: []}),
update: CounterList.update,
view: CounterList.view
});
window.app = app
const renderer = new Renderer({target: document.body})
app.view.subscribe(renderer.address)

Просмотреть файл

@ -0,0 +1,18 @@
/* @flow */
import {init, update, view} from "./counter-list"
import {start, beginner} from "reflex"
export const app = start
( beginner
( { model:
( window.app == null
? init()
: window.app.model.value
)
, update: update
, view: view
}
)
)
window.app = app

Просмотреть файл

@ -0,0 +1,18 @@
/* @flow */
import {app} from "./main"
import {Renderer} from "reflex-virtual-dom-driver"
import {Effects} from "reflex"
const renderer = new Renderer
( { target: document.body
}
)
app.view.subscribe
( renderer.address
)
app.task.subscribe
( Effects.driver(app.address)
)

Просмотреть файл

@ -1,39 +0,0 @@
/* @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

Просмотреть файл

@ -1,15 +0,0 @@
/* @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

Просмотреть файл

@ -0,0 +1,12 @@
{
"sourceMaps": "inline",
"comments": false,
"presets": [
"es2015"
],
"plugins": [
"syntax-flow",
"transform-flow-strip-types",
"remove-comments"
]
}

Просмотреть файл

@ -1,16 +1,14 @@
[ignore]
.*/src/test/.*
.*/dist/.*
.*/node_modules/reflex/examples/.*
.*/node_modules/reflex-virtual-dom-driver/lib/.*
.*/node_modules/reflex/lib/.*
.*/node_modules/babel.*
.*/node_modules/tap/.*
.*/node_modules/reflex-virtual-dom-driver/examlpes/.*
[libs]
./node_modules/reflex/interfaces/
./node_modules/reflex-virtual-dom-driver/interfaces/
[include]
[options]
module.name_mapper='reflex-virtual-dom-driver' -> 'reflex-virtual-dom-driver/src/index'
module.name_mapper='reflex' -> 'reflex/src/index'
suppress_comment= \\(.\\|\n\\)*\\@FlowIssue
suppress_comment= \\(.\\|\n\\)*\\@FlowIgnore

Просмотреть файл

@ -0,0 +1,14 @@
# Counter
### Demo
```
npm start
```
Navigate to [demo][]
[demo]:http://localhost:6061/

Просмотреть файл

@ -1,5 +1,3 @@
'use strict';
import browserify from 'browserify';
import gulp from 'gulp';
import source from 'vinyl-source-stream';
@ -16,120 +14,119 @@ import sequencial from 'gulp-sequence';
import ecstatic from 'ecstatic';
import hmr from 'browserify-hmr';
import hotify from 'hotify';
import manifest from './package.json';
var settings = {
port: process.env.DEV_PORT || '6061',
cache: {},
plugin: [],
transform: [
babelify.configure({
"optional": [
"spec.protoToAssign",
"runtime"
],
"blacklist": []
})
],
debug: true,
watch: false,
compression: null
};
class Bundler {
constructor(options) {
this.options = options
var Bundler = function(entry) {
this.entry = entry
this.compression = settings.compression
this.build = this.build.bind(this);
this.build = this.build.bind(this);
this.plugin = []
this.transform = []
this.cache = {}
this.entry = path.join
( options.input
, options.main
)
this.bundler = browserify({
entries: ['./src/' + entry],
debug: settings.debug,
cache: {},
transform: settings.transform,
plugin: settings.plugin
});
this.watcher = settings.watch &&
watchify(this.bundler)
.on('update', this.build);
}
Bundler.prototype.bundle = function() {
gutil.log(`Begin bundling: '${this.entry}'`);
return this.watcher ? this.watcher.bundle() : this.bundler.bundle();
if (options.babel != null) {
this.transform.push(babelify)
}
if (options.hotreload != null) {
this.plugin.push(hmr)
this.transform.push(hotify)
}
this.bundler = browserify
( { entries: [this.entry]
, debug:
( options.sourceMaps == null
? false
: options.sourceMaps === false
? false
: true
)
, cache: this.cache
, transform: this.transform
, plugin: this.plugin
}
)
if (options.watch) {
this.watcher =
watchify(this.bundler)
.on('update', this.build)
}
}
bundle() {
gutil.log(`Begin bundling: '${this.entry}'`);
const output =
( this.options.watch
? this.watcher.bundle()
: this.bundler.bundle()
)
return output
}
build() {
const transforms =
[ source(this.options.main)
, buffer()
, ( this.options.sourceMaps == null
? null
: sourcemaps.init({loadMaps: true})
)
, ( this.options.compression == null
? null
: uglify(this.options.compression)
)
, ( this.options.sourceMaps == null
? null
: sourcemaps.write(this.options.sourceMaps.output)
)
, gulp.dest(this.options.output)
]
const output =
transforms.reduce
( (input, transform) =>
( transform != null
? input.pipe(transform)
: input
)
, this
.bundle()
.on('error', (error) => {
gutil.beep();
console.error(`Failed to browserify: '${this.entry}'`, error.message)
})
)
return output.on('end', () => gutil.log(`Completed bundling: '${this.entry}'`))
}
}
Bundler.prototype.build = function() {
var bundle = this
.bundle()
.on('error', (error) => {
gutil.beep();
console.error(`Failed to browserify: '${this.entry}'`, error.message);
})
.pipe(source(this.entry + '.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true}))
.on('error', (error) => {
gutil.beep();
console.error(`Failed to make source maps for: '${this.entry}'`,
error.message);
});
return (this.compression ? bundle.pipe(uglify(this.compression)) : bundle)
.on('error', (error) => {
gutil.beep();
console.error(`Failed to bundle: '${this.entry}'`,
error.message);
})
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('./dist/'))
.on('end', () => {
gutil.log(`Completed bundling: '${this.entry}'`);
});
}
var bundler = function(entry) {
return gulp.task(entry, function() {
return new Bundler(entry).build();
});
}
// Starts a static http server that serves browser.html directory.
gulp.task('server', function() {
var server = config => () => {
var server = http.createServer(ecstatic({
root: path.join(module.filename, '../'),
cache: 0
root: path.join(module.filename, config.root),
cache: config.cache
}));
server.listen(settings.port);
});
server.listen(config.port);
gutil.log(`Navigate to http://localhost:${config.port}/`)
}
var bundler = config => () => new Bundler(config).build()
gulp.task('compressor', function() {
settings.compression = {
mangle: true,
compress: true,
acorn: true
};
});
gulp.task('watcher', function() {
settings.watch = true
});
Object.keys(manifest.builds).forEach(name => {
const config = manifest.builds[name]
gulp.task('hotreload', function() {
settings.plugin.push(hmr);
settings.transform.push(hotify);
});
bundler('index');
gulp.task('build', [
'compressor',
'index'
]);
gulp.task('watch', [
'watcher',
'index'
]);
gulp.task('develop', sequencial('watch', 'server'));
gulp.task('live', ['hotreload', 'develop']);
gulp.task('default', ['live']);
if (config.server) {
gulp.task(`serve ${name}`, server(config.server))
gulp.task(`build ${name}`, bundler(config))
gulp.task(name, sequencial(`build ${name}`, `serve ${name}`))
}
else {
gulp.task(name, bundler(config))
}
})

Просмотреть файл

@ -1,10 +1,11 @@
<html>
<head>
<title>Sample App</title>
<title></title>
</head>
<body>
<div id='root'>
</div>
<script src="./dist/index.js"></script>
<main>
</main>
<script src="./dist/virtual-dom-driver.js"></script>
<scrit src="./dist/reflex-driver.js"></script>
</body>
</html>

Просмотреть файл

@ -1,34 +1,58 @@
{
"name": "counter-pair",
"description": "Reflex counter pair example",
"version": "0.0.0",
"scripts": {
"test": "flow check",
"start": "gulp live",
"build": "NODE_ENV=production gulp build"
"start": "gulp"
},
"dependencies": {
"reflex": "latest",
"reflex-virtual-dom-driver": "latest"
"reflex": "0.2.0",
"reflex-virtual-dom-driver": "0.2.0"
},
"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",
"babel-core": "6.7.2",
"babel-plugin-remove-comments": "2.0.0",
"babel-plugin-syntax-flow": "6.3.13",
"babel-plugin-transform-es2015-modules-umd": "6.4.3",
"babel-plugin-transform-flow-strip-types": "6.4.0",
"babel-preset-es2015": "6.3.13",
"babelify": "7.2.0",
"browserify": "13.0.0",
"browserify-hmr": "0.3.1",
"ecstatic": "0.8.0",
"flow-bin": "0.17.0",
"gulp": "3.9.0",
"gulp-sequence": "0.4.1",
"gulp-sourcemaps": "1.5.2",
"gulp-uglify": "^1.2.0",
"gulp-util": "^3.0.6",
"flow-bin": "0.22.1",
"gulp": "3.9.1",
"gulp-sequence": "0.4.5",
"gulp-sourcemaps": "1.6.0",
"gulp-uglify": "1.5.3",
"gulp-util": "3.0.7",
"hotify": "0.1.0",
"vinyl-buffer": "1.0.0",
"vinyl-source-stream": "1.1.0"
"vinyl-source-stream": "1.1.0",
"watchify": "3.7.0"
},
"builds": {
"default": {
"//compression": {
"mangle": true,
"compress": true,
"acorn": true
},
"babel": true,
"hotreload": true,
"watch": true,
"server": {
"port": 6061,
"cache": 0,
"root": "../"
},
"sourceMaps": {
"output": "./"
},
"main": "./virtual-dom-driver.js",
"input": "./src/",
"output": "./dist/"
}
}
}

Просмотреть файл

@ -4,55 +4,89 @@ import * as Counter from "./counter";
import {html, forward} from "reflex";
/*::
import type {VirtualNode, Address} from "reflex/type"
import * as type from "../type/counter-pair"
import type {Address, DOM} from "reflex"
import type {Action, Model} from "./counter-pair"
*/
export const asTop/*:type.asTop*/ = act =>
({type: "CounterPair.Top", act})
const Top =
(action) =>
( { type: "Top"
, source: action
}
)
export const asBottom/*:type.asBottom*/ = act =>
({type: "CounterPair.Bottom", act})
const Bottom =
(action) =>
( { type: "Bottom"
, source: action
}
)
export const asReset/*:type.asReset*/ = () =>
({type: "CounterPair.Reset"})
const Reset =
() =>
( { type: "Reset"
}
)
export const init =
(top/*:number*/, bottom/*:number*/)/*:Model*/ =>
( { top: Counter.init(top)
, bottom: Counter.init(bottom)
}
)
export const update =
(model/*:Model*/, action/*:Action*/)/*:Model*/ =>
( action.type === "Top"
? { top: Counter.update(model.top, action.source)
, bottom: model.bottom
}
: action.type === "Bottom"
? { top: model.top
, bottom: Counter.update(model.bottom, action.source)
}
: action.type === "Reset"
? init(0, 0)
: model
)
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"])
])
])
export const view =
( model/*:Model*/
, address/*:Address<Action>*/
)/*:DOM*/ =>
html.div
( { key: "counter-pair"
}
, [ html.div
( { key: "top"
}
, [ Counter.view
( model.top
, forward(address, Top)
)
]
)
, html.div
( { key: "bottom"
}
, [ Counter.view
( model.bottom
, forward(address, Bottom)
)
]
)
, html.div
( { key: "controls"
}
, [ html.button
( { key: "reset"
, onClick: forward(address, Reset)
}
, ["Reset"]
)
]
)
]
)

Просмотреть файл

@ -0,0 +1,25 @@
/* @flow */
import type {Address, DOM} from "reflex"
import * as Counter from "./counter"
export type Model =
{ top: Counter.Model
, bottom: Counter.Model
}
export type Action
= { type: "Top", source: Counter.Action }
| { type: "Bottom", source: Counter.Action }
| { type: "Reset" }
declare export function init
(top:number, bottom:number):Model
declare export function update
(model:Model, action:Action):
Model
declare export function view
(model:Model, address:Address<Action>):
DOM

Просмотреть файл

@ -1,45 +1,70 @@
/* @flow */
import {html, forward} from "reflex";
import {html, forward} from "reflex"
/*::
import * as type from "../type/counter"
import type {Model, Action} from "./counter"
import type {Address, DOM} from "reflex"
*/
export const asIncrement/*:type.asIncrement*/ = () =>
({type: "Counter.Increment"})
export const asDecrement/*:type.asDecrement*/ = () =>
({type: "Counter.Decrement"})
const Increment =
() =>
( { type: "Increment"
}
)
const Decrement =
() =>
( { type: "Decrement"
}
)
export const create/*:type.create*/ = ({value}) =>
({type: "Counter.Model", value})
export const init =
(value/*:number*/)/*:Model*/ =>
({ value })
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
export const update =
( model/*:Model*/
, action/*:Action*/
)/*:Model*/ =>
( action.type === "Increment"
? { value: model.value + 1 }
: action.type === "Decrement"
? { value: model.value - 1 }
: model
)
const counterStyle = {
value: {
fontWeight: "bold"
const counterStyle =
{ value:
{ fontWeight: "bold"
}
}
}
// View
export const view/*:type.view*/ = (model, address) =>
html.span({key: "counter"}, [
html.button({
key: "decrement",
onClick: forward(address, asDecrement)
}, ["-"]),
html.span({
key: "value",
style: counterStyle.value,
}, [String(model.value)]),
html.button({
key: "increment",
onClick: forward(address, asIncrement)
}, ["+"])
])
export const view =
( model/*:Model*/
, address/*:Address<Action>*/
)/*:DOM*/ =>
html.span
( { key: "counter"
}
, [ html.button
( { key: "decrement"
, onClick: forward(address, Decrement)
}
, ["-"]
)
, html.span
( { key: "value"
, style: counterStyle.value
}
, [ `${model.value}` ]
)
, html.button
( { key: "increment"
, onClick: forward(address, Increment)
}
, ["+"]
)
]
)

Просмотреть файл

@ -0,0 +1,21 @@
/* @flow */
import type {Address, DOM} from "reflex"
export type Model =
{ value: number
}
export type Action
= {type: "Increment"}
| {type: "Decrement"}
declare export function init (value:number):
Model
declare export function update (model:Model, action:Action):
Model
declare export function view (model:Model, address:Address<Action>):
DOM

Просмотреть файл

@ -1,19 +0,0 @@
/* @flow */
import * as CounterPair from "./counter-pair"
import {start} from "reflex"
import {Renderer} from "reflex-virtual-dom-driver"
var app = start({
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
var renderer = new Renderer({target: document.body})
app.view.subscribe(renderer.address)

Просмотреть файл

@ -0,0 +1,18 @@
/* @flow */
import {init, update, view} from "./counter-pair"
import {start, beginner} from "reflex"
export const app = start
( beginner
( { model:
( window.app == null
? init(0, 0)
: window.app.model.value
)
, update: update
, view: view
}
)
)
window.app = app

Просмотреть файл

@ -0,0 +1,18 @@
/* @flow */
import {app} from "./main"
import {Renderer} from "reflex-virtual-dom-driver"
import {Effects} from "reflex"
const renderer = new Renderer
( { target: document.body
}
)
app.view.subscribe
( renderer.address
)
app.task.subscribe
( Effects.driver(app.address)
)

Просмотреть файл

@ -1,37 +0,0 @@
// @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

Просмотреть файл

@ -1,15 +0,0 @@
/* @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

Просмотреть файл

@ -0,0 +1,12 @@
{
"sourceMaps": "inline",
"comments": false,
"presets": [
"es2015"
],
"plugins": [
"syntax-flow",
"transform-flow-strip-types",
"remove-comments"
]
}

Просмотреть файл

@ -1,16 +1,14 @@
[ignore]
.*/src/test/.*
.*/dist/.*
.*/node_modules/reflex/examples/.*
.*/node_modules/reflex-virtual-dom-driver/lib/.*
.*/node_modules/reflex/lib/.*
.*/node_modules/babel.*
.*/node_modules/tap/.*
.*/node_modules/reflex-virtual-dom-driver/examlpes/.*
[libs]
./node_modules/reflex/interfaces/
./node_modules/reflex-virtual-dom-driver/interfaces/
[include]
[options]
module.name_mapper='reflex-virtual-dom-driver' -> 'reflex-virtual-dom-driver/src/index'
module.name_mapper='reflex' -> 'reflex/src/index'
suppress_comment= \\(.\\|\n\\)*\\@FlowIssue
suppress_comment= \\(.\\|\n\\)*\\@FlowIgnore

Просмотреть файл

@ -0,0 +1,14 @@
# Counter
### Demo
```
npm start
```
Navigate to [demo][]
[demo]:http://localhost:6061/

Просмотреть файл

@ -1,5 +1,3 @@
'use strict';
import browserify from 'browserify';
import gulp from 'gulp';
import source from 'vinyl-source-stream';
@ -16,120 +14,119 @@ import sequencial from 'gulp-sequence';
import ecstatic from 'ecstatic';
import hmr from 'browserify-hmr';
import hotify from 'hotify';
import manifest from './package.json';
var settings = {
port: process.env.DEV_PORT || '6061',
cache: {},
plugin: [],
transform: [
babelify.configure({
"optional": [
"spec.protoToAssign",
"runtime"
],
"blacklist": []
})
],
debug: true,
watch: false,
compression: null
};
class Bundler {
constructor(options) {
this.options = options
var Bundler = function(entry) {
this.entry = entry
this.compression = settings.compression
this.build = this.build.bind(this);
this.build = this.build.bind(this);
this.plugin = []
this.transform = []
this.cache = {}
this.entry = path.join
( options.input
, options.main
)
this.bundler = browserify({
entries: ['./src/' + entry],
debug: settings.debug,
cache: {},
transform: settings.transform,
plugin: settings.plugin
});
this.watcher = settings.watch &&
watchify(this.bundler)
.on('update', this.build);
}
Bundler.prototype.bundle = function() {
gutil.log(`Begin bundling: '${this.entry}'`);
return this.watcher ? this.watcher.bundle() : this.bundler.bundle();
if (options.babel != null) {
this.transform.push(babelify)
}
if (options.hotreload != null) {
this.plugin.push(hmr)
this.transform.push(hotify)
}
this.bundler = browserify
( { entries: [this.entry]
, debug:
( options.sourceMaps == null
? false
: options.sourceMaps === false
? false
: true
)
, cache: this.cache
, transform: this.transform
, plugin: this.plugin
}
)
if (options.watch) {
this.watcher =
watchify(this.bundler)
.on('update', this.build)
}
}
bundle() {
gutil.log(`Begin bundling: '${this.entry}'`);
const output =
( this.options.watch
? this.watcher.bundle()
: this.bundler.bundle()
)
return output
}
build() {
const transforms =
[ source(this.options.main)
, buffer()
, ( this.options.sourceMaps == null
? null
: sourcemaps.init({loadMaps: true})
)
, ( this.options.compression == null
? null
: uglify(this.options.compression)
)
, ( this.options.sourceMaps == null
? null
: sourcemaps.write(this.options.sourceMaps.output)
)
, gulp.dest(this.options.output)
]
const output =
transforms.reduce
( (input, transform) =>
( transform != null
? input.pipe(transform)
: input
)
, this
.bundle()
.on('error', (error) => {
gutil.beep();
console.error(`Failed to browserify: '${this.entry}'`, error.message)
})
)
return output.on('end', () => gutil.log(`Completed bundling: '${this.entry}'`))
}
}
Bundler.prototype.build = function() {
var bundle = this
.bundle()
.on('error', (error) => {
gutil.beep();
console.error(`Failed to browserify: '${this.entry}'`, error.message);
})
.pipe(source(this.entry + '.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true}))
.on('error', (error) => {
gutil.beep();
console.error(`Failed to make source maps for: '${this.entry}'`,
error.message);
});
return (this.compression ? bundle.pipe(uglify(this.compression)) : bundle)
.on('error', (error) => {
gutil.beep();
console.error(`Failed to bundle: '${this.entry}'`,
error.message);
})
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('./dist/'))
.on('end', () => {
gutil.log(`Completed bundling: '${this.entry}'`);
});
}
var bundler = function(entry) {
return gulp.task(entry, function() {
return new Bundler(entry).build();
});
}
// Starts a static http server that serves browser.html directory.
gulp.task('server', function() {
var server = config => () => {
var server = http.createServer(ecstatic({
root: path.join(module.filename, '../'),
cache: 0
root: path.join(module.filename, config.root),
cache: config.cache
}));
server.listen(settings.port);
});
server.listen(config.port);
gutil.log(`Navigate to http://localhost:${config.port}/`)
}
var bundler = config => () => new Bundler(config).build()
gulp.task('compressor', function() {
settings.compression = {
mangle: true,
compress: true,
acorn: true
};
});
gulp.task('watcher', function() {
settings.watch = true
});
Object.keys(manifest.builds).forEach(name => {
const config = manifest.builds[name]
gulp.task('hotreload', function() {
settings.plugin.push(hmr);
settings.transform.push(hotify);
});
bundler('index');
gulp.task('build', [
'compressor',
'index'
]);
gulp.task('watch', [
'watcher',
'index'
]);
gulp.task('develop', sequencial('watch', 'server'));
gulp.task('live', ['hotreload', 'develop']);
gulp.task('default', ['live']);
if (config.server) {
gulp.task(`serve ${name}`, server(config.server))
gulp.task(`build ${name}`, bundler(config))
gulp.task(name, sequencial(`build ${name}`, `serve ${name}`))
}
else {
gulp.task(name, bundler(config))
}
})

Просмотреть файл

@ -1,10 +1,11 @@
<html>
<head>
<title>Sample App</title>
<title></title>
</head>
<body>
<div id='root'>
</div>
<script src="./dist/index.js"></script>
<main>
</main>
<script src="./dist/virtual-dom-driver.js"></script>
<scrit src="./dist/reflex-driver.js"></script>
</body>
</html>

Просмотреть файл

@ -1,34 +1,58 @@
{
"name": "counter-set",
"description": "Reflex counter set example",
"version": "0.0.0",
"scripts": {
"test": "flow check",
"start": "gulp live",
"build": "NODE_ENV=production gulp build"
"start": "gulp"
},
"dependencies": {
"reflex": "latest",
"reflex-virtual-dom-driver": "latest"
"reflex": "0.2.0",
"reflex-virtual-dom-driver": "0.2.0"
},
"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",
"babel-core": "6.7.2",
"babel-plugin-remove-comments": "2.0.0",
"babel-plugin-syntax-flow": "6.3.13",
"babel-plugin-transform-es2015-modules-umd": "6.4.3",
"babel-plugin-transform-flow-strip-types": "6.4.0",
"babel-preset-es2015": "6.3.13",
"babelify": "7.2.0",
"browserify": "13.0.0",
"browserify-hmr": "0.3.1",
"ecstatic": "0.8.0",
"flow-bin": "0.17.0",
"gulp": "3.9.0",
"gulp-sequence": "0.4.1",
"gulp-sourcemaps": "1.5.2",
"gulp-uglify": "^1.2.0",
"gulp-util": "^3.0.6",
"flow-bin": "0.22.1",
"gulp": "3.9.1",
"gulp-sequence": "0.4.5",
"gulp-sourcemaps": "1.6.0",
"gulp-uglify": "1.5.3",
"gulp-util": "3.0.7",
"hotify": "0.1.0",
"vinyl-buffer": "1.0.0",
"vinyl-source-stream": "1.1.0"
"vinyl-source-stream": "1.1.0",
"watchify": "3.7.0"
},
"builds": {
"default": {
"//compression": {
"mangle": true,
"compress": true,
"acorn": true
},
"babel": true,
"hotreload": true,
"watch": true,
"server": {
"port": 6061,
"cache": 0,
"root": "../"
},
"sourceMaps": {
"output": "./"
},
"main": "./virtual-dom-driver.js",
"input": "./src/",
"output": "./dist/"
}
}
}

Просмотреть файл

@ -4,72 +4,122 @@ import * as Counter from "./counter";
import {html, forward, thunk} from "reflex";
/*::
import * as type from "../type/counter-list"
import type {ID, Model, Action} from "./counter-list"
import type {Address, DOM} from "reflex";
*/
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})
const Add =
() =>
( { type: "Add"
}
)
const Remove =
() =>
( { type: "Remove"
}
)
export const create/*:type.create*/ = ({nextID, entries}) =>
({type: "CounterList.Model", nextID, entries})
const Modify =
(name/*:ID*/) =>
(action/*:Counter.Action*/)/*:Action*/ =>
( { type: "Modify"
, source: {name, action}
}
)
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 init =
()/*:Model*/ =>
( { nextID: 1
, counters: []
}
)
export const remove/*:type.remove*/ = model => create({
nextID: model.nextID,
entries: model.entries.slice(1)
})
const add =
model =>
( { nextID: model.nextID + 1
, counters:
[ ...model.counters
, { name: model.nextID
, counter: Counter.init(0)
}
]
}
)
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)})
})
const remove =
model =>
( { nextID: model.nextID
, counters: model.counters.slice(1)
}
)
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;
const modify =
(model, {name, action}) =>
( { nextID: model.nextID
, counters:
model.counters.map
( ( named ) =>
( named.name === name
? { name
, counter: Counter.update(named.counter, action)
}
: named
)
)
}
)
export const update =
(model/*:Model*/, action/*:Action*/)/*:Model*/ =>
( action.type === "Add"
? add(model)
: action.type === "Remove"
? remove(model)
: action.type === "Modify"
? modify(model, action.source)
: model
);
// View
const viewEntry/*:type.viewEntry*/ = ({id, model}, address) =>
html.div({key: id}, [
Counter.view(model, forward(address, asBy(id)))
])
const viewNamed =
(model, address) =>
thunk
( String(model.name)
, Counter.view
, model.counter
, forward(address, Modify(model.name))
)
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)))
])
export const view =
(model/*:Model*/, address/*:Address<Action>*/)/*:DOM*/ =>
html.div
( { key: "CounterList"
}
, [ html.div
( { key: "controls"
}
, [ html.button
( { key: "add"
, onClick: forward(address, Add)
}
, ["Add"]
)
, html.button
( { key: "remove"
, onClick: forward(address, Remove)
}
, ["Remove"]
)
]
)
, html.div
( { key: "counters"
}
, model.counters.map
( named => viewNamed(named, address)
)
)
]
)

Просмотреть файл

@ -0,0 +1,41 @@
/* @flow */
import type {Address, DOM} from "reflex"
import * as Counter from "./counter"
export type ID = number
export type NamedCounter =
{ name: ID
, counter: Counter.Model
}
export type Model =
{ nextID: ID
, counters: Array<NamedCounter>
}
export type Action
= { type: "Add" }
| { type: "Remove" }
| { type: "Modify"
, source:
{ name: ID
, action: Counter.Action
}
}
declare export function Modify
( name:ID
, action:Counter.Action
):Action
declare export function init (): Model
declare export function update
(model:Model, action:Action):
Model
declare export function view
(model:Model, address:Address<Action>):
DOM

Просмотреть файл

@ -4,45 +4,123 @@ import * as CounterList from "./counter-list";
import * as Counter from "./counter";
import {html, forward, thunk} from "reflex";
/*:: import * as type from "../type/counter-set" */
/*::
import type {ID, Model, Action} from "./counter-set"
import type {DOM, Address} from "reflex"
*/
export const create = CounterList.create
const Add =
() =>
( { type: "Add"
}
)
export const asRemoveBy/*:type.asRemoveBy*/ = id => () =>
({type: "CounterSet.RemoveByID", id})
const Remove =
(name) =>
() =>
( { type: "Remove"
, source: name
}
)
export const removeByID/*:type.removeByID*/ = (model, id) => create({
nextID: model.nextID,
entries: model.entries.filter(entry => entry.id != id)
})
const Modify =
(name) =>
(action) =>
( { type: "Modify"
, source: {name, action}
}
)
export const update/*:type.update*/ = (model, action) =>
action.type === "CounterSet.RemoveByID" ?
removeByID(model, action.id) :
CounterList.update(model, action)
const remove =
(model/*:Model*/, name/*:ID*/)/*:Model*/ =>
( { nextID: model.nextID
, counters:
model.counters.filter
( named => named.name !== name )
}
)
const modify =
(model, {name, action}) =>
( { nextID: model.nextID
, counters:
model.counters.map
( named =>
( named.name === name
? { name
, counter: Counter.update(named.counter, action)
}
: named
)
)
}
)
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 init = CounterList.init
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)))
])
export const update =
( model/*:Model*/, action/*:Action*/)/*:Model*/ =>
( action.type === "Add"
? CounterList.update(model, action)
: action.type === "Remove"
? remove(model, action.source)
: action.type === "Modify"
? modify(model, action.source)
: model
)
const viewNamed =
(model, address) =>
thunk
( String(model.name)
, renderNamed
, model
, address
)
const renderNamed =
(model, address) =>
html.div
( { key: model.name
}
, [ Counter.view
( model.counter
, forward(address, Modify(model.name))
)
, html.button
( { key: "remove"
, onClick: forward(address, Remove(model.name))
}
, ["x"]
)
]
)
export const view =
( model/*:Model*/
, address/*:Address<Action>*/
)/*:DOM*/ =>
html.div
( { key: "CounterList"
}
, [ html.div
( { key: "controls"
}
, [ html.button
( { key: "add"
, onClick: forward(address, Add)
}
, ["Add"]
)
]
)
, html.div
( { key: "counters"
}
, model.counters.map
( named =>
viewNamed(named, address)
)
)
]
)

Просмотреть файл

@ -0,0 +1,26 @@
/* @flow */
import * as Counter from "./counter"
import type {Model, ID} from "./counter-list"
import type {Address, DOM} from "reflex"
export type {Model, ID}
export type Action
= { type: "Add" }
| { type: "Remove", source: ID }
| { type: "Modify"
, source: {name: ID, action: Counter.Action}
}
declare export function init
():
Model
declare export function update
(model:Model, action:Action)
:Model
declare export function view
(model:Model, address:Address<Action>):
DOM

Просмотреть файл

@ -1,45 +1,70 @@
/* @flow */
import {html, forward} from "reflex";
import {html, forward} from "reflex"
/*::
import * as type from "../type/counter"
import type {Model, Action} from "./counter"
import type {Address, DOM} from "reflex"
*/
export const asIncrement/*:type.asIncrement*/ = () =>
({type: "Counter.Increment"})
export const asDecrement/*:type.asDecrement*/ = () =>
({type: "Counter.Decrement"})
const Increment =
() =>
( { type: "Increment"
}
)
const Decrement =
() =>
( { type: "Decrement"
}
)
export const create/*:type.create*/ = ({value}) =>
({type: "Counter.Model", value})
export const init =
(value/*:number*/)/*:Model*/ =>
({ value })
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
export const update =
( model/*:Model*/
, action/*:Action*/
)/*:Model*/ =>
( action.type === "Increment"
? { value: model.value + 1 }
: action.type === "Decrement"
? { value: model.value - 1 }
: model
)
const counterStyle = {
value: {
fontWeight: "bold"
const counterStyle =
{ value:
{ fontWeight: "bold"
}
}
}
// View
export const view/*:type.view*/ = (model, address) =>
html.span({key: "counter"}, [
html.button({
key: "decrement",
onClick: forward(address, asDecrement)
}, ["-"]),
html.span({
key: "value",
style: counterStyle.value,
}, [String(model.value)]),
html.button({
key: "increment",
onClick: forward(address, asIncrement)
}, ["+"])
])
export const view =
( model/*:Model*/
, address/*:Address<Action>*/
)/*:DOM*/ =>
html.span
( { key: "counter"
}
, [ html.button
( { key: "decrement"
, onClick: forward(address, Decrement)
}
, ["-"]
)
, html.span
( { key: "value"
, style: counterStyle.value
}
, [ `${model.value}` ]
)
, html.button
( { key: "increment"
, onClick: forward(address, Increment)
}
, ["+"]
)
]
)

Просмотреть файл

@ -0,0 +1,21 @@
/* @flow */
import type {Address, DOM} from "reflex"
export type Model =
{ value: number
}
export type Action
= {type: "Increment"}
| {type: "Decrement"}
declare export function init (value:number):
Model
declare export function update (model:Model, action:Action):
Model
declare export function view (model:Model, address:Address<Action>):
DOM

Просмотреть файл

@ -1,18 +0,0 @@
/* @flow */
import * as CounterSet from "./counter-set"
import {start} from "reflex"
import {Renderer} from "reflex-virtual-dom-driver"
var app = start({
initial: CounterSet.create(window.app != null ?
window.app.model.value :
{nextID: 0, entries: []}),
update: CounterSet.update,
view: CounterSet.view
});
window.app = app
var renderer = new Renderer({target: document.body})
app.view.subscribe(renderer.address)

Просмотреть файл

@ -0,0 +1,18 @@
/* @flow */
import {init, update, view} from "./counter-set"
import {start, beginner} from "reflex"
export const app = start
( beginner
( { model:
( window.app == null
? init(0)
: window.app.model.value
)
, update: update
, view: view
}
)
)
window.app = app

Просмотреть файл

@ -0,0 +1,18 @@
/* @flow */
import {app} from "./main"
import {Renderer} from "reflex-virtual-dom-driver"
import {Effects} from "reflex"
const renderer = new Renderer
( { target: document.body
}
)
app.view.subscribe
( renderer.address
)
app.task.subscribe
( Effects.driver(app.address)
)

Просмотреть файл

@ -1,39 +0,0 @@
/* @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

Просмотреть файл

@ -1,23 +0,0 @@
/* @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

Просмотреть файл

@ -1,15 +0,0 @@
/* @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

12
examples/counter/.babelrc Normal file
Просмотреть файл

@ -0,0 +1,12 @@
{
"sourceMaps": "inline",
"comments": false,
"presets": [
"es2015"
],
"plugins": [
"syntax-flow",
"transform-flow-strip-types",
"remove-comments"
]
}

Просмотреть файл

@ -1,16 +1,14 @@
[ignore]
.*/src/test/.*
.*/dist/.*
.*/node_modules/reflex/examples/.*
.*/node_modules/reflex-virtual-dom-driver/lib/.*
.*/node_modules/reflex/lib/.*
.*/node_modules/babel.*
.*/node_modules/tap/.*
.*/node_modules/reflex-virtual-dom-driver/examlpes/.*
[libs]
./node_modules/reflex/interfaces/
./node_modules/reflex-virtual-dom-driver/interfaces/
[include]
[options]
module.name_mapper='reflex-virtual-dom-driver' -> 'reflex-virtual-dom-driver/src/index'
module.name_mapper='reflex' -> 'reflex/src/index'
suppress_comment= \\(.\\|\n\\)*\\@FlowIssue
suppress_comment= \\(.\\|\n\\)*\\@FlowIgnore

Просмотреть файл

@ -0,0 +1,14 @@
# Counter
### Demo
```
npm start
```
Navigate to [demo][]
[demo]:http://localhost:6061/

Просмотреть файл

@ -1,5 +1,3 @@
'use strict';
import browserify from 'browserify';
import gulp from 'gulp';
import source from 'vinyl-source-stream';
@ -16,120 +14,119 @@ import sequencial from 'gulp-sequence';
import ecstatic from 'ecstatic';
import hmr from 'browserify-hmr';
import hotify from 'hotify';
import manifest from './package.json';
var settings = {
port: process.env.DEV_PORT || '6061',
cache: {},
plugin: [],
transform: [
babelify.configure({
"optional": [
"spec.protoToAssign",
"runtime"
],
"blacklist": []
})
],
debug: true,
watch: false,
compression: null
};
class Bundler {
constructor(options) {
this.options = options
var Bundler = function(entry) {
this.entry = entry
this.compression = settings.compression
this.build = this.build.bind(this);
this.build = this.build.bind(this);
this.plugin = []
this.transform = []
this.cache = {}
this.entry = path.join
( options.input
, options.main
)
this.bundler = browserify({
entries: ['./src/' + entry],
debug: settings.debug,
cache: {},
transform: settings.transform,
plugin: settings.plugin
});
this.watcher = settings.watch &&
watchify(this.bundler)
.on('update', this.build);
}
Bundler.prototype.bundle = function() {
gutil.log(`Begin bundling: '${this.entry}'`);
return this.watcher ? this.watcher.bundle() : this.bundler.bundle();
if (options.babel != null) {
this.transform.push(babelify)
}
if (options.hotreload != null) {
this.plugin.push(hmr)
this.transform.push(hotify)
}
this.bundler = browserify
( { entries: [this.entry]
, debug:
( options.sourceMaps == null
? false
: options.sourceMaps === false
? false
: true
)
, cache: this.cache
, transform: this.transform
, plugin: this.plugin
}
)
if (options.watch) {
this.watcher =
watchify(this.bundler)
.on('update', this.build)
}
}
bundle() {
gutil.log(`Begin bundling: '${this.entry}'`);
const output =
( this.options.watch
? this.watcher.bundle()
: this.bundler.bundle()
)
return output
}
build() {
const transforms =
[ source(this.options.main)
, buffer()
, ( this.options.sourceMaps == null
? null
: sourcemaps.init({loadMaps: true})
)
, ( this.options.compression == null
? null
: uglify(this.options.compression)
)
, ( this.options.sourceMaps == null
? null
: sourcemaps.write(this.options.sourceMaps.output)
)
, gulp.dest(this.options.output)
]
const output =
transforms.reduce
( (input, transform) =>
( transform != null
? input.pipe(transform)
: input
)
, this
.bundle()
.on('error', (error) => {
gutil.beep();
console.error(`Failed to browserify: '${this.entry}'`, error.message)
})
)
return output.on('end', () => gutil.log(`Completed bundling: '${this.entry}'`))
}
}
Bundler.prototype.build = function() {
var bundle = this
.bundle()
.on('error', (error) => {
gutil.beep();
console.error(`Failed to browserify: '${this.entry}'`, error.message);
})
.pipe(source(this.entry + '.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true}))
.on('error', (error) => {
gutil.beep();
console.error(`Failed to make source maps for: '${this.entry}'`,
error.message);
});
return (this.compression ? bundle.pipe(uglify(this.compression)) : bundle)
.on('error', (error) => {
gutil.beep();
console.error(`Failed to bundle: '${this.entry}'`,
error.message);
})
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('./dist/'))
.on('end', () => {
gutil.log(`Completed bundling: '${this.entry}'`);
});
}
var bundler = function(entry) {
return gulp.task(entry, function() {
return new Bundler(entry).build();
});
}
// Starts a static http server that serves browser.html directory.
gulp.task('server', function() {
var server = config => () => {
var server = http.createServer(ecstatic({
root: path.join(module.filename, '../'),
cache: 0
root: path.join(module.filename, config.root),
cache: config.cache
}));
server.listen(settings.port);
});
server.listen(config.port);
gutil.log(`Navigate to http://localhost:${config.port}/`)
}
var bundler = config => () => new Bundler(config).build()
gulp.task('compressor', function() {
settings.compression = {
mangle: true,
compress: true,
acorn: true
};
});
gulp.task('watcher', function() {
settings.watch = true
});
Object.keys(manifest.builds).forEach(name => {
const config = manifest.builds[name]
gulp.task('hotreload', function() {
settings.plugin.push(hmr);
settings.transform.push(hotify);
});
bundler('index');
gulp.task('build', [
'compressor',
'index'
]);
gulp.task('watch', [
'watcher',
'index'
]);
gulp.task('develop', sequencial('watch', 'server'));
gulp.task('live', ['hotreload', 'develop']);
gulp.task('default', ['live']);
if (config.server) {
gulp.task(`serve ${name}`, server(config.server))
gulp.task(`build ${name}`, bundler(config))
gulp.task(name, sequencial(`build ${name}`, `serve ${name}`))
}
else {
gulp.task(name, bundler(config))
}
})

Просмотреть файл

@ -1,10 +1,11 @@
<html>
<head>
<title>Sample App</title>
<title></title>
</head>
<body>
<div id='root'>
</div>
<script src="./dist/index.js"></script>
<main>
</main>
<script src="./dist/virtual-dom-driver.js"></script>
<scrit src="./dist/reflex-driver.js"></script>
</body>
</html>

Просмотреть файл

@ -1,34 +1,58 @@
{
"name": "counter",
"description": "Reflex counter example",
"version": "0.0.0",
"scripts": {
"test": "flow check",
"start": "gulp live",
"build": "NODE_ENV=production gulp build"
"start": "gulp"
},
"dependencies": {
"reflex": "latest",
"reflex-virtual-dom-driver": "latest"
"reflex": "0.2.0",
"reflex-virtual-dom-driver": "0.2.0"
},
"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",
"babel-core": "6.7.2",
"babel-plugin-remove-comments": "2.0.0",
"babel-plugin-syntax-flow": "6.3.13",
"babel-plugin-transform-es2015-modules-umd": "6.4.3",
"babel-plugin-transform-flow-strip-types": "6.4.0",
"babel-preset-es2015": "6.3.13",
"babelify": "7.2.0",
"browserify": "13.0.0",
"browserify-hmr": "0.3.1",
"ecstatic": "0.8.0",
"flow-bin": "0.17.0",
"gulp": "3.9.0",
"gulp-sequence": "0.4.1",
"gulp-sourcemaps": "1.5.2",
"gulp-uglify": "^1.2.0",
"gulp-util": "^3.0.6",
"flow-bin": "0.22.1",
"gulp": "3.9.1",
"gulp-sequence": "0.4.5",
"gulp-sourcemaps": "1.6.0",
"gulp-uglify": "1.5.3",
"gulp-util": "3.0.7",
"hotify": "0.1.0",
"vinyl-buffer": "1.0.0",
"vinyl-source-stream": "1.1.0"
"vinyl-source-stream": "1.1.0",
"watchify": "3.7.0"
},
"builds": {
"default": {
"//compression": {
"mangle": true,
"compress": true,
"acorn": true
},
"babel": true,
"hotreload": true,
"watch": true,
"server": {
"port": 6061,
"cache": 0,
"root": "../"
},
"sourceMaps": {
"output": "./"
},
"main": "./virtual-dom-driver.js",
"input": "./src/",
"output": "./dist/"
}
}
}

Просмотреть файл

@ -1,45 +1,70 @@
/* @flow */
import {html, forward} from "reflex";
import {html, forward} from "reflex"
/*::
import * as type from "../type/counter"
import type {Model, Action} from "./counter"
import type {Address, DOM} from "reflex"
*/
export const asIncrement/*:type.asIncrement*/ = () =>
({type: "Counter.Increment"})
export const asDecrement/*:type.asDecrement*/ = () =>
({type: "Counter.Decrement"})
const Increment =
() =>
( { type: "Increment"
}
)
const Decrement =
() =>
( { type: "Decrement"
}
)
export const create/*:type.create*/ = ({value}) =>
({type: "Counter.Model", value})
export const init =
(value/*:number*/)/*:Model*/ =>
({ value })
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
export const update =
( model/*:Model*/
, action/*:Action*/
)/*:Model*/ =>
( action.type === "Increment"
? { value: model.value + 1 }
: action.type === "Decrement"
? { value: model.value - 1 }
: model
)
const counterStyle = {
value: {
fontWeight: "bold"
const counterStyle =
{ value:
{ fontWeight: "bold"
}
}
}
// View
export const view/*:type.view*/ = (model, address) =>
html.span({key: "counter"}, [
html.button({
key: "decrement",
onClick: forward(address, asDecrement)
}, ["-"]),
html.span({
key: "value",
style: counterStyle.value,
}, [String(model.value)]),
html.button({
key: "increment",
onClick: forward(address, asIncrement)
}, ["+"])
])
export const view =
( model/*:Model*/
, address/*:Address<Action>*/
)/*:DOM*/ =>
html.span
( { key: "counter"
}
, [ html.button
( { key: "decrement"
, onClick: forward(address, Decrement)
}
, ["-"]
)
, html.span
( { key: "value"
, style: counterStyle.value
}
, [ `${model.value}` ]
)
, html.button
( { key: "increment"
, onClick: forward(address, Increment)
}
, ["+"]
)
]
)

Просмотреть файл

@ -0,0 +1,21 @@
/* @flow */
import type {Address, DOM} from "reflex"
export type Model =
{ value: number
}
export type Action
= {type: "Increment"}
| {type: "Decrement"}
declare export function init (value:number):
Model
declare export function update (model:Model, action:Action):
Model
declare export function view (model:Model, address:Address<Action>):
DOM

Просмотреть файл

@ -1,18 +0,0 @@
/* @flow */
import * as Counter from "./counter"
import {start} from "reflex"
import {Renderer} from "reflex-virtual-dom-driver"
const app = start({
initial: Counter.create(window.app != null ?
window.app.model.value :
{value: 0}),
update: Counter.update,
view: Counter.view
});
window.app = app
const renderer = new Renderer({target: document.body})
app.view.subscribe(renderer.address)

Просмотреть файл

@ -0,0 +1,18 @@
/* @flow */
import {init, update, view} from "./counter"
import {start, beginner} from "reflex"
export const app = start
( beginner
( { model:
( window.app == null
? init(0)
: window.app.model.value
)
, update: update
, view: view
}
)
)
window.app = app

Просмотреть файл

@ -0,0 +1,18 @@
/* @flow */
import {app} from "./main"
import {Renderer} from "reflex-virtual-dom-driver"
import {Effects} from "reflex"
const renderer = new Renderer
( { target: document.body
}
)
app.view.subscribe
( renderer.address
)
app.task.subscribe
( Effects.driver(app.address)
)

Просмотреть файл

@ -1,15 +0,0 @@
/* @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

Просмотреть файл

@ -0,0 +1,12 @@
{
"sourceMaps": "inline",
"comments": false,
"presets": [
"es2015"
],
"plugins": [
"syntax-flow",
"transform-flow-strip-types",
"remove-comments"
]
}

Просмотреть файл

@ -1,16 +1,14 @@
[ignore]
.*/src/test/.*
.*/dist/.*
.*/node_modules/reflex/examples/.*
.*/node_modules/reflex-virtual-dom-driver/lib/.*
.*/node_modules/reflex/lib/.*
.*/node_modules/babel.*
.*/node_modules/tap/.*
.*/node_modules/reflex-virtual-dom-driver/examlpes/.*
[libs]
./node_modules/reflex/interfaces/
./node_modules/reflex-virtual-dom-driver/interfaces/
[include]
[options]
module.name_mapper='reflex-virtual-dom-driver' -> 'reflex-virtual-dom-driver/src/index'
module.name_mapper='reflex' -> 'reflex/src/index'
suppress_comment= \\(.\\|\n\\)*\\@FlowIssue
suppress_comment= \\(.\\|\n\\)*\\@FlowIgnore

Просмотреть файл

@ -0,0 +1,14 @@
# Counter
### Demo
```
npm start
```
Navigate to [demo][]
[demo]:http://localhost:6061/

Просмотреть файл

@ -1,5 +1,3 @@
'use strict';
import browserify from 'browserify';
import gulp from 'gulp';
import source from 'vinyl-source-stream';
@ -16,120 +14,119 @@ import sequencial from 'gulp-sequence';
import ecstatic from 'ecstatic';
import hmr from 'browserify-hmr';
import hotify from 'hotify';
import manifest from './package.json';
var settings = {
port: process.env.DEV_PORT || '6061',
cache: {},
plugin: [],
transform: [
babelify.configure({
"optional": [
"spec.protoToAssign",
"runtime"
],
"blacklist": []
})
],
debug: true,
watch: false,
compression: null
};
class Bundler {
constructor(options) {
this.options = options
var Bundler = function(entry) {
this.entry = entry
this.compression = settings.compression
this.build = this.build.bind(this);
this.build = this.build.bind(this);
this.plugin = []
this.transform = []
this.cache = {}
this.entry = path.join
( options.input
, options.main
)
this.bundler = browserify({
entries: ['./src/' + entry],
debug: settings.debug,
cache: {},
transform: settings.transform,
plugin: settings.plugin
});
this.watcher = settings.watch &&
watchify(this.bundler)
.on('update', this.build);
}
Bundler.prototype.bundle = function() {
gutil.log(`Begin bundling: '${this.entry}'`);
return this.watcher ? this.watcher.bundle() : this.bundler.bundle();
if (options.babel != null) {
this.transform.push(babelify)
}
if (options.hotreload != null) {
this.plugin.push(hmr)
this.transform.push(hotify)
}
this.bundler = browserify
( { entries: [this.entry]
, debug:
( options.sourceMaps == null
? false
: options.sourceMaps === false
? false
: true
)
, cache: this.cache
, transform: this.transform
, plugin: this.plugin
}
)
if (options.watch) {
this.watcher =
watchify(this.bundler)
.on('update', this.build)
}
}
bundle() {
gutil.log(`Begin bundling: '${this.entry}'`);
const output =
( this.options.watch
? this.watcher.bundle()
: this.bundler.bundle()
)
return output
}
build() {
const transforms =
[ source(this.options.main)
, buffer()
, ( this.options.sourceMaps == null
? null
: sourcemaps.init({loadMaps: true})
)
, ( this.options.compression == null
? null
: uglify(this.options.compression)
)
, ( this.options.sourceMaps == null
? null
: sourcemaps.write(this.options.sourceMaps.output)
)
, gulp.dest(this.options.output)
]
const output =
transforms.reduce
( (input, transform) =>
( transform != null
? input.pipe(transform)
: input
)
, this
.bundle()
.on('error', (error) => {
gutil.beep();
console.error(`Failed to browserify: '${this.entry}'`, error.message)
})
)
return output.on('end', () => gutil.log(`Completed bundling: '${this.entry}'`))
}
}
Bundler.prototype.build = function() {
var bundle = this
.bundle()
.on('error', (error) => {
gutil.beep();
console.error(`Failed to browserify: '${this.entry}'`, error.message);
})
.pipe(source(this.entry + '.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true}))
.on('error', (error) => {
gutil.beep();
console.error(`Failed to make source maps for: '${this.entry}'`,
error.message);
});
return (this.compression ? bundle.pipe(uglify(this.compression)) : bundle)
.on('error', (error) => {
gutil.beep();
console.error(`Failed to bundle: '${this.entry}'`,
error.message);
})
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('./dist/'))
.on('end', () => {
gutil.log(`Completed bundling: '${this.entry}'`);
});
}
var bundler = function(entry) {
return gulp.task(entry, function() {
return new Bundler(entry).build();
});
}
// Starts a static http server that serves browser.html directory.
gulp.task('server', function() {
var server = config => () => {
var server = http.createServer(ecstatic({
root: path.join(module.filename, '../'),
cache: 0
root: path.join(module.filename, config.root),
cache: config.cache
}));
server.listen(settings.port);
});
server.listen(config.port);
gutil.log(`Navigate to http://localhost:${config.port}/`)
}
var bundler = config => () => new Bundler(config).build()
gulp.task('compressor', function() {
settings.compression = {
mangle: true,
compress: true,
acorn: true
};
});
gulp.task('watcher', function() {
settings.watch = true
});
Object.keys(manifest.builds).forEach(name => {
const config = manifest.builds[name]
gulp.task('hotreload', function() {
settings.plugin.push(hmr);
settings.transform.push(hotify);
});
bundler('index');
gulp.task('build', [
'compressor',
'index'
]);
gulp.task('watch', [
'watcher',
'index'
]);
gulp.task('develop', sequencial('watch', 'server'));
gulp.task('live', ['hotreload', 'develop']);
gulp.task('default', ['live']);
if (config.server) {
gulp.task(`serve ${name}`, server(config.server))
gulp.task(`build ${name}`, bundler(config))
gulp.task(name, sequencial(`build ${name}`, `serve ${name}`))
}
else {
gulp.task(name, bundler(config))
}
})

Просмотреть файл

@ -1,10 +1,11 @@
<html>
<head>
<title>Sample App</title>
<title></title>
</head>
<body>
<div id='root'>
</div>
<script src="./dist/index.js"></script>
<main>
</main>
<script src="./dist/virtual-dom-driver.js"></script>
<scrit src="./dist/reflex-driver.js"></script>
</body>
</html>

Просмотреть файл

@ -1,34 +1,58 @@
{
"name": "gif-viewer-list",
"description": "Reflex random gif viewer list example",
"version": "0.0.0",
"scripts": {
"test": "flow check",
"start": "gulp live",
"build": "NODE_ENV=production gulp build"
"start": "gulp"
},
"dependencies": {
"reflex": "latest",
"reflex-virtual-dom-driver": "latest"
"reflex": "0.2.0",
"reflex-virtual-dom-driver": "0.2.0"
},
"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",
"babel-core": "6.7.2",
"babel-plugin-remove-comments": "2.0.0",
"babel-plugin-syntax-flow": "6.3.13",
"babel-plugin-transform-es2015-modules-umd": "6.4.3",
"babel-plugin-transform-flow-strip-types": "6.4.0",
"babel-preset-es2015": "6.3.13",
"babelify": "7.2.0",
"browserify": "13.0.0",
"browserify-hmr": "0.3.1",
"ecstatic": "0.8.0",
"flow-bin": "0.17.0",
"gulp": "3.9.0",
"gulp-sequence": "0.4.1",
"gulp-sourcemaps": "1.5.2",
"gulp-uglify": "^1.2.0",
"gulp-util": "^3.0.6",
"flow-bin": "0.22.1",
"gulp": "3.9.1",
"gulp-sequence": "0.4.5",
"gulp-sourcemaps": "1.6.0",
"gulp-uglify": "1.5.3",
"gulp-util": "3.0.7",
"hotify": "0.1.0",
"vinyl-buffer": "1.0.0",
"vinyl-source-stream": "1.1.0"
"vinyl-source-stream": "1.1.0",
"watchify": "3.7.0"
},
"builds": {
"default": {
"//compression": {
"mangle": true,
"compress": true,
"acorn": true
},
"babel": true,
"hotreload": true,
"watch": true,
"server": {
"port": 6061,
"cache": 0,
"root": "../"
},
"sourceMaps": {
"output": "./"
},
"main": "./virtual-dom-driver.js",
"input": "./src/",
"output": "./dist/"
}
}
}

Просмотреть файл

@ -1,15 +0,0 @@
/* @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,31 @@
/* @flow */
export const find =
( Array.prototype.find != null
? /*::<a>*/ (array/*:Array<a>*/, p/*:(a:a) => boolean*/)/*:?a*/ =>
array.find(p)
: /*::<a>*/ (array/*:Array<a>*/, p/*:(a:a) => boolean*/)/*:?a*/ => {
let index = 0
while (index < array.length) {
if (p(array[index])) {
return array[index]
}
index = index + 1
}
}
)
export const set = /*::<a>*/
( index/*:number*/
, element/*:a*/
, array/*:Array<a>*/
)/*:Array<a>*/ => {
if (array[index] === element) {
return array
}
else {
const next = array.slice(0)
next[index] = element
return next
}
}

Просмотреть файл

@ -0,0 +1,10 @@
/* @flow */
declare export function find <a>
(array:Array<a>, p:(a:a) => boolean): ?a
declare export function set <a>
( index:number
, element:a
, array:Array<a>
): Array<a>

Просмотреть файл

@ -1,30 +1,35 @@
/* @flow */
/*:: import * as type from "../type/fetch" */
/*::
import type {Response} from "./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))
})
}
})
export const fetch =
( window.fetch != null
? window.fetch
: (uri/*:string*/)/*:Promise<Response>*/ =>
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()
})
request.onerror = () => {
reject(Error("Network request failed"))
}
request.send()
})
)

Просмотреть файл

@ -13,4 +13,6 @@ export type Response = {
json: ()=> Promise<JSONValue>
}
export type fetch = (uri:string) => Promise<Response>
declare export function fetch
(uri:string):
Promise<Response>

Просмотреть файл

@ -1,19 +0,0 @@
/* @flow */
import * as RandemGifList from "./random-gif-list"
import {start, Effects} from "reflex"
import {Renderer} from "reflex-virtual-dom-driver"
var app = start({
initial: window.app != null ?
[RandemGifList.create(window.app.model.value)] :
RandemGifList.initialize(),
step: RandemGifList.step,
view: RandemGifList.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,22 @@
/* @flow */
import * as RandomGifList from "./random-gif-list"
import {start, Effects} from "reflex"
const restore =
_ =>
[ window.app.model.value
, Effects.none
]
export const app = start
( { flags: void(0)
, init: ( window.app != null
? restore
: RandomGifList.init
)
, update: RandomGifList.update
, view: RandomGifList.view
}
)
window.app = app

Просмотреть файл

@ -1,83 +1,178 @@
/* @flow */
import * as RandomGif from "./random-gif"
import {find} from "./array-find"
import * as array from "./array"
import {html, forward, thunk, Effects} from "reflex"
/*:: import * as type from "../type/random-gif-list" */
/*::
import type {ID, Action, Model} from "./random-gif-list"
import type {Address, DOM} from "reflex"
*/
export const create/*:type.create*/ = ({topic, entries, nextID}) =>
({type: "RandomGifList.Model", topic, entries, nextID})
const Create =
() =>
( { type: "Create"
}
)
export const initialize/*:type.initialize*/ = () =>
[create({topic: "", entries: [], nextID: 0}, Effects.none)]
const Topic =
(subject:string) =>
( { type: "Topic"
, source: subject
}
)
export const asTopic/*:type.asTopic*/ = topic =>
({type: "RandomGifList.Topic", topic})
const Modify =
(name/*:ID*/) =>
(action/*:RandomGif.Action*/)/*:Action*/ =>
( { type: "Modify"
, source:
{ name
, action
}
}
)
export const asCreate/*:type.asCreate*/ = () =>
({type: "RandomGifList.Create"})
const nofx =
model =>
[ model
, Effects.none
]
export const asByID/*:type.asByID*/ = id => act =>
({type: "RandomGifList.UpdateByID", id, act})
export const init =
()/*:[Model, Effects<Action>]*/ =>
nofx
( { topic: ""
, nextID: 0
, viewers: []
}
)
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
export const update =
(model/*:Model*/, action/*:Action*/)/*:[Model, Effects<Action>]*/ =>
( action.type === "Create"
? createViewer(model)
: action.type === "Topic"
? updateTopic(model, action.source)
: action.type === "Modify"
? updateNamed(model, action.source)
: nofx(model)
)
const updateTopic =
(model, topic) =>
nofx
( { topic
, nextID: model.nextID
, viewers: model.viewers
}
)
const createViewer =
(model) => {
const [viewer, fx] = RandomGif.init(model.topic)
const next =
{ topic: ""
, nextID: model.nextID + 1
, viewers:
[ ...model.viewers
, { name: model.nextID
, viewer
}
]
}
return [
create({topic, nextID, entries}),
fx.map(asByID(id))
const result =
[ next
, fx.map(Modify(model.nextID))
]
}
return result
}
return [model, Effects.none]
}
const updateNamed =
(model, {name, action}) => {
const named = array.find
( model.viewers
, viewer => viewer.name === name
)
if (named != null) {
const [viewer, fx] = RandomGif.update
( named.viewer
, action
)
const viewers = array.set
( model.viewers.indexOf(named)
, { name: named.name
, viewer
}
, model.viewers
)
const next =
[ { topic: model.topic
, nextID: model.nextID
, viewers
}
, fx.map(Modify(named.name))
]
return next
}
return nofx(model)
}
export const view =
(model/*:Model*/, address/*:Address<Action>*/)/*:DOM*/ =>
html.main
( { key: "random-gif-list"
}
, [ html.form
( { onSubmit: forward(address, decodeSubmit)
}
, [ html.input
( { style: style.input
, placeholder: "What kind of gifs do you want?"
, value: model.topic
, onChange: forward(address, decodeTopicChange)
//, onChange: forward(address, event => Topic(event.target.value))
// , onKeyUp: event => {
// if (event.keyCode === 13) {
// address(asCreate())
// }
}
)
]
)
, html.div
( { key: "random-gifs-list-box"
, style: style.container
}
, model.viewers.map(named => viewNamed(named, address))
)
]
)
const decodeSubmit =
event =>
Create(event.preventDefault())
const decodeTopicChange =
event =>
Topic(event.target.value)
const style = {
input: {
width: "100%",
height: "40px",
padding: "10px 0",
margin: "10px 0",
fontSize: "2em",
textAlign: "center"
},
@ -87,25 +182,11 @@ const style = {
}
}
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)))
])
const viewNamed =
(model, address) =>
thunk
( String(model.name)
, RandomGif.view
, model.viewer
, forward(address, Modify(model.name))
)

Просмотреть файл

@ -0,0 +1,41 @@
/* @flow */
import {Effects} from "reflex"
import type {Address, DOM} from "reflex"
import * as RandomGif from "./random-gif"
export type ID = number
type NamedViewer =
{ name: ID
, viewer: RandomGif.Model
}
export type Model =
{ topic: string
, nextID: ID
, viewers: Array<NamedViewer>
}
export type Action
= { type: "Create" }
| { type: "Topic"
, source: string
}
| { type: "Modify"
, source:
{ name: ID
, action: RandomGif.Action
}
}
declare export function init ():
[Model, Effects<Action>]
declare export function update
(model:Model, action:Action):
[Model, Effects<Action>]
declare export function view
(model:Model, address:Address<Action>):
DOM

Просмотреть файл

@ -3,76 +3,139 @@
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})
/*::
import type {URI, Model, Action} from "./random-gif"
import type {Address, DOM, Never} from "reflex"
*/
export const initialize/*:type.initialize*/ = topic =>
[create({topic, uri: "assets/waiting.gif"}), getRandomGif(topic)]
const RequestMore =
() =>
( { type: "RequestMore"
}
)
const ReceiveNewGif =
(uri) =>
( { type: "ReceiveNewGif"
, source: uri
}
)
export const asRequestMore/*:type.asRequestMore*/ = () =>
({type: "RandomGif.RequestMore"})
export const init =
(topic/*:string*/)/*:[Model, Effects<Action>]*/ =>
requestMore
( { topic
, uri: "assets/waiting.gif"
}
)
export const asReceiveNewGif/*:type.asReceiveNewGif*/ = uri =>
({type: "RandomGif.ReceiveNewGif", uri})
const nofx =
(model/*:Model*/)/*:[Model, Effects<Action>]*/ =>
[ model
, Effects.none
]
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 =>
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()
const decodeResponseBody =
body =>
( body == null
? null
: ( body.data == null
? null
: ( body.data.image_url == null
? null
: String(body.data.image_url)
)
)
)
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}")`
const getRandomGif =
(topic) =>
Task.future
( () =>
fetch(makeRandomURI(topic))
.then(response => response.json())
.then(decodeResponseBody)
)
const requestMore =
model =>
[ model
, Effects.task(getRandomGif(model.topic))
.map(ReceiveNewGif)
]
const receivedNewGif =
(model, uri) =>
nofx
( { topic: model.topic
, uri:
( uri == null
? model.uri
: 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!"
])
])
export const update =
( model/*:Model*/, action/*:Action*/)/*:[Model, Effects<Action>]*/ =>
( action.type === "ReceiveNewGif"
? receivedNewGif(model, action.source)
: action.type === "RequestMore"
? requestMore(model)
: nofx(model)
)
const style =
{ viewer:
{ width: "200px"
}
, header:
{ width: "200px"
, textAlign: "center"
}
, image: (uri) =>
( { display: "inline-block"
, width: "200px"
, height: "200px"
, backgroundPosition: "center center"
, backgroundSize: "cover"
, backgroundImage: `url("${uri}")`
}
)
}
export const view =
(model/*:Model*/, address/*:Address<Action>*/)/*:DOM*/ =>
html.main
( { 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, RequestMore)
}
, [ "More please!" ]
)
]
)

Просмотреть файл

@ -0,0 +1,29 @@
/* @flow */
import type {DOM, Address} from "reflex"
import {Effects} from "reflex"
export type URI = string
export type Model =
{ topic: string
, uri: URI
}
export type Action
= { type: "RequestMore" }
| { type: "ReceiveNewGif"
, source: ?URI
}
declare export function init
(topic:string):
[Model, Effects<Action>]
declare export function update
(model:Model, action:Action):
[Model, Effects<Action>]
declare export function view
(model:Model, address:Address<Action>):
DOM

Просмотреть файл

@ -0,0 +1,18 @@
/* @flow */
import {app} from "./main"
import {Renderer} from "reflex-virtual-dom-driver"
import {Effects} from "reflex"
const renderer = new Renderer
( { target: document.body
}
)
app.view.subscribe
( renderer.address
)
app.task.subscribe
( Effects.driver(app.address)
)

Просмотреть файл

@ -1,3 +0,0 @@
/* @flow */
export type find <a> = (array:Array<a>, p:(a:a) => boolean) => ?a

Просмотреть файл

@ -1,54 +0,0 @@
/* @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

Просмотреть файл

@ -1,32 +0,0 @@
/* @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

Просмотреть файл

@ -0,0 +1,12 @@
{
"sourceMaps": "inline",
"comments": false,
"presets": [
"es2015"
],
"plugins": [
"syntax-flow",
"transform-flow-strip-types",
"remove-comments"
]
}

Просмотреть файл

@ -1,16 +1,14 @@
[ignore]
.*/src/test/.*
.*/dist/.*
.*/node_modules/reflex/examples/.*
.*/node_modules/reflex-virtual-dom-driver/lib/.*
.*/node_modules/reflex/lib/.*
.*/node_modules/babel.*
.*/node_modules/tap/.*
.*/node_modules/reflex-virtual-dom-driver/examlpes/.*
[libs]
./node_modules/reflex/interfaces/
./node_modules/reflex-virtual-dom-driver/interfaces/
[include]
[options]
module.name_mapper='reflex-virtual-dom-driver' -> 'reflex-virtual-dom-driver/src/index'
module.name_mapper='reflex' -> 'reflex/src/index'
suppress_comment= \\(.\\|\n\\)*\\@FlowIssue
suppress_comment= \\(.\\|\n\\)*\\@FlowIgnore

Просмотреть файл

@ -0,0 +1,14 @@
# Counter
### Demo
```
npm start
```
Navigate to [demo][]
[demo]:http://localhost:6061/

Просмотреть файл

@ -1,5 +1,3 @@
'use strict';
import browserify from 'browserify';
import gulp from 'gulp';
import source from 'vinyl-source-stream';
@ -16,120 +14,119 @@ import sequencial from 'gulp-sequence';
import ecstatic from 'ecstatic';
import hmr from 'browserify-hmr';
import hotify from 'hotify';
import manifest from './package.json';
var settings = {
port: process.env.DEV_PORT || '6061',
cache: {},
plugin: [],
transform: [
babelify.configure({
"optional": [
"spec.protoToAssign",
"runtime"
],
"blacklist": []
})
],
debug: true,
watch: false,
compression: null
};
class Bundler {
constructor(options) {
this.options = options
var Bundler = function(entry) {
this.entry = entry
this.compression = settings.compression
this.build = this.build.bind(this);
this.build = this.build.bind(this);
this.plugin = []
this.transform = []
this.cache = {}
this.entry = path.join
( options.input
, options.main
)
this.bundler = browserify({
entries: ['./src/' + entry],
debug: settings.debug,
cache: {},
transform: settings.transform,
plugin: settings.plugin
});
this.watcher = settings.watch &&
watchify(this.bundler)
.on('update', this.build);
}
Bundler.prototype.bundle = function() {
gutil.log(`Begin bundling: '${this.entry}'`);
return this.watcher ? this.watcher.bundle() : this.bundler.bundle();
if (options.babel != null) {
this.transform.push(babelify)
}
if (options.hotreload != null) {
this.plugin.push(hmr)
this.transform.push(hotify)
}
this.bundler = browserify
( { entries: [this.entry]
, debug:
( options.sourceMaps == null
? false
: options.sourceMaps === false
? false
: true
)
, cache: this.cache
, transform: this.transform
, plugin: this.plugin
}
)
if (options.watch) {
this.watcher =
watchify(this.bundler)
.on('update', this.build)
}
}
bundle() {
gutil.log(`Begin bundling: '${this.entry}'`);
const output =
( this.options.watch
? this.watcher.bundle()
: this.bundler.bundle()
)
return output
}
build() {
const transforms =
[ source(this.options.main)
, buffer()
, ( this.options.sourceMaps == null
? null
: sourcemaps.init({loadMaps: true})
)
, ( this.options.compression == null
? null
: uglify(this.options.compression)
)
, ( this.options.sourceMaps == null
? null
: sourcemaps.write(this.options.sourceMaps.output)
)
, gulp.dest(this.options.output)
]
const output =
transforms.reduce
( (input, transform) =>
( transform != null
? input.pipe(transform)
: input
)
, this
.bundle()
.on('error', (error) => {
gutil.beep();
console.error(`Failed to browserify: '${this.entry}'`, error.message)
})
)
return output.on('end', () => gutil.log(`Completed bundling: '${this.entry}'`))
}
}
Bundler.prototype.build = function() {
var bundle = this
.bundle()
.on('error', (error) => {
gutil.beep();
console.error(`Failed to browserify: '${this.entry}'`, error.message);
})
.pipe(source(this.entry + '.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true}))
.on('error', (error) => {
gutil.beep();
console.error(`Failed to make source maps for: '${this.entry}'`,
error.message);
});
return (this.compression ? bundle.pipe(uglify(this.compression)) : bundle)
.on('error', (error) => {
gutil.beep();
console.error(`Failed to bundle: '${this.entry}'`,
error.message);
})
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('./dist/'))
.on('end', () => {
gutil.log(`Completed bundling: '${this.entry}'`);
});
}
var bundler = function(entry) {
return gulp.task(entry, function() {
return new Bundler(entry).build();
});
}
// Starts a static http server that serves browser.html directory.
gulp.task('server', function() {
var server = config => () => {
var server = http.createServer(ecstatic({
root: path.join(module.filename, '../'),
cache: 0
root: path.join(module.filename, config.root),
cache: config.cache
}));
server.listen(settings.port);
});
server.listen(config.port);
gutil.log(`Navigate to http://localhost:${config.port}/`)
}
var bundler = config => () => new Bundler(config).build()
gulp.task('compressor', function() {
settings.compression = {
mangle: true,
compress: true,
acorn: true
};
});
gulp.task('watcher', function() {
settings.watch = true
});
Object.keys(manifest.builds).forEach(name => {
const config = manifest.builds[name]
gulp.task('hotreload', function() {
settings.plugin.push(hmr);
settings.transform.push(hotify);
});
bundler('index');
gulp.task('build', [
'compressor',
'index'
]);
gulp.task('watch', [
'watcher',
'index'
]);
gulp.task('develop', sequencial('watch', 'server'));
gulp.task('live', ['hotreload', 'develop']);
gulp.task('default', ['live']);
if (config.server) {
gulp.task(`serve ${name}`, server(config.server))
gulp.task(`build ${name}`, bundler(config))
gulp.task(name, sequencial(`build ${name}`, `serve ${name}`))
}
else {
gulp.task(name, bundler(config))
}
})

Просмотреть файл

@ -1,10 +1,11 @@
<html>
<head>
<title>Sample App</title>
<title></title>
</head>
<body>
<div id='root'>
</div>
<script src="./dist/index.js"></script>
<main>
</main>
<script src="./dist/virtual-dom-driver.js"></script>
<scrit src="./dist/reflex-driver.js"></script>
</body>
</html>

Просмотреть файл

@ -1,34 +1,58 @@
{
"name": "gif-viewer-pair",
"description": "Reflex random gif pair example",
"version": "0.0.0",
"scripts": {
"test": "flow check",
"start": "gulp live",
"build": "NODE_ENV=production gulp build"
"start": "gulp"
},
"dependencies": {
"reflex": "latest",
"reflex-virtual-dom-driver": "latest"
"reflex": "0.2.0",
"reflex-virtual-dom-driver": "0.2.0"
},
"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",
"babel-core": "6.7.2",
"babel-plugin-remove-comments": "2.0.0",
"babel-plugin-syntax-flow": "6.3.13",
"babel-plugin-transform-es2015-modules-umd": "6.4.3",
"babel-plugin-transform-flow-strip-types": "6.4.0",
"babel-preset-es2015": "6.3.13",
"babelify": "7.2.0",
"browserify": "13.0.0",
"browserify-hmr": "0.3.1",
"ecstatic": "0.8.0",
"flow-bin": "0.17.0",
"gulp": "3.9.0",
"gulp-sequence": "0.4.1",
"gulp-sourcemaps": "1.5.2",
"gulp-uglify": "^1.2.0",
"gulp-util": "^3.0.6",
"flow-bin": "0.22.1",
"gulp": "3.9.1",
"gulp-sequence": "0.4.5",
"gulp-sourcemaps": "1.6.0",
"gulp-uglify": "1.5.3",
"gulp-util": "3.0.7",
"hotify": "0.1.0",
"vinyl-buffer": "1.0.0",
"vinyl-source-stream": "1.1.0"
"vinyl-source-stream": "1.1.0",
"watchify": "3.7.0"
},
"builds": {
"default": {
"//compression": {
"mangle": true,
"compress": true,
"acorn": true
},
"babel": true,
"hotreload": true,
"watch": true,
"server": {
"port": 6061,
"cache": 0,
"root": "../"
},
"sourceMaps": {
"output": "./"
},
"main": "./virtual-dom-driver.js",
"input": "./src/",
"output": "./dist/"
}
}
}

Просмотреть файл

@ -1,30 +1,35 @@
/* @flow */
/*:: import * as type from "../type/fetch" */
/*::
import type {Response} from "./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))
})
}
})
export const fetch =
( window.fetch != null
? window.fetch
: (uri/*:string*/)/*:Promise<Response>*/ =>
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()
})
request.onerror = () => {
reject(Error("Network request failed"))
}
request.send()
})
)

Просмотреть файл

@ -13,4 +13,6 @@ export type Response = {
json: ()=> Promise<JSONValue>
}
export type fetch = (uri:string) => Promise<Response>
declare export function fetch
(uri:string):
Promise<Response>

Просмотреть файл

@ -1,19 +0,0 @@
/* @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,25 @@
/* @flow */
import * as RandomGifPair from "./random-gif-pair"
import {start, Effects} from "reflex"
const restore =
_ =>
[ window.app.model.value
, Effects.none
]
export const app = start
( { flags:
[ "funny cats"
, "funny dogs"
]
, init: ( window.app != null
? restore
: RandomGifPair.init
)
, update: RandomGifPair.update
, view: RandomGifPair.view
}
)
window.app = app

Просмотреть файл

@ -1,57 +1,104 @@
/* @flow */
import * as RandomGif from "./random-gif"
import {html, forward, Task, Effects} from "reflex"
import * as RandomGif from "./random-gif"
/*:: 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)
})
/*::
import type {DOM, Address} from "reflex"
import type {Model, Action} from "./random-gif-pair"
*/
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)
])
const Left =
action =>
( { type: "Left"
, source: action
}
)
const Right =
action =>
( { type: "Right"
, source: action
}
)
export const init =
([leftTopic, rightTopic]/*:[string, string]*/)/*:[Model, Effects<Action>]*/ => {
const [left, leftFx] = RandomGif.init(leftTopic)
const [right, rightFx] = RandomGif.init(rightTopic)
const model = { left, right }
const fx = Effects.batch
( [ leftFx.map(Left)
, rightFx.map(Right)
]
)
return [model, fx]
}
const nofx =
model =>
[ model
, Effects.none
]
}
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)]
const updateLeft =
(model, action) => {
const [left, fx] = RandomGif.update(model.left, action)
const next =
[ { left
, right: model.right
}
, fx.map(Left)
]
return next
}
if (action.type === "RandomGifPair.Right") {
const [right, fx] = RandomGif.step(model.right, action.act)
return [create({left:model.left, right}), fx.map(asRight)]
const updateRight =
(model, action) => {
const [right, fx] = RandomGif.update(model.right, action)
const next =
[ { left: model.left
, right
}
, fx.map(Right)
]
return next
}
return [model, Effects.none]
}
export const update =
(model/*:Model*/, action/*:Action*/)/*:[Model, Effects<Action>]*/ =>
( action.type === "Left"
? updateLeft(model, action.source)
: action.type === "Right"
? updateRight(model, action.source)
: nofx(model)
)
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))
])
]);
};
export const view =
(model/*:Model*/, address/*:Address<Action>*/)/*:DOM*/ =>
html.div
( { key: "random-gif-pair"
, style: {display: "flex"}
}
, [ html.div
( { key: "left"
}
, [ RandomGif.view
( model.left
, forward(address, Left)
)
]
)
, html.div
( { key: "right"
}
, [ RandomGif.view
( model.right
, forward(address, Right)
)
]
)
]
)

Просмотреть файл

@ -0,0 +1,30 @@
/* @flow */
import {Effects} from "reflex"
import type {Address, DOM} from "reflex"
import * as RandomGif from "./random-gif"
export type Model =
{ left: RandomGif.Model
, right: RandomGif.Model
}
export type Action
= { type: "Left"
, source: RandomGif.Action
}
| { type: "Right"
, source: RandomGif.Action
}
declare export function init
(flags:[string, string]):
[Model, Effects<Action>]
declare export function update
(model:Model, action:Action):
[Model, Effects<Action>]
declare export function view
(model:Model, address:Address<Action>):
DOM

Просмотреть файл

@ -3,76 +3,139 @@
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})
/*::
import type {URI, Model, Action} from "./random-gif"
import type {Address, DOM, Never} from "reflex"
*/
export const initialize/*:type.initialize*/ = topic =>
[create({topic, uri: "assets/waiting.gif"}), getRandomGif(topic)]
const RequestMore =
() =>
( { type: "RequestMore"
}
)
const ReceiveNewGif =
(uri) =>
( { type: "ReceiveNewGif"
, source: uri
}
)
export const asRequestMore/*:type.asRequestMore*/ = () =>
({type: "RandomGif.RequestMore"})
export const init =
(topic/*:string*/)/*:[Model, Effects<Action>]*/ =>
requestMore
( { topic
, uri: "assets/waiting.gif"
}
)
export const asReceiveNewGif/*:type.asReceiveNewGif*/ = uri =>
({type: "RandomGif.ReceiveNewGif", uri})
const nofx =
(model/*:Model*/)/*:[Model, Effects<Action>]*/ =>
[ model
, Effects.none
]
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 =>
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()
const decodeResponseBody =
body =>
( body == null
? null
: ( body.data == null
? null
: ( body.data.image_url == null
? null
: String(body.data.image_url)
)
)
)
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}")`
const getRandomGif =
(topic) =>
Task.future
( () =>
fetch(makeRandomURI(topic))
.then(response => response.json())
.then(decodeResponseBody)
)
const requestMore =
model =>
[ model
, Effects.task(getRandomGif(model.topic))
.map(ReceiveNewGif)
]
const receivedNewGif =
(model, uri) =>
nofx
( { topic: model.topic
, uri:
( uri == null
? model.uri
: 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!"
])
])
export const update =
( model/*:Model*/, action/*:Action*/)/*:[Model, Effects<Action>]*/ =>
( action.type === "ReceiveNewGif"
? receivedNewGif(model, action.source)
: action.type === "RequestMore"
? requestMore(model)
: nofx(model)
)
const style =
{ viewer:
{ width: "200px"
}
, header:
{ width: "200px"
, textAlign: "center"
}
, image: (uri) =>
( { display: "inline-block"
, width: "200px"
, height: "200px"
, backgroundPosition: "center center"
, backgroundSize: "cover"
, backgroundImage: `url("${uri}")`
}
)
}
export const view =
(model/*:Model*/, address/*:Address<Action>*/)/*:DOM*/ =>
html.main
( { 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, RequestMore)
}
, [ "More please!" ]
)
]
)

Просмотреть файл

@ -0,0 +1,29 @@
/* @flow */
import type {DOM, Address} from "reflex"
import {Effects} from "reflex"
export type URI = string
export type Model =
{ topic: string
, uri: URI
}
export type Action
= { type: "RequestMore" }
| { type: "ReceiveNewGif"
, source: ?URI
}
declare export function init
(topic:string):
[Model, Effects<Action>]
declare export function update
(model:Model, action:Action):
[Model, Effects<Action>]
declare export function view
(model:Model, address:Address<Action>):
DOM

Просмотреть файл

@ -0,0 +1,18 @@
/* @flow */
import {app} from "./main"
import {Renderer} from "reflex-virtual-dom-driver"
import {Effects} from "reflex"
const renderer = new Renderer
( { target: document.body
}
)
app.view.subscribe
( renderer.address
)
app.task.subscribe
( Effects.driver(app.address)
)

Просмотреть файл

@ -1,36 +0,0 @@
/* @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

Просмотреть файл

@ -1,32 +0,0 @@
/* @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

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше