diff options
Diffstat (limited to 'src/features/todos/store.ts')
-rw-r--r-- | src/features/todos/store.ts | 327 |
1 files changed, 327 insertions, 0 deletions
diff --git a/src/features/todos/store.ts b/src/features/todos/store.ts new file mode 100644 index 000000000..5cf5e1d75 --- /dev/null +++ b/src/features/todos/store.ts | |||
@@ -0,0 +1,327 @@ | |||
1 | import { Webview } from 'react-electron-web-view'; | ||
2 | import { computed, action, observable, makeObservable } from 'mobx'; | ||
3 | import localStorage from 'mobx-localstorage'; | ||
4 | import { Actions } from '../../actions/lib/actions'; | ||
5 | |||
6 | import { ThemeType } from '../../themes'; | ||
7 | import { todoActions } from './actions'; | ||
8 | import { | ||
9 | CUSTOM_TODO_SERVICE, | ||
10 | TODO_SERVICE_RECIPE_IDS, | ||
11 | DEFAULT_TODOS_WIDTH, | ||
12 | TODOS_MIN_WIDTH, | ||
13 | DEFAULT_TODOS_VISIBLE, | ||
14 | DEFAULT_IS_TODO_FEATURE_ENABLED_BY_USER, | ||
15 | } from '../../config'; | ||
16 | import { isValidExternalURL } from '../../helpers/url-helpers'; | ||
17 | import FeatureStore from '../utils/FeatureStore'; | ||
18 | import Reaction, { createReactions } from '../../stores/lib/Reaction'; | ||
19 | import { createActionBindings } from '../utils/ActionBinding'; | ||
20 | import { IPC, TODOS_ROUTES } from './constants'; | ||
21 | import UserAgent from '../../models/UserAgent'; | ||
22 | |||
23 | const debug = require('../../preload-safe-debug')( | ||
24 | 'Ferdium:feature:todos:store', | ||
25 | ); | ||
26 | |||
27 | export default class TodoStore extends FeatureStore { | ||
28 | @observable stores: any = null; | ||
29 | |||
30 | @observable isFeatureActive = false; | ||
31 | |||
32 | @observable webview: Webview | undefined; | ||
33 | |||
34 | @observable userAgentModel = new UserAgent(); | ||
35 | |||
36 | isInitialized = false; | ||
37 | |||
38 | actions: Actions | undefined; | ||
39 | |||
40 | _allReactions: Reaction[] | undefined; | ||
41 | |||
42 | constructor() { | ||
43 | super(); | ||
44 | |||
45 | makeObservable(this); | ||
46 | } | ||
47 | |||
48 | @computed get width() { | ||
49 | const width = this.settings.width || DEFAULT_TODOS_WIDTH; | ||
50 | |||
51 | return width < TODOS_MIN_WIDTH ? TODOS_MIN_WIDTH : width; | ||
52 | } | ||
53 | |||
54 | @computed get isTodosPanelForceHidden() { | ||
55 | return !this.isFeatureEnabledByUser; | ||
56 | } | ||
57 | |||
58 | @computed get isTodosPanelVisible() { | ||
59 | if (this.settings.isTodosPanelVisible === undefined) { | ||
60 | return DEFAULT_TODOS_VISIBLE; | ||
61 | } | ||
62 | return this.settings.isTodosPanelVisible; | ||
63 | } | ||
64 | |||
65 | @computed get isFeatureEnabledByUser() { | ||
66 | return this.settings.isFeatureEnabledByUser; | ||
67 | } | ||
68 | |||
69 | @computed get settings() { | ||
70 | return localStorage.getItem('todos') || {}; | ||
71 | } | ||
72 | |||
73 | @computed get userAgent() { | ||
74 | return this.userAgentModel.userAgent; | ||
75 | } | ||
76 | |||
77 | @computed get isUsingPredefinedTodoServer() { | ||
78 | return ( | ||
79 | this.stores && | ||
80 | this.stores.settings.app.predefinedTodoServer !== CUSTOM_TODO_SERVICE | ||
81 | ); | ||
82 | } | ||
83 | |||
84 | @computed get todoUrl() { | ||
85 | if (!this.stores) { | ||
86 | return null; | ||
87 | } | ||
88 | return this.isUsingPredefinedTodoServer | ||
89 | ? this.stores.settings.app.predefinedTodoServer | ||
90 | : this.stores.settings.app.customTodoServer; | ||
91 | } | ||
92 | |||
93 | @computed get isTodoUrlValid() { | ||
94 | return ( | ||
95 | !this.isUsingPredefinedTodoServer || isValidExternalURL(this.todoUrl) | ||
96 | ); | ||
97 | } | ||
98 | |||
99 | @computed get todoRecipeId() { | ||
100 | if ( | ||
101 | this.isFeatureEnabledByUser && | ||
102 | this.isUsingPredefinedTodoServer && | ||
103 | this.todoUrl in TODO_SERVICE_RECIPE_IDS | ||
104 | ) { | ||
105 | return TODO_SERVICE_RECIPE_IDS[this.todoUrl]; | ||
106 | } | ||
107 | return null; | ||
108 | } | ||
109 | |||
110 | // ========== PUBLIC API ========= // | ||
111 | |||
112 | @action start(stores, actions) { | ||
113 | debug('TodoStore::start'); | ||
114 | this.stores = stores; | ||
115 | this.actions = actions; | ||
116 | |||
117 | // ACTIONS | ||
118 | |||
119 | this._registerActions( | ||
120 | createActionBindings([ | ||
121 | [todoActions.resize, this._resize], | ||
122 | [todoActions.toggleTodosPanel, this._toggleTodosPanel], | ||
123 | [todoActions.setTodosWebview, this._setTodosWebview], | ||
124 | [todoActions.handleHostMessage, this._handleHostMessage], | ||
125 | [todoActions.handleClientMessage, this._handleClientMessage], | ||
126 | [ | ||
127 | todoActions.toggleTodosFeatureVisibility, | ||
128 | this._toggleTodosFeatureVisibility, | ||
129 | ], | ||
130 | [todoActions.openDevTools, this._openDevTools], | ||
131 | [todoActions.reload, this._reload], | ||
132 | ]), | ||
133 | ); | ||
134 | |||
135 | // REACTIONS | ||
136 | |||
137 | this._allReactions = createReactions([ | ||
138 | this._updateTodosConfig, | ||
139 | this._firstLaunchReaction, | ||
140 | this._routeCheckReaction, | ||
141 | ]); | ||
142 | |||
143 | this._registerReactions(this._allReactions); | ||
144 | |||
145 | this.isFeatureActive = true; | ||
146 | } | ||
147 | |||
148 | @action stop() { | ||
149 | super.stop(); | ||
150 | debug('TodoStore::stop'); | ||
151 | // this.reset(); // TODO - [TECH DEBT][PROP NOT IN CLASS] check it later | ||
152 | this.isFeatureActive = false; | ||
153 | } | ||
154 | |||
155 | // ========== PRIVATE METHODS ========= // | ||
156 | |||
157 | _updateSettings = changes => { | ||
158 | localStorage.setItem('todos', { | ||
159 | ...this.settings, | ||
160 | ...changes, | ||
161 | }); | ||
162 | }; | ||
163 | |||
164 | // Actions | ||
165 | |||
166 | @action _resize = ({ width }) => { | ||
167 | this._updateSettings({ | ||
168 | width, | ||
169 | }); | ||
170 | }; | ||
171 | |||
172 | @action _toggleTodosPanel = () => { | ||
173 | this._updateSettings({ | ||
174 | isTodosPanelVisible: !this.isTodosPanelVisible, | ||
175 | }); | ||
176 | }; | ||
177 | |||
178 | @action _setTodosWebview = ({ webview }) => { | ||
179 | debug('_setTodosWebview', webview); | ||
180 | if (this.webview !== webview) { | ||
181 | this.webview = webview; | ||
182 | this.userAgentModel.setWebviewReference(webview); | ||
183 | } | ||
184 | }; | ||
185 | |||
186 | @action _handleHostMessage = message => { | ||
187 | debug('_handleHostMessage', message); | ||
188 | if (message.action === 'todos:create') { | ||
189 | this.webview.send(IPC.TODOS_HOST_CHANNEL, message); | ||
190 | } | ||
191 | }; | ||
192 | |||
193 | @action _handleClientMessage = ({ | ||
194 | channel, | ||
195 | message = { action: '', data: { url: '', serviceId: '' } }, | ||
196 | }) => { | ||
197 | debug('_handleClientMessage', channel, message); | ||
198 | switch (message.action) { | ||
199 | case 'todos:initialized': | ||
200 | this._onTodosClientInitialized(); | ||
201 | break; | ||
202 | case 'todos:goToService': | ||
203 | this._goToService(message.data); | ||
204 | break; | ||
205 | default: | ||
206 | debug('Other message received', channel, message); | ||
207 | if (this.stores.services.isTodosServiceAdded && this.actions) { | ||
208 | this.actions.service.handleIPCMessage({ | ||
209 | serviceId: this.stores.services.isTodosServiceAdded.id, | ||
210 | channel, | ||
211 | args: message, | ||
212 | }); | ||
213 | } | ||
214 | } | ||
215 | }; | ||
216 | |||
217 | _handleNewWindowEvent = ({ url }) => { | ||
218 | if (!this.actions) { | ||
219 | return; | ||
220 | } | ||
221 | this.actions.app.openExternalUrl({ url }); | ||
222 | }; | ||
223 | |||
224 | @action _toggleTodosFeatureVisibility = () => { | ||
225 | debug('_toggleTodosFeatureVisibility'); | ||
226 | |||
227 | const isFeatureEnabled = !this.settings.isFeatureEnabledByUser; | ||
228 | this._updateSettings({ | ||
229 | isFeatureEnabledByUser: isFeatureEnabled, | ||
230 | isTodosPanelVisible: isFeatureEnabled, | ||
231 | }); | ||
232 | }; | ||
233 | |||
234 | _openDevTools = () => { | ||
235 | debug('_openDevTools'); | ||
236 | |||
237 | const webview = document.querySelector<Webview>('#todos-panel webview'); | ||
238 | if (webview) { | ||
239 | webview.openDevTools(); | ||
240 | } | ||
241 | }; | ||
242 | |||
243 | _reload = () => { | ||
244 | debug('_reload'); | ||
245 | |||
246 | const webview = document.querySelector<Webview>('#todos-panel webview'); | ||
247 | if (webview) { | ||
248 | webview.reload(); | ||
249 | } | ||
250 | }; | ||
251 | |||
252 | // Todos client message handlers | ||
253 | |||
254 | _onTodosClientInitialized = async () => { | ||
255 | const { authToken } = this.stores.user; | ||
256 | const { isDarkThemeActive } = this.stores.ui; | ||
257 | const { locale } = this.stores.app; | ||
258 | if (!this.webview) return; | ||
259 | await this.webview.send(IPC.TODOS_HOST_CHANNEL, { | ||
260 | action: 'todos:configure', | ||
261 | data: { | ||
262 | authToken, | ||
263 | locale, | ||
264 | theme: isDarkThemeActive ? ThemeType.dark : ThemeType.default, | ||
265 | }, | ||
266 | }); | ||
267 | |||
268 | if (!this.isInitialized) { | ||
269 | this.webview.addEventListener('new-window', this._handleNewWindowEvent); | ||
270 | |||
271 | this.isInitialized = true; | ||
272 | } | ||
273 | }; | ||
274 | |||
275 | _goToService = ({ url, serviceId }) => { | ||
276 | if (url) { | ||
277 | this.stores.services.one(serviceId).webview.loadURL(url); | ||
278 | } | ||
279 | if (this.actions) { | ||
280 | this.actions.service.setActive({ serviceId }); | ||
281 | } | ||
282 | }; | ||
283 | |||
284 | // Reactions | ||
285 | |||
286 | _updateTodosConfig = () => { | ||
287 | // Resend the config if any part changes in Franz: | ||
288 | this._onTodosClientInitialized(); | ||
289 | }; | ||
290 | |||
291 | _firstLaunchReaction = () => { | ||
292 | const { stats } = this.stores.settings.all; | ||
293 | |||
294 | if (this.settings.isFeatureEnabledByUser === undefined) { | ||
295 | this._updateSettings({ | ||
296 | isFeatureEnabledByUser: DEFAULT_IS_TODO_FEATURE_ENABLED_BY_USER, | ||
297 | }); | ||
298 | } | ||
299 | |||
300 | // Hide todos layer on first app start but show on second | ||
301 | if (stats.appStarts <= 1) { | ||
302 | this._updateSettings({ | ||
303 | isTodosPanelVisible: false, | ||
304 | }); | ||
305 | } else if (stats.appStarts <= 2) { | ||
306 | this._updateSettings({ | ||
307 | isTodosPanelVisible: true, | ||
308 | }); | ||
309 | } | ||
310 | }; | ||
311 | |||
312 | _routeCheckReaction = () => { | ||
313 | const { pathname } = this.stores.router.location; | ||
314 | |||
315 | if (pathname === TODOS_ROUTES.TARGET) { | ||
316 | debug('Router is on todos route, show todos panel'); | ||
317 | // todosStore.start(stores, actions); | ||
318 | this.stores.router.push('/'); | ||
319 | |||
320 | if (!this.isTodosPanelVisible) { | ||
321 | this._updateSettings({ | ||
322 | isTodosPanelVisible: true, | ||
323 | }); | ||
324 | } | ||
325 | } | ||
326 | }; | ||
327 | } | ||