git ssb

10+

Matt McKegg / patchwork



Tree: a8957703ffdfbeec50be89b480213a0c41d12389

Files: a8957703ffdfbeec50be89b480213a0c41d12389 / main-window.js

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

Built with git-ssb-web