├── .gitignore ├── app ├── components │ ├── friends.js │ ├── new-user.js │ └── user.js ├── dist │ └── .keep ├── elements │ └── status-box.js ├── index.html ├── index.js ├── models │ ├── friends.js │ └── user.js └── style.css ├── index.js ├── package.json ├── readme.md └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .aya.db 3 | app/dist/bundle.css -------------------------------------------------------------------------------- /app/components/friends.js: -------------------------------------------------------------------------------- 1 | const html = require('choo/html') 2 | const statusBox = require('../elements/status-box') 3 | 4 | module.exports = (state, prev, send) => { 5 | return html` 6 |
7 |
9 | 11 |
12 |
13 | ${state.group.loading.length ? html`

Loading Friends

` : ''} 14 | ${Object.keys(state.group.friends).map(function (key) { 15 | const friend = state.group.friends[key] 16 | return statusBox(friend) 17 | })} 18 |
19 |
20 | ` 21 | 22 | function addFriend (e) { 23 | const input = e.target.children[0] 24 | send('group:addFriend', { key: input.value }) 25 | input.value = '' 26 | e.preventDefault() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/components/new-user.js: -------------------------------------------------------------------------------- 1 | const html = require('choo/html') 2 | 3 | module.exports = (state, prev, send) => { 4 | return html` 5 |
6 |

7 | Create Account 8 |

9 |
10 | 11 |
12 |
13 | ` 14 | function onSubmit (e) { 15 | const input = e.target.children[0] 16 | send('user:createNew', { name: input.value }) 17 | input.value = '' 18 | e.preventDefault() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/components/user.js: -------------------------------------------------------------------------------- 1 | const html = require('choo/html') 2 | const newUser = require('./new-user') 3 | const statusBox = require('../elements/status-box') 4 | 5 | module.exports = (state, prev, send) => { 6 | console.log('component/user', state) 7 | if (state.user.loading) return '' // TODO: loading screen 8 | return html` 9 |
10 | ${state.user.newUser ? newUser(state, prev, send) : user()} 11 |
12 | ` 13 | 14 | function user () { 15 | return html` 16 |
17 | ${statusBox(state.user)} 18 |
21 |
22 | Status 23 | ${['online', 'busy', 'offline'].map((status) => { 24 | return html` 25 | 32 | ` 33 | })} 34 |
35 | 39 |
40 |
Share Your Status:
${state.user.key}
41 |
42 |
43 | ` 44 | 45 | function onSubmit (e) { 46 | const input = e.target.children[1] 47 | send('user:updateStatus', { message: input.value, status: state.user.status }) 48 | e.preventDefault() 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /app/dist/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joehand/are-you-around/b7e82c22114dd98d1b143680046123028d27d6d4/app/dist/.keep -------------------------------------------------------------------------------- /app/elements/status-box.js: -------------------------------------------------------------------------------- 1 | const html = require('choo/html') 2 | 3 | module.exports = (person) => { 4 | const statusColor = (status) => { 5 | if (status === 'online') return 'bg-green' 6 | if (status === 'busy') return 'bg-orange' 7 | if (status === 'offline') return 'bg-white' 8 | } 9 | 10 | return html` 11 |
12 |
${person.name}
13 |
${person.message}
14 |
15 | ` 16 | } -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | const choo = require('choo') 2 | const html = require('choo/html') 3 | const css = require('sheetify') 4 | const PeerStatus = require('peer-status-feed') 5 | 6 | const friends = require('./components/friends') 7 | const user = require('./components/user') 8 | 9 | const userFeed = PeerStatus() 10 | 11 | css('tachyons') 12 | css('./style', {global: true}) 13 | 14 | const app = choo() 15 | app.model({ 16 | effects: { 17 | initDb: (data, state, send, done) => { 18 | userFeed.open(function () { 19 | send('user:receiveKey', userFeed.key, () => { 20 | if (!userFeed.status) return send('user:newUser', null, done) 21 | 22 | send('user:receiveName', userFeed.status.name, () => { 23 | send('user:receiveStatus', userFeed.status, done) 24 | }) 25 | }) 26 | }) 27 | }, 28 | appendStatus: (data, state, send, done) => { 29 | data.timestamp = new Date() 30 | if (!data.name) data.name = state.user.name 31 | userFeed.appendStatus(data, done) 32 | }, 33 | addFriendFeed: (data, state, send, done) => { 34 | userFeed.addPeer(data, done) 35 | } 36 | }, 37 | subscriptions: [ 38 | (send, done) => { 39 | userFeed.on('peer-data', function (data) { 40 | send('group:friendData', data, done) 41 | }) 42 | } 43 | ] 44 | }) 45 | app.model(require('./models/user')) 46 | app.model(require('./models/friends')) 47 | 48 | const mainView = (state, prev, send) => { 49 | return html` 50 |
send('initDb')} 53 | class=''> 54 |
55 |

56 | Are You Around? 57 |

58 |

Welcome ${state.user.name}!

59 |
60 |
61 |
62 |
63 | ${user(state, prev, send)} 64 |
65 |
66 | ${state.user.newUser ? '' : friends(state, prev, send)} 67 |
68 |
69 |
70 | ` 71 | } 72 | 73 | app.router((route) => [ 74 | route('/', mainView) 75 | ]) 76 | 77 | const tree = app.start() 78 | document.body.appendChild(tree) 79 | -------------------------------------------------------------------------------- /app/models/friends.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | namespace: 'group', 3 | state: { 4 | loading: [], 5 | friends : { 6 | // hypercorekeyasdlkfj: { 7 | // name: 'fred', 8 | // message: 'I am around!', 9 | // status: 'online' 10 | // } 11 | } 12 | }, 13 | reducers: { 14 | receiveFriend: (data, state) => { 15 | const key = data.key 16 | const status = data.data 17 | const friends = state.friends 18 | const loading = state.loading 19 | loading.splice(loading.indexOf(key), 1) 20 | friends[key] = { name: status.name, message: status.message, status: status.status } 21 | return { friends: friends, loading: loading} 22 | }, 23 | loadingFriend: (data, state) => { 24 | return { loading: state.loading.concat(data) } 25 | } 26 | }, 27 | effects: { 28 | addFriend: (data, state, send, done) => { 29 | send('group:loadingFriend', data.key, () => { 30 | send('addFriendFeed', data.key, done) 31 | }) 32 | }, 33 | friendData: (data, state, send, done) => { 34 | console.log('got friend data', data) 35 | send('group:receiveFriend', data, done) 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /app/models/user.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | namespace: 'user', 3 | state: { 4 | message: 'I am around!', 5 | status: 'offline', // 'busy', 'offline' 6 | name: null, 7 | key: null, 8 | newUser: true, 9 | loading: true 10 | }, 11 | reducers: { 12 | newUser: (data, state) => { 13 | return { 14 | loading: false, 15 | newUser: true 16 | } 17 | }, 18 | receiveKey: (data, state) => { 19 | return {key: data} 20 | }, 21 | receiveName: (data, state) => { 22 | return { loading: false, newUser: false, name: data } 23 | }, 24 | receiveStatus: (data, state) => { 25 | return { loading: false, status: data.status, message: data.message } 26 | } 27 | }, 28 | effects: { 29 | createNew: (data, state, send, done) => { 30 | send('user:receiveName', data.name, () => { 31 | // put new users online 32 | send('user:updateStatus', { 33 | name: data.name, 34 | status: 'online', 35 | message: state.message 36 | }, done) 37 | }) 38 | }, 39 | updateStatus: (data, state, send, done) => { 40 | send('appendStatus', data, () => { 41 | send('user:receiveStatus', data, done) 42 | }) 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /app/style.css: -------------------------------------------------------------------------------- 1 | input { 2 | background-color: transparent; 3 | border: none; 4 | border-bottom: 1px solid #9e9e9e; 5 | border-radius: 0; 6 | outline: none; 7 | padding: 0; 8 | box-shadow: none; 9 | transition: all 0.3s; 10 | text-align: center; 11 | } 12 | 13 | input:focus { 14 | border-bottom: 1px solid blue; 15 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const {app, BrowserWindow, ipcMain, Menu, shell} = require('electron') 2 | 3 | const env = process.env.NODE_ENV 4 | 5 | let win 6 | 7 | ipcMain.on('ready', () => { 8 | 9 | }) 10 | 11 | function createWindow () { 12 | win = new BrowserWindow({ width: 400 }) 13 | win.loadURL(`file://${__dirname}/app/index.html`) 14 | if (env === 'development') { 15 | win.webContents.openDevTools({ 16 | mode: 'detach' 17 | }) 18 | } 19 | win.on('closed', () => { win = null }) 20 | } 21 | 22 | app.on('ready', createWindow) 23 | app.on('window-all-closed', () => { 24 | if (process.platform !== 'darwin') app.quit() 25 | }) 26 | app.on('activate', () => { 27 | if (!win) createWindow() 28 | }) 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "are-you-around", 3 | "version": "1.1.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "npm run build && electron .", 8 | "start:dev": "npm run watch & NODE_ENV=development electron .", 9 | "test": "standard", 10 | "build": "browserify app/index.js -p [ css-extract -o app/dist/bundle.css ] -o /dev/null", 11 | "watch": "watchify app/index.js -p [ css-extract -o app/dist/bundle.css ] -o /dev/null", 12 | "rebuild": "npm rebuild --runtime=electron --target=1.3.4 --disturl=https://atom.io/download/atom-shell --build-from-source" 13 | }, 14 | "browserify": { 15 | "transform": [ 16 | "sheetify/transform" 17 | ] 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/joehand/are-you-around.git" 22 | }, 23 | "author": "Joe Hand (https://joeahand.com/)", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/joehand/are-you-around/issues" 27 | }, 28 | "homepage": "https://github.com/joehand/are-you-around#readme", 29 | "dependencies": { 30 | "choo": "^3.2.2", 31 | "electron-prebuilt": "^1.3.4", 32 | "peer-status-feed": "^1.1.1", 33 | "sheetify": "^5.1.0", 34 | "tachyons": "^4.1.2" 35 | }, 36 | "devDependencies": { 37 | "css-extract": "^1.1.2", 38 | "watchify": "^3.7.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Are you around? 2 | 3 | Let your friends know if you are around! Publish your status so friends can subscribe. All status updates are sent directly to your friends via peer to peer networks. 4 | 5 | The app post updates and subscribes to friends via a [peer-status-feed](https://github.com/joehand/peer-status-feed). So you can join the party outside of just the app! 6 | 7 | Want a to see if your app is working but your friends aren't around? Meet [Peer Robot](https://github.com/joehand/peer-robot)! The friend that is always around. Install with `npm install -g peer-robot` and run `peer-robot` for an insta-friend. It will give you a key you can add as a friend. 8 | 9 | Development Status: **Very unstable.** 10 | 11 |

12 | 13 |

14 | 15 | ## Run 16 | 17 | * Clone from Github 18 | * `npm install` 19 | * `npm start` 20 | * Profit! 21 | 22 | If you receive a version mismatch error in the console, try `npm run rebuild`. 23 | 24 | ## Development 25 | 26 | Built with these awesome tools: 27 | 28 | ### [Choo](https://github.com/yoshuawuyts/choo) | [Dat](http://dat-data.com) | [Electron](https://github.com/electron/electron) | [Tacyons](http://tachyons.io) 29 | 30 | Run `npm run start:dev` to run in development mode and watch file changes. 31 | 32 | Status updates are saved in a local level database, `~/.peer-status.db`. If you delete the database your account name & key will be reset. 33 | 34 | ## TODO 35 | 36 | * ~~Make feeds download only~~ 37 | * ~~Move database to user root~~ 38 | * ~~make a robot to beep boop at friends~~ 39 | * Store last status/name of friends in DB. Use <- to show feed offline = friend offline 40 | * Change status to offline on exit 41 | * Nicer UI (thinking two tab panes, one for friends and one user) 42 | * Add dates to status and show last update time 43 | * Make status updates "tweets" and change "subscribe" to "follow". 44 | 45 | ## License 46 | 47 | MIT 48 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joehand/are-you-around/b7e82c22114dd98d1b143680046123028d27d6d4/screenshot.png --------------------------------------------------------------------------------