git ssb

10+

Matt McKegg / patchwork



Commit 572440feaf959755763efb726087066a6f5b29db

quick dump

Matt McKegg committed on 10/27/2016, 3:07:41 AM

Files changed

.gitignoreadded
README.mdadded
api/index.jsadded
assets/base.htmladded
assets/img/emojiadded
index.jsadded
lib/context-menu.jsadded
lib/h.jsadded
lib/make-single-instance.jsadded
lib/serve-blobs.jsadded
lib/ssb-server.jsadded
lib/window.jsadded
main-window.jsadded
package.jsonadded
styles/base.mcssadded
styles/index.jsadded
styles/main-window.mcssadded
.gitignoreView
@@ -1,0 +1,1 @@
1 +node_modules
README.mdView
@@ -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.jsView
@@ -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 +}
assets/base.htmlView
@@ -1,0 +1,5 @@
1 +<!DOCTYPE html>
2 +<html>
3 + <head></head>
4 + <body></body>
5 +</html>
assets/img/emojiView
@@ -1,0 +1,1 @@
1 +../../node_modules/emoji-named-characters/pngs
index.jsView
@@ -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.jsView
@@ -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.jsView
@@ -1,0 +1,1 @@
1 +module.exports = require('micro-css/h')(require('@mmckegg/mutant/html-element'))
lib/make-single-instance.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsonView
@@ -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.mcssView
@@ -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.jsView
@@ -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.mcssView
@@ -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