git ssb

16+

Dominic / patchbay



Commit 4ed25d0653cf7fe85f8e3eb411723ec297f60638

a beginning gesture

mix irving committed on 2/20/2017, 3:02:35 AM

Files changed

.gitignoreadded
app/html/render.jsadded
app/html/render.mcssadded
app/mcss/global.jsadded
app/mcss/hypertabs.jsadded
app/mcss/hypertabs.mcssadded
app/mcss/mixins.jsadded
app/mcss/render.jsadded
index.jsadded
package.jsonadded
page/html/render/all.jsadded
page/html/render/private.jsadded
page/html/render/tabs.jsadded
.gitignoreView
@@ -1,0 +1,1 @@
1+node_modules
app/html/render.jsView
@@ -1,0 +1,39 @@
1+const fs = require('fs')
2+const h = require('../h')
3+const { Value } = require('mutant')
4+const insertCss = require('insert-css')
5+
6+exports.gives = nest([
7+ 'app.html.render',
8+ 'app.mcss.render'
9+])
10+
11+exports.needs = nest({
12+ 'page.html.render': 'first',
13+ styles: 'first'
14+})
15+
16+exports.create = function (api) {
17+ return {
18+ app,
19+ mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
20+ }
21+
22+ function app () {
23+ process.nextTick(() => insertCss(api.styles()))
24+
25+ var view = Value(getView())
26+ var screen = h('App', view)
27+
28+ window.onhashchange = () => view.set(getView())
29+ document.body.appendChild(screen)
30+
31+ return screen
32+ }
33+
34+ function getView () {
35+ const view = window.location.hash.substring(1) || 'tabs'
36+ return api.page(view)
37+ }
38+}
39+
app/html/render.mcssView
@@ -1,0 +1,11 @@
1+App {
2+ position: absolute
3+
4+ top: 0
5+ bottom: 0
6+ left: 0
7+ right: 0
8+ overflow: hidden
9+ min-height: 0px
10+}
11+
app/mcss/global.jsView
@@ -1,0 +1,26 @@
1+
2+const mixins = `
3+ _textPrimary {
4+ color: #222
5+ }
6+
7+ _textSubtle {
8+ color: gray
9+ }
10+
11+ _backgroundPrimary {
12+ background-color: #50aadf
13+ }
14+`
15+
16+module.exports = {
17+ gives: {
18+ mcss: true
19+ },
20+ create: function (api) {
21+ return {
22+ mcss: () => mixins
23+ }
24+ }
25+}
26+
app/mcss/hypertabs.jsView
@@ -1,0 +1,13 @@
1+const fs = require('fs')
2+
3+module.exports = {
4+ gives: {
5+ mcss: true
6+ },
7+ create: function (api) {
8+ return {
9+ mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
10+ }
11+ }
12+}
13+
app/mcss/hypertabs.mcssView
@@ -1,0 +1,94 @@
1+Hypertabs {
2+ display: flex
3+ flex-direction: column
4+
5+ height: 100% /* needed to stop scroller blowing out */
6+
7+ nav {
8+ display: flex
9+
10+ background: linear-gradient(to bottom, #efefef, #e5e5e5)
11+
12+ section.tabs {
13+ flex-grow: 1
14+ display: flex
15+ min-width: 0
16+
17+ div.tab {
18+ flex-grow: 1
19+
20+ display: flex
21+ align-items: center
22+ justify-content: space-between
23+
24+ min-width: 3.5rem
25+ font-size: .9rem
26+ background-color: #efefef
27+ overflow-x: hidden
28+
29+ padding: 0 .4rem
30+ margin-left: .6rem
31+ border: 1px gainsboro solid
32+ border-bottom: none
33+
34+ -selected {
35+ color: #222
36+ background-color: #fff
37+
38+ a.close {
39+ visibility: visible
40+ }
41+ }
42+
43+ -notify {
44+ background-color: orange;
45+ }
46+
47+
48+ a {
49+ color: #666
50+
51+ :hover {
52+ color: #0088cc
53+ text-decoration: none
54+ }
55+ }
56+
57+ a.link {
58+ flex-grow: 1
59+ flex-shrink: 0
60+ overflow-x: hidden
61+ min-width: 0
62+ max-width: 90%
63+ white-space: nowrap
64+ text-overflow: ellipsis
65+ }
66+
67+ a.close {
68+ visibility: hidden
69+ }
70+ }
71+
72+ }
73+
74+ div.extra {
75+ display: flex
76+ align-items: center
77+ }
78+ }
79+
80+ section.content {
81+ display: flex
82+
83+ height: 100% /* needed to stop making nav weird */
84+
85+ div.page {
86+ flex-grow: 1
87+
88+ display: flex /*hack to get give Scroller context it needs */
89+
90+ padding-top: .2rem
91+ }
92+ }
93+}
94+
app/mcss/mixins.jsView
@@ -1,0 +1,26 @@
1+
2+const mixins = `
3+ _textPrimary {
4+ color: #222
5+ }
6+
7+ _textSubtle {
8+ color: gray
9+ }
10+
11+ _backgroundPrimary {
12+ background-color: #50aadf
13+ }
14+`
15+
16+module.exports = {
17+ gives: {
18+ mcss: true
19+ },
20+ create: function (api) {
21+ return {
22+ mcss: () => mixins
23+ }
24+ }
25+}
26+
app/mcss/render.jsView
@@ -1,0 +1,30 @@
1+const compile = require('micro-css')
2+const fs = require('fs')
3+const Path = require('path')
4+
5+module.exports = {
6+ gives: {
7+ mcss: true,
8+ css: true,
9+ styles: true
10+ },
11+ needs: {
12+ mcss: 'map',
13+ css: 'map'
14+ },
15+ create: function (api) {
16+ var styles = ''
17+ process.nextTick(function () {
18+ const mcss = api.mcss().join('\n')
19+ const css = api.css().join('\n')
20+ styles = coreStyle + compile(mcss) + css
21+ })
22+
23+ return {
24+ styles: () => styles,
25+ // export empty styles
26+ mcss: () => '',
27+ css: () => ''
28+ }
29+ }
30+}
index.jsView
@@ -1,0 +1,27 @@
1+const combine = require('depject')
2+const bulk = require('bulk-require')
3+
4+// polyfills
5+require('setimmediate')
6+
7+// from more specialized to more general
8+const sockets = combine(
9+ // require(patchgit)
10+ bulk(__dirname, [
11+ 'page/**/*.js',
12+ 'app/**/*.js'
13+ ]),
14+ require('patchcore')
15+)
16+
17+const app = entry(sockets)
18+
19+app()
20+
21+
22+
23+
24+function entry (sockets) {
25+ return sockets.app.html.render[0]()
26+}
27+
package.jsonView
@@ -1,0 +1,33 @@
1+{
2+ "name": "picknmix",
3+ "version": "0.0.1",
4+ "description": "patchbay 2? building on patchcore",
5+ "main": "index.js",
6+ "scripts": {
7+ "start": "electro index.js",
8+ "test": "echo \"Error: no test specified\" && exit 1"
9+ },
10+ "repository": {
11+ "type": "git",
12+ "url": "git+https://github.com/ssbc/picknmix.git"
13+ },
14+ "author": "mixmix",
15+ "license": "GPL-3.0",
16+ "bugs": {
17+ "url": "https://github.com/ssbc/picknmix/issues"
18+ },
19+ "homepage": "https://github.com/ssbc/picknmix#readme",
20+ "dependencies": {
21+ "bulk-require": "^1.0.0",
22+ "depject": "^3.1.6",
23+ "depnest": "^1.0.2",
24+ "electro": "^2.0.3",
25+ "electron": "^1.4.15",
26+ "hypertabs": "^4.1.1",
27+ "insert-css": "^2.0.0",
28+ "micro-css": "^1.0.0",
29+ "mutant": "^3.14.2",
30+ "open-external": "^0.1.1",
31+ "setimmediate": "^1.0.5"
32+ }
33+}
page/html/render/all.jsView
page/html/render/private.jsView
page/html/render/tabs.jsView
@@ -1,0 +1,262 @@
1+const Tabs = require('hypertabs')
2+const open = require('open-external')
3+const { webFrame, remote, clipboard } = require('electron') || {}
4+const nest = require('depnest')
5+
6+const keyscroll = require('../../keyscroll')
7+const h = require('../../h')
8+
9+exports.needs = nest({
10+ 'page.html.render': 'first',
11+ 'app.html.menu': 'first',
12+ helpers: {
13+ build_error: 'first',
14+ build_scroller: 'first',
15+ external_confirm:'first',
16+ },
17+ 'app.html.search_box': 'first'
18+})
19+
20+exports.gives = nest('page.html.render')
21+
22+exports.create = function (api) {
23+ return function (path) {
24+ if(path !== 'tabs') return
25+
26+ function setSelected (indexes) {
27+ const ids = indexes.map(index => tabs.get(index).content.id)
28+ if(search)
29+ if(ids.length > 1)
30+ search.input.value = 'split('+ids.join(',')+')'
31+ else
32+ search.input.value = ids[0]
33+ }
34+
35+ const tabs = Tabs(setSelected)
36+ const search = api.search_box((path, change) => {
37+ if(tabs.has(path)) {
38+ tabs.select(path)
39+ return true
40+ }
41+
42+ const el = api.page(path)
43+ if (!el) return
44+
45+ if(!el.title) el.title = path
46+ el.scroll = keyscroll(el.querySelector('.Scroller .content'))
47+ tabs.add(el, change)
48+// localStorage.openTabs = JSON.stringify(tabs.tabs)
49+ return change
50+ })
51+
52+ // TODO add options to Tabs : e.g. Tabs(setSelected, { append: el })
53+ tabs.firstChild.appendChild(
54+ h('div.extra', [
55+ search,
56+ api.menu()
57+ ])
58+ )
59+
60+ var saved = []
61+ // try { saved = JSON.parse(localStorage.openTabs) }
62+ // catch (_) { }
63+
64+ if(!saved || saved.length < 3)
65+ saved = ['/public', '/private', '/notifications']
66+
67+ saved.forEach(function (path) {
68+ var el = api.page(path)
69+ if(!el) return
70+ el.id = el.id || path
71+ if (!el) return
72+ el.scroll = keyscroll(el.querySelector('.Scroller .content'))
73+ if(el) tabs.add(el, false, false)
74+ })
75+
76+ tabs.select(0)
77+ search.input.value = null // start with an empty field to show placeholder
78+
79+ //handle link clicks
80+ window.onclick = function (ev) {
81+ var link = ancestorAnchor(ev.target)
82+ if(!link) return
83+ var path = link.hash.substring(1)
84+
85+ ev.preventDefault()
86+ ev.stopPropagation()
87+
88+ //let the application handle this link
89+ if (link.getAttribute('href') === '#') return
90+
91+ //open external links.
92+ //this ought to be made into something more runcible
93+ if(link.href && open.isExternal(link.href)) return api.helpers.external_confirm(link.href)
94+
95+ if(tabs.has(path))
96+ return tabs.select(path, !ev.ctrlKey, !!ev.shiftKey)
97+
98+ var el = api.page(path)
99+ if(el) {
100+ el.id = el.id || path
101+ el.scroll = keyscroll(el.querySelector('.Scroller .content'))
102+ tabs.add(el, !ev.ctrlKey, !!ev.shiftKey)
103+ // localStorage.openTabs = JSON.stringify(tabs.tabs)
104+ }
105+
106+ return false
107+ }
108+
109+ var gPressed = false
110+ window.addEventListener('keydown', function (ev) {
111+ if (ev.target.nodeName === 'INPUT' || ev.target.nodeName === 'TEXTAREA')
112+ return
113+
114+ // scroll to top
115+ if (ev.keyCode == 71) { // g
116+ if (!gPressed) return gPressed = true
117+ var el = tabs.get(tabs.selected[0]).firstChild.scroll('first')
118+ gPressed = false
119+ } else {
120+ gPressed = false
121+ }
122+
123+ switch(ev.keyCode) {
124+ // scroll through tabs
125+ case 72: // h
126+ return tabs.selectRelative(-1)
127+ case 76: // l
128+ return tabs.selectRelative(1)
129+
130+ // scroll through messages
131+ case 74: // j
132+ return tabs.get(tabs.selected[0]).firstChild.scroll(1)
133+ case 75: // k
134+ return tabs.get(tabs.selected[0]).firstChild.scroll(-1)
135+
136+ // close a tab
137+ case 88: // x
138+ if (tabs.selected) {
139+ var sel = tabs.selected
140+ var i = sel.reduce(function (a, b) { return Math.min(a, b) })
141+ tabs.remove(sel)
142+ tabs.select(Math.max(i-1, 0))
143+ }
144+ return
145+
146+ // activate the search field
147+ case 191: // /
148+ if (ev.shiftKey)
149+ search.activate('?', ev)
150+ else
151+ search.activate('/', ev)
152+ return
153+
154+ // navigate to a feed
155+ case 50: // 2
156+ if (ev.shiftKey)
157+ search.activate('@', ev)
158+ return
159+
160+ // navigate to a channel
161+ case 51: // 3
162+ if (ev.shiftKey)
163+ search.activate('#', ev)
164+ return
165+
166+ // navigate to a message
167+ case 53: // 5
168+ if (ev.shiftKey)
169+ search.activate('%', ev)
170+ return
171+ }
172+ })
173+
174+ // errors tab
175+ var {
176+ container: errorsScroller,
177+ content: errorsContent
178+ } = api.helpers.build_scroller()
179+
180+ errorsScroller.id = '/errors'
181+ errorsScroller.classList.add('-errors')
182+
183+ // remove loader error handler (currently disabled)
184+ // if (window.onError) {
185+ // window.removeEventListener('error', window.onError)
186+ // delete window.onError
187+ // }
188+
189+ // put errors in a tab
190+ window.addEventListener('error', ev => {
191+ const err = ev.error || ev
192+ if(!tabs.has('/errors'))
193+ tabs.add(errorsScroller, false)
194+
195+ const el = api.helpers.build_error(err)
196+ if (errorsContent.firstChild)
197+ errorsContent.insertBefore(el, errorsContent.firstChild)
198+ else
199+ errorsContent.appendChild(el)
200+ })
201+
202+ if (process.versions.electron) {
203+ window.addEventListener('mousewheel', ev => {
204+ const { ctrlKey, deltaY } = ev
205+ if (ctrlKey) {
206+ const direction = (deltaY / Math.abs(deltaY))
207+ const currentZoom = webFrame.getZoomLevel()
208+ webFrame.setZoomLevel(currentZoom - direction)
209+ }
210+ })
211+
212+ window.addEventListener('contextmenu', ev => {
213+ ev.preventDefault()
214+ const Menu = remote.Menu
215+ const MenuItem = remote.MenuItem
216+ const menu = new Menu()
217+ menu.append(new MenuItem({
218+ label: 'Inspect Element',
219+ click: () => {
220+ remote.getCurrentWindow().inspectElement(ev.x, ev.y)
221+ }
222+ }))
223+
224+ var message = ancestorMessage(ev.target)
225+ if (message && message.dataset.key) {
226+ menu.append(new MenuItem({
227+ label: 'Copy id',
228+ click: () => clipboard.writeText(message.dataset.key)
229+ }))
230+ }
231+ if (message && message.dataset.text) {
232+ menu.append(new MenuItem({
233+ label: 'Copy text',
234+ click: () => clipboard.writeText(message.dataset.text)
235+ }))
236+ }
237+ menu.popup(remote.getCurrentWindow())
238+ })
239+ }
240+
241+ return tabs
242+ }
243+}
244+
245+function ancestorAnchor (el) {
246+ if(!el) return
247+ if(el.tagName !== 'A') return ancestorAnchor(el.parentElement)
248+ return el
249+}
250+
251+function ancestorMessage (el) {
252+ if(!el) return
253+ if(!el.classList.contains('Message')) {
254+ if (el.parentElement)
255+ return ancestorMessage(el.parentElement)
256+ else
257+ return null
258+ }
259+ return el
260+}
261+
262+

Built with git-ssb-web