This commit is contained in:
Irakli Gozalishvili 2015-10-16 00:21:03 -07:00
Родитель 6498c51d0d
Коммит cbccf8c273
55 изменённых файлов: 3132 добавлений и 0 удалений

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

@ -0,0 +1,16 @@
[ignore]
.*/src/test/.*
.*/dist/.*
.*/node_modules/reflex/examples/.*
.*/node_modules/reflex-virtual-dom-renderer/lib/.*
.*/node_modules/reflex/lib/.*
[libs]
./node_modules/reflex/interfaces/
./node_modules/reflex-virtual-dom-renderer/interfaces/
[include]
[options]
module.name_mapper='reflex-virtual-dom-renderer' -> 'reflex-virtual-dom-renderer/src/index'
module.name_mapper='reflex' -> 'reflex/src/index'

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

@ -0,0 +1,135 @@
'use strict';
import browserify from 'browserify';
import gulp from 'gulp';
import source from 'vinyl-source-stream';
import buffer from 'vinyl-buffer';
import uglify from 'gulp-uglify';
import sourcemaps from 'gulp-sourcemaps';
import gutil from 'gulp-util';
import watchify from 'watchify';
import child from 'child_process';
import http from 'http';
import path from 'path';
import babelify from 'babelify';
import sequencial from 'gulp-sequence';
import ecstatic from 'ecstatic';
import hmr from 'browserify-hmr';
import hotify from 'hotify';
var settings = {
port: process.env.DEV_PORT || '6061',
cache: {},
plugin: [],
transform: [
babelify.configure({
"optional": [
"spec.protoToAssign",
"runtime"
],
"blacklist": []
})
],
debug: true,
watch: false,
compression: null
};
var Bundler = function(entry) {
this.entry = entry
this.compression = settings.compression
this.build = this.build.bind(this);
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();
}
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 = http.createServer(ecstatic({
root: path.join(module.filename, '../'),
cache: 0
}));
server.listen(settings.port);
});
gulp.task('compressor', function() {
settings.compression = {
mangle: true,
compress: true,
acorn: true
};
});
gulp.task('watcher', function() {
settings.watch = true
});
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']);

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

@ -0,0 +1,10 @@
<html>
<head>
<title>Sample App</title>
</head>
<body>
<div id='root'>
</div>
<script src="./dist/index.js"></script>
</body>
</html>

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

@ -0,0 +1,34 @@
{
"name": "counter-list",
"version": "0.0.0",
"scripts": {
"test": "flow check",
"start": "gulp live",
"build": "NODE_ENV=production gulp build"
},
"dependencies": {
"reflex": "latest",
"reflex-virtual-dom-renderer": "latest"
},
"devDependencies": {
"browserify": "11.0.1",
"watchify": "3.3.1",
"babelify": "6.1.3",
"browserify-hmr": "0.3.0",
"hotify": "0.0.1",
"babel-core": "5.8.23",
"babel-runtime": "5.8.20",
"ecstatic": "0.8.0",
"flow-bin": "0.17.0",
"gulp": "3.9.0",
"gulp-sequence": "0.4.1",
"gulp-sourcemaps": "1.5.2",
"gulp-uglify": "^1.2.0",
"gulp-util": "^3.0.6",
"vinyl-buffer": "1.0.0",
"vinyl-source-stream": "1.1.0"
}
}

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

@ -0,0 +1,55 @@
/* @flow */
import {Record, Union} from "typed-immutable";
import {html, forward} from "reflex";
/*::
import type {Address} from "reflex/type/signal";
import type {VirtualNode} from "reflex/type/renderer";
export type Model = {value:number};
export type Increment = {$typeof: 'Increment'};
export type Decrement = {$typeof: 'Decrement'};
export type Action = Increment|Decrement;
*/
const set = /*::<T>*/(record/*:T*/, field/*:string*/, value/*:any*/)/*:T*/ => {
const result = Object.assign({}, record)
result[field] = value
return result
}
export const create = ({value}/*:Model*/)/*:Model*/ => ({value})
export const Inc = ()/*:Increment*/ => ({$typeof: 'Increment'})
export const Dec = ()/*:Decrement*/ => ({$typeof: 'Decrement'})
export const update = (model/*:Model*/, action/*:Action*/)/*:Model*/ =>
action.$typeof === 'Increment' ?
set(model, 'value', model.value + 1) :
action.$typeof === 'Decrement' ?
set(model, 'value', model.value - 1) :
model
const counterStyle = {
value: {
fontWeight: 'bold'
}
}
// View
export var view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ => {
return html.span({key: 'counter'}, [
html.button({
key: 'decrement',
onClick: forward(address, Dec)
}, ["-"]),
html.span({
key: 'value',
style: counterStyle.value,
}, [String(model.value)]),
html.button({
key: 'increment',
onClick: forward(address, Inc)
}, ["+"])
]);
};

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

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

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

@ -0,0 +1,108 @@
/* @flow */
import * as Counter from "./counter";
import {html, forward, thunk} from "reflex";
/*::
import type {Address} from "reflex/type/signal";
import type {VirtualNode} from "reflex/type/renderer";
export type ID = number;
export type Entry = {
id: ID,
model: Counter.Model
};
export type Model = {
nextID: ID,
entries: Array<Entry>
};
*/
export const create = ({nextID, entries}/*:Model*/)/*:Model*/ =>
({nextID, entries});
// Update
/*::
export type Add = {$typeof: "Add"}
export type Remove = {$typeof: "Remove"}
export type ModifyByID = {$typeof: "ByID", id: ID, act: Counter.Action}
export type Action = Add|Remove|ModifyByID
*/
export const AddAction = ()/*:Add*/ => ({$typeof: "Add"})
export const RemoveAction = ()/*:Remove*/ => ({$typeof: "Remove"})
export const by = (id/*:ID*/)/*:(act:Counter.Action)=>ModifyByID*/ =>
act => ({$typeof: "ByID", id, act});
export const add = (model/*:Model*/)/*:Model*/ =>
create({
nextID: model.nextID + 1,
entries: model.entries.concat([{
id: model.nextID,
model: Counter.create({value: 0})
}])
})
export const remove = (model/*:Model*/)/*:Model*/ =>
create({
nextID: model.nextID,
entries: model.entries.slice(1)
})
export const modify = (model/*:Model*/, id/*:ID*/, action/*:Counter.Action*/)/*:Model*/ =>
create({
nextID: model.nextID,
entries: model.entries.map(entry =>
entry.id === id
?
{id: id, model: Counter.update(entry.model, action)}
:
entry)
})
export const update = (model/*:Model*/, action/*:Action*/)/*:Model*/ =>
action.$typeof === 'Add'
?
add(model, action)
:
action.$typeof === 'Remove'
?
remove(model, action)
:
action.$typeof === 'ByID'
?
modify(model, action.id, action.act)
:
model;
// View
const viewEntry = ({id, model}/*:Entry*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
html.div({key: id}, [
Counter.view(model, forward(address, by(id)))
])
export const view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
html.div({key: 'CounterList'}, [
html.div({key: 'controls'}, [
html.button({
key: "remove",
onClick: forward(address, RemoveAction)
}, ["Remove"]),
html.button({
key: "add",
onClick: forward(address, AddAction)
}, ["Add"])
]),
html.div({
key: "entries"
}, model.entries.map(entry => thunk(String(entry.id),
viewEntry,
entry,
address)))
])

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

@ -0,0 +1,16 @@
[ignore]
.*/src/test/.*
.*/dist/.*
.*/node_modules/reflex/examples/.*
.*/node_modules/reflex-virtual-dom-renderer/lib/.*
.*/node_modules/reflex/lib/.*
[libs]
./node_modules/reflex/interfaces/
./node_modules/reflex-virtual-dom-renderer/interfaces/
[include]
[options]
module.name_mapper='reflex-virtual-dom-renderer' -> 'reflex-virtual-dom-renderer/src/index'
module.name_mapper='reflex' -> 'reflex/src/index'

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

@ -0,0 +1,135 @@
'use strict';
import browserify from 'browserify';
import gulp from 'gulp';
import source from 'vinyl-source-stream';
import buffer from 'vinyl-buffer';
import uglify from 'gulp-uglify';
import sourcemaps from 'gulp-sourcemaps';
import gutil from 'gulp-util';
import watchify from 'watchify';
import child from 'child_process';
import http from 'http';
import path from 'path';
import babelify from 'babelify';
import sequencial from 'gulp-sequence';
import ecstatic from 'ecstatic';
import hmr from 'browserify-hmr';
import hotify from 'hotify';
var settings = {
port: process.env.DEV_PORT || '6061',
cache: {},
plugin: [],
transform: [
babelify.configure({
"optional": [
"spec.protoToAssign",
"runtime"
],
"blacklist": []
})
],
debug: true,
watch: false,
compression: null
};
var Bundler = function(entry) {
this.entry = entry
this.compression = settings.compression
this.build = this.build.bind(this);
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();
}
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 = http.createServer(ecstatic({
root: path.join(module.filename, '../'),
cache: 0
}));
server.listen(settings.port);
});
gulp.task('compressor', function() {
settings.compression = {
mangle: true,
compress: true,
acorn: true
};
});
gulp.task('watcher', function() {
settings.watch = true
});
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']);

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

