git ssb

10+

Matt McKegg / patchwork



Tree: ef8bbd8ead3c1ca190843d0695258a73898dab24

Files: ef8bbd8ead3c1ca190843d0695258a73898dab24 / main-window.js

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

Built with git-ssb-web