git ssb

10+

Matt McKegg / patchwork



Tree: 20e56e6bb267f73d48091b7c9576ae3f9bab0bee

Files: 20e56e6bb267f73d48091b7c9576ae3f9bab0bee / main-window.js

8465 bytesRaw
1var combine = require('depject')
2var entry = require('depject/entry')
3var electron = require('electron')
4var h = require('mutant/h')
5var when = require('mutant/when')
6var onceTrue = require('mutant/once-true')
7var computed = require('mutant/computed')
8var catchLinks = require('./lib/catch-links')
9var themes = require('./styles')
10var nest = require('depnest')
11var LatestUpdate = require('./lib/latest-update')
12var ref = require('ssb-ref')
13var setupContextMenuAndSpellCheck = require('./lib/context-menu-and-spellcheck')
14var watch = require('mutant/watch')
15var mousetrap = require("mousetrap")
16
17
18module.exports = function (config) {
19 var sockets = combine(
20 overrideConfig(config),
21 addCommand('app.navigate', navigate),
22 require('./modules'),
23 require('./plugs'),
24 require('patch-settings'),
25 require('patchcore'),
26 require('./overrides')
27 )
28
29 var api = entry(sockets, nest({
30 'config.sync.load': 'first',
31 'keys.sync.id': 'first',
32 'sbot.obs.connection': 'first',
33 'sbot.async.get': 'first',
34 'blob.sync.url': 'first',
35 'page.html.render': 'first',
36 'app.html.search': 'first',
37 'app.html.channels': 'first',
38 'app.views': 'first',
39 'app.fullscreen': 'first',
40 'app.sync.externalHandler': 'first',
41 'app.html.progressNotifier': 'first',
42 'profile.sheet.edit': 'first',
43 'profile.html.preview': 'first',
44 'app.navigate': 'first',
45 'app.linkPreview': 'first',
46 'channel.obs.subscribed': 'first',
47 'settings.obs.get': 'first',
48 'intl.sync.i18n': 'first'
49 }))
50
51 setupContextMenuAndSpellCheck(api.config.sync.load(), navigate)
52
53 const i18n = api.intl.sync.i18n
54
55 var id = api.keys.sync.id()
56 var latestUpdate = LatestUpdate()
57 var subscribedChannels = api.channel.obs.subscribed(id)
58
59 // prompt to setup profile on first use
60 onceTrue(api.sbot.obs.connection, (sbot) => {
61 sbot.latestSequence(sbot.id, (_, key) => {
62 if (key == null) {
63 api.profile.sheet.edit()
64 }
65 })
66 })
67
68 var views = api.app.views(api.page.html.render, [
69 '/public', '/private', id, '/mentions'
70 ])
71
72 var pendingCount = views.get('/mentions').pendingUpdates
73
74 watch(pendingCount, count => {
75 electron.remote.app.setBadgeCount(count)
76 })
77
78
79
80
81 document.head.appendChild(
82 h('style', {
83 innerHTML: computed(api.settings.obs.get('patchwork.theme', 'light'), themeName => {
84 return themes[themeName] || themes['light']
85 })
86 })
87 )
88
89 document.head.appendChild(
90 h('style', {
91 innerHTML: computed(api.settings.obs.get('patchwork.fontSize'), size => {
92 if (size) {
93 return 'html, body {font-size: ' + size + ';}'
94 }
95 })
96 })
97 )
98
99 var container = h(`MainWindow -${process.platform}`, {
100 classList: [ when(api.app.fullscreen(), '-fullscreen') ]
101 }, [
102 h('div.top', [
103 h('span.history', [
104 h('a', {
105 'ev-click': views.goBack,
106 classList: [ when(views.canGoBack, '-active') ]
107 }),
108 h('a', {
109 'ev-click': views.goForward,
110 classList: [ when(views.canGoForward, '-active') ]
111 })
112 ]),
113 h('span.nav', [
114 tab(i18n('Public'), '/public'),
115 tab(i18n('Private'), '/private'),
116 dropTab(i18n('More'), [
117 getSubscribedChannelMenu,
118 [i18n('Gatherings'), '/gatherings'],
119 [i18n('Extended Network'), '/all'],
120 {separator: true},
121 [i18n('Settings'), '/settings']
122 ])
123 ]),
124 h('span.appTitle', [
125 h('span.title', i18n('Patchwork')),
126 api.app.html.progressNotifier()
127 ]),
128 h('span', [ api.app.html.search(api.app.navigate) ]),
129 h('span.nav', [
130 tab(i18n('Profile'), id),
131 tab(i18n('Mentions'), '/mentions')
132 ])
133 ]),
134 when(latestUpdate,
135 h('div.info', [
136 h('a.message -update', { href: 'https://github.com/ssbc/patchwork/releases' }, [
137 h('strong', ['Patchwork ', latestUpdate, i18n(' has been released.')]), i18n(' Click here to download and view more info!'),
138 h('a.ignore', {'ev-click': latestUpdate.ignore}, 'X')
139 ])
140 ])
141 ),
142 views.html
143 ])
144
145 var previewElement = api.app.linkPreview(container, 500)
146
147 catchLinks(container, (href, external, anchor) => {
148 if (!href) return
149 if (external) {
150 electron.shell.openExternal(href)
151 } else {
152 api.app.navigate(href)
153 }
154 })
155
156
157 // TODO - should this be in a separate file instead?
158 // And should we be using the Electron MenuItem API over mousetrap?
159
160 mousetrap.bind(["command+[", "command+left"], views.goBack)
161 mousetrap.bind(["command+]", "command+right"], views.goForward)
162
163
164 return [container, previewElement]
165
166 // scoped
167
168 function getSubscribedChannelMenu () {
169 var channels = Array.from(subscribedChannels()).sort(localeCompare)
170
171 if (channels.length) {
172 return {
173 label: i18n('Channels'),
174 submenu: [
175 { label: i18n('Browse All'),
176 click () {
177 navigate('/channels')
178 }
179 },
180 {type: 'separator'}
181 ].concat(channels.map(channel => {
182 return {
183 label: `#${channel}`,
184 click () {
185 navigate(`#${channel}`)
186 }
187 }
188 }))
189 }
190 } else {
191 return {
192 label: i18n('Browse Channels'),
193 click () {
194 navigate('/channels')
195 }
196 }
197 }
198 }
199
200 function dropTab (title, items) {
201 var element = h('a -drop', {
202 'ev-click': (ev) => {
203 var rects = element.getBoundingClientRect()
204 electron.remote.getCurrentWindow().webContents.getZoomFactor((factor) => {
205 var menu = electron.remote.Menu.buildFromTemplate(items.map(item => {
206 if (typeof item === 'function') {
207 return item()
208 } else if (item.separator) {
209 return { type: 'separator' }
210 } else {
211 return {
212 label: item[0],
213 click () {
214 navigate(item[1])
215 }
216 }
217 }
218 }))
219 menu.popup(electron.remote.getCurrentWindow(), {
220 x: Math.round(rects.left * factor),
221 y: Math.round(rects.bottom * factor) + 4,
222 async: true
223 })
224 })
225 }
226 }, title)
227 return element
228 }
229
230 function navigate (href, anchor) {
231 if (typeof href !== 'string') return false
232 getExternalHandler(href, (err, handler) => {
233 if (!err && handler) {
234 handler(href)
235 } else {
236 // no external handler found, use page.html.render
237 previewElement.cancel()
238 views.setView(href, anchor)
239 }
240 })
241 }
242
243 function getExternalHandler (href, cb) {
244 var link = ref.parseLink(href)
245 if (link && ref.isMsg(link.link)) {
246 api.sbot.async.get(link.link, function (err, value) {
247 if (err) return cb(err)
248 cb(null, api.app.sync.externalHandler({key: link.link, value, query: link.query}))
249 })
250 } else if (link && ref.isBlob(link.link)) {
251 cb(null, function (href) {
252 electron.shell.openExternal(api.blob.sync.url(href))
253 })
254 } else {
255 cb()
256 }
257 }
258
259 function tab (name, view) {
260 var instance = views.get(view)
261 return h('a', {
262 'ev-click': function (ev) {
263 var isSelected = views.currentView() === view
264 var needsRefresh = instance.pendingUpdates && instance.pendingUpdates()
265
266 // refresh if tab is clicked when there are pending items or the page is already selected
267 if ((needsRefresh || isSelected) && instance.reload) {
268 instance.reload()
269 }
270 },
271 href: view,
272 classList: [
273 when(selected(view), '-selected')
274 ]
275 }, [
276 name,
277 when(instance.pendingUpdates, [
278 ' (', instance.pendingUpdates, ')'
279 ])
280 ])
281 }
282
283 function selected (view) {
284 return computed([views.currentView, view], (currentView, view) => {
285 return currentView === view
286 })
287 }
288}
289
290function overrideConfig (config) {
291 return {
292 'patchwork/config': {
293 gives: nest('config.sync.load'),
294 create: function (api) {
295 return nest('config.sync.load', () => config)
296 }
297 }
298 }
299}
300
301function addCommand (id, cb) {
302 return {
303 [`patchwork/command/${id}`]: {
304 gives: nest(id),
305 create: function (api) {
306 return nest(id, cb)
307 }
308 }
309 }
310}
311
312function localeCompare (a, b) {
313 return a.localeCompare(b)
314}
315

Built with git-ssb-web