@ -0,0 +1,10 @@
<html>
<head>
<title>Sample App</title>
</head>
<body>
<div id='root'>
</div>
<script src="./dist/index.js"></script>
</body>
</html>

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

@ -0,0 +1,34 @@
{
"name": "counter-pair",
"version": "0.0.0",
"scripts": {
"test": "flow check",
"start": "gulp live",
"build": "NODE_ENV=production gulp build"
},
"dependencies": {
"reflex": "latest",
"reflex-virtual-dom-renderer": "latest"
},
"devDependencies": {
"browserify": "11.0.1",
"watchify": "3.3.1",
"babelify": "6.1.3",
"browserify-hmr": "0.3.0",
"hotify": "0.0.1",
"babel-core": "5.8.23",
"babel-runtime": "5.8.20",
"ecstatic": "0.8.0",
"flow-bin": "0.17.0",
"gulp": "3.9.0",
"gulp-sequence": "0.4.1",
"gulp-sourcemaps": "1.5.2",
"gulp-uglify": "^1.2.0",
"gulp-util": "^3.0.6",
"vinyl-buffer": "1.0.0",
"vinyl-source-stream": "1.1.0"
}
}

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

@ -0,0 +1,55 @@
/* @flow */
import {Record, Union} from "typed-immutable";
import {html, forward} from "reflex";
/*::
import type {Address} from "reflex/type/signal";
import type {VirtualNode} from "reflex/type/renderer";
export type Model = {value:number};
export type Increment = {$typeof: 'Increment'};
export type Decrement = {$typeof: 'Decrement'};
export type Action = Increment|Decrement;
*/
const set = /*::<T>*/(record/*:T*/, field/*:string*/, value/*:any*/)/*:T*/ => {
const result = Object.assign({}, record)
result[field] = value
return result
}
export const create = ({value}/*:Model*/)/*:Model*/ => ({value})
export const Inc = ()/*:Increment*/ => ({$typeof: 'Increment'})
export const Dec = ()/*:Decrement*/ => ({$typeof: 'Decrement'})
export const update = (model/*:Model*/, action/*:Action*/)/*:Model*/ =>
action.$typeof === 'Increment' ?
set(model, 'value', model.value + 1) :
action.$typeof === 'Decrement' ?
set(model, 'value', model.value - 1) :
model
const counterStyle = {
value: {
fontWeight: 'bold'
}
}
// View
export var view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ => {
return html.span({key: 'counter'}, [
html.button({
key: 'decrement',
onClick: forward(address, Dec)
}, ["-"]),
html.span({
key: 'value',
style: counterStyle.value,
}, [String(model.value)]),
html.button({
key: 'increment',
onClick: forward(address, Inc)
}, ["+"])
]);
};

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

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

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

@ -0,0 +1,64 @@
/* @flow */
import * as Counter from "./counter";
import {html, forward} from "reflex";
/*::
import type {Address} from "reflex/type/signal";
import type {VirtualNode} from "reflex/type/renderer";
export type Model = {top: Counter.Model; bottom: Counter.Model};
export type Top = {$typeof: 'Top', act: Counter.Action};
export type Bottom = {$typeof: 'Bottom', act: Counter.Action};
export type Reset = {$typeof: 'Reset'};
export type Action = Reset|Top|Bottom;
*/
export const ModifyTop = (act/*:Counter.Action*/)/*:Top*/ =>
({$typeof: "Top", act})
export const ModifyBottom = (act/*:Counter.Action*/)/*:Bottom*/ =>
({$typeof: "Bottom", act})
export const Clear = ()/*:Reset*/ =>
({$typeof: "Reset" })
const set = /*::<T>*/(record/*:T*/, field/*:string*/, value/*:any*/)/*:T*/ => {
const result = Object.assign({}, record)
result[field] = value
return result
}
export const create = ({top, bottom}/*:Model*/)/*:Model*/ => ({
top: Counter.create(top),
bottom: Counter.create(bottom)
})
export const update = (model/*:Model*/, action/*:Action*/)/*:Model*/ =>
action.$typeof === 'Top' ?
set(model, 'top', Counter.update(model.top, action.act)) :
action.$typeof === 'Bottom' ?
set(model, 'bottom', Counter.update(model.bottom, action.act)) :
action.$typeof === 'Reset' ?
create({top: {value: 0},
bottom: {value: 0}}) :
model
// View
export var view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ => {
return html.div({key: 'counter-pair'}, [
html.div({key: 'top'}, [
Counter.view(model.top, forward(address, ModifyTop))
]),
html.div({key: 'bottom'}, [
Counter.view(model.bottom, forward(address, ModifyBottom)),
]),
html.div({key: 'controls'}, [
html.button({
key: 'reset',
onClick: forward(address, Clear)
}, ["Reset"])
])
]);
};

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

@ -0,0 +1,16 @@
[ignore]
.*/src/test/.*
.*/dist/.*
.*/node_modules/reflex/examples/.*
.*/node_modules/reflex-virtual-dom-renderer/lib/.*
.*/node_modules/reflex/lib/.*
[libs]
./node_modules/reflex/interfaces/
./node_modules/reflex-virtual-dom-renderer/interfaces/
[include]
[options]
module.name_mapper='reflex-virtual-dom-renderer' -> 'reflex-virtual-dom-renderer/src/index'
module.name_mapper='reflex' -> 'reflex/src/index'

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

@ -0,0 +1,135 @@
'use strict';
import browserify from 'browserify';
import gulp from 'gulp';
import source from 'vinyl-source-stream';
import buffer from 'vinyl-buffer';
import uglify from 'gulp-uglify';
import sourcemaps from 'gulp-sourcemaps';
import gutil from 'gulp-util';
import watchify from 'watchify';
import child from 'child_process';
import http from 'http';
import path from 'path';
import babelify from 'babelify';
import sequencial from 'gulp-sequence';
import ecstatic from 'ecstatic';
import hmr from 'browserify-hmr';
import hotify from 'hotify';
var settings = {
port: process.env.DEV_PORT || '6061',
cache: {},
plugin: [],
transform: [
babelify.configure({
"optional": [
"spec.protoToAssign",
"runtime"
],
"blacklist": []
})
],
debug: true,
watch: false,
compression: null
};
var Bundler = function(entry) {
this.entry = entry
this.compression = settings.compression
this.build = this.build.bind(this);
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();
}
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 = http.createServer(ecstatic({
root: path.join(module.filename, '../'),
cache: 0
}));
server.listen(settings.port);
});
gulp.task('compressor', function() {
settings.compression = {
mangle: true,
compress: true,
acorn: true
};
});
gulp.task('watcher', function() {
settings.watch = true
});
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']);

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

@ -0,0 +1,10 @@
<html>
<head>
<title>Sample App</title>
</head>
<body>
<div id='root'>
</div>
<script src="./dist/index.js"></script>
</body>
</html>

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

@ -0,0 +1,34 @@
{
"name": "counter-set",
"version": "0.0.0",
"scripts": {
"test": "flow check",
"start": "gulp live",
"build": "NODE_ENV=production gulp build"
},
"dependencies": {
"reflex": "latest",
"reflex-virtual-dom-renderer": "latest"
},
"devDependencies": {
"browserify": "11.0.1",
"watchify": "3.3.1",
"babelify": "6.1.3",
"browserify-hmr": "0.3.0",
"hotify": "0.0.1",
"babel-core": "5.8.23",
"babel-runtime": "5.8.20",
"ecstatic": "0.8.0",
"flow-bin": "0.17.0",
"gulp": "3.9.0",
"gulp-sequence": "0.4.1",
"gulp-sourcemaps": "1.5.2",
"gulp-uglify": "^1.2.0",
"gulp-util": "^3.0.6",
"vinyl-buffer": "1.0.0",
"vinyl-source-stream": "1.1.0"
}
}

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

@ -0,0 +1,55 @@
/* @flow */
import {Record, Union} from "typed-immutable";
import {html, forward} from "reflex";
/*::
import type {Address} from "reflex/type/signal";
import type {VirtualNode} from "reflex/type/renderer";
export type Model = {value:number};
export type Increment = {$typeof: 'Increment'};
export type Decrement = {$typeof: 'Decrement'};
export type Action = Increment|Decrement;
*/
const set = /*::<T>*/(record/*:T*/, field/*:string*/, value/*:any*/)/*:T*/ => {
const result = Object.assign({}, record)
result[field] = value
return result
}
export const create = ({value}/*:Model*/)/*:Model*/ => ({value})
export const Inc = ()/*:Increment*/ => ({$typeof: 'Increment'})
export const Dec = ()/*:Decrement*/ => ({$typeof: 'Decrement'})
export const update = (model/*:Model*/, action/*:Action*/)/*:Model*/ =>
action.$typeof === 'Increment' ?
set(model, 'value', model.value + 1) :
action.$typeof === 'Decrement' ?
set(model, 'value', model.value - 1) :
model
const counterStyle = {
value: {
fontWeight: 'bold'
}
}
// View
export var view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ => {
return html.span({key: 'counter'}, [
html.button({
key: 'decrement',
onClick: forward(address, Dec)
}, ["-"]),
html.span({
key: 'value',
style: counterStyle.value,
}, [String(model.value)]),
html.button({
key: 'increment',
onClick: forward(address, Inc)
}, ["+"])
]);
};

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

