diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..2cc7b00606 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +public/gen +node_modules +*.swp diff --git a/README.md b/README.md new file mode 100644 index 0000000000..e0e7a9156f --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# first-project diff --git a/lib/app/index.js b/lib/app/index.js new file mode 100644 index 0000000000..33e25b9301 --- /dev/null +++ b/lib/app/index.js @@ -0,0 +1,83 @@ +var app = require('derby').createApp(module) + , get = app.get + , view = app.view + , ready = app.ready + , start + +// ROUTES // + +start = +new Date() + +// Derby routes can be rendered on the client and the server +get('/:roomName?', function(page, model, params) { + var roomName = params.roomName || 'home' + + // Subscribes the model to any updates on this room's object. Calls back + // with a scoped model equivalent to: + // room = model.at('rooms.' + roomName) + model.subscribe('rooms.' + roomName, function(err, room) { + model.ref('_room', room) + + // setNull will set a value if the object is currently null or undefined + room.setNull('welcome', 'Welcome to ' + roomName + '!') + + room.incr('visits') + + // This value is set for when the page initially renders + model.set('_timer', '0.0') + // Reset the counter when visiting a new route client-side + start = +new Date() + + // Render will use the model data as well as an optional context object + page.render({ + roomName: roomName + , randomUrl: parseInt(Math.random() * 1e9).toString(36) + }) + }) +}) + + +// CONTROLLER FUNCTIONS // + +ready(function(model) { + var timer + + // Expose the model as a global variable in the browser. This is fun in + // development, but it should be removed when writing an app + window.model = model + + // Exported functions are exposed as a global in the browser with the same + // name as the module that includes Derby. They can also be bound to DOM + // events using the "x-bind" attribute in a template. + exports.stop = function() { + + // Any path name that starts with an underscore is private to the current + // client. Nothing set under a private path is synced back to the server. + model.set('_stopped', true) + clearInterval(timer) + } + + exports.start = function() { + model.set('_stopped', false) + timer = setInterval(function() { + model.set('_timer', (((+new Date()) - start) / 1000).toFixed(1)) + }, 100) + } + exports.start() + + + model.set('_showReconnect', true) + exports.connect = function() { + // Hide the reconnect link for a second after clicking it + model.set('_showReconnect', false) + setTimeout(function() { + model.set('_showReconnect', true) + }, 1000) + model.socket.socket.connect() + } + + exports.reload = function() { + window.location.reload() + } + +}) diff --git a/lib/server/index.js b/lib/server/index.js new file mode 100644 index 0000000000..5b04574623 --- /dev/null +++ b/lib/server/index.js @@ -0,0 +1,53 @@ +var http = require('http') + , path = require('path') + , express = require('express') + , gzippo = require('gzippo') + , derby = require('derby') + , app = require('../app') + , serverError = require('./serverError') + + +// SERVER CONFIGURATION // + +var ONE_YEAR = 1000 * 60 * 60 * 24 * 365 + , root = path.dirname(path.dirname(__dirname)) + , publicPath = path.join(root, 'public') + , expressApp, server, store + +;(expressApp = express()) + .use(express.favicon()) + // Gzip static files and serve from memory + .use(gzippo.staticGzip(publicPath, {maxAge: ONE_YEAR})) + + // Gzip dynamically rendered content + .use(express.compress()) + + // Uncomment to add form data parsing support + // .use(express.bodyParser()) + // .use(express.methodOverride()) + + // Derby session middleware creates req.model and subscribes to _session + // .use(express.cookieParser('secret_sauce')) + // .use(express.session({ + // cookie: {maxAge: ONE_YEAR} + // }) + // .use(app.session()) + + // The router method creates an express middleware from the app's routes + .use(app.router()) + .use(expressApp.router) + .use(serverError(root)) + +module.exports = server = http.createServer(expressApp) + + +// SERVER ONLY ROUTES // + +expressApp.all('*', function(req) { + throw '404: ' + req.url +}) + + +// STORE SETUP // + +store = app.createStore({listen: server}) diff --git a/lib/server/serverError.js b/lib/server/serverError.js new file mode 100644 index 0000000000..3d24be86ec --- /dev/null +++ b/lib/server/serverError.js @@ -0,0 +1,21 @@ +var derby = require('derby') + , isProduction = derby.util.isProduction + +module.exports = function(root) { + var staticPages = derby.createStatic(root) + + return function(err, req, res, next) { + if (err == null) return next() + + console.log(err.stack ? err.stack : err) + + // Customize error handling here + var message = err.message || err.toString() + , status = parseInt(message) + if (status === 404) { + staticPages.render('404', res, {url: req.url}, 404) + } else { + res.send( ((status >= 400) && (status < 600)) ? status : 500) + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000000..2eebe98495 --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "first-project", + "description": "", + "version": "0.0.0", + "main": "./server.js", + "dependencies": { + "derby": "*", + "express": "3.x", + "gzippo": ">=0.1.4" + }, + "private": true +} \ No newline at end of file diff --git a/server.js b/server.js new file mode 100644 index 0000000000..d7ccc61898 --- /dev/null +++ b/server.js @@ -0,0 +1 @@ +require('derby').run(__dirname + '/lib/server') diff --git a/styles/404.styl b/styles/404.styl new file mode 100644 index 0000000000..079e71b08e --- /dev/null +++ b/styles/404.styl @@ -0,0 +1 @@ +@import "./base"; diff --git a/styles/app/index.styl b/styles/app/index.styl new file mode 100644 index 0000000000..6c064f7150 --- /dev/null +++ b/styles/app/index.styl @@ -0,0 +1,20 @@ +@import "../base"; + +#alert { + position: absolute; + text-align: center; + top: 0; + left: 0; + width: 100%; + height: 0; + z-index: 99; +} +#alert > p { + background: #fff1a8; + border: 1px solid #999; + border-top: 0; + border-radius: 0 0 3px 3px; + display: inline-block; + line-height: 21px; + padding: 0 12px; +} diff --git a/styles/base.styl b/styles/base.styl new file mode 100644 index 0000000000..f5e0233db5 --- /dev/null +++ b/styles/base.styl @@ -0,0 +1,13 @@ +@import "./reset"; +@import "nib/vendor"; + +body { + padding: 2em; +} +h1 { + font-size: 2em; + margin-bottom: .5em; +} +p { + line-height: 2em; +} diff --git a/styles/reset.styl b/styles/reset.styl new file mode 100644 index 0000000000..8f42218c28 --- /dev/null +++ b/styles/reset.styl @@ -0,0 +1,21 @@ +body,h1,h2,h3,h4,th { + font: 13px/normal Arial,sans-serif; +} +body { + background: #fff; + color: #000; +} +body,fieldset,form,h1,h2,h3,h4,li,ol,p,td,th,ul { + margin: 0; + padding: 0; +} +ul { + margin: 0 normal; +} +table { + border-collapse: collapse; + border-spacing: 0; +} +fieldset,img { + border: 0; +} diff --git a/views/404.html b/views/404.html new file mode 100644 index 0000000000..af0dc135a4 --- /dev/null +++ b/views/404.html @@ -0,0 +1,16 @@ + + + + Not found + + +

404

+

Sorry, we can't find anything at {{url}}. +

Try heading back to the home page. diff --git a/views/app/index.html b/views/app/index.html new file mode 100644 index 0000000000..ebcaeaf488 --- /dev/null +++ b/views/app/index.html @@ -0,0 +1,57 @@ + + + + {{roomName}} - {_room.visits} visits + + + + + + +

{_room.welcome}

+

+ +

This page has been visted {_room.visits} times.

+ +

Let's go somewhere random.

+ + + {#if _stopped} + Start timer + {else} + You have been here for {_timer} seconds. Stop + {/} + + + +
+ {#unless connected} +

+ {#if canConnect} + + Offline + {#if _showReconnect}– Reconnect{/} + {else} + Unable to reconnect – Reload + {/} +

+ {/} +