git ssb

0+

alanz / patchwork



forked from Matt McKegg / patchwork

Tree: c9ba3630ee9b32a904d47de7b87c1e101e5d59c5

Files: c9ba3630ee9b32a904d47de7b87c1e101e5d59c5 / main-window.js

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

Built with git-ssb-web