implement choo v5 API (#425)

* implement choo v5 API

* fix tests

* 5.0.0-3

* 4kb now

* bump nanomorph

* 5.0.0-4

* fix missing deps stuff

* 5.0.0-5

* fix travis

* fixup! oops standard

* chooexpose

* fix state passing

* fixup! update example

* fixup! incorporate feedback

* fixup! more docs

* fixup! even more docs
This commit is contained in:
Yoshua Wuyts
2017-03-21 03:00:45 +01:00
committed by GitHub
parent 6fc525fe05
commit 8e8e870d45
61 changed files with 807 additions and 2707 deletions
-42
View File
@@ -1,42 +0,0 @@
const choo = require('../../')
const html = require('../../html')
const plur = require('plur')
const app = choo()
app.model({
state: {
counter: 0
},
reducers: {
increment: (state, data) => ({ counter: state.counter + 1 }),
decrement: (state, data) => ({ counter: state.counter - 1 })
},
effects: {
incrementAsync: function (state, data, send, done) {
setTimeout(() => send('increment', done), 1000)
},
decrementAsync: function (state, data, send, done) {
setTimeout(() => send('decrement', done), 1000)
}
}
})
function mainView (state, prev, send) {
const count = state.counter
return html`
<main class="app">
<h1>Async counter</h1>
<p>Clicked ${count} ${plur('time', count)}!</p>
<button onclick=${() => send('increment')}>Increment</button>
<button onclick=${() => send('decrement')}>Decrement</button>
<button onclick=${() => send('incrementAsync')}>Increment async</button>
<button onclick=${() => send('decrementAsync')}>Decrement async</button>
</main>
`
}
app.router([ '/', mainView ])
const tree = app.start()
document.body.appendChild(tree)
-16
View File
@@ -1,16 +0,0 @@
{
"name": "cancellable-counter",
"version": "1.0.0",
"description": "",
"main": "client.js",
"scripts": {
"start": "bankai start --entry=client -p 8080"
},
"keywords": [],
"author": "Juan Soto <juan@juansoto.me>",
"license": "ISC",
"dependencies": {
"bankai": "^3.2.0",
"plur": "^2.1.2"
}
}
-33
View File
@@ -1,33 +0,0 @@
const choo = require('../../')
const mainView = require('./views/main')
const app = choo({
onError: function (err, state, createSend) {
console.trace()
console.groupCollapsed(`Error: ${err.message}`)
console.error(err)
console.groupEnd()
const send = createSend('onError: ')
send('app:error', err)
},
onAction: function (state, data, name, caller, createSend) {
console.groupCollapsed(`Action: ${caller} -> ${name}`)
console.log(data)
console.groupEnd()
},
onStateChange: function (state, data, prev, createSend) {
console.groupCollapsed('State')
console.log(prev)
console.log(state)
console.groupEnd()
}
})
app.model(require('./models/error'))
app.model(require('./models/api'))
app.router(['/', mainView])
const tree = app.start()
document.body.appendChild(tree)
-31
View File
@@ -1,31 +0,0 @@
const http = require('../../../http')
module.exports = {
namespace: 'api',
state: {
title: 'Button pushing machine 3000'
},
reducers: {
set: (state, data) => ({ 'title': data })
},
effects: {
good: function (state, data, send, done) {
request('/good', send, done)
},
bad: (state, data, send, done) => request('/bad', send, done)
}
}
function request (uri, send, done) {
http(uri, { json: true }, function (err, res, body) {
if (err) return done(new Error('HTTP error'))
if (res.statusCode !== 200) {
const message = (body && body.message)
? body.message
: 'unknown server error'
return done(new Error(message))
}
if (!body) return done(new Error('fatal: no body received'))
send('api:set', body.message || body.title, done)
})
}
-52
View File
@@ -1,52 +0,0 @@
// Create a new error. Errors are pushed into an array so they can be
// queued. We're doing a bit of timestamp trickery here so each error
// is shown for exactly one second before being dropped. Probably
// useful for real world scenarios; especially when coupled to
// fancy CSS animation thingies
// Each error is displayed 1 second
const ERROR_TIMEOUT = 1000
module.exports = {
namespace: 'app',
state: {
errors: [],
errorTimeDone: 0,
triggerTime: null
},
reducers: {
setError: function (state, data) {
return {
errors: state.errors.concat(data.message),
errorTimeDone: data.errorTimeDone
}
},
'delError': function (state, data) {
state.errors.shift()
return { errors: state.errors }
}
},
effects: {
error: function (err, state, send, done) {
const timeDone = state.errorTimeDone
const now = Date.now()
const timeStamp = (timeDone && timeDone >= now)
? timeDone + ERROR_TIMEOUT
: now + ERROR_TIMEOUT
const timeout = timeStamp - now
const errAction = {
message: err.message,
errorTimeDone: timeStamp
}
send('app:setError', errAction, function (err) {
if (err) return done(err)
setTimeout(function () {
send('app:delError', done)
}, timeout)
})
}
}
}
-13
View File
@@ -1,13 +0,0 @@
{
"name": "http",
"private": true,
"main": "client.js",
"scripts": {
"start": "NODE_ENV=development node server.js"
},
"dependencies": {
"bankai": "^2.0.2",
"browserify": "^13.0.1",
"server-router": "^2.1.0"
}
}
-45
View File
@@ -1,45 +0,0 @@
const serverRouter = require('server-router')
const browserify = require('browserify')
const bankai = require('bankai')
const http = require('http')
const PORT = 8080
const server = http.createServer(createRouter())
server.listen(PORT, () => process.stdout.write(`listening on port ${PORT}\n`))
var index = 0
const errors = [
'a giant robot invaded robot town!',
'something weird crawled out of the swamp',
'oh no, the bear people invaded the server!',
'the NSA',
'12 goats started a band',
'we kinda just gave up'
]
function createRouter () {
const router = serverRouter('/404')
const html = bankai.html({ css: false })
router.on('/', (req, res) => html(req, res).pipe(res))
const js = bankai.js(browserify, require.resolve('./client.js'))
router.on('/bundle.js', (req, res) => js(req, res).pipe(res))
router.on('/good', (req, res) => {
res.end('{ "message": "all is well in robo town!" }')
})
router.on('/bad', (req, res) => {
res.statusCode = 500
res.end(`{ "message": "${errors[index++ % (errors.length)]}" }`)
})
router.on('/404', (req, res) => {
res.statusCode = 404
res.end('{ "message": "the server is confused" }')
})
return router
}
-14
View File
@@ -1,14 +0,0 @@
const html = require('../../../html')
module.exports = function (state, prev, send) {
const error = state.app.errors[0]
const title = state.api.title
return html`
<section>
<h1>${title}</h1>
<h2>Latest error: ${error}</h2>
<button onclick=${(e) => send('api:good')}>OK!</button>
<button onclick=${(e) => send('api:bad')}>Naughty</button>
</section>
`
}
-23
View File
@@ -1,23 +0,0 @@
const log = require('choo-log')
const sf = require('sheetify')
const choo = require('../../')
sf('css-wipe/dest/bundle')
const app = choo()
app.use(log())
app.model(require('./models/inbox'))
app.model(require('./models/spam'))
app.model(require('./models/sent'))
app.router([
['/', require('./views/empty')],
['/:mailbox', require('./views/mailbox'), [
['/:message', require('./views/email')]
]]
])
const tree = app.start()
document.body.appendChild(tree)
-36
View File
@@ -1,36 +0,0 @@
const dateformat = require('dateformat')
const html = require('../../../html')
module.exports = function (state, prev, send) {
const params = state.params
const mailbox = params.mailbox
const messages = state[mailbox].messages
return html`
<div>
<div class="db cf w-100">
<div class="fl mb3 w-25 mt0 b">Date</th>
<div class="fl mb3 w-25 mt0 b">Subject</th>
<div class="fl mb3 w-25 mt0 b">From</th>
<div class="fl mb3 w-25 mt0 b">To</th>
</div>
${messages.map(function (msg) {
return createMessage(msg, mailbox)
})}
</div>
`
}
function createMessage (message, mailbox) {
return html`
<div class="db cf w-100">
<a href="${'/' + mailbox + '/' + message.id}">
<div class="fl mb3 w-25 f6 link">
${dateformat(message.date, 'mmmm dS')}
</div>
<div class="fl mb3 w-25 f6 link">${message.subject}</div>
<div class="fl mb3 w-25 f6 link">${message.from}</div>
<div class="fl mb3 w-25 f6 link">${message.to}</div>
</a>
</div>
`
}
-34
View File
@@ -1,34 +0,0 @@
const html = require('../../../html')
module.exports = function (state, prev, send) {
const params = state.params
const mailbox = params.mailbox
const message = params.message
const email = state[mailbox].messages.filter(function (msg) {
return String(msg.id) === message
})[0]
return html`
<div>
${email ? createEmail(email) : 'error: no email found'}
</div
`
}
function createEmail (message) {
return html`
<div class="mail">
<dl>
<dt>From</dt>
<dd>${message.from}</dd>
<dt>To</dt>
<dd>${message.to}</dd>
<dt>Date</dt>
<dd>${message.date}</dd>
</dl>
<h4>${message.subject}</h4>
<p>${message.body}</p>
</div>
`
}
@@ -1,9 +0,0 @@
const html = require('../../../html')
module.exports = function (state, prev, send) {
return html`
<section>
<p>Select a mailbox</p>
</section>
`
}
-83
View File
@@ -1,83 +0,0 @@
const dateformat = require('dateformat')
const html = require('../../../html')
module.exports = function () {
return function (state, prev, send) {
const params = state.params
const mailbox = params.mailbox
const message = params.message
const messages = state[mailbox].messages
if (message) {
const email = state[mailbox].messages.filter(function (msg) {
return String(msg.id) === message
})[0]
return html`
<section class="fl mt4 w-80 db">
<div>
${createHeader()}
${messages.map(function (msg) {
return createMessage(msg, mailbox)
})}
</div>
<div>
${email ? createEmail(email) : 'error: no email found'}
</div
</section>
`
} else {
return html`
<section class="fl mt4 w-80 db">
${createHeader()}
${messages.map(function (msg) {
return createMessage(msg, mailbox)
})}
</section>
`
}
}
}
function createHeader () {
return html`
<div class="db cf w-100">
<div class="fl mb3 w-25 mt0 b">Date</th>
<div class="fl mb3 w-25 mt0 b">Subject</th>
<div class="fl mb3 w-25 mt0 b">From</th>
<div class="fl mb3 w-25 mt0 b">To</th>
</div>
`
}
function createMessage (message, mailbox) {
return html`
<div class="db cf w-100">
<a href="${'/' + mailbox + '/' + message.id}">
<div class="fl mb3 w-25 f6 link">
${dateformat(message.date, 'mmmm dS')}
</div>
<div class="fl mb3 w-25 f6 link">${message.subject}</div>
<div class="fl mb3 w-25 f6 link">${message.from}</div>
<div class="fl mb3 w-25 f6 link">${message.to}</div>
</a>
</div>
`
}
function createEmail (message) {
return html`
<div class="mail">
<dl>
<dt>From</dt>
<dd>${message.from}</dd>
<dt>To</dt>
<dd>${message.to}</dd>
<dt>Date</dt>
<dd>${message.date}</dd>
</dl>
<h4>${message.subject}</h4>
<p>${message.body}</p>
</div>
`
}
-30
View File
@@ -1,30 +0,0 @@
const html = require('../../../html')
const mailboxes = [ 'inbox', 'spam', 'sent' ]
module.exports = function (state, prev, send) {
return html`
<aside class="fl mt4 w-20 db">
<ul>
<li>
<h2 class="f4 b lh0">Mailbox</h2>
</li>
${mailboxes.map(function (mailbox) {
const messages = mailbox.messages
return createLi(mailbox, messages)
})}
</ul>
</aside>
`
}
function createLi (mailbox, messages) {
return html`
<li class="mt4 f6">
<a href="/${mailbox}">
${mailbox.charAt(0).toUpperCase() + mailbox.slice(1)}
<span>${messages}</span>
</a>
</li>
`
}
-11
View File
@@ -1,11 +0,0 @@
const pathname = require('pathname-match')
const html = require('../../../html')
module.exports = function (state, prev, send) {
const location = state.location.pathname
return html`
<span class="fl mt4 w-100 f4 b">
URL: ${pathname(location) || '/'}
</span>
`
}
-23
View File
@@ -1,23 +0,0 @@
module.exports = {
namespace: 'inbox',
state: {
messages: [
{
id: 1,
subject: 'Welcome to Choo',
from: 'choochoo@choojs.com',
to: 'user@example.com',
date: new Date(),
body: 'Welcome to Choo. We hope you enjoy your stay'
}, {
id: 2,
subject: 'Great Choo Resources',
from: 'choochoo@choojs.com',
to: 'user@example.com',
date: new Date(),
body: 'Have you seen choocasts.com? How about chooaddons.com?'
}
]
},
reducers: { }
}
-16
View File
@@ -1,16 +0,0 @@
module.exports = {
namespace: 'sent',
state: {
messages: [
{
id: 4,
subject: 'Should I use Choo',
from: 'user@example.com',
to: 'choochoo@choojs.com',
date: new Date(),
body: 'Choo looks pretty good, should I use it?'
}
]
},
reducers: { }
}
-16
View File
@@ -1,16 +0,0 @@
module.exports = {
namespace: 'spam',
state: {
messages: [
{
id: 3,
subject: 'You have one the lottery!!!111ONEONE',
from: '419@thereallotteryhonest.com',
to: 'user@example.com',
date: new Date(),
body: 'You have ONE the lottery! You only have to send us a small amount of monies to claim your prize'
}
]
},
reducers: { }
}
-25
View File
@@ -1,25 +0,0 @@
{
"name": "mailbox",
"version": "1.0.0",
"private": "true",
"main": "client.js",
"scripts": {
"start": "NODE_ENV=development node server.js"
},
"author": "Yoshua Wuyts <i@yoshuawuyts.com>",
"license": "ISC",
"dependencies": {
"choo-log": "^1.4.1",
"css-wipe": "^4.2.1",
"dateformat": "^1.0.12",
"pathname-match": "^1.1.3",
"tachyons": "^4.0.0-beta.33"
},
"devDependencies": {
"bankai": "^2.0.5",
"browserify": "^13.0.1",
"insert-css": "^0.2.0",
"server-router": "^2.1.0",
"sheetify": "^5.0.3"
}
}
-30
View File
@@ -1,30 +0,0 @@
const serverRouter = require('server-router')
const browserify = require('browserify')
const bankai = require('bankai')
const http = require('http')
const PORT = 8080
const server = http.createServer(createRouter())
server.listen(PORT, () => process.stdout.write(`listening on port ${PORT}\n`))
function createRouter () {
const router = serverRouter('/404')
const js = bankai.js(browserify, require.resolve('./client.js'))
router.on('/bundle.js', (req, res) => js(req, res).pipe(res))
router.on('/:inbox/bundle.js', (req, res) => js(req, res).pipe(res))
const html = bankai.html({ css: false })
router.on('/', (req, res) => html(req, res).pipe(res))
router.on('/:inbox', (req, res) => html(req, res).pipe(res))
router.on('/:inbox/:message_id', (req, res) => html(req, res).pipe(res))
router.on('/hi', (req, res) => res.end('{ "message": "hi back!" }'))
router.on('/404', (req, res) => {
res.statusCode = 404
res.end('{ "message": "the server is confused" }')
})
return router
}
-19
View File
@@ -1,19 +0,0 @@
const html = require('../../../html')
const emailList = require('../elements/email-list')
const pathname = require('../elements/pathname')
const email = require('../elements/email')
const nav = require('../elements/nav')
module.exports = function (state, prev, send) {
return html`
<main class="mw5 mw7-ns center cf">
${pathname(state, prev, send)}
${nav(state, prev, send)}
<section class="fl mt4 w-80 db">
${emailList(state, prev, send)}
${email(state, prev, send)}
</section>
</main>
`
}
-15
View File
@@ -1,15 +0,0 @@
const html = require('../../../html')
const empty = require('../elements/empty-mailbox')
const pathname = require('../elements/pathname')
const nav = require('../elements/nav')
module.exports = function (state, prev, send) {
return html`
<main class="mw5 mw7-ns center cf">
${pathname(state, prev, send)}
${nav(state, prev, send)}
${empty(state, prev, send)}
</main>
`
}
-22
View File
@@ -1,22 +0,0 @@
const html = require('../../../html')
const emailList = require('../elements/email-list')
const pathname = require('../elements/pathname')
const nav = require('../elements/nav')
module.exports = function (state, prev, send) {
return html`
<main class="mw5 mw7-ns center cf">
<button onclick=${goHome}>Home</button>
${pathname(state, prev, send)}
${nav(state, prev, send)}
<section class="fl mt4 w-80 db">
${emailList(state, prev, send)}
</section>
</main>
`
function goHome () {
send('location:set', { pathname: '/' })
}
}
-23
View File
@@ -1,23 +0,0 @@
const mount = require('../../mount')
const choo = require('../../')
const mainView = require('./view')
const app = choo()
app.model({
namespace: 'message',
state: {
server: 'rehydration has kicked in, server data was tossed',
client: 'hello client!'
}
})
app.router(['/', mainView])
if (module.parent) {
module.exports = app
} else {
const tree = app.start()
mount('#app-root', tree)
}
-15
View File
@@ -1,15 +0,0 @@
{
"name": "http",
"private": true,
"main": "client.js",
"scripts": {
"start": "NODE_ENV=development node server.js"
},
"dependencies": {
"bankai": "^2.0.2",
"bl": "^1.1.2",
"browserify": "^13.0.1",
"hyperstream": "^1.2.2",
"server-router": "^2.1.0"
}
}
-45
View File
@@ -1,45 +0,0 @@
const serverRouter = require('server-router')
const hyperstream = require('hyperstream')
const browserify = require('browserify')
const bankai = require('bankai')
const http = require('http')
const PORT = 8080
const client = require('./client')
// If an incoming request accepts "text/html", render the
// appropriate HTML. Else use the API server
const apiRouter = createRouter()
const server = http.createServer(function (req, res) {
if (/text\/html/.test(req.headers.accept)) handleHtml(req, res)
else apiRouter(req, res)
})
server.listen(PORT, () => process.stdout.write(`listening on port ${PORT}\n`))
// create a new router
// null -> fn
function createRouter () {
const apiRouter = serverRouter('/404')
apiRouter.on('/404', (req, res) => res.end('404 not found'))
apiRouter.on('/', (req, res) => res.end('nothing to be found here'))
const js = bankai.js(browserify, require.resolve('./client.js'))
apiRouter.on('/bundle.js', (req, res) => js(req, res).pipe(res))
return apiRouter
}
// render the client to string
// based on the requested url
// (obj, obj) -> null
const createIndex = bankai.html({ favicon: false, css: false })
function handleHtml (req, res) {
res.setHeader('Content-Type', 'text/html; charset=utf-8')
const state = { message: { server: 'hello server!' } }
const inner = client.toString(req.url, state)
const hs = hyperstream({ 'body': { _appendHtml: inner } })
createIndex(req, res).pipe(hs).pipe(res)
}
-25
View File
@@ -1,25 +0,0 @@
const assert = require('assert')
const html = require('../../html')
module.exports = function (state, prev, send) {
const serverMessage = state.message.server
const clientMessage = state.message.client
assert.equal(typeof serverMessage, 'string', 'server should be a string')
assert.equal(typeof clientMessage, 'string', 'client should be a string')
return html`
<section id="app-root">
<h1>server message: ${serverMessage}</h1>
<h1>client message: ${clientMessage}</h1>
<p>${`
The first message is passed in by the server on compile time,
the second message was set by the client.
The more static the data you pass in, the more cachable your site
becomes (and thus performant). Try and keep the amount of properties
you pass in on the server to a minimum for most applications - it'll
make life a lot easier in the long run, hah.
`}</p>
</section>
`
}
-51
View File
@@ -1,51 +0,0 @@
const choo = require('../../')
const html = require('../../html')
const app = choo()
app.model(createModel())
app.router(['/', mainView])
const tree = app.start()
document.body.appendChild(tree)
function mainView (state, prev, send) {
return html`
<div>${state.logger.msg}</div>
`
}
function createModel () {
const stream = new window.EventSource('/sse')
return {
namespace: 'logger',
state: {
msg: ''
},
subscriptions: [
function (send, done) {
stream.onerror = (e) => {
send('logger:error', { payload: JSON.stringify(e) }, done)
}
stream.onmessage = (e) => {
const msg = JSON.parse(e.data).message
send('logger:print', { payload: msg }, done)
}
}
],
reducers: {
'print': (state, data) => {
return ({ msg: state.msg + ' ' + data.payload })
}
},
effects: {
close: (state, data, send, done) => {
stream.close()
done()
},
error: (state, data, send, done) => {
console.error(`error: ${data.payload}`)
done()
}
}
}
}
-14
View File
@@ -1,14 +0,0 @@
{
"name": "http",
"private": true,
"main": "client.js",
"scripts": {
"start": "NODE_ENV=development node server.js"
},
"dependencies": {
"bankai": "^2.0.2",
"browserify": "^13.0.1",
"server-router": "^2.1.0",
"sse": "0.0.6"
}
}
-55
View File
@@ -1,55 +0,0 @@
const serverRouter = require('server-router')
const browserify = require('browserify')
const bankai = require('bankai')
const http = require('http')
const SSE = require('sse')
const clients = []
const PORT = 8080
const server = http.createServer(createRouter())
server.listen(PORT, () => {
process.stdout.write(`listening on port ${PORT}\n`)
var sse = new SSE(server)
// code adapted from: https://github.com/markbrown4/server-sent-events-demo
sse.on('connection', (stream) => {
clients.push(stream)
console.log('Opened connection 🎉')
var json = JSON.stringify({ message: 'Gotcha' })
stream.send(json)
console.log('Sent: ' + json)
stream.on('close', () => {
clients.splice(clients.indexOf(stream), 1)
console.log('Closed connection 😱')
})
})
})
// emit data every 1.5 seconds
setInterval(() => {
var json = JSON.stringify({ message: 'Hello hello!' })
clients.forEach((stream) => {
stream.send(json)
console.log('Sent: ' + json)
})
}, 1500)
function createRouter () {
const router = serverRouter('/404')
const html = bankai.html({ css: false })
router.on('/', (req, res) => html(req, res).pipe(res))
const js = bankai.js(browserify, require.resolve('./client.js'))
router.on('/bundle.js', (req, res) => js(req, res).pipe(res))
router.on('/404', (req, res) => {
res.statusCode = 404
res.end('{ "message": "the server is confused" }')
})
return router
}
-14
View File
@@ -1,14 +0,0 @@
const html = require('../../../html')
module.exports = function (params, state, send) {
const error = state.app.error[0]
const title = state.api.title
return html`
<section>
<h1>${title}</h1>
<h2>Latest error: ${error}</h2>
<button onclick=${(e) => send('api:good')}>OK!</button>
<button onclick=${(e) => send('api:bad')}>Naughty</button>
</section>
`
}
-13
View File
@@ -1,13 +0,0 @@
const choo = require('../../')
const stopwatch = require('./models/stopwatch')
const mainView = require('./views/main')
const app = choo()
app.model(stopwatch)
app.router(['/', mainView])
const tree = app.start()
document.body.appendChild(tree)
-33
View File
@@ -1,33 +0,0 @@
const raf = require('raf')
module.exports = {
state: {
elapsed: 0,
startTime: 0,
start: false,
laps: []
},
reducers: {
start: (state, data) => ({ start: true, startTime: Date.now() - state.elapsed }),
stop: (state, data) => ({ start: false }),
update: (state, data) => ({ elapsed: data }),
reset: (state, data) => ({ startTime: Date.now(), elapsed: 0, laps: [] }),
add: (state, data) => ({ laps: state.laps.concat(data) })
},
effects: {
now: (state, data, send, done) => {
if (state.start) {
let elapsed = data - state.startTime
send('update', elapsed, done)
}
}
},
subscriptions: [
(send, done) => {
raf(function loop () {
send('now', Date.now(), done)
raf(loop)
})
}
]
}
-19
View File
@@ -1,19 +0,0 @@
{
"name": "stopwatch",
"version": "1.0.0",
"description": "",
"main": "client.js",
"scripts": {
"start": "bankai start --entry=client.js -p 8080"
},
"keywords": [],
"author": "traducer <traducer21@gmail.com>",
"license": "ISC",
"dependencies": {
"bankai": "^3.2.0",
"raf": "^3.2.0"
},
"devDependencies": {
"es2020": "^1.1.7"
}
}
-15
View File
@@ -1,15 +0,0 @@
function formatMinutes (minutes) {
return `${minutes >= 10 ? minutes : (minutes < 10) ? '0' + minutes : '00'}`
}
function formatSeconds (seconds) {
return `${seconds < 10 ? '0' + seconds : seconds}`
}
module.exports = function format (elapsed) {
const minutes = formatMinutes(Math.floor((elapsed / 1000) / 60))
const seconds = formatSeconds(Math.floor((elapsed / 1000) % 60))
const ms = formatSeconds(Math.floor((elapsed % 1000) / 10))
return `${minutes}:${seconds}.${ms}`
}
-36
View File
@@ -1,36 +0,0 @@
const html = require('../../../html')
const format = require('../utilities/format')
function toggle (state, send) {
if (state.start) {
send('stop')
} else {
send('start')
}
}
module.exports = (state, prev, send) => {
const start = state.start
const elapsed = state.elapsed
return html`
<main class="app">
<h1>stopwatch</h1>
<p>${format(elapsed)}</p>
<button
onclick=${e => toggle(state, send)}
>${start ? 'stop' : 'start'}</button>
<button
style="display:${start ? 'none' : 'inline'};"
onclick=${e => send('reset')}
>reset</button>
<button
style="display:${start ? 'inline' : 'none'};"
onclick=${e => send('add', format(elapsed))}
>lap</button>
<ol>
${state.laps.map(lap => html`<li>${lap}</li>`)}
</ol>
</main>
`
}
-36
View File
@@ -1,36 +0,0 @@
const choo = require('../../')
const html = require('../../html')
const app = choo()
app.model({
namespace: 'input',
state: {
title: 'my demo app'
},
reducers: {
update: (state, data) => ({ title: data.payload })
},
effects: {
update: (state, data, send, done) => {
document.title = data.payload
done()
}
}
})
const mainView = (state, prev, send) => {
return html`
<main class="app">
<h1>${state.input.title}</h1>
<label>Set the title</label>
<input
type="text"
placeholder=${state.input.title}
oninput=${(e) => send('input:update', { payload: e.target.value })}>
</main>
`
}
app.router(['/', mainView])
const tree = app.start()
document.body.appendChild(tree)
-16
View File
@@ -1,16 +0,0 @@
{
"name": "title",
"version": "1.0.0",
"description": "",
"main": "client.js",
"scripts": {
"start": "bankai start --entry=client.js -p 8080 --open"
},
"keywords": [],
"author": "Yoshua Wuyts <i@yoshuawuyts.com>",
"license": "ISC",
"dependencies": {},
"devDependencies": {
"bankai": "^3.2.0"
}
}
-38
View File
@@ -1,38 +0,0 @@
<html>
<head>
<title>Vanilla example</title>
</head>
<body>
<script src="../../dist/choo.min.js"></script>
<script src="../../dist/html.min.js"></script>
<script>
const app = choo()
app.model({
namespace: 'counter',
state: { count: 0 },
reducers: {
increment: (state, data) => ({count: state.count + 1}),
decrement: (state, data) => ({count: state.count - 1})
}
})
const mainView = (state, prev, send) => {
return html`
<main class="app">
<h1>Counter example</h1>
<div>
Count: ${state.counter.count}
<button onclick=${(e) => send('counter:increment')}>+</button>
<button onclick=${(e) => send('counter:decrement')}>-</button>
</div>
</main>
`
}
app.router(['/', mainView])
const tree = app.start()
document.body.appendChild(tree)
</script>
</body>
</html>