@ -0,0 +1,18 @@
/* @flow */
import * as CounterSet from "./set"
import {start} from "reflex"
import {Renderer} from "reflex-virtual-dom-renderer"
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,108 @@
/* @flow */
import * as Counter from "./counter";
import {html, forward, thunk} from "reflex";
/*::
import type {Address} from "reflex/type/signal";
import type {VirtualNode} from "reflex/type/renderer";
export type ID = number;
export type Entry = {
id: ID,
model: Counter.Model
};
export type Model = {
nextID: ID,
entries: Array<Entry>
};
*/
export const create = ({nextID, entries}/*:Model*/)/*:Model*/ =>
({nextID, entries});
// Update
/*::
export type Add = {$typeof: "Add"}
export type Remove = {$typeof: "Remove"}
export type ModifyByID = {$typeof: "ByID", id: ID, act: Counter.Action}
export type Action = Add|Remove|ModifyByID
*/
export const AddAction = ()/*:Add*/ => ({$typeof: "Add"})
export const RemoveAction = ()/*:Remove*/ => ({$typeof: "Remove"})
export const by = (id/*:ID*/)/*:(act:Counter.Action)=>ModifyByID*/ =>
act => ({$typeof: "ByID", id, act});
export const add = (model/*:Model*/)/*:Model*/ =>
create({
nextID: model.nextID + 1,
entries: model.entries.concat([{
id: model.nextID,
model: Counter.create({value: 0})
}])
})
export const remove = (model/*:Model*/)/*:Model*/ =>
create({
nextID: model.nextID,
entries: model.entries.slice(1)
})
export const modify = (model/*:Model*/, id/*:ID*/, action/*:Counter.Action*/)/*:Model*/ =>
create({
nextID: model.nextID,
entries: model.entries.map(entry =>
entry.id === id
?
{id: id, model: Counter.update(entry.model, action)}
:
entry)
})
export const update = (model/*:Model*/, action/*:Action*/)/*:Model*/ =>
action.$typeof === 'Add'
?
add(model, action)
:
action.$typeof === 'Remove'
?
remove(model, action)
:
action.$typeof === 'ByID'
?
modify(model, action.id, action.act)
:
model;
// View
const viewEntry = ({id, model}/*:Entry*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
html.div({key: id}, [
Counter.view(model, forward(address, by(id)))
])
export const view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
html.div({key: 'CounterList'}, [
html.div({key: 'controls'}, [
html.button({
key: "remove",
onClick: forward(address, RemoveAction)
}, ["Remove"]),
html.button({
key: "add",
onClick: forward(address, AddAction)
}, ["Add"])
]),
html.div({
key: "entries"
}, model.entries.map(entry => thunk(String(entry.id),
viewEntry,
entry,
address)))
])

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

@ -0,0 +1,68 @@
/* @flow */
import * as CounterList from "./list";
import * as Counter from "./counter";
import {html, forward, thunk} from "reflex";
/*::
import type {Address} from "reflex/type/signal";
import type {VirtualNode} from "reflex/type/renderer";
export type ID = number;
export type Entry = CounterList.Entry;
export type Model = CounterList.Model;
*/
export const create = ({nextID, entries}/*:Model*/)/*:Model*/ =>
({nextID, entries});
// Update
/*::
export type RemoveByID = {$typeof: "RemoveByID", id: ID}
export type Action = RemoveByID|CounterList.Action
*/
export const removeForID = (id/*:ID*/)/*:()=>RemoveByID*/ => () =>
({$typeof: "RemoveByID", id})
export const removeByID = (model/*:Model*/, id/*:ID*/)/*:Model*/ =>
create({
nextID: model.nextID,
entries: model.entries.filter(entry => entry.id != id)
})
export const update = (model/*:Model*/, action/*:Action*/)/*:Model*/ =>
action.$typeof === 'RemoveByID'
?
removeByID(model, action.id)
:
CounterList.update(model, action)
// View
const viewEntry = ({id, model}/*:Entry*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
html.div({key: id}, [
Counter.view(model, forward(address, CounterList.by(id))),
html.button({
key: 'remove',
onClick: forward(address, removeForID(id))
}, ['x'])
])
export const view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
html.div({key: 'CounterList'}, [
html.div({key: 'controls'}, [
html.button({
key: "add",
onClick: forward(address, CounterList.AddAction)
}, ["Add"])
]),
html.div({
key: "entries"
}, model.entries.map(entry => thunk(String(entry.id),
viewEntry,
entry,
address)))
])

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

@ -0,0 +1,16 @@
[ignore]
.*/src/test/.*
.*/dist/.*
.*/node_modules/reflex/examples/.*
.*/node_modules/reflex-virtual-dom-renderer/lib/.*
.*/node_modules/reflex/lib/.*
[libs]
./node_modules/reflex/interfaces/
./node_modules/reflex-virtual-dom-renderer/interfaces/
[include]
[options]
module.name_mapper='reflex-virtual-dom-renderer' -> 'reflex-virtual-dom-renderer/src/index'
module.name_mapper='reflex' -> 'reflex/src/index'

Двоичные данные
examples/gif-viewer-list/assets/waiting.gif Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 8.3 KiB

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

@ -0,0 +1,135 @@
'use strict';
import browserify from 'browserify';
import gulp from 'gulp';
import source from 'vinyl-source-stream';
import buffer from 'vinyl-buffer';
import uglify from 'gulp-uglify';
import sourcemaps from 'gulp-sourcemaps';
import gutil from 'gulp-util';
import watchify from 'watchify';
import child from 'child_process';
import http from 'http';
import path from 'path';
import babelify from 'babelify';
import sequencial from 'gulp-sequence';
import ecstatic from 'ecstatic';
import hmr from 'browserify-hmr';
import hotify from 'hotify';
var settings = {
port: process.env.DEV_PORT || '6061',
cache: {},
plugin: [],
transform: [
babelify.configure({
"optional": [
"spec.protoToAssign",
"runtime"
],
"blacklist": []
})
],
debug: true,
watch: false,
compression: null
};
var Bundler = function(entry) {
this.entry = entry
this.compression = settings.compression
this.build = this.build.bind(this);
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();
}
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 = http.createServer(ecstatic({
root: path.join(module.filename, '../'),
cache: 0
}));
server.listen(settings.port);
});
gulp.task('compressor', function() {
settings.compression = {
mangle: true,
compress: true,
acorn: true
};
});
gulp.task('watcher', function() {
settings.watch = true
});
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']);

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

@ -0,0 +1,10 @@
<html>
<head>
<title>Sample App</title>
</head>
<body>
<div id='root'>
</div>
<script src="./dist/index.js"></script>
</body>
</html>

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

@ -0,0 +1,34 @@
{
"name": "gif-viewer-list",
"version": "0.0.0",
"scripts": {
"test": "flow check",
"start": "gulp live",
"build": "NODE_ENV=production gulp build"
},
"dependencies": {
"reflex": "latest",
"reflex-virtual-dom-renderer": "latest"
},
"devDependencies": {
"browserify": "11.0.1",
"watchify": "3.3.1",
"babelify": "6.1.3",
"browserify-hmr": "0.3.0",
"hotify": "0.0.1",
"babel-core": "5.8.23",
"babel-runtime": "5.8.20",
"ecstatic": "0.8.0",
"flow-bin": "0.17.0",
"gulp": "3.9.0",
"gulp-sequence": "0.4.1",
"gulp-sourcemaps": "1.5.2",
"gulp-uglify": "^1.2.0",
"gulp-util": "^3.0.6",
"vinyl-buffer": "1.0.0",
"vinyl-source-stream": "1.1.0"
}
}

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

