Commit 572440feaf959755763efb726087066a6f5b29db
quick dump
Matt McKegg committed on 10/27/2016, 3:07:41 AMFiles changed
.gitignore | added |
README.md | added |
api/index.js | added |
assets/base.html | added |
assets/img/emoji | added |
index.js | added |
lib/context-menu.js | added |
lib/h.js | added |
lib/make-single-instance.js | added |
lib/serve-blobs.js | added |
lib/ssb-server.js | added |
lib/window.js | added |
main-window.js | added |
package.json | added |
styles/base.mcss | added |
styles/index.js | added |
styles/main-window.mcss | added |
README.md | ||
---|---|---|
@@ -1,0 +1,26 @@ | ||
1 … | +patchwork-next | |
2 … | +=== | |
3 … | + | |
4 … | +Very early **work-in-progress** attempt at remaking [Patchwork](https://github.com/ssbc/patchwork) using [patchbay](https://github.com/dominictarr/patchbay) and UX/ideas from [ferment](https://github.com/mmckegg/ferment). | |
5 … | + | |
6 … | +The goal is to make a standalone, easy to install, "social" view into the ssb world. | |
7 … | + | |
8 … | +## Install and run | |
9 … | + | |
10 … | +```shell | |
11 … | +$ git clone https://github.com/mmckegg/patchwork-next | |
12 … | +$ cd patchwork-next | |
13 … | +$ npm install | |
14 … | +$ npm start | |
15 … | +``` | |
16 … | + | |
17 … | +## TODO | |
18 … | + | |
19 … | +- [ ] Preserve scroll on back button | |
20 … | +- [ ] Main navigation buttons | |
21 … | +- [ ] Easy navigation sidebar | |
22 … | +- [ ] Contacts sidebar | |
23 … | +- [ ] Lots of styling | |
24 … | +- [ ] Compressed feed (the algorithm :wink:) | |
25 … | + - [ ] Thread previews in feed [figure out how to do this streaming] | |
26 … | +- [ ] Add more todos! |
api/index.js | ||
---|---|---|
@@ -1,0 +1,124 @@ | ||
1 … | +var pull = require('pull-stream') | |
2 … | +var ssbKeys = require('ssb-keys') | |
3 … | +var ref = require('ssb-ref') | |
4 … | + | |
5 … | +function Hash (onHash) { | |
6 … | + var buffers = [] | |
7 … | + return pull.through(function (data) { | |
8 … | + buffers.push('string' === typeof data | |
9 … | + ? new Buffer(data, 'utf8') | |
10 … | + : data | |
11 … | + ) | |
12 … | + }, function (err) { | |
13 … | + if(err && !onHash) throw err | |
14 … | + var b = buffers.length > 1 ? Buffer.concat(buffers) : buffers[0] | |
15 … | + var h = '&'+ssbKeys.hash(b) | |
16 … | + onHash && onHash(err, h) | |
17 … | + }) | |
18 … | +} | |
19 … | +//uncomment this to use from browser... | |
20 … | +//also depends on having ssb-ws installed. | |
21 … | +//var createClient = require('ssb-lite') | |
22 … | + | |
23 … | +var createFeed = require('ssb-feed') | |
24 … | +var cache = CACHE = {} | |
25 … | + | |
26 … | +module.exports = function (sbot, opts) { | |
27 … | + var connection_status = [] | |
28 … | + var keys = opts.keys | |
29 … | + | |
30 … | + var internal = { | |
31 … | + getLatest: function (id, cb) { | |
32 … | + sbot.getLatest(id, cb) | |
33 … | + }, | |
34 … | + add: function (msg, cb) { | |
35 … | + sbot.add(msg, cb) | |
36 … | + } | |
37 … | + } | |
38 … | + | |
39 … | + var feed = createFeed(internal, keys, {remote: true}) | |
40 … | + | |
41 … | + setImmediate((x) => { | |
42 … | + connection_status.forEach(fn => fn()) | |
43 … | + }) | |
44 … | + | |
45 … | + return { | |
46 … | + connection_status: connection_status, | |
47 … | + sbot_blobs_add: function (cb) { | |
48 … | + return pull( | |
49 … | + Hash(function (err, id) { | |
50 … | + if(err) return cb(err) | |
51 … | + //completely UGLY hack to tell when the blob has been sucessfully written... | |
52 … | + var start = Date.now(), n = 5 | |
53 … | + ;(function next () { | |
54 … | + setTimeout(function () { | |
55 … | + sbot.blobs.has(id, function (err, has) { | |
56 … | + if(has) return cb(null, id) | |
57 … | + if(n--) next() | |
58 … | + else cb(new Error('write failed')) | |
59 … | + }) | |
60 … | + }, Date.now() - start) | |
61 … | + })() | |
62 … | + }), | |
63 … | + sbot.blobs.add() | |
64 … | + ) | |
65 … | + }, | |
66 … | + sbot_links: function (query) { | |
67 … | + return sbot.links(query) | |
68 … | + }, | |
69 … | + sbot_links2: function (query) { | |
70 … | + return sbot.links2.read(query) | |
71 … | + }, | |
72 … | + sbot_query: function (query) { | |
73 … | + return sbot.query.read(query) | |
74 … | + }, | |
75 … | + sbot_log: function (opts) { | |
76 … | + return pull( | |
77 … | + sbot.createLogStream(opts), | |
78 … | + pull.through(function (e) { | |
79 … | + CACHE[e.key] = CACHE[e.key] || e.value | |
80 … | + }) | |
81 … | + ) | |
82 … | + }, | |
83 … | + sbot_user_feed: function (opts) { | |
84 … | + return sbot.createUserStream(opts) | |
85 … | + }, | |
86 … | + sbot_get: function (key, cb) { | |
87 … | + if(CACHE[key]) cb(null, CACHE[key]) | |
88 … | + else sbot.get(key, function (err, value) { | |
89 … | + if(err) return cb(err) | |
90 … | + cb(null, CACHE[key] = value) | |
91 … | + }) | |
92 … | + }, | |
93 … | + sbot_gossip_peers: function (cb) { | |
94 … | + sbot.gossip.peers(cb) | |
95 … | + }, | |
96 … | + //liteclient won't have permissions for this | |
97 … | + sbot_gossip_connect: function (opts, cb) { | |
98 … | + sbot.gossip.connect(opts, cb) | |
99 … | + }, | |
100 … | + sbot_publish: function (content, cb) { | |
101 … | + if(content.recps) | |
102 … | + content = ssbKeys.box(content, content.recps.map(function (e) { | |
103 … | + return ref.isFeed(e) ? e : e.link | |
104 … | + })) | |
105 … | + else if(content.mentions) | |
106 … | + content.mentions.forEach(function (mention) { | |
107 … | + if(ref.isBlob(mention.link)) { | |
108 … | + sbot.blobs.push(mention.link, function (err) { | |
109 … | + if(err) console.error(err) | |
110 … | + }) | |
111 … | + } | |
112 … | + }) | |
113 … | + | |
114 … | + feed.add(content, function (err, msg) { | |
115 … | + if(err) console.error(err) | |
116 … | + else if(!cb) console.log(msg) | |
117 … | + cb && cb(err, msg) | |
118 … | + }) | |
119 … | + }, | |
120 … | + sbot_whoami: function (cb) { | |
121 … | + sbot.whoami(cb) | |
122 … | + } | |
123 … | + } | |
124 … | +} |
index.js | ||
---|---|---|
@@ -1,0 +1,100 @@ | ||
1 … | +process.on('uncaughtException', function (err) { | |
2 … | + console.log(err) | |
3 … | + process.exit() | |
4 … | +}) | |
5 … | + | |
6 … | +var electron = require('electron') | |
7 … | +var openWindow = require('./lib/window') | |
8 … | +var createSbot = require('./lib/ssb-server') | |
9 … | +var serveBlobs = require('./lib/serve-blobs') | |
10 … | +var makeSingleInstance = require('./lib/make-single-instance') | |
11 … | +var pull = require('pull-stream') | |
12 … | +var pullFile = require('pull-file') | |
13 … | +var Path = require('path') | |
14 … | +var fs = require('fs') | |
15 … | +var defaultMenu = require('electron-default-menu') | |
16 … | +var Menu = electron.Menu | |
17 … | +var dataUriToBuffer = require('data-uri-to-buffer') | |
18 … | +var extend = require('xtend') | |
19 … | +var ssbKeys = require('ssb-keys') | |
20 … | + | |
21 … | +var windows = { | |
22 … | + dialogs: new Set() | |
23 … | +} | |
24 … | + | |
25 … | +var context = null | |
26 … | +if (process.argv.includes('--use-global-ssb') || process.argv.includes('-g')) { | |
27 … | + context = setupContext('ssb', { | |
28 … | + server: false | |
29 … | + }) | |
30 … | +} else { | |
31 … | + makeSingleInstance(windows, openMainWindow) | |
32 … | + context = setupContext('ssb') | |
33 … | +} | |
34 … | + | |
35 … | +electron.ipcMain.on('add-blob', (ev, id, path, cb) => { | |
36 … | + pull( | |
37 … | + path.startsWith('data:') ? pull.values([dataUriToBuffer(path)]) : pullFile(path), | |
38 … | + context.sbot.blobs.add((err, hash) => { | |
39 … | + if (err) return ev.sender.send('response', id, err) | |
40 … | + ev.sender.send('response', id, null, hash) | |
41 … | + }) | |
42 … | + ) | |
43 … | +}) | |
44 … | + | |
45 … | +electron.app.on('ready', function () { | |
46 … | + Menu.setApplicationMenu(Menu.buildFromTemplate(defaultMenu(electron.app, electron.shell))) | |
47 … | + openMainWindow() | |
48 … | +}) | |
49 … | + | |
50 … | +electron.app.on('activate', function (e) { | |
51 … | + openMainWindow() | |
52 … | +}) | |
53 … | + | |
54 … | +function openMainWindow () { | |
55 … | + if (!windows.main) { | |
56 … | + windows.main = openWindow(context, Path.join(__dirname, 'main-window.js'), { | |
57 … | + minWidth: 800, | |
58 … | + width: 1024, | |
59 … | + height: 768, | |
60 … | + titleBarStyle: 'hidden-inset', | |
61 … | + title: 'Ferment', | |
62 … | + show: true, | |
63 … | + backgroundColor: '#444', | |
64 … | + webPreferences: { | |
65 … | + experimentalFeatures: true | |
66 … | + }, | |
67 … | + icon: './ferment-logo.png' | |
68 … | + }) | |
69 … | + windows.main.setSheetOffset(40) | |
70 … | + windows.main.on('closed', function () { | |
71 … | + windows.main = null | |
72 … | + }) | |
73 … | + } | |
74 … | +} | |
75 … | + | |
76 … | +function setupContext (appName, opts) { | |
77 … | + var ssbConfig = require('ssb-config/inject')(appName, extend({ | |
78 … | + port: 8008, | |
79 … | + blobsPort: 7777 | |
80 … | + }, opts)) | |
81 … | + | |
82 … | + if (opts && opts.server === false) { | |
83 … | + return { | |
84 … | + config: ssbConfig | |
85 … | + } | |
86 … | + } else { | |
87 … | + ssbConfig.keys = ssbKeys.loadOrCreateSync(Path.join(ssbConfig.path, 'secret')) | |
88 … | + var context = { | |
89 … | + sbot: createSbot(ssbConfig), | |
90 … | + config: ssbConfig | |
91 … | + } | |
92 … | + ssbConfig.manifest = context.sbot.getManifest() | |
93 … | + serveBlobs(context) | |
94 … | + fs.writeFileSync(Path.join(ssbConfig.path, 'manifest.json'), JSON.stringify(ssbConfig.manifest)) | |
95 … | + console.log(`Address: ${context.sbot.getAddress()}`) | |
96 … | + return context | |
97 … | + } | |
98 … | + | |
99 … | + return ssbConfig | |
100 … | +} |
lib/context-menu.js | ||
---|---|---|
@@ -1,0 +1,44 @@ | ||
1 … | +var electron = require('electron') | |
2 … | +var Menu = electron.remote.Menu | |
3 … | +var MenuItem = electron.remote.MenuItem | |
4 … | +var BrowserWindow = electron.remote.BrowserWindow | |
5 … | + | |
6 … | +window.addEventListener('contextmenu', function (e) { | |
7 … | + module.exports(null, null, e) | |
8 … | +}, false) | |
9 … | + | |
10 … | +module.exports = function (context, item, ev) { | |
11 … | + ev.preventDefault() | |
12 … | + ev.stopPropagation() | |
13 … | + var menu = new Menu() | |
14 … | + menu.append(new MenuItem({ | |
15 … | + label: 'Reload', | |
16 … | + click: function (item, focusedWindow) { | |
17 … | + if (focusedWindow) { | |
18 … | + focusedWindow.reload() | |
19 … | + } | |
20 … | + } | |
21 … | + })) | |
22 … | + menu.append(new MenuItem({ | |
23 … | + type: 'separator' | |
24 … | + })) | |
25 … | + menu.append(new MenuItem({ | |
26 … | + label: 'Inspect Element', | |
27 … | + click: function () { | |
28 … | + var x = ev.clientX | |
29 … | + var y = ev.clientY | |
30 … | + BrowserWindow.getFocusedWindow().inspectElement(x, y) | |
31 … | + } | |
32 … | + })) | |
33 … | + | |
34 … | + if (item && item.id) { | |
35 … | + menu.append(new MenuItem({ | |
36 … | + label: 'Copy SSB ID', | |
37 … | + click: function () { | |
38 … | + electron.clipboard.writeText(item.id) | |
39 … | + } | |
40 … | + })) | |
41 … | + } | |
42 … | + | |
43 … | + menu.popup(electron.remote.getCurrentWindow()) | |
44 … | +} |
lib/h.js | ||
---|---|---|
@@ -1,0 +1,1 @@ | ||
1 … | +module.exports = require('micro-css/h')(require('@mmckegg/mutant/html-element')) |
lib/make-single-instance.js | ||
---|---|---|
@@ -1,0 +1,13 @@ | ||
1 … | +var electron = require('electron') | |
2 … | +module.exports = function (windows, openMainWindow) { | |
3 … | + if (electron.app.makeSingleInstance((commandLine, workingDirectory) => { | |
4 … | + if (windows.main) { | |
5 … | + if (windows.main.isMinimized()) windows.main.restore() | |
6 … | + windows.main.focus() | |
7 … | + } else { | |
8 … | + openMainWindow() | |
9 … | + } | |
10 … | + })) { | |
11 … | + electron.app.quit() | |
12 … | + } | |
13 … | +} |
lib/serve-blobs.js | ||
---|---|---|
@@ -1,0 +1,60 @@ | ||
1 … | +var pull = require('pull-stream') | |
2 … | +var cat = require('pull-cat') | |
3 … | +var toPull = require('stream-to-pull-stream') | |
4 … | +var ident = require('pull-identify-filetype') | |
5 … | +var mime = require('mime-types') | |
6 … | +var URL = require('url') | |
7 … | +var http = require('http') | |
8 … | + | |
9 … | +module.exports = function (context, cb) { | |
10 … | + return http.createServer(ServeBlobs(context.sbot)).listen(context.config.blobsPort, cb) | |
11 … | +} | |
12 … | + | |
13 … | +function ServeBlobs (sbot) { | |
14 … | + return function (req, res, next) { | |
15 … | + var parsed = URL.parse(req.url, true) | |
16 … | + var hash = decodeURIComponent(parsed.pathname.slice(1)) | |
17 … | + sbot.blobs.want(hash, function (_, has) { | |
18 … | + if (!has) return respond(res, 404, 'File not found') | |
19 … | + // optional name override | |
20 … | + if (parsed.query.name) { | |
21 … | + res.setHeader('Content-Disposition', 'inline; filename=' + encodeURIComponent(parsed.query.name)) | |
22 … | + } | |
23 … | + | |
24 … | + // serve | |
25 … | + res.setHeader('Content-Security-Policy', BlobCSP()) | |
26 … | + respondSource(res, sbot.blobs.get(hash), false) | |
27 … | + }) | |
28 … | + } | |
29 … | +} | |
30 … | + | |
31 … | +function respondSource (res, source, wrap) { | |
32 … | + if (wrap) { | |
33 … | + res.writeHead(200, {'Content-Type': 'text/html'}) | |
34 … | + pull( | |
35 … | + cat([ | |
36 … | + pull.once('<html><body><script>'), | |
37 … | + source, | |
38 … | + pull.once('</script></body></html>') | |
39 … | + ]), | |
40 … | + toPull.sink(res) | |
41 … | + ) | |
42 … | + } else { | |
43 … | + pull( | |
44 … | + source, | |
45 … | + ident(function (type) { | |
46 … | + if (type) res.writeHead(200, {'Content-Type': mime.lookup(type)}) | |
47 … | + }), | |
48 … | + toPull.sink(res) | |
49 … | + ) | |
50 … | + } | |
51 … | +} | |
52 … | + | |
53 … | +function respond (res, status, message) { | |
54 … | + res.writeHead(status) | |
55 … | + res.end(message) | |
56 … | +} | |
57 … | + | |
58 … | +function BlobCSP () { | |
59 … | + return 'default-src none; sandbox' | |
60 … | +} |
lib/ssb-server.js | ||
---|---|---|
@@ -1,0 +1,13 @@ | ||
1 … | +module.exports = require('scuttlebot') | |
2 … | + .use(require('scuttlebot/plugins/master')) | |
3 … | + .use(require('scuttlebot/plugins/gossip')) | |
4 … | + .use(require('scuttlebot/plugins/friends')) | |
5 … | + .use(require('scuttlebot/plugins/replicate')) | |
6 … | + .use(require('ssb-blobs')) | |
7 … | + .use(require('scuttlebot/plugins/invite')) | |
8 … | + .use(require('scuttlebot/plugins/block')) | |
9 … | + .use(require('scuttlebot/plugins/local')) | |
10 … | + .use(require('scuttlebot/plugins/logging')) | |
11 … | + .use(require('scuttlebot/plugins/private')) | |
12 … | + .use(require('ssb-links')) | |
13 … | + .use(require('ssb-query')) |
lib/window.js | ||
---|---|---|
@@ -1,0 +1,92 @@ | ||
1 … | +var Path = require('path') | |
2 … | +var electron = require('electron') | |
3 … | +var extend = require('xtend/mutable') | |
4 … | + | |
5 … | +module.exports = function Window (context, path, opts) { | |
6 … | + var window = new electron.BrowserWindow(extend({ | |
7 … | + show: false | |
8 … | + }, opts)) | |
9 … | + | |
10 … | + window.setMenu(null) | |
11 … | + electron.ipcMain.on('ready-to-show', handleReadyToShow) | |
12 … | + | |
13 … | + window.webContents.on('dom-ready', function () { | |
14 … | + window.webContents.executeJavaScript(` | |
15 … | + var electron = require('electron') | |
16 … | + var rootView = require(${JSON.stringify(path)}) | |
17 … | + var insertCss = require('insert-css') | |
18 … | + var h = require('../lib/h') | |
19 … | + var createClient = require('ssb-client') | |
20 … | + | |
21 … | + require('../lib/context-menu') | |
22 … | + insertCss(require('../styles')) | |
23 … | + electron.webFrame.setZoomLevelLimits(1, 1) | |
24 … | + | |
25 … | + var config = ${JSON.stringify(context.config)} | |
26 … | + var data = ${JSON.stringify(opts.data)} | |
27 … | + var title = ${JSON.stringify(opts.title || 'Patchwork')} | |
28 … | + | |
29 … | + document.documentElement.querySelector('head').appendChild( | |
30 … | + h('title', title) | |
31 … | + ) | |
32 … | + | |
33 … | + var shouldShow = ${opts.show !== false} | |
34 … | + var shouldConnect = ${opts.connect !== false} | |
35 … | + | |
36 … | + if (shouldConnect) { | |
37 … | + createClient(config.keys, config, (err, client) => { | |
38 … | + if (err) { | |
39 … | + electron.remote.getGlobal('console').log(err) | |
40 … | + throw err | |
41 … | + } else { | |
42 … | + render(client) | |
43 … | + } | |
44 … | + }) | |
45 … | + } else { | |
46 … | + render() | |
47 … | + } | |
48 … | + | |
49 … | + function render (client) { | |
50 … | + try { | |
51 … | + document.documentElement.replaceChild(h('body', [ | |
52 … | + rootView(client, config, data) | |
53 … | + ]), document.body) | |
54 … | + } catch (ex) { | |
55 … | + electron.ipcRenderer.send('ready-to-show') | |
56 … | + throw ex | |
57 … | + } | |
58 … | + shouldShow && electron.ipcRenderer.send('ready-to-show') | |
59 … | + } | |
60 … | + `) | |
61 … | + }) | |
62 … | + | |
63 … | + // setTimeout(function () { | |
64 … | + // window.show() | |
65 … | + // }, 3000) | |
66 … | + | |
67 … | + window.webContents.on('will-navigate', function (e, url) { | |
68 … | + e.preventDefault() | |
69 … | + electron.shell.openExternal(url) | |
70 … | + }) | |
71 … | + | |
72 … | + window.webContents.on('new-window', function (e, url) { | |
73 … | + e.preventDefault() | |
74 … | + electron.shell.openExternal(url) | |
75 … | + }) | |
76 … | + | |
77 … | + window.on('closed', function () { | |
78 … | + electron.ipcMain.removeListener('ready-to-show', handleReadyToShow) | |
79 … | + }) | |
80 … | + | |
81 … | + window.loadURL('file://' + Path.join(__dirname, '..', 'assets', 'base.html')) | |
82 … | + return window | |
83 … | + | |
84 … | + // scoped | |
85 … | + | |
86 … | + function handleReadyToShow (ev) { | |
87 … | + if (ev.sender === window) { | |
88 … | + window.show() | |
89 … | + electron.ipcMain.removeListener('ready-to-show', handleReadyToShow) | |
90 … | + } | |
91 … | + } | |
92 … | +} |
main-window.js | ||
---|---|---|
@@ -1,0 +1,116 @@ | ||
1 … | +var combine = require('depject') | |
2 … | +var Modules = require('patchbay/modules') | |
3 … | +var SbotApi = require('./api') | |
4 … | +var extend = require('xtend') | |
5 … | +var h = require('./lib/h') | |
6 … | +var plugs = require('patchbay/plugs') | |
7 … | +var Value = require('@mmckegg/mutant/value') | |
8 … | +var when = require('@mmckegg/mutant/when') | |
9 … | +var computed = require('@mmckegg/mutant/computed') | |
10 … | + | |
11 … | +module.exports = function (ssbClient, config) { | |
12 … | + var api = SbotApi(ssbClient, config) | |
13 … | + var modules = combine(extend(Modules, { | |
14 … | + 'sbot-api.js': api, | |
15 … | + 'blob-url.js': { | |
16 … | + blob_url: function (link) { | |
17 … | + var prefix = config.blobsPrefix != null ? config.blobsPrefix : `http://localhost:${config.blobsPort}` | |
18 … | + if (typeof link.link === 'string') { | |
19 … | + link = link.link | |
20 … | + } | |
21 … | + return `${prefix}/${encodeURIComponent(link)}` | |
22 … | + } | |
23 … | + } | |
24 … | + })) | |
25 … | + | |
26 … | + var screenView = plugs.first(modules.plugs.screen_view) | |
27 … | + var forwardHistory = [] | |
28 … | + var backHistory = [] | |
29 … | + var views = { | |
30 … | + '/public': screenView('/public') | |
31 … | + } | |
32 … | + var canGoForward = Value(false) | |
33 … | + var canGoBack = Value(false) | |
34 … | + var currentView = Value(['/public']) | |
35 … | + var rootElement = computed(currentView, (data) => { | |
36 … | + if (Array.isArray(data)) { | |
37 … | + return views[data[0]] | |
38 … | + } | |
39 … | + }) | |
40 … | + | |
41 … | + window.onhashchange = function (ev) { | |
42 … | + setView(window.location.hash.substring(1)) | |
43 … | + } | |
44 … | + | |
45 … | + var mainElement = h('div.main', [ | |
46 … | + rootElement | |
47 … | + ]) | |
48 … | + | |
49 … | + return h('MainWindow', { | |
50 … | + classList: [ '-' + process.platform ] | |
51 … | + }, [ | |
52 … | + h('div.top', [ | |
53 … | + h('span.history', [ | |
54 … | + h('a', { | |
55 … | + 'ev-click': goBack, | |
56 … | + classList: [ when(canGoBack, '-active') ] | |
57 … | + }, '<'), | |
58 … | + h('a', { | |
59 … | + 'ev-click': goForward, | |
60 … | + classList: [ when(canGoForward, '-active') ] | |
61 … | + }, '>') | |
62 … | + ]), | |
63 … | + h('span.appTitle', ['Patchwork']) | |
64 … | + ]), | |
65 … | + mainElement | |
66 … | + ]) | |
67 … | + | |
68 … | + // scoped | |
69 … | + | |
70 … | + function goBack () { | |
71 … | + if (backHistory.length) { | |
72 … | + canGoForward.set(true) | |
73 … | + forwardHistory.push(currentView()) | |
74 … | + currentView.set(backHistory.pop()) | |
75 … | + canGoBack.set(backHistory.length > 0) | |
76 … | + } | |
77 … | + } | |
78 … | + | |
79 … | + function goForward () { | |
80 … | + if (forwardHistory.length) { | |
81 … | + backHistory.push(currentView()) | |
82 … | + currentView.set(forwardHistory.pop()) | |
83 … | + canGoForward.set(forwardHistory.length > 0) | |
84 … | + canGoBack.set(true) | |
85 … | + } | |
86 … | + } | |
87 … | + | |
88 … | + function setView (view, ...args) { | |
89 … | + var newView = [view, ...args] | |
90 … | + if (!isSame(newView, currentView())) { | |
91 … | + if (!views[view]) { | |
92 … | + views[view] = screenView(view, ...args) | |
93 … | + } | |
94 … | + | |
95 … | + canGoForward.set(false) | |
96 … | + canGoBack.set(true) | |
97 … | + forwardHistory.length = 0 | |
98 … | + backHistory.push(currentView()) | |
99 … | + currentView.set(newView) | |
100 … | + currentView().scrollTop = 0 | |
101 … | + } | |
102 … | + } | |
103 … | +} | |
104 … | + | |
105 … | +function isSame (a, b) { | |
106 … | + if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) { | |
107 … | + for (var i = 0; i < a.length; i++) { | |
108 … | + if (a[i] !== b[i]) { | |
109 … | + return false | |
110 … | + } | |
111 … | + } | |
112 … | + return true | |
113 … | + } else if (a === b) { | |
114 … | + return true | |
115 … | + } | |
116 … | +} |
package.json | ||
---|---|---|
@@ -1,0 +1,33 @@ | ||
1 … | +{ | |
2 … | + "name": "patchwork-next", | |
3 … | + "version": "0.0.0", | |
4 … | + "description": "", | |
5 … | + "main": "index.js", | |
6 … | + "scripts": { | |
7 … | + "test": "echo \"Error: no test specified\" && exit 1", | |
8 … | + "start": "electron index.js", | |
9 … | + "postinstall": "npm run rebuild", | |
10 … | + "rebuild": "npm rebuild --runtime=electron --target=1.4.3 --abi=50 --disturl=https://atom.io/download/atom-shell" | |
11 … | + }, | |
12 … | + "author": "", | |
13 … | + "license": "ISC", | |
14 … | + "dependencies": { | |
15 … | + "@mmckegg/mutant": "^3.6.1", | |
16 … | + "data-uri-to-buffer": "0.0.4", | |
17 … | + "electron": "^1.4.4", | |
18 … | + "electron-default-menu": "^1.0.0", | |
19 … | + "insert-css": "^1.0.0", | |
20 … | + "micro-css": "^0.6.2", | |
21 … | + "patchbay": "^3.5.0", | |
22 … | + "pull-file": "^1.0.0", | |
23 … | + "pull-identify-filetype": "^1.1.0", | |
24 … | + "pull-stream": "^3.4.5", | |
25 … | + "scuttlebot": "^9.2.0", | |
26 … | + "ssb-blobs": "^0.1.7", | |
27 … | + "ssb-keys": "^7.0.0", | |
28 … | + "ssb-links": "^2.0.0", | |
29 … | + "ssb-query": "^0.1.1", | |
30 … | + "ssb-ref": "^2.6.2", | |
31 … | + "ssb-ws": "^0.6.2" | |
32 … | + } | |
33 … | +} |
styles/base.mcss | ||
---|---|---|
@@ -1,0 +1,101 @@ | ||
1 … | +html, body { | |
2 … | + background: #ccc | |
3 … | + margin: 0 | |
4 … | + font-family: caption, sans-serif | |
5 … | + overflow: hidden | |
6 … | + height: 100% | |
7 … | + font-size: 12px | |
8 … | + -webkit-user-select: none | |
9 … | + color: #2b2b2b | |
10 … | +} | |
11 … | + | |
12 … | +body { | |
13 … | + display: flex | |
14 … | + flex-direction: column | |
15 … | + line-height: 1.2 | |
16 … | +} | |
17 … | + | |
18 … | +h1 { | |
19 … | + color: #C0C0C0 | |
20 … | + font-size: 200% | |
21 … | + margin: 4px 0 | |
22 … | + font-weight: normal | |
23 … | + text-shadow: 0px 0px 2px black | |
24 … | + flex: 1 | |
25 … | +} | |
26 … | + | |
27 … | +select { | |
28 … | + font-size: 80% | |
29 … | + display: inline-block | |
30 … | + padding: 2px 4px | |
31 … | + height: 18px | |
32 … | + border: 1px solid #A9A9A9 | |
33 … | + background: #666 svg(dropArrow) no-repeat right | |
34 … | + -webkit-appearance: none | |
35 … | + color: #FFF | |
36 … | + padding-right: 12px | |
37 … | + border-radius: 0 | |
38 … | + | |
39 … | + :hover { | |
40 … | + background-image: svg(dropArrow -active) | |
41 … | + } | |
42 … | + | |
43 … | + @svg dropArrow { | |
44 … | + width: 12px | |
45 … | + height: 6px | |
46 … | + content: "<path d='M2,0 L10,0 L6,6 Z' />" | |
47 … | + | |
48 … | + path { | |
49 … | + fill: #888 | |
50 … | + } | |
51 … | + | |
52 … | + -active { | |
53 … | + path { | |
54 … | + fill: #DDD | |
55 … | + } | |
56 … | + } | |
57 … | + } | |
58 … | +} | |
59 … | + | |
60 … | +input { | |
61 … | + [type='text'] { | |
62 … | + font-size: 80% | |
63 … | + display: inline-block | |
64 … | + padding: 2px 4px | |
65 … | + height: 18px | |
66 … | + border: 1px solid #A9A9A9 | |
67 … | + background: #333 | |
68 … | + color: #FFF | |
69 … | + padding-right: 12px | |
70 … | + border-radius: 0 | |
71 … | + } | |
72 … | +} | |
73 … | + | |
74 … | +::-webkit-file-upload-button { | |
75 … | + font-family: inherit | |
76 … | +} | |
77 … | + | |
78 … | +a { | |
79 … | + color: #ebf7b1 | |
80 … | + text-decoration: none | |
81 … | + | |
82 … | + code { | |
83 … | + color: #8EC1FC | |
84 … | + } | |
85 … | + | |
86 … | + :hover { | |
87 … | + text-decoration: underline | |
88 … | + } | |
89 … | +} | |
90 … | + | |
91 … | +* + h1 { | |
92 … | + margin-top: 16px | |
93 … | +} | |
94 … | + | |
95 … | +* { | |
96 … | + box-sizing:border-box | |
97 … | +} | |
98 … | + | |
99 … | +input, textarea, keygen, select, button { | |
100 … | + font-family: '.SFNSText-Regular', sans-serif | |
101 … | +} |
styles/index.js | ||
---|---|---|
@@ -1,0 +1,19 @@ | ||
1 … | +var fs = require('fs') | |
2 … | +var path = require('path') | |
3 … | +var compile = require('micro-css') | |
4 … | +var result = '' | |
5 … | +var additional = '' | |
6 … | + | |
7 … | +fs.readdirSync(__dirname).forEach(function (file) { | |
8 … | + if (/\.mcss$/i.test(file)) { | |
9 … | + result += fs.readFileSync(path.resolve(__dirname, file), 'utf8') + '\n' | |
10 … | + } | |
11 … | + | |
12 … | + if (/\.css$/i.test(file)) { | |
13 … | + additional += fs.readFileSync(path.resolve(__dirname, file), 'utf8') + '\n' | |
14 … | + } | |
15 … | +}) | |
16 … | + | |
17 … | +additional += fs.readFileSync(require.resolve('patchbay/style.css'), 'utf8') | |
18 … | + | |
19 … | +module.exports = compile(result) + additional |
styles/main-window.mcss | ||
---|---|---|
@@ -1,0 +1,158 @@ | ||
1 … | +MainWindow { | |
2 … | + height: 100% | |
3 … | + display: flex | |
4 … | + flex-direction: column | |
5 … | + | |
6 … | + -darwin { | |
7 … | + div.top { | |
8 … | + padding-left: 70px | |
9 … | + } | |
10 … | + div.top { | |
11 … | + span.appTitle { | |
12 … | + margin-right: 40px; | |
13 … | + } | |
14 … | + } | |
15 … | + } | |
16 … | + | |
17 … | + -win32 { | |
18 … | + div.top { | |
19 … | + padding-right: 70px | |
20 … | + } | |
21 … | + } | |
22 … | + | |
23 … | + div.top { | |
24 … | + -webkit-app-region: drag; | |
25 … | + display: flex; | |
26 … | + background: #fff; | |
27 … | + padding: 10px; | |
28 … | + border-bottom: 2px solid #e2e2e2; | |
29 … | + box-shadow: 0 0 3px #7f7f7f; | |
30 … | + position: relative; | |
31 … | + | |
32 … | + span { | |
33 … | + a { | |
34 … | + padding: 2px 8px; | |
35 … | + border: 2px solid #bbbbbb; | |
36 … | + border-radius: 4px; | |
37 … | + background: #a7a6a6; | |
38 … | + color: #000000; | |
39 … | + font-size: 120%; | |
40 … | + cursor: pointer; | |
41 … | + margin-left: 5px; | |
42 … | + | |
43 … | + :hover { | |
44 … | + text-decoration: none | |
45 … | + color: black | |
46 … | + border-color: #888 | |
47 … | + } | |
48 … | + | |
49 … | + -selected { | |
50 … | + border-color: #444 | |
51 … | + background: #CCC | |
52 … | + color: black | |
53 … | + } | |
54 … | + | |
55 … | + -add { | |
56 … | + border-color: #498849 | |
57 … | + background-color: #255D24 | |
58 … | + text-shadow: 1px 1px 1px #000 | |
59 … | + color: white | |
60 … | + | |
61 … | + :active { | |
62 … | + background-color: #1F331F !important | |
63 … | + } | |
64 … | + | |
65 … | + :hover { | |
66 … | + background-color: #356D34 | |
67 … | + border-color: #4CB54C | |
68 … | + } | |
69 … | + } | |
70 … | + } | |
71 … | + } | |
72 … | + | |
73 … | + span.history { | |
74 … | + a { | |
75 … | + opacity: 0.3 | |
76 … | + | |
77 … | + -active { | |
78 … | + opacity: 1 | |
79 … | + } | |
80 … | + } | |
81 … | + | |
82 … | + a + a { | |
83 … | + margin-left: 0 | |
84 … | + } | |
85 … | + } | |
86 … | + | |
87 … | + span.appTitle { | |
88 … | + flex: 1; | |
89 … | + text-align: center; | |
90 … | + font-size: 130%; | |
91 … | + color: #757575; | |
92 … | + letter-spacing: 0.1em; | |
93 … | + font-weight: bold; | |
94 … | + font-weight: normal; | |
95 … | + } | |
96 … | + } | |
97 … | + | |
98 … | + div.info { | |
99 … | + a.message { | |
100 … | + display: block | |
101 … | + padding: 10px | |
102 … | + background: #314427 | |
103 … | + transition: color 0.2s, background-color 0.2s | |
104 … | + | |
105 … | + :hover { | |
106 … | + text-decoration: none | |
107 … | + color: white | |
108 … | + background: #315427 | |
109 … | + } | |
110 … | + } | |
111 … | + box-shadow: 0 0 3px black | |
112 … | + overflow: hidden | |
113 … | + animation: 0.5s slide-in | |
114 … | + position: relative | |
115 … | + } | |
116 … | + | |
117 … | + div.main { | |
118 … | + flex: 1 | |
119 … | + overflow: auto | |
120 … | + display: flex | |
121 … | + | |
122 … | + div { | |
123 … | + flex: 1 | |
124 … | + } | |
125 … | + } | |
126 … | + | |
127 … | + div.bottom { | |
128 … | + position: relative | |
129 … | + box-shadow: 0 0 3px #222 | |
130 … | + background: #222 | |
131 … | + align-items: center | |
132 … | + display: flex | |
133 … | + padding: 5px | |
134 … | + | |
135 … | + audio { | |
136 … | + color: #EEE | |
137 … | + | |
138 … | + ::-webkit-media-controls-panel { | |
139 … | + background: transparent | |
140 … | + } | |
141 … | + | |
142 … | + ::-webkit-media-controls-current-time-display { | |
143 … | + color: inherit | |
144 … | + } | |
145 … | + | |
146 … | + width: 100% | |
147 … | + } | |
148 … | + } | |
149 … | +} | |
150 … | + | |
151 … | +@keyframes slide-in { | |
152 … | + 0% { | |
153 … | + max-height: 0 | |
154 … | + } | |
155 … | + 100% { | |
156 … | + max-height: 100px | |
157 … | + } | |
158 … | +} |
Built with git-ssb-web