Commit 3752a1567791fcb0bffec326af555ae3e16045f5
Srs bizniz refactor (#1)
* re-write everything, just because, not yet working * it works again! * update based on feedback from @mixmix, thanks!Mikey authored on 12/16/2016, 2:19:30 AM
GitHub committed on 12/16/2016, 2:19:30 AM
Parent: 4c04551a3e25f880780438f3d6ba6a54f100d610
Files changed
index.js | changed |
package.json | changed |
graph.js | deleted |
api.js | added |
app.js | added |
output.js | deleted |
assets/api.js | added |
browser.js | added |
renderer.js | deleted |
config.js | added |
graph/actions.js | added |
graph/api.js | added |
graph/app.js | added |
graph/helpers/build-links.js | added |
graph/helpers/build-nodes.js | added |
graph/view.js | added |
profiles/actions.js | added |
profiles/api.js | added |
profiles/app.js | added |
profiles/view.js | added |
server.js | added |
index.js | ||
---|---|---|
@@ -1,34 +1,57 @@ | ||
1 | -const fromJson = require('ngraph.fromjson') | |
2 | -const Renderer = require('./renderer') | |
3 | -const Graph = require('./graph') | |
1 … | +const http = require('http') | |
2 … | +const defaultsDeep = require('lodash/defaultsDeep') | |
4 | 3 … | |
5 | -const config = { | |
6 | - physics: { | |
7 | - springLength: 80, | |
8 | - springCoeff: 0.0001, | |
9 | - gravity: -1.4, | |
10 | - theta: 0.4, | |
11 | - dragCoeff: 0.04 | |
12 | - }, | |
13 | - link: (link) => { | |
14 | - // if (link.data.hidden) return | |
15 | - // makes linkUI element not exist ? => display.getLink doesn't work | |
4 … | +const defaultVizConfig = require('./config') | |
5 … | +const Api = require('./api') | |
16 | 6 … | |
17 | - return { | |
18 | - fromColor: 0x000066, | |
19 | - toColor: 0x000066 | |
20 | - } | |
7 … | +var _server | |
8 … | + | |
9 … | +module.exports = { | |
10 … | + name: 'ssb-graphviz', | |
11 … | + version: require('./package.json').version, | |
12 … | + manifest: {}, | |
13 … | + init: function (ssb, config, reconnect) { | |
14 … | + // close existing server. when scuttlebot plugins get a deinit method, we | |
15 … | + // will close it in that instead it | |
16 … | + if (_server) _server.close() | |
17 … | + | |
18 … | + _server = Server(ssb, config, reconnect) | |
19 … | + _server.listen() | |
20 … | + | |
21 … | + return {} | |
21 | 22 … | } |
22 | 23 … | } |
23 | 24 … | |
24 | -module.exports = function (sbot, cb) { | |
25 | - Graph(sbot, (err, data) => { | |
26 | - if (err) return cb(err) | |
27 | - const str = JSON.stringify(data) | |
28 | - var graph = fromJson(str) | |
29 | - var display = Renderer(graph, config, sbot) | |
25 … | +function Server (ssb, config, reconnect) { | |
26 … | + const vizConfig = defaultsDeep(config['ssb-graphviz'], defaultVizConfig) | |
27 … | + const { host, port } = parseAddr(config.listenAddr, { | |
28 … | + host: vizConfig.host, | |
29 … | + port: vizConfig.port | |
30 … | + }) | |
30 | 31 … | |
31 | - cb(null, display) | |
32 | - }) | |
32 … | + var server = http.createServer(Api(ssb, config)) | |
33 … | + | |
34 … | + return { | |
35 … | + listen, | |
36 … | + close | |
37 … | + } | |
38 … | + | |
39 … | + function listen () { | |
40 … | + server.listen(port, host, function () { | |
41 … | + var hostName = ~host.indexOf(':') ? `[${host}]` : host | |
42 … | + console.log(`Listening on http://${hostName}:${port}/`) | |
43 … | + }) | |
44 … | + } | |
45 … | + | |
46 … | + function close () { | |
47 … | + server.close() | |
48 … | + } | |
33 | 49 … | } |
34 | 50 … | |
51 … | +function parseAddr (str, def) { | |
52 … | + if (!str) return def | |
53 … | + var i = str.lastIndexOf(':') | |
54 … | + if (~i) return { host: str.substr(0, i), port: str.substr(i + 1) } | |
55 … | + if (isNaN(str)) return { host: str, port: def.port } | |
56 … | + return { host: def.host, port: str } | |
57 … | +} |
package.json | ||
---|---|---|
@@ -4,9 +4,10 @@ | ||
4 | 4 … | "description": "visualize the ssb network graph", |
5 | 5 … | "main": "index.js", |
6 | 6 … | "scripts": { |
7 | 7 … | "test": "standard", |
8 | - "start": "electro output.js" | |
8 … | + "start": "node server", | |
9 … | + "start:dev": "node-dev server" | |
9 | 10 … | }, |
10 | 11 … | "repository": { |
11 | 12 … | "type": "git", |
12 | 13 … | "url": " ssb://%hNm67sfnZFtWkD/+1qxH3UfzhXykfpKtOL1C/XbLANA=.sha256" |
@@ -18,23 +19,32 @@ | ||
18 | 19 … | "url": "https://git.scuttlebot.io/%25hNm67sfnZFtWkD%2F%2B1qxH3UfzhXykfpKtOL1C%2FXbLANA%3D.sha256/issues" |
19 | 20 … | }, |
20 | 21 … | "homepage": "https://git.scuttlebot.io/%25hNm67sfnZFtWkD%2F%2B1qxH3UfzhXykfpKtOL1C%2FXbLANA%3D.sha256#readme", |
21 | 22 … | "devDependencies": { |
23 … | + "inu-log": "^1.0.2", | |
24 … | + "node-dev": "^3.1.3", | |
22 | 25 … | "standard": "^8.6.0", |
23 | 26 … | "tape": "^4.5.1" |
24 | 27 … | }, |
25 | 28 … | "dependencies": { |
26 | - "electro": "^2.0.2", | |
27 | - "electron": "^1.4.4", | |
28 | - "hyperscript": "^2.0.2", | |
29 | - "insert-css": "^1.0.0", | |
29 … | + "bankai": "^5.1.3", | |
30 … | + "cache-element": "^2.0.0", | |
31 … | + "http-routes": "^1.2.3", | |
30 | 32 … | "inu": "^3.1.3", |
33 … | + "inux": "^2.1.0", | |
34 … | + "lodash": "^4.17.2", | |
31 | 35 … | "ngraph.fromjson": "^0.1.8", |
32 | - "ngraph.pixel": "^2.2.0", | |
36 … | + "ngraph.pixel": "github:ahdinosaur/ngraph.pixel#sheetify-compatible-styles", | |
37 … | + "pull-cont": "0.0.0", | |
33 | 38 … | "pull-stream": "^3.4.5", |
34 | 39 … | "run-waterfall": "^1.1.3", |
40 … | + "send-data": "^8.0.0", | |
35 | 41 … | "ssb-avatar": "^0.2.0", |
36 | 42 … | "ssb-client": "^4.3.0", |
43 … | + "ssb-config": "^2.2.0", | |
44 … | + "ssb-keys": "^7.0.3", | |
45 … | + "ssb-reconnect": "^0.1.1", | |
46 … | + "stack": "^0.1.0", | |
37 | 47 … | "stream-to-pull-stream": "^1.7.2", |
38 | - "yo-yo": "^1.3.1" | |
48 … | + "xhr": "^2.3.1" | |
39 | 49 … | } |
40 | 50 … | } |
graph.js | ||
---|---|---|
@@ -1,67 +1,0 @@ | ||
1 | -const waterfall = require('run-waterfall') | |
2 | - | |
3 | -const { keys } = Object | |
4 | - | |
5 | -module.exports = Graph | |
6 | - | |
7 | -function Graph (sbot, cb) { | |
8 | - waterfall([ | |
9 | - (cb) => sbot.friends.all(cb), | |
10 | - (friends, cb) => { | |
11 | - const filteredFriends = friends // activeFriends(friends) | |
12 | - | |
13 | - cb(null, { | |
14 | - nodes: buildNodes(filteredFriends), | |
15 | - links: buildLinks(filteredFriends) | |
16 | - }) | |
17 | - } | |
18 | - ], cb) | |
19 | -} | |
20 | - | |
21 | -function buildNodes (friends) { | |
22 | - return keys(friends).map(id => { | |
23 | - return { | |
24 | - id, | |
25 | - data: { | |
26 | - friends: keys(friends[id]) | |
27 | - } | |
28 | - } | |
29 | - }) | |
30 | -} | |
31 | - | |
32 | -function buildLinks (friends) { | |
33 | - return keys(friends).reduce((sofar, friend) => { | |
34 | - const friendOfFriends = keys(friends[friend]) | |
35 | - .filter(id => friends[friend][id] === true) | |
36 | - | |
37 | - const edges = friendOfFriends.map(friendOfFriend => { | |
38 | - return { | |
39 | - fromId: friend, | |
40 | - toId: friendOfFriend, | |
41 | - data: { | |
42 | - hidden: true | |
43 | - } | |
44 | - } | |
45 | - }) | |
46 | - | |
47 | - return [ | |
48 | - ...sofar, | |
49 | - ...edges | |
50 | - ] | |
51 | - }, []) | |
52 | -} | |
53 | - | |
54 | -/* | |
55 | -function activeFriends (friends) { | |
56 | - return keys(friends) | |
57 | - .reduce( | |
58 | - (sofar, current) => { | |
59 | - if (keys(friends[current]).length < 4) return sofar | |
60 | - | |
61 | - sofar[current] = friends[current] | |
62 | - return sofar | |
63 | - }, | |
64 | - {} | |
65 | - ) | |
66 | -} | |
67 | -*/ |
api.js | ||
---|---|---|
@@ -1,0 +1,11 @@ | ||
1 … | +const Stack = require('stack') | |
2 … | + | |
3 … | +module.exports = VizApi | |
4 … | + | |
5 … | +function VizApi (ssb, config) { | |
6 … | + return Stack(...[ | |
7 … | + require('./graph/api'), | |
8 … | + require('./profiles/api'), | |
9 … | + require('./assets/api') | |
10 … | + ].map(m => m(ssb, config))) | |
11 … | +} |
app.js | ||
---|---|---|
@@ -1,0 +1,10 @@ | ||
1 … | +const { App } = require('inux') | |
2 … | + | |
3 … | +module.exports = VizApp | |
4 … | + | |
5 … | +function VizApp (config) { | |
6 … | + return App([ | |
7 … | + require('./graph/app')(config), | |
8 … | + require('./profiles/app')(config) | |
9 … | + ]) | |
10 … | +} |
output.js | ||
---|---|---|
@@ -1,31 +1,0 @@ | ||
1 | -const insertCss = require('insert-css') | |
2 | -const Sbot = require('ssb-client') | |
3 | -const waterfall = require('run-waterfall') | |
4 | - | |
5 | -const Viz = require('./') | |
6 | - | |
7 | -insertCss(` | |
8 | - html, body { | |
9 | - width: 100%; | |
10 | - height: 100%; | |
11 | - position: absolute; | |
12 | - overflow: hidden; | |
13 | - padding: 0; | |
14 | - margin: 0; | |
15 | - } | |
16 | - | |
17 | - .avatar { | |
18 | - position: fixed; | |
19 | - left: 10px; | |
20 | - bottom: 10px; | |
21 | - color: #fff; | |
22 | - } | |
23 | - | |
24 | - .avatar .image { | |
25 | - max-height: 160px; | |
26 | - } | |
27 | -`) | |
28 | - | |
29 | -waterfall([Sbot, Viz], (err) => { | |
30 | - if (err) throw err | |
31 | -}) |
assets/api.js | ||
---|---|---|
@@ -1,0 +1,37 @@ | ||
1 … | +const { join } = require('path') | |
2 … | +const pull = require('pull-stream') | |
3 … | +const toPull = require('stream-to-pull-stream') | |
4 … | +const Assets = require('bankai') | |
5 … | +const Routes = require('http-routes') | |
6 … | + | |
7 … | +module.exports = AssetsApi | |
8 … | + | |
9 … | +function AssetsApi (ssb, config) { | |
10 … | + const clientPath = join(__dirname, '../browser.js') | |
11 … | + const assets = Assets(clientPath) | |
12 … | + | |
13 … | + return Routes([ | |
14 … | + ['/', (req, res, next) => { | |
15 … | + assets.html(req, res).pipe(res) | |
16 … | + }], | |
17 … | + ['/bundle.js', (req, res, next) => { | |
18 … | + assets.js(req, res).pipe(res) | |
19 … | + }], | |
20 … | + ['/bundle.css', (req, res, next) => { | |
21 … | + assets.css(req, res).pipe(res) | |
22 … | + }], | |
23 … | + ['/blobs/:blobId(.*)', { | |
24 … | + get: (req, res, next) => { | |
25 … | + sendBlob(req.params.blobId, req, res) | |
26 … | + } | |
27 … | + }] | |
28 … | + ]) | |
29 … | + | |
30 … | + function sendBlob (id, req, res) { | |
31 … | + pull(getBlob(id), toPull(res)) | |
32 … | + } | |
33 … | + | |
34 … | + function getBlob (id) { | |
35 … | + return ssb.blobs.get(id) | |
36 … | + } | |
37 … | +} |
browser.js | ||
---|---|---|
@@ -1,0 +1,33 @@ | ||
1 … | +const css = require('sheetify') | |
2 … | +const { start, html, pull } = require('inu') | |
3 … | +const log = require('inu-log') | |
4 … | + | |
5 … | +const config = require('./config') | |
6 … | +const App = require('./app') | |
7 … | + | |
8 … | +css` | |
9 … | + html, body, .main, .graph { | |
10 … | + width: 100%; | |
11 … | + height: 100%; | |
12 … | + position: absolute; | |
13 … | + overflow: hidden; | |
14 … | + padding: 0; | |
15 … | + margin: 0; | |
16 … | + } | |
17 … | +` | |
18 … | + | |
19 … | +var app = App(config) | |
20 … | +if (process.env.NODE_ENV !== 'production') { | |
21 … | + app = log(app) | |
22 … | +} | |
23 … | +const { views } = start(app) | |
24 … | +const main = document.createElement('div') | |
25 … | +main.className = 'main' | |
26 … | +document.body.appendChild(main) | |
27 … | + | |
28 … | +pull( | |
29 … | + views(), | |
30 … | + pull.drain(view => { | |
31 … | + html.update(main, view) | |
32 … | + }) | |
33 … | +) |
renderer.js | ||
---|---|---|
@@ -1,72 +1,0 @@ | ||
1 | -const Renderer = require('ngraph.pixel') | |
2 | -const avatar = require('ssb-avatar') | |
3 | -const html = require('yo-yo') | |
4 | - | |
5 | -module.exports = createRenderer | |
6 | - | |
7 | -function createRenderer (graph, config, sbot) { | |
8 | - var avatarEl = html`<div class="avatar" />` | |
9 | - document.body.appendChild(avatarEl) | |
10 | - | |
11 | - var display = Renderer(graph, config) | |
12 | - | |
13 | - display.on('nodehover', handleNodeHover) | |
14 | - | |
15 | - return display | |
16 | - | |
17 | - function handleNodeHover (node) { | |
18 | - if (node === undefined) return | |
19 | - | |
20 | - avatar(sbot, node.id, node.id, (err, { name, image }) => { | |
21 | - // handle this error! | |
22 | - if (err) throw err | |
23 | - | |
24 | - const imgSrc = image ? `http://localhost:7777/${image}` : '' | |
25 | - | |
26 | - const newAvatarEl = html` | |
27 | - <div class="avatar"> | |
28 | - <img class="image" src=${imgSrc} /> | |
29 | - <div class="name">${name}</div> | |
30 | - </div> | |
31 | - ` | |
32 | - | |
33 | - html.update(avatarEl, newAvatarEl) | |
34 | - }) | |
35 | - | |
36 | - display.forEachLink(linkUI => { | |
37 | - const { from, to } = linkUI | |
38 | - const friends = node.data.friends | |
39 | - | |
40 | - const isFromTarget = node.id === from.id | |
41 | - const isToTarget = node.id === to.id | |
42 | - | |
43 | - const isFromFriend = friends.indexOf(from.id) > -1 | |
44 | - const isToFriend = friends.indexOf(to.id) > -1 | |
45 | - const involvesFoaF = isFromFriend || isToTarget | |
46 | - | |
47 | - var fromColor = 0x000066 | |
48 | - var toColor = 0x000066 | |
49 | - | |
50 | - const close = 0xffffff | |
51 | - const mid = 0xa94caf | |
52 | - const far = 0x000066 | |
53 | - | |
54 | - if (isFromTarget) { | |
55 | - fromColor = close | |
56 | - toColor = mid | |
57 | - } else if (isToTarget) { | |
58 | - fromColor = mid | |
59 | - toColor = close | |
60 | - } else if (involvesFoaF && isFromFriend) { | |
61 | - fromColor = mid | |
62 | - toColor = far | |
63 | - } else if (involvesFoaF && isToFriend) { | |
64 | - fromColor = far | |
65 | - toColor = mid | |
66 | - } | |
67 | - | |
68 | - linkUI.fromColor = fromColor | |
69 | - linkUI.toColor = toColor | |
70 | - }) | |
71 | - } | |
72 | -} |
config.js | ||
---|---|---|
@@ -1,0 +1,22 @@ | ||
1 … | +const config = { | |
2 … | + host: 'localhost', | |
3 … | + port: 7781, | |
4 … | + physics: { | |
5 … | + springLength: 80, | |
6 … | + springCoeff: 0.0001, | |
7 … | + gravity: -1.4, | |
8 … | + theta: 0.4, | |
9 … | + dragCoeff: 0.04 | |
10 … | + }, | |
11 … | + link: (link) => { | |
12 … | + // if (link.data.hidden) return | |
13 … | + // makes linkUI element not exist ? => display.getLink doesn't work | |
14 … | + | |
15 … | + return { | |
16 … | + fromColor: 0x000066, | |
17 … | + toColor: 0x000066 | |
18 … | + } | |
19 … | + } | |
20 … | +} | |
21 … | + | |
22 … | +module.exports = config |
graph/actions.js | ||
---|---|---|
@@ -1,0 +1,22 @@ | ||
1 … | +const { Action } = require('inux') | |
2 … | + | |
3 … | +const SET = Symbol('set') | |
4 … | +const SET_HOVER = Symbol('setHover') | |
5 … | +const FETCH = Symbol('fetch') | |
6 … | +const HOVER = Symbol('hover') | |
7 … | + | |
8 … | +const set = Action(SET) | |
9 … | +const setHover = Action(SET_HOVER) | |
10 … | +const fetch = Action(FETCH) | |
11 … | +const hover = Action(HOVER) | |
12 … | + | |
13 … | +module.exports = { | |
14 … | + SET, | |
15 … | + SET_HOVER, | |
16 … | + FETCH, | |
17 … | + HOVER, | |
18 … | + set, | |
19 … | + setHover, | |
20 … | + fetch, | |
21 … | + hover | |
22 … | +} |
graph/api.js | |||
---|---|---|---|
@@ -1,0 +1,41 @@ | |||
1 … | +const waterfall = require('run-waterfall') | ||
2 … | +const sendJson = require('send-data/json') | ||
3 … | +const sendError = require('send-data/error') | ||
4 … | +const Routes = require('http-routes') | ||
5 … | + | ||
6 … | +const buildLinks = require('./helpers/build-links') | ||
7 … | +const buildNodes = require('./helpers/build-nodes') | ||
8 … | + | ||
9 … | +module.exports = GraphApi | ||
10 … | + | ||
11 … | +function GraphApi (ssb, config) { | ||
12 … | + return Routes([ | ||
13 … | + ['/api/graph', { | ||
14 … | + get: (req, res, next) => { | ||
15 … | + sendGraph(req, res) | ||
16 … | + } | ||
17 … | + }] | ||
18 … | + ]) | ||
19 … | + | ||
20 … | + function sendGraph (req, res) { | ||
21 … | + getGraph((err, graph) => { | ||
22 … | + if (err) { | ||
23 … | + sendError(req, res, { body: err }) | ||
24 … | + } else { | ||
25 … | + sendJson(req, res, { body: graph }) | ||
26 … | + } | ||
27 … | + }) | ||
28 … | + } | ||
29 … | + | ||
30 … | + function getGraph (cb) { | ||
31 … | + waterfall([ | ||
32 … | + ssb.friends.all, | ||
33 … | + (friends, cb) => { | ||
34 … | + cb(null, { | ||
35 … | + nodes: buildNodes(friends), | ||
36 … | + links: buildLinks(friends) | ||
37 … | + }) | ||
38 … | + } | ||
39 … | + ], cb) | ||
40 … | + } | ||
41 … | +} |
graph/app.js | ||
---|---|---|
@@ -1,0 +1,68 @@ | ||
1 … | +const { assign } = Object | |
2 … | +const xhr = require('xhr') | |
3 … | +const { Domain, run } = require('inux') | |
4 … | +const pullContinuable = require('pull-cont') | |
5 … | +const html = require('inu/html') | |
6 … | +const pull = require('pull-stream') | |
7 … | + | |
8 … | +const { SET, SET_HOVER, FETCH, HOVER, set, setHover, fetch } = require('./actions') | |
9 … | +const GraphView = require('./view') | |
10 … | +const { fetchOne: fetchProfile } = require('../profiles/actions') | |
11 … | +const ProfileView = require('../profiles/view') | |
12 … | + | |
13 … | +module.exports = GraphApp | |
14 … | + | |
15 … | +function GraphApp (config) { | |
16 … | + const graphView = GraphView(config) | |
17 … | + const profileView = ProfileView(config) | |
18 … | + | |
19 … | + return Domain({ | |
20 … | + name: 'graph', | |
21 … | + init: () => ({ | |
22 … | + model: { | |
23 … | + nodes: [], | |
24 … | + links: [] | |
25 … | + }, | |
26 … | + effect: fetch() | |
27 … | + }), | |
28 … | + update: { | |
29 … | + [SET]: (model, graph) => ({ model: graph }), | |
30 … | + [SET_HOVER]: (model, hover) => ({ | |
31 … | + model: assign({}, model, { hover }) | |
32 … | + }) | |
33 … | + }, | |
34 … | + run: { | |
35 … | + [FETCH]: () => { | |
36 … | + return pullContinuable(cb => { | |
37 … | + xhr({ | |
38 … | + url: '/api/graph', | |
39 … | + json: true | |
40 … | + }, (err, resp, { body } = {}) => { | |
41 … | + if (err) return cb(err) | |
42 … | + cb(null, pull.values([set(body)])) | |
43 … | + }) | |
44 … | + }) | |
45 … | + }, | |
46 … | + [HOVER]: (id) => { | |
47 … | + return pull.values([ | |
48 … | + setHover(id), | |
49 … | + run(fetchProfile(id)) | |
50 … | + ]) | |
51 … | + } | |
52 … | + }, | |
53 … | + routes: [ | |
54 … | + ['/', (params, model, dispatch) => { | |
55 … | + const { graph, profiles } = model | |
56 … | + const { hover } = graph | |
57 … | + const hoveredProfile = hover ? profiles[hover] : undefined | |
58 … | + | |
59 … | + return html` | |
60 … | + <div class='main'> | |
61 … | + ${graphView(graph, dispatch)} | |
62 … | + ${profileView(hoveredProfile, dispatch)} | |
63 … | + </div> | |
64 … | + ` | |
65 … | + }] | |
66 … | + ] | |
67 … | + }) | |
68 … | +} |
graph/helpers/build-links.js | ||
---|---|---|
@@ -1,0 +1,25 @@ | ||
1 … | +const { keys } = Object | |
2 … | + | |
3 … | +module.exports = buildLinks | |
4 … | + | |
5 … | +function buildLinks (friends) { | |
6 … | + return keys(friends).reduce((sofar, friend) => { | |
7 … | + const friendOfFriends = keys(friends[friend]) | |
8 … | + .filter(id => friends[friend][id] === true) | |
9 … | + | |
10 … | + const edges = friendOfFriends.map(friendOfFriend => { | |
11 … | + return { | |
12 … | + fromId: friend, | |
13 … | + toId: friendOfFriend, | |
14 … | + data: { | |
15 … | + hidden: true | |
16 … | + } | |
17 … | + } | |
18 … | + }) | |
19 … | + | |
20 … | + return [ | |
21 … | + ...sofar, | |
22 … | + ...edges | |
23 … | + ] | |
24 … | + }, []) | |
25 … | +} |
graph/helpers/build-nodes.js | ||
---|---|---|
@@ -1,0 +1,15 @@ | ||
1 … | +const { keys } = Object | |
2 … | + | |
3 … | +module.exports = buildNodes | |
4 … | + | |
5 … | +function buildNodes (friends) { | |
6 … | + return keys(friends).map(id => { | |
7 … | + return { | |
8 … | + id, | |
9 … | + data: { | |
10 … | + friends: keys(friends[id]) | |
11 … | + } | |
12 … | + } | |
13 … | + }) | |
14 … | +} | |
15 … | + |
graph/view.js | ||
---|---|---|
@@ -1,0 +1,102 @@ | ||
1 … | +const { assign } = Object | |
2 … | +const Renderer = require('ngraph.pixel') | |
3 … | +const html = require('inu/html') | |
4 … | +const { run } = require('inux') | |
5 … | +const Widget = require('cache-element/widget') | |
6 … | + | |
7 … | +const fromJson = require('ngraph.fromjson') | |
8 … | + | |
9 … | +const { hover } = require('./actions') | |
10 … | + | |
11 … | +module.exports = GraphView | |
12 … | + | |
13 … | +function GraphView (config) { | |
14 … | + var ngraph | |
15 … | + var display | |
16 … | + | |
17 … | + return Widget({ | |
18 … | + render: function (graph, dispatch) { | |
19 … | + return html`<div class='graph'></div>` | |
20 … | + }, | |
21 … | + onupdate: function (el, graph, dispatch) { | |
22 … | + if (!display) { | |
23 … | + ngraph = fromJson(graph) | |
24 … | + display = Display(el) | |
25 … | + display.on('nodehover', NodeHoverHandler(dispatch)) | |
26 … | + } else { | |
27 … | + // TODO a better way to update the graph | |
28 … | + // updateGraph(graph) | |
29 … | + // display.off('nodehover') | |
30 … | + // display.on('nodehover', NodeHoverHandler(dispatch)) | |
31 … | + } | |
32 … | + } | |
33 … | + }) | |
34 … | + | |
35 … | + /* | |
36 … | + function updateGraph ({ nodes, links }) { | |
37 … | + ngraph.beginUpdate() | |
38 … | + ngraph.forEachLink(link => { | |
39 … | + ngraph.removeLink(link) | |
40 … | + }) | |
41 … | + ngraph.forEachNode(node => { | |
42 … | + ngraph.removeNode(node) | |
43 … | + }) | |
44 … | + nodes.forEach(node => { | |
45 … | + ngraph.addNode(node.id, node.data) | |
46 … | + }) | |
47 … | + links.forEach(link => { | |
48 … | + ngraph.addLink(link.fromId, link.toId, link.data) | |
49 … | + }) | |
50 … | + ngraph.endUpdate() | |
51 … | + } | |
52 … | + */ | |
53 … | + | |
54 … | + function Display (node) { | |
55 … | + const ngraphConfig = assign({ container: node }, config) | |
56 … | + return Renderer(ngraph, ngraphConfig) | |
57 … | + } | |
58 … | + | |
59 … | + function NodeHoverHandler (dispatch) { | |
60 … | + return (node) => { | |
61 … | + if (node === undefined) return | |
62 … | + | |
63 … | + dispatch(run(hover(node.id))) | |
64 … | + | |
65 … | + display.forEachLink(linkUI => { | |
66 … | + const { from, to } = linkUI | |
67 … | + const friends = node.data.friends | |
68 … | + | |
69 … | + const isFromTarget = node.id === from.id | |
70 … | + const isToTarget = node.id === to.id | |
71 … | + | |
72 … | + const isFromFriend = friends.indexOf(from.id) > -1 | |
73 … | + const isToFriend = friends.indexOf(to.id) > -1 | |
74 … | + const involvesFoaF = isFromFriend || isToTarget | |
75 … | + | |
76 … | + var fromColor = 0x000066 | |
77 … | + var toColor = 0x000066 | |
78 … | + | |
79 … | + const close = 0xffffff | |
80 … | + const mid = 0xa94caf | |
81 … | + const far = 0x000066 | |
82 … | + | |
83 … | + if (isFromTarget) { | |
84 … | + fromColor = close | |
85 … | + toColor = mid | |
86 … | + } else if (isToTarget) { | |
87 … | + fromColor = mid | |
88 … | + toColor = close | |
89 … | + } else if (involvesFoaF && isFromFriend) { | |
90 … | + fromColor = mid | |
91 … | + toColor = far | |
92 … | + } else if (involvesFoaF && isToFriend) { | |
93 … | + fromColor = far | |
94 … | + toColor = mid | |
95 … | + } | |
96 … | + | |
97 … | + linkUI.fromColor = fromColor | |
98 … | + linkUI.toColor = toColor | |
99 … | + }) | |
100 … | + } | |
101 … | + } | |
102 … | +} |
profiles/actions.js | ||
---|---|---|
@@ -1,0 +1,14 @@ | ||
1 … | +const { Action } = require('inux') | |
2 … | + | |
3 … | +const SET_ONE = Symbol('setOne') | |
4 … | +const FETCH_ONE = Symbol('fetchOne') | |
5 … | + | |
6 … | +const setOne = Action(SET_ONE) | |
7 … | +const fetchOne = Action(FETCH_ONE) | |
8 … | + | |
9 … | +module.exports = { | |
10 … | + SET_ONE, | |
11 … | + FETCH_ONE, | |
12 … | + setOne, | |
13 … | + fetchOne | |
14 … | +} |
profiles/api.js | ||
---|---|---|
@@ -1,0 +1,34 @@ | ||
1 … | +const Avatar = require('ssb-avatar') | |
2 … | +const sendJson = require('send-data/json') | |
3 … | +const sendError = require('send-data/error') | |
4 … | +const Routes = require('http-routes') | |
5 … | + | |
6 … | +module.exports = ProfilesApi | |
7 … | + | |
8 … | +function ProfilesApi (ssb, config) { | |
9 … | + return Routes([ | |
10 … | + ['/api/profiles/:profileId(.*)', { | |
11 … | + get: (req, res, next) => { | |
12 … | + sendProfile(req.params.profileId, req, res) | |
13 … | + } | |
14 … | + }] | |
15 … | + ]) | |
16 … | + | |
17 … | + function sendProfile (id, req, res) { | |
18 … | + getProfile(id, (err, profile) => { | |
19 … | + if (err) { | |
20 … | + sendError(req, res, { body: err }) | |
21 … | + } else { | |
22 … | + sendJson(req, res, { body: profile }) | |
23 … | + } | |
24 … | + }) | |
25 … | + } | |
26 … | + | |
27 … | + function getProfile (id, cb) { | |
28 … | + Avatar(ssb, id, id, (err, { name, image } = {}) => { | |
29 … | + if (err) return cb(err) | |
30 … | + const imgSrc = image ? `/blobs/${image}` : null | |
31 … | + cb(null, { id, name, image: imgSrc }) | |
32 … | + }) | |
33 … | + } | |
34 … | +} |
profiles/app.js | ||
---|---|---|
@@ -1,0 +1,48 @@ | ||
1 … | +const { assign } = Object | |
2 … | +const { Domain } = require('inux') | |
3 … | +const pull = require('pull-stream') | |
4 … | +const pullContinuable = require('pull-cont') | |
5 … | +const xhr = require('xhr') | |
6 … | + | |
7 … | +const { SET_ONE, FETCH_ONE, setOne } = require('./actions') | |
8 … | + | |
9 … | +module.exports = ProfilesApp | |
10 … | + | |
11 … | +function ProfilesApp (config) { | |
12 … | + return Domain({ | |
13 … | + name: 'profiles', | |
14 … | + init: () => ({ | |
15 … | + model: {} | |
16 … | + }), | |
17 … | + update: { | |
18 … | + [SET_ONE]: (model, profile) => ({ | |
19 … | + model: assign({}, model, { [profile.id]: profile }) | |
20 … | + }) | |
21 … | + }, | |
22 … | + run: { | |
23 … | + [FETCH_ONE]: (profileId, sources) => { | |
24 … | + // don't fetch profile if already have it | |
25 … | + var hasProfile | |
26 … | + pull( | |
27 … | + sources.models(), | |
28 … | + pull.take(1), | |
29 … | + pull.drain(({ profiles }) => { | |
30 … | + hasProfile = !!profiles[profileId] | |
31 … | + }) | |
32 … | + ) | |
33 … | + if (hasProfile) return | |
34 … | + | |
35 … | + return pullContinuable(cb => { | |
36 … | + xhr({ | |
37 … | + url: `/api/profiles/${profileId}`, | |
38 … | + json: true | |
39 … | + }, (err, resp, { body } = {}) => { | |
40 … | + if (err) return cb(err) | |
41 … | + if (!body) return | |
42 … | + cb(null, pull.values([setOne(body)])) | |
43 … | + }) | |
44 … | + }) | |
45 … | + } | |
46 … | + } | |
47 … | + }) | |
48 … | +} |
profiles/view.js | ||
---|---|---|
@@ -1,0 +1,30 @@ | ||
1 … | +const html = require('inu/html') | |
2 … | +const css = require('sheetify') | |
3 … | + | |
4 … | +module.exports = ProfileView | |
5 … | + | |
6 … | +css` | |
7 … | + .profile { | |
8 … | + position: fixed; | |
9 … | + left: 10px; | |
10 … | + bottom: 10px; | |
11 … | + color: #fff; | |
12 … | + } | |
13 … | + | |
14 … | + .profile .image { | |
15 … | + max-height: 160px; | |
16 … | + } | |
17 … | +` | |
18 … | + | |
19 … | +function ProfileView (config) { | |
20 … | + return (profile = {}, dispatch) => { | |
21 … | + const { name, image } = profile | |
22 … | + | |
23 … | + return html` | |
24 … | + <div id='profile' class='profile'> | |
25 … | + ${image && html`<img class='image' src=${image} />`} | |
26 … | + <div class='name'>${name}</div> | |
27 … | + </div> | |
28 … | + ` | |
29 … | + } | |
30 … | +} |
server.js | ||
---|---|---|
@@ -1,0 +1,21 @@ | ||
1 … | +#!/bin/sh | |
2 … | +':' // ; exec "$(command -v node || command -v nodejs)" "$0" "$@" | |
3 … | +// http://unix.stackexchange.com/questions/65235/universal-node-js-shebang | |
4 … | +// vi: ft=javascript | |
5 … | + | |
6 … | +const appName = process.env.appName | |
7 … | +const config = require('ssb-config/inject')(appName) | |
8 … | +const ssbClient = require('ssb-client') | |
9 … | +const keys = require('ssb-keys') | |
10 … | + .loadOrCreateSync(require('path').join(config.path, 'secret')) | |
11 … | +const Viz = require('.') | |
12 … | + | |
13 … | +config.listenAddr = config._[1] | |
14 … | +config.appName = appName | |
15 … | + | |
16 … | +require('ssb-reconnect')(function (cb) { | |
17 … | + ssbClient(keys, config, cb) | |
18 … | +}, function (err, ssb, reconnect) { | |
19 … | + if (err) throw err | |
20 … | + Viz.init(ssb, config, reconnect) | |
21 … | +}) |
Built with git-ssb-web