@ -0,0 +1,19 @@
/* @flow */
import * as RandemGifList from "./list"
import {start, Effects} from "reflex"
import {Renderer} from "reflex-virtual-dom-renderer"
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,137 @@
/* @flow */
import * as RandomGif from "./random-gif";
import {html, forward, thunk, Effects} from "reflex";
/*::
import type {Address} from "reflex/type/signal"
import type {VirtualNode} from "reflex/type/renderer"
export type ID = number
export type Entry = {
id: ID,
model: RandomGif.Model
}
export type Model = {
topic: string,
entries: Array<Entry>,
nextID: ID
}
*/
export const create = ({topic, entries, nextID}/*:Model*/)/*:Model*/ =>
({topic, entries, nextID})
export const initialize = ()/*:[Model, Effects<Action>]*/ =>
[create({topic: "", entries: [], nextID: 0}, Effects.none)]
/*::
export type Topic = {$typeof: "Topic", topic: string}
export type Create = {$typeof: "Create"}
export type UpdateByID = {$typeof: "UpdateByID", id: ID, act: RandomGif.Action}
export type Action
= Topic
| Create
| UpdateByID
*/
export const TopicAction = (topic/*:string*/)/*:Topic*/ =>
({$typeof: "Topic", topic})
export const CreateAction = ()/*:Create*/=>
({$typeof: "Create"})
export const byID = (id/*:ID*/)/*:(action:RandomGif.Action)=>UpdateByID*/=>
action => ({$typeof: "UpdateByID", id, act: action})
const find = Array.prototype.find != null ? (array, p) => array.find(p) :
(array, p) => {
let index = 0
while (index < array.length) {
if (p(array[index])) {
return array[index]
}
index = index + 1
}
}
export const step = (model/*:Model*/, action/*:Action*/)/*:[Model, Effects<Action>]*/ => {
if (action.$typeof === "Topic") {
return [
{topic: action.topic, entries: model.entries, nextID: model.nextID},
Effects.none
]
}
if (action.$typeof === "Create") {
const [gif, fx] = RandomGif.initialize(model.topic)
return [
{
topic: "",
nextID: model.nextID + 1,
entries: model.entries.concat([{id: model.nextID, model: gif}])
},
fx.map(byID(model.nextID))
]
}
if (action.$typeof === "UpdateByID") {
const {id} = action
const {entries, topic, nextID} = model
const entry = find(entries, entry => entry.id === id)
const index = entry != null ? entries.indexOf(entry) : -1
if (index >= 0 && entry != null && entry.model != null && entry.id != null){
const [gif, fx] = RandomGif.step(entry.model, action.act)
const entries = model.entries.slice(0)
entries[index] = {id, model: gif}
return [
{topic, nextID, entries},
fx.map(byID(id))
]
}
}
return [model, Effects.none]
}
const style = {
input: {
width: "100%",
height: "40px",
padding: "10px 0",
fontSize: "2em",
textAlign: "center"
},
container: {
display: "flex",
flexWrap: "wrap"
}
}
// View
const viewEntry = ({id, model}/*:Entry*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
RandomGif.view(model, forward(address, byID(id)))
export const view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
html.div({key: "random-gif-list"}, [
html.input({
style: style.input,
placeholder: "What kind of gifs do you want?",
value: model.topic,
onChange: forward(address, event => TopicAction(event.target.value)),
onKeyUp: event => {
if (event.keyCode === 13) {
address(CreateAction())
}
}
}),
html.div({key: "random-gifs-list-box", style: style.container},
model.entries.map(entry => thunk(String(entry.id),
viewEntry,
entry,
address)))
])

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

@ -0,0 +1,113 @@
/* @flow */
import {Record, Union} from "typed-immutable";
import {html, forward, Effects, Task} from "reflex";
/*::
import type {Address} from "reflex/type/signal"
import type {VirtualNode} from "reflex/type/renderer"
export type Model = {
topic:string,
uri:string
}
*/
export const create = ({topic, uri}/*:Model*/)/*:Model*/=>
({topic, uri})
export const initialize = (topic/*:string*/)/*:[Model, Effects<Action>]*/ =>
[create({topic, uri: "assets/waiting.gif"}), getRandomGif(topic)]
/*::
export type RequestMore = {$typeof: "RequestMore"}
export type NewGif = {$typeof: "NewGif", uri: ?string}
export type Action = RequestMore|NewGif
*/
export const RequestMoreAction = ()/*:RequestMore*/ =>
({$typeof: "RequestMore"})
export const NewGifAction = (uri/*:?string*/)/*:NewGif*/ =>
({$typeof: "NewGif", uri})
export const step = (model/*:Model*/, action/*:Action*/)/*:[Model, Effects<Action>]*/ =>
action.$typeof === "RequestMore" ? [model, getRandomGif(model.topic)] :
action.$typeof === "NewGif" ?
[create({topic: model.topic,
uri: action.uri != null ? action.uri : model.uri})
,Effects.none] :
[model, Effects.none]
const randomURI = (topic/*:string*/)/*:string*/ =>
`http://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=${topic}`
/*::
type ResponseBody = {data: ?{image_uri: ?string}}
type Response = {json: () => Promise<ResponseBody>}
*/
const decodeResponseBody = (body/*:ResponseBody*/)/*:?string*/ =>
(body.data && body.data.image_url) || null
const readResponseAsJSON = (response/*:Response*/) => response.json()
const fetch = global.fetch != null ? global.fetch :
uri => new Promise((resolve, reject) => {
const request = new XMLHttpRequest()
request.open('GET', uri, true)
request.onload = () => {
const status = request.status === 1223 ? 204 : request.status
if (status < 100 || status > 599) {
reject(Error('Network request failed'))
} else {
resolve({
json() {
return new Promise(resolve => {
resolve(JSON.parse(request.responseText))
})
}
})
}
}
request.onerror = () => {
reject(Error('Network request failed'))
}
request.send()
})
export const getRandomGif = (topic/*:string*/)/*:Effects<Action>*/ =>
Effects.task(Task.future(() => fetch(randomURI(topic))
.then(readResponseAsJSON)
.then(decodeResponseBody)
.then(NewGifAction)))
const style = {
viewer: {
width: "200px"
},
header: {
width: "200px",
textAlign: "center"
},
image(uri) {
return {
display: "inline-block",
width: "200px",
height: "200px",
backgroundPosition: "center center",
backgroundSize: "cover",
backgroundImage: `url('${uri}')`
}
}
}
export const view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
html.div({key: "gif-viewer", style: style.viewer}, [
html.h2({key: "header", style: style.header}, [model.topic]),
html.div({key: "image", style: style.image(model.uri)}),
html.button({key: "button", onClick: forward(address, RequestMoreAction)}, [
"More please!"
])
])

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

@ -0,0 +1,16 @@
[ignore]
.*/src/test/.*
.*/dist/.*
.*/node_modules/reflex/examples/.*
.*/node_modules/reflex-virtual-dom-renderer/lib/.*
.*/node_modules/reflex/lib/.*
[libs]
./node_modules/reflex/interfaces/
./node_modules/reflex-virtual-dom-renderer/interfaces/
[include]
[options]
module.name_mapper='reflex-virtual-dom-renderer' -> 'reflex-virtual-dom-renderer/src/index'
module.name_mapper='reflex' -> 'reflex/src/index'

Двоичные данные
examples/gif-viewer-pair/assets/waiting.gif Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 8.3 KiB

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

@ -0,0 +1,135 @@
'use strict';
import browserify from 'browserify';
import gulp from 'gulp';
import source from 'vinyl-source-stream';
import buffer from 'vinyl-buffer';
import uglify from 'gulp-uglify';
import sourcemaps from 'gulp-sourcemaps';
import gutil from 'gulp-util';
import watchify from 'watchify';
import child from 'child_process';
import http from 'http';
import path from 'path';
import babelify from 'babelify';
import sequencial from 'gulp-sequence';
import ecstatic from 'ecstatic';
import hmr from 'browserify-hmr';
import hotify from 'hotify';
var settings = {
port: process.env.DEV_PORT || '6061',
cache: {},
plugin: [],
transform: [
babelify.configure({
"optional": [
"spec.protoToAssign",
"runtime"
],
"blacklist": []
})
],
debug: true,
watch: false,
compression: null
};
var Bundler = function(entry) {
this.entry = entry
this.compression = settings.compression
this.build = this.build.bind(this);
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();
}
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 = http.createServer(ecstatic({
root: path.join(module.filename, '../'),
cache: 0
}));
server.listen(settings.port);
});
gulp.task('compressor', function() {
settings.compression = {
mangle: true,
compress: true,
acorn: true
};
});
gulp.task('watcher', function() {
settings.watch = true
});
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']);

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

@ -0,0 +1,10 @@
<html>
<head>
<title>Sample App</title>
</head>
<body>
<div id='root'>
</div>
<script src="./dist/index.js"></script>
</body>
</html>

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

@ -0,0 +1,34 @@
{
"name": "gif-viewer-pair",
"version": "0.0.0",
"scripts": {
"test": "flow check",
"start": "gulp live",
"build": "NODE_ENV=production gulp build"
},
"dependencies": {
"reflex": "latest",
"reflex-virtual-dom-renderer": "latest"
},
"devDependencies": {
"browserify": "11.0.1",
"watchify": "3.3.1",
"babelify": "6.1.3",
"browserify-hmr": "0.3.0",
"hotify": "0.0.1",
"babel-core": "5.8.23",
"babel-runtime": "5.8.20",
"ecstatic": "0.8.0",
"flow-bin": "0.17.0",
"gulp": "3.9.0",
"gulp-sequence": "0.4.1",
"gulp-sourcemaps": "1.5.2",
"gulp-uglify": "^1.2.0",
"gulp-util": "^3.0.6",
"vinyl-buffer": "1.0.0",
"vinyl-source-stream": "1.1.0"
}
}

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

@ -0,0 +1,19 @@
/* @flow */
import * as Pair from "./pair"
import {start, Effects} from "reflex"
import {Renderer} from "reflex-virtual-dom-renderer"
var app = start({
initial: window.app != null ?
[Pair.create(window.app.model.value)] :
Pair.initialize("funny cats", "funny hamsters"),
step: Pair.step,
view: Pair.view
});
window.app = app
var renderer = new Renderer({target: document.body})
app.view.subscribe(renderer.address)
app.task.subscribe(Effects.service(app.address))

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

@ -0,0 +1,70 @@
/* @flow */
import * as RandomGif from "./random-gif";
import {html, forward, Task, Effects} from "reflex";
/*::
import type {Address} from "reflex/type/signal"
import type {VirtualNode} from "reflex/type/renderer"
export type Model = {
left: RandomGif.Model,
right: RandomGif.Model
};
*/
export const create = ({left, right}/*:Model*/)/*:Model*/ => ({
left: RandomGif.create(left),
right: RandomGif.create(right)
})
export const initialize = (leftTopic/*:string*/, rightTopic/*:string*/)/*:[Model, Effects<Action>]*/ => {
const [left, leftFx] = RandomGif.initialize(leftTopic)
const [right, rightFx] = RandomGif.initialize(rightTopic)
return [
{left, right},
Effects.batch([
leftFx.map(LeftAction),
rightFx.map(RightAction)
])
]
}
/*::
export type Left = {$typeof: 'Left', act: RandomGif.Action};
export type Right = {$typeof: 'Right', act: RandomGif.Action};
export type Action = Left|Right;
*/
export const LeftAction = (act/*:RandomGif.Action*/)/*:Left*/ =>
({$typeof: "Left", act})
export const RightAction = (act/*:RandomGif.Action*/)/*:Right*/ =>
({$typeof: "Right", act})
export const step = (model/*:Model*/, action/*:Action*/)/*:[Model, Effects<Action>]*/ => {
if (action.$typeof === "Left") {
const [left, fx] = RandomGif.step(model.left, action.act)
return [create({left, right: model.right}), fx.map(LeftAction)]
}
if (action.$typeof === "Right") {
const [right, fx] = RandomGif.step(model.right, action.act)
return [create({left:model.left, right}), fx.map(RightAction)]
}
return [model, Effects.none]
}
// View
export var view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ => {
return html.div({key: "random-gif-pair",
style: {display: "flex"}}, [
html.div({key: 'left'}, [
RandomGif.view(model.left, forward(address, LeftAction))
]),
html.div({key: 'right'}, [
RandomGif.view(model.right, forward(address, RightAction))
])
]);
};

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

@ -0,0 +1,113 @@
/* @flow */
import {Record, Union} from "typed-immutable";
import {html, forward, Effects, Task} from "reflex";
/*::
import type {Address} from "reflex/type/signal"
import type {VirtualNode} from "reflex/type/renderer"
export type Model = {
topic:string,
uri:string
}
*/
export const create = ({topic, uri}/*:Model*/)/*:Model*/=>
({topic, uri})
export const initialize = (topic/*:string*/)/*:[Model, Effects<Action>]*/ =>
[create({topic, uri: "assets/waiting.gif"}), getRandomGif(topic)]
/*::
export type RequestMore = {$typeof: "RequestMore"}
export type NewGif = {$typeof: "NewGif", uri: ?string}
export type Action = RequestMore|NewGif
*/
export const RequestMoreAction = ()/*:RequestMore*/ =>
({$typeof: "RequestMore"})
export const NewGifAction = (uri/*:?string*/)/*:NewGif*/ =>
({$typeof: "NewGif", uri})
export const step = (model/*:Model*/, action/*:Action*/)/*:[Model, Effects<Action>]*/ =>
action.$typeof === "RequestMore" ? [model, getRandomGif(model.topic)] :
action.$typeof === "NewGif" ?
[create({topic: model.topic,
uri: action.uri != null ? action.uri : model.uri})
,Effects.none] :
[model, Effects.none]
const randomURI = (topic/*:string*/)/*:string*/ =>
`http://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=${topic}`
/*::
type ResponseBody = {data: ?{image_uri: ?string}}
type Response = {json: () => Promise<ResponseBody>}
*/
const decodeResponseBody = (body/*:ResponseBody*/)/*:?string*/ =>
(body.data && body.data.image_url) || null
const readResponseAsJSON = (response/*:Response*/) => response.json()
const fetch = global.fetch != null ? global.fetch :
uri => new Promise((resolve, reject) => {
const request = new XMLHttpRequest()
request.open('GET', uri, true)
request.onload = () => {
const status = request.status === 1223 ? 204 : request.status
if (status < 100 || status > 599) {
reject(Error('Network request failed'))
} else {
resolve({
json() {
return new Promise(resolve => {
resolve(JSON.parse(request.responseText))
})
}
})
}
}
request.onerror = () => {
reject(Error('Network request failed'))
}
request.send()
})
export const getRandomGif = (topic/*:string*/)/*:Effects<Action>*/ =>
Effects.task(Task.future(() => fetch(randomURI(topic))
.then(readResponseAsJSON)
.then(decodeResponseBody)
.then(NewGifAction)))
const style = {
viewer: {
width: "200px"
},
header: {
width: "200px",
textAlign: "center"
},
image(uri) {
return {
display: "inline-block",
width: "200px",
height: "200px",
backgroundPosition: "center center",
backgroundSize: "cover",
backgroundImage: `url('${uri}')`
}
}
}
export const view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
html.div({key: "gif-viewer", style: style.viewer}, [
html.h2({key: "header", style: style.header}, [model.topic]),
html.div({key: "image", style: style.image(model.uri)}),
html.button({key: "button", onClick: forward(address, RequestMoreAction)}, [
"More please!"
])
])

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

@ -0,0 +1,16 @@
[ignore]
.*/src/test/.*
.*/dist/.*
.*/node_modules/reflex/examples/.*
.*/node_modules/reflex-virtual-dom-renderer/lib/.*
.*/node_modules/reflex/lib/.*
[libs]
./node_modules/reflex/interfaces/
./node_modules/reflex-virtual-dom-renderer/interfaces/
[include]
[options]
module.name_mapper='reflex-virtual-dom-renderer' -> 'reflex-virtual-dom-renderer/src/index'
module.name_mapper='reflex' -> 'reflex/src/index'

Двоичные данные
examples/gif-viewer/assets/waiting.gif Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 8.3 KiB

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

@ -0,0 +1,135 @@
'use strict';
import browserify from 'browserify';
import gulp from 'gulp';
import source from 'vinyl-source-stream';
import buffer from 'vinyl-buffer';
import uglify from 'gulp-uglify';
import sourcemaps from 'gulp-sourcemaps';
import gutil from 'gulp-util';
import watchify from 'watchify';
import child from 'child_process';
import http from 'http';
import path from 'path';
import babelify from 'babelify';
import sequencial from 'gulp-sequence';
import ecstatic from 'ecstatic';
import hmr from 'browserify-hmr';
import hotify from 'hotify';
var settings = {
port: process.env.DEV_PORT || '6061',
cache: {},
plugin: [],
transform: [
babelify.configure({
"optional": [
"spec.protoToAssign",
"runtime"
],
"blacklist": []
})
],
debug: true,
watch: false,
compression: null
};
var Bundler = function(entry) {
this.entry = entry
this.compression = settings.compression
this.build = this.build.bind(this);
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();
}
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 = http.createServer(ecstatic({
root: path.join(module.filename, '../'),
cache: 0
}));
server.listen(settings.port);
});
gulp.task('compressor', function() {
settings.compression = {
mangle: true,
compress: true,
acorn: true
};
});
gulp.task('watcher', function() {
settings.watch = true
});
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']);

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

@ -0,0 +1,10 @@
<html>
<head>
<title>Sample App</title>
</head>
<body>
<div id='root'>
</div>
<script src="./dist/index.js"></script>
</body>
</html>

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

@ -0,0 +1,34 @@
{
"name": "gif-viewer",
"version": "0.0.0",
"scripts": {
"test": "flow check",
"start": "gulp live",
"build": "NODE_ENV=production gulp build"
},
"dependencies": {
"reflex": "latest",
"reflex-virtual-dom-renderer": "latest"
},
"devDependencies": {
"browserify": "11.0.1",
"watchify": "3.3.1",
"babelify": "6.1.3",
"browserify-hmr": "0.3.0",
"hotify": "0.0.1",
"babel-core": "5.8.23",
"babel-runtime": "5.8.20",
"ecstatic": "0.8.0",
"flow-bin": "0.17.0",
"gulp": "3.9.0",
"gulp-sequence": "0.4.1",
"gulp-sourcemaps": "1.5.2",
"gulp-uglify": "^1.2.0",
"gulp-util": "^3.0.6",
"vinyl-buffer": "1.0.0",
"vinyl-source-stream": "1.1.0"
}
}

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

@ -0,0 +1,19 @@
/* @flow */
import * as RandomGif from "./random-gif"
import {start, Effects} from "reflex"
import {Renderer} from "reflex-virtual-dom-renderer"
var app = start({
initial: window.app != null ?
[RandomGif.create(window.app.model.value)] :
RandomGif.initialize("funny cats"),
step: RandomGif.step,
view: RandomGif.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,113 @@
/* @flow */
import {Record, Union} from "typed-immutable";
import {html, forward, Effects, Task} from "reflex";
/*::
import type {Address} from "reflex/type/signal"
import type {VirtualNode} from "reflex/type/renderer"
export type Model = {
topic:string,
uri:string
}
*/
export const create = ({topic, uri}/*:Model*/)/*:Model*/=>
({topic, uri})
export const initialize = (topic/*:string*/)/*:[Model, Effects<Action>]*/ =>
[create({topic, uri: "assets/waiting.gif"}), getRandomGif(topic)]
/*::
export type RequestMore = {$typeof: "RequestMore"}
export type NewGif = {$typeof: "NewGif", uri: ?string}
export type Action = RequestMore|NewGif
*/
export const RequestMoreAction = ()/*:RequestMore*/ =>
({$typeof: "RequestMore"})
export const NewGifAction = (uri/*:?string*/)/*:NewGif*/ =>
({$typeof: "NewGif", uri})
export const step = (model/*:Model*/, action/*:Action*/)/*:[Model, Effects<Action>]*/ =>
action.$typeof === "RequestMore" ? [model, getRandomGif(model.topic)] :
action.$typeof === "NewGif" ?
[create({topic: model.topic,
uri: action.uri != null ? action.uri : model.uri})
,Effects.none] :
[model, Effects.none]
const randomURI = (topic/*:string*/)/*:string*/ =>
`http://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=${topic}`
/*::
type ResponseBody = {data: ?{image_uri: ?string}}
type Response = {json: () => Promise<ResponseBody>}
*/
const decodeResponseBody = (body/*:ResponseBody*/)/*:?string*/ =>
(body.data && body.data.image_url) || null
const readResponseAsJSON = (response/*:Response*/) => response.json()
const fetch = global.fetch != null ? global.fetch :
uri => new Promise((resolve, reject) => {
const request = new XMLHttpRequest()
request.open('GET', uri, true)
request.onload = () => {
const status = request.status === 1223 ? 204 : request.status
if (status < 100 || status > 599) {
reject(Error('Network request failed'))
} else {
resolve({
json() {
return new Promise(resolve => {
resolve(JSON.parse(request.responseText))
})
}
})
}
}
request.onerror = () => {
reject(Error('Network request failed'))
}
request.send()
})
export const getRandomGif = (topic/*:string*/)/*:Effects<Action>*/ =>
Effects.task(Task.future(() => fetch(randomURI(topic))
.then(readResponseAsJSON)
.then(decodeResponseBody)
.then(NewGifAction)))
const style = {
viewer: {
width: "200px"
},
header: {
width: "200px",
textAlign: "center"
},
image(uri) {
return {
display: "inline-block",
width: "200px",
height: "200px",
backgroundPosition: "center center",
backgroundSize: "cover",
backgroundImage: `url('${uri}')`
}
}
}
export const view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
html.div({key: "gif-viewer", style: style.viewer}, [
html.h2({key: "header", style: style.header}, [model.topic]),
html.div({key: "image", style: style.image(model.uri)}),
html.button({key: "button", onClick: forward(address, RequestMoreAction)}, [
"More please!"
])
])

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

@ -0,0 +1,16 @@
[ignore]
.*/src/test/.*
.*/dist/.*
.*/node_modules/reflex/examples/.*
.*/node_modules/reflex-virtual-dom-renderer/lib/.*
.*/node_modules/reflex/lib/.*
[libs]
./node_modules/reflex/interfaces/
./node_modules/reflex-virtual-dom-renderer/interfaces/
[include]
[options]
module.name_mapper='reflex-virtual-dom-renderer' -> 'reflex-virtual-dom-renderer/src/index'
module.name_mapper='reflex' -> 'reflex/src/index'

Двоичные данные
examples/spin-squares/assets/waiting.gif Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 8.3 KiB

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

@ -0,0 +1,135 @@
'use strict';
import browserify from 'browserify';
import gulp from 'gulp';
import source from 'vinyl-source-stream';
import buffer from 'vinyl-buffer';
import uglify from 'gulp-uglify';
import sourcemaps from 'gulp-sourcemaps';
import gutil from 'gulp-util';
import watchify from 'watchify';
import child from 'child_process';
import http from 'http';
import path from 'path';
import babelify from 'babelify';
import sequencial from 'gulp-sequence';
import ecstatic from 'ecstatic';
import hmr from 'browserify-hmr';
import hotify from 'hotify';
var settings = {
port: process.env.DEV_PORT || '6061',
cache: {},
plugin: [],
transform: [
babelify.configure({
"optional": [
"spec.protoToAssign",
"runtime"
],
"blacklist": []
})
],
debug: true,
watch: false,
compression: null
};
var Bundler = function(entry) {
this.entry = entry
this.compression = settings.compression
this.build = this.build.bind(this);
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();
}
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 = http.createServer(ecstatic({
root: path.join(module.filename, '../'),
cache: 0
}));
server.listen(settings.port);
});
gulp.task('compressor', function() {
settings.compression = {
mangle: true,
compress: true,
acorn: true
};
});
gulp.task('watcher', function() {
settings.watch = true
});
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']);

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

@ -0,0 +1,10 @@
<html>
<head>
<title>Sample App</title>
</head>
<body>
<div id='root'>
</div>
<script src="./dist/index.js"></script>
</body>
</html>

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

@ -0,0 +1,34 @@
{
"name": "spin-squares",
"version": "0.0.0",
"scripts": {
"test": "flow check",
"start": "gulp live",
"build": "NODE_ENV=production gulp build"
},
"dependencies": {
"reflex": "latest",
"reflex-virtual-dom-renderer": "latest"
},
"devDependencies": {
"browserify": "11.0.1",
"watchify": "3.3.1",
"babelify": "6.1.3",
"browserify-hmr": "0.3.0",
"hotify": "0.0.1",
"babel-core": "5.8.23",
"babel-runtime": "5.8.20",
"ecstatic": "0.8.0",
"flow-bin": "0.17.0",
"gulp": "3.9.0",
"gulp-sequence": "0.4.1",
"gulp-sourcemaps": "1.5.2",
"gulp-uglify": "^1.2.0",
"gulp-util": "^3.0.6",
"vinyl-buffer": "1.0.0",
"vinyl-source-stream": "1.1.0"
}
}

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

@ -0,0 +1,215 @@
/* @flow */
// Port of http://package.elm-lang.org/packages/elm-lang/core/2.1.0/Color
/*::
export type Int = number
export type Float = number
*/
class RGBAType {
/*::
$typeof: "Color.RGBA";
red: Int;
green: Int;
blue: Int;
alpha: Float;
*/
constructor(red/*:Int*/, green/*:Int*/, blue/*:Int*/, alpha/*:Float*/) {
this.$typeof = "Color.RGBA"
this.red = red
this.green = green
this.blue = blue
this.alpha = alpha
}
}
class HSLAType {
/*::
$typeof: "Color.HSLA";
hue: Float;
saturation: Float;
lightness: Float;
alpha: Float;
*/
constructor(hue/*:Float*/, saturation/*:Float*/, lightness/*:Float*/, alpha/*:Float*/) {
this.$typeof = "Color.HSLA"
this.hue = hue
this.saturation = saturation
this.lightness = lightness
this.alpha = alpha
}
}
/*::
export type RGBA = {$typeof: "Color.RGBA", red: Int, green: Int, blue: Int, alpha: Float}
export type HSLA = {$typeof: "Color.HSLA", hue: Float, saturation: Float, lightness: Float, alpha: Float}
export type Color = RGBA|HSLA
*/
// Create RGB colors with an alpha component for transparency.
// The alpha component is specified with numbers between 0 and 1
export const rgba = (red/*:Int*/, green/*:Int*/, blue/*:Int*/, alpha/*:Float*/)/*:Color*/ =>
new RGBAType(red, green, blue, alpha)
// Create RGB colors from numbers between 0 and 255 inclusive
export const rgb = (red/*:Int*/, green/*:Int*/, blue/*:Int*/)/*:Color*/ =>
new RGBAType(red, green, blue, 1)
export const turns = (n/*:Float*/)/*:Float*/ => 2 * Math.PI * n
export const degrees = (n/*:Float*/)/*:Float*/ => n * Math.PI / 180
// Create [HSL colors](http://en.wikipedia.org/wiki/HSL_and_HSV)
// with an alpha component for transparency.
export const hsla = (hue/*:Float*/, saturation/*:Float*/, lightness/*:Float*/, alpha/*:Float*/)/*:Color*/ =>
new HSLAType(hue - turns(Math.floor(hue / (2 * Math.PI))), saturation, lightness, alpha)
// Create [HSL colors](http://en.wikipedia.org/wiki/HSL_and_HSV). This gives
// you access to colors more like a color wheel, where all hues are aranged in a
// circle that you specify with angles (radians).
//
// const red = hsl(degrees(0), 1, 0.5)
// const green = hsl(degrees(120), 1, 0.5)
// const blue = hsl(degrees(240), 1, 0.5)
// const pastelRed = hsl(degrees(0), 0.7, 0.7)
//
// To cycle through all colors, just cycle through degrees. The saturation level
// is how vibrant the color is, like a dial between grey and bright colors. The
// lightness level is a dial between white and black.
export const hsl = (hue/*:Float*/, saturation/*:Float*/, lightness/*:Float*/)/*:Color*/ =>
hsla(hue, saturation, lightness, 1)
// Produce a gray based on the input. 0 is white, 1 is black.
export const grayscale = (p/*:Float*/)/*:Color*/=>
new HSLAType(0, 0, 1 - p, 1)
// Produce a "complementary color". The two colors will
// accent each other. This is the same as rotating the hue by 180&deg;
export const complement = (color/*:Color*/)/*:Color*/ => {
if (color.$typeof === "Color.HSLA") {
return hsla(color.hue + degrees(180),
color.saturation,
color.lightness,
color.alpha)
}
if (color.$typeof === "Color.RGBA") {
return complement(rgba2hsla(color))
}
throw TypeError("Invalid color value was passed")
}
// Convert given color into the HSL format.
export const toHSL = (color/*:Color*/)/*:HSLA*/ => {
if (color.$typeof === "Color.HSLA") {
return color
}
if (color.$typeof === "Color.RGBA") {
return rgba2hsla(color)
}
throw TypeError("Invalid color value was passed")
}
// Convert given color into the RGB format.
export const toRGB = (color/*:Color*/)/*:RGBA*/ => {
if (color.$typeof === "Color.RGBA") {
return color
}
if (color.$typeof === "Color.HSLA") {
return hsla2rgba(color)
}
throw TypeError("Invalid color value was passed")
}
const fmod = (f/*:Float*/, n/*:Int*/)/*:Float*/ => {
const integer = Math.floor(f)
return integer % n + f - integer
}
const rgba2hsla = ({red, green, blue, alpha}/*:RGBA*/)/*:HSLA*/ => {
const [r, g, b] = [red/255, green/255, blue/255]
const max = Math.max(Math.max(r, g), b)
const min = Math.min(Math.min(r, g), b)
const delta = max - min
const h = max === r ? fmod(((g - b) / delta), 6) :
max === g ? (((b - r) / delta) + 2) :
((r - g) / delta) + 4
const hue = degrees(60) * h
const lightness = (max + min) / 2
const saturation = lightness === 0 ? 0 :
delta / (1 - Math.abs(2 * lightness - 1))
return new HSLAType(hue, lightness, saturation, alpha)
}
const hsla2rgba = ({hue, saturation, lightness, alpha}/*:HSLA*/)/*:RGBA*/ => {
const chroma = (1 - Math.abs(2 * lightness - 1)) * saturation
const h = hue / degrees(60)
const x = chroma * (1 - Math.abs(fmod(h, 2 - 1)))
const [r, g, b] = h < 0 ? [0, 0, 0] :
h < 1 ? [chroma, x, 0] :
h < 2 ? [x, chroma, 0] :
h < 3 ? [0, chroma, x] :
h < 4 ? [0, x, chroma] :
h < 5 ? [x, 0, chroma] :
h < 6 ? [chroma, 0, x] :
[0, 0, 0]
const m = lightness - chroma / 2
return new RGBAType(Math.round(255 * (r + m)),
Math.round(255 * (g + m)),
Math.round(255 * (b + m)),
alpha)
}
export const lightRed = rgb(239, 41, 41)
export const red = rgb(204, 0, 0)
export const darkRed = rgb(164, 0, 0)
export const lightOrange = rgb(252, 175, 62)
export const orange = rgb(245, 121, 0)
export const darkOrange = rgb(206, 92, 0)
export const lightYellow = rgb(255, 233, 79)
export const yellow = rgb(237, 212, 0)
export const darkYellow = rgb(196, 160, 0)
export const lightGreen = rgb(138, 226, 52)
export const green = rgb(115, 210, 22)
export const darkGreen = rgb(78, 154, 6)
export const lightBlue = rgb(114, 159, 207)
export const blue = rgb(52, 101, 164)
export const darkBlue = rgb(32, 74, 135)
export const lightPurple = rgb(173, 127, 168)
export const purple = rgb(117, 80, 123)
export const darkPurple = rgb(92, 53, 102)
export const lightBrown = rgb(233, 185, 110)
export const brown = rgb(193, 125, 17)
export const darkBrown = rgb(143, 89, 2)
export const black = rgb(0, 0, 0)
export const white = rgb(255, 255, 255)
export const lightGrey = rgb(238, 238, 236)
export const grey = rgb(211, 215, 207)
export const darkGrey = rgb(186, 189, 182)
export const lightCharcoal = rgb(136, 138, 133)
export const charcoal = rgb(85, 87, 83)
export const darkCharcoal = rgb(46, 52, 54)

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

@ -0,0 +1,188 @@
/* @flow */
// Port of http://package.elm-lang.org/packages/Dandandan/Easing/2.0.0
import {toRGB, rgba} from "./color";
/*::
import type {Color} from "./color";
export type Time = number
export type Float = number
// Type alias for Easing functions.
export type Easing = (x:Float) => Float
// An interpolation of two values using a Float value.
export type Interpolation <a> = (from:a, to:a, value:Float) => a
// An `Animation` is a function that returns a value given a duration and the current time.
export type Animation <a> = (duration:Time, time:Time) => a
// Compute an animation using the parameters.
// Parameters are: an `Easing` function, an `Interpolation` function, a `from` value, a `to` value, the duration of the transition and the elapsed time.
// ease(linear, float, 0, 20, second, 0) // => 0
// ease(linear, float, 0, 20, second, second) // => 20
// ease(linear, color, blue, red, second, second) // => red
*/
export const ease = /*::<a>*/(easing/*:Easing*/, interpolation/*:Interpolation<a>*/, from/*:a*/, to/*:a*/, duration/*:Time*/, time/*:Time*/)/*:a*/ =>
interpolation(from, to, easing(Math.min(time / duration, 1)))
// Interpolation of two Floats
export const float = (from/*:Float*/, to/*:Float*/, progress/*:Float*/)/*:Float*/ =>
from + (to - from) * progress
// Interpolation of two points in 2D
/*::
export type Point2D = {x: Float, y:Float}
*/
export const point2D = (from/*:Point2D*/, to/*:Point2D*/, progress/*:Float*/)/*:Point2D*/ =>
({
x: float(from.x, to.x, progress),
y: float(from.y, to.y, progress)
})
/*::
export type Point3D = {x: Float, y:Float, z:Float}
*/
export const point3D = (from/*:Point3D*/, to/*:Point3D*/, progress/*:Float*/)/*:Point3D*/ =>
({
x: float(from.x, to.x, progress),
y: float(from.y, to.y, progress),
z: float(from.z, to.z, progress)
})
export const color = (from/*:Color*/, to/*:Color*/, progress/*:Float*/)/*Color*/ => {
const [begin, end] = [toRGB(from), toRGB(to)]
return rgba(Math.round(float(begin.red, end.red, progress)),
Math.round(float(begin.green, end.green, progress)),
Math.round(float(begin.blue, end.blue, progress)),
float(begin.alpha, end.alpha, progress))
}
export const pair = /*::<a>*/(interpolate/*:Interpolation<a>*/)/*:Interpolation<[a,a]>*/ =>
([a0, b0]/*:[a,a]*/, [a1, b1]/*:[a,a]*/, progress/*:Float*/)/*:[a,a]*/ =>
[interpolate(a0, a1, progress), interpolate(b0, b1, progress)]
// Inverts an `Easing` function. A transition that starts fast and continues
// slow now starts slow and continues fast.
export const invert = (easing/*:Easing*/)/*:Easing*/=>
time => 1 - easing(1 - time)
// Flips an `Easing` function. A transition that looks like `/`, now looks like
// `\`.
export const flip = (easing/*:Easing*/)/*:Easing*/=>
time => easing(1 - time)
// Makes an Easing function using two `Easing` functions. The first half the
// first `Easing` function is used, the other half the second.
export const inOut = (begin/*:Easing*/, end/*:Easing*/)/*:Easing*/=>
time =>
time < 0.5 ? begin(time * 2) / 2 :
0.5 + (end((time - 0.5) * 2) / 2)
// Makes an `Easing` function go to the end first and then back to the start.
// A transition that looks like `/` now looks like `/\`.
export const retour = (easing/*:Easing*/)/*:Easing*/=>
time =>
time < 0.5 ? easing(time * 2) :
easing(1 - ((time - 0.5) * 2))
// Repeats an `Animation` infinitely
// const rotate = cycle(ease(linear, float, 0, 360), second)
export const cycle = /*::<a>*/ (animation/*:Animation<a>*/)/*:Animation<a>*/=>
(duration, time) =>
animation(1, (time / duration) - Math.floor(time / duration))
export const linear/*:Easing*/ = x => x
export const easeInQuad/*:Easing*/ = time => time ^ 2
export const easeOutQuad/*:Easing*/ = invert(easeInQuad)
export const easeInOutQuad/*:Easing*/ = inOut(easeInQuad, easeOutQuad)
export const easeInCubic/*:Easing*/ = time => time ^ 3
export const easeOutCubic/*:Easing*/ = invert(easeInCubic)
export const easeInOutCubic/*:Easing*/ = inOut(easeInCubic, easeOutCubic)
export const easeInQuart/*:Easing*/ = time => time ^ 4
export const easeOutQuart/*:Easing*/ = invert(easeInQuad)
export const easeInOutQuart/*:Easing*/ = inOut(easeInQuart, easeOutQuart)
export const easeInQuint/*:Easing*/ = time => time ^ 5
export const easeOutQuint/*:Easing*/ = invert(easeInQuint)
export const easeInOutQuint/*:Easing*/ = inOut(easeInQuint, easeOutQuint)
export const easeOutSine/*:Easing*/ = time => Math.sin(time * (Math.PI / 2))
export const easeInSine/*:Easing*/ = invert(easeOutSine)
export const easeInOutSine/*:Easing*/ = inOut(easeInSine, easeOutSine)
export const easeInExpo/*:Easing*/ = time => 2 ^ (10 * (time - 1))
export const easeOutExpo/*:Easing*/ = invert(easeInExpo)
export const easeInOutExpo/*:Easing*/ = inOut(easeInExpo, easeOutExpo)
export const easeOutCirc/*:Easing*/ = time => Math.sqrt(1 - (time - 1) ^ 2)
export const easeInCirc/*:Easing*/ = invert(easeOutCirc)
export const easeInOutCirc/*:Easing*/ = inOut(easeInCirc, easeOutCirc)
export const easeInBack/*:Easing*/ = time =>
time * time * (2.70158 * time - 1.70158)
export const easeOutBack/*:Easing*/ = invert(easeInBack)
export const easeInOutBack/*:Easing*/ = inOut(easeInBack, easeOutBack)
export const easeOutBounce/*:Easing*/ = time => {
const a = 7.5625
const t2 = time - (1.5 / 2.75)
const t3 = time - (2.25 / 2.75)
const t4 = time - (2.65 / 2.75)
return time < 1 / 2.75 ? a * time * time :
time < 2 / 2.75 ? a * t2 * t2 + 0.75 :
time < 2.5 / 2.75 ? a * t3 * t3 + 0.9375 :
a * t4 * t4 + 0.984375
}
export const easeInBounce/*:Easing*/ = invert(easeOutBounce)
export const easeInOutBounce/*:Easing*/ = inOut(easeInBounce, easeOutBounce)
export const easeInElastic/*:Easing*/ = time => {
const s = 0.075
const p = 0.3
const t = time - 1
return -((2 ^ (10 * t)) * Math.sin((t - s) * (2 * Math.PI) / p))
}
export const easeOutElastic/*:Easing*/ = invert(easeInElastic)
export const easeInOutElastic/*:Easing*/ = inOut(easeInElastic, easeOutElastic)
const floats = pair(float)
const zip = /*::<x,y,z>*/(f/*:(x:x,y:y)=>z*/, xs/*:Array<x>*/, ys/*:Array<y>*/)/*:Array<z>*/ => {
const zs = []
const count = Math.min(xs.length, ys.length)
let index = 0
while (index < count) {
zs.push(f(xs[index], ys[index]))
index = index + 1
}
return zs
}
// A cubic bezier function using 4 parameters: x and y position of first
// control point, and x and y position of second control point.
export const bezier = (x1/*:Float*/, y1/*:Float*/, x2/*:Float*/, y2/*:Float*/)/*:Easing*/ =>
time => {
const f = (xs, ys) => floats(xs, ys, time)
const casteljau = points => {
if (points.length === 1) {
const [[x, y]] = points
return y
} else {
return casteljau(zip(f, points, points.slice(1)))
}
}
return casteljau([[0,0], [x1,y1], [x2,y2], [1,1]])
}

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

@ -0,0 +1,19 @@
/* @flow */
import * as SpinSquarePair from "./spin-square-pair"
import {start, Effects} from "reflex"
import {Renderer} from "reflex-virtual-dom-renderer"
var app = start({
initial: window.app != null ?
[SpinSquarePair.create(window.app.model.value)] :
SpinSquarePair.initialize(),
step: SpinSquarePair.step,
view: SpinSquarePair.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,65 @@
/* @flow */
import * as SpinSquare from "./spin-square";
import {html, forward, thunk, Effects} from "reflex";
/*::
import type {Address} from "reflex/type/signal";
import type {VirtualNode} from "reflex/type/renderer";
export type Model = {
left: SpinSquare.Model,
right: SpinSquare.Model
};
*/
export const create = ({left, right}/*:Model*/)/*:Model*/ => ({
left: SpinSquare.create(left),
right: SpinSquare.create(right)
})
export const initialize = ()/*:[Model, Effects<Action>]*/ => {
const [left, leftFx] = SpinSquare.initialize()
const [right, rightFx] = SpinSquare.initialize()
return [
{left, right},
Effects.batch([
leftFx.map(LeftAction),
rightFx.map(RightAction)
])
]
}
/*::
export type Left = {$typeof: 'Left', act: SpinSquare.Action};
export type Right = {$typeof: 'Right', act: SpinSquare.Action};
export type Action = Left|Right;
*/
export const LeftAction = (act/*:SpinSquare.Action*/)/*:Left*/ =>
({$typeof: "Left", act})
export const RightAction = (act/*:SpinSquare.Action*/)/*:Right*/ =>
({$typeof: "Right", act})
export const step = (model/*:Model*/, action/*:Action*/)/*:[Model, Effects<Action>]*/ => {
if (action.$typeof === "Left") {
const [left, fx] = SpinSquare.step(model.left, action.act)
return [create({left, right: model.right}), fx.map(LeftAction)]
}
if (action.$typeof === "Right") {
const [right, fx] = SpinSquare.step(model.right, action.act)
return [create({left:model.left, right}), fx.map(RightAction)]
}
return [model, Effects.none]
}
// View
export var view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
html.div({key: "spin-square-pair",
style: {display: "flex"}}, [
thunk("left", SpinSquare.view, model.left, forward(address, LeftAction)),
thunk("right", SpinSquare.view, model.right, forward(address, RightAction))
])

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

@ -0,0 +1,109 @@
/* @flow */
import {Record, Union} from "typed-immutable"
import {html, forward, Effects, Task} from "reflex"
import {ease, easeOutBounce, float} from "./easing"
/*::
import type {Float, Time} from "./easing"
import type {Address} from "reflex/type/signal"
import type {VirtualNode} from "reflex/type/renderer"
export type AnimationState = {
lastTime: Time,
elapsedTime: Time
}
export type Model = {
angle:Float,
animationState:?AnimationState
}
*/
export const create = ({angle, animationState}/*:Model*/)/*:Model*/=>
({angle, animationState})
export const initialize = ()/*:[Model, Effects<Action>]*/ =>
[create({angle: 0, animationState:null}), Effects.none]
const rotateStep = 90
const ms = 1
const second = 1000 * ms
const duration = second
/*::
export type Spin = {$typeof: "Spin"}
export type Tick = {$typeof: "Tick", time: Time}
export type Action = Spin|Tick
*/
export const SpinAction = ()/*:Spin*/ =>
({$typeof: "Spin"})
export const TickAction = (time/*:Time*/)/*:Tick*/ =>
({$typeof: "Tick", time})
export const step = (model/*:Model*/, action/*:Action*/)/*:[Model, Effects<Action>]*/ => {
if (action.$typeof === "Spin") {
if (model.animationState == null) {
return [model, Effects.tick(TickAction)]
} else {
return [model, Effects.none]
}
}
if (action.$typeof === "Tick") {
const {animationState, angle} = model
const elapsedTime = animationState == null ? 0 :
animationState.elapsedTime + (action.time - animationState.lastTime)
if (elapsedTime > duration) {
return [
{angle: angle + rotateStep, animationState: null},
Effects.none
]
} else {
return [
{angle, animationState: {elapsedTime, lastTime: action.time}},
Effects.tick(TickAction)
]
}
}
return [model, Effects.none]
}
// View
const toOffset = (animationState/*:?AnimationState*/)/*:Float*/ =>
animationState == null ? 0 :
ease(easeOutBounce, float, 0, rotateStep, duration, animationState.elapsedTime)
const style = {
square: {
width: "200px",
height: "200px",
display: "flex",
alignItems: "center",
justifyContent: "center",
backgroundColor: "#60B5CC",
color: "#fff",
cursor: "pointer",
borderRadius: "30px"
},
spin({angle, animationState}/*:Model*/)/*:Object*/{
return {
transform: `translate(100px, 100px) rotate(${angle + toOffset(animationState)}deg)`
}
}
}
export const view = (model/*:Model*/, address/*:Address<Action>*/)/*:VirtualNode*/ =>
html.figure({
key: "spin-square",
style: Object.assign(style.spin(model), style.square),
onClick: forward(address, SpinAction)
}, ["Click me!"])