aboutsummaryrefslogtreecommitdiffstats
path: root/src/containers/settings
diff options
context:
space:
mode:
Diffstat (limited to 'src/containers/settings')
-rw-r--r--src/containers/settings/AccountScreen.js9
-rw-r--r--src/containers/settings/EditServiceScreen.js29
-rw-r--r--src/containers/settings/EditSettingsScreen.js174
-rw-r--r--src/containers/settings/RecipesScreen.js51
-rw-r--r--src/containers/settings/SupportScreen.js36
-rw-r--r--src/containers/settings/TeamScreen.js6
6 files changed, 292 insertions, 13 deletions
diff --git a/src/containers/settings/AccountScreen.js b/src/containers/settings/AccountScreen.js
index 88ecd55d5..93ab44690 100644
--- a/src/containers/settings/AccountScreen.js
+++ b/src/containers/settings/AccountScreen.js
@@ -28,7 +28,14 @@ export default @inject('stores', 'actions') @observer class AccountScreen extend
28 handleWebsiteLink(route) { 28 handleWebsiteLink(route) {
29 const { actions, stores } = this.props; 29 const { actions, stores } = this.props;
30 30
31 const url = stores.user.getAuthURL(`${WEBSITE}${route}?utm_source=app&utm_medium=account_dashboard`); 31 const api = stores.settings.all.app.server;
32
33 let url;
34 if (api === 'https://api.franzinfra.com') {
35 url = stores.user.getAuthURL(`${WEBSITE}${route}?utm_source=app&utm_medium=account_dashboard`);
36 } else {
37 url = `${api}${route}`;
38 }
32 39
33 actions.app.openExternalUrl({ url }); 40 actions.app.openExternalUrl({ url });
34 } 41 }
diff --git a/src/containers/settings/EditServiceScreen.js b/src/containers/settings/EditServiceScreen.js
index e4ff03bb3..d18d7fb9b 100644
--- a/src/containers/settings/EditServiceScreen.js
+++ b/src/containers/settings/EditServiceScreen.js
@@ -92,6 +92,10 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
92 intl: intlShape, 92 intl: intlShape,
93 }; 93 };
94 94
95 state = {
96 isOpeningDarkModeCss: false,
97 }
98
95 onSubmit(data) { 99 onSubmit(data) {
96 const { action } = this.props.router.params; 100 const { action } = this.props.router.params;
97 const { recipes, services } = this.props.stores; 101 const { recipes, services } = this.props.stores;
@@ -278,6 +282,28 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
278 } 282 }
279 } 283 }
280 284
285 openDarkmodeCss() {
286 const { openDarkmodeCss } = this.props.actions.service;
287 const { action } = this.props.router.params;
288
289 if (action === 'edit') {
290 this.setState({
291 isOpeningDarkModeCss: true,
292 });
293
294 const { activeSettings: service } = this.props.stores.services;
295 openDarkmodeCss({
296 recipe: service.recipe.id,
297 });
298
299 setTimeout(() => {
300 this.setState({
301 isOpeningDarkModeCss: false,
302 });
303 }, 2500);
304 }
305 }
306
281 render() { 307 render() {
282 const { recipes, services, user } = this.props.stores; 308 const { recipes, services, user } = this.props.stores;
283 const { action } = this.props.router.params; 309 const { action } = this.props.router.params;
@@ -329,6 +355,8 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
329 isDeleting={services.deleteServiceRequest.isExecuting} 355 isDeleting={services.deleteServiceRequest.isExecuting}
330 onSubmit={d => this.onSubmit(d)} 356 onSubmit={d => this.onSubmit(d)}
331 onDelete={() => this.deleteService()} 357 onDelete={() => this.deleteService()}
358 openDarkmodeCss={() => this.openDarkmodeCss()}
359 isOpeningDarkModeCss={this.state.isOpeningDarkModeCss}
332 isProxyFeatureEnabled={proxyFeature.isEnabled} 360 isProxyFeatureEnabled={proxyFeature.isEnabled}
333 isServiceProxyIncludedInCurrentPlan={proxyFeature.isIncludedInCurrentPlan} 361 isServiceProxyIncludedInCurrentPlan={proxyFeature.isIncludedInCurrentPlan}
334 isSpellcheckerIncludedInCurrentPlan={spellcheckerFeature.isIncludedInCurrentPlan} 362 isSpellcheckerIncludedInCurrentPlan={spellcheckerFeature.isIncludedInCurrentPlan}
@@ -356,6 +384,7 @@ EditServiceScreen.wrappedComponent.propTypes = {
356 createService: PropTypes.func.isRequired, 384 createService: PropTypes.func.isRequired,
357 updateService: PropTypes.func.isRequired, 385 updateService: PropTypes.func.isRequired,
358 deleteService: PropTypes.func.isRequired, 386 deleteService: PropTypes.func.isRequired,
387 openDarkmodeCss: PropTypes.func.isRequired,
359 }).isRequired, 388 }).isRequired,
360 // settings: PropTypes.shape({ 389 // settings: PropTypes.shape({
361 // update: PropTypes.func.isRequred, 390 // update: PropTypes.func.isRequred,
diff --git a/src/containers/settings/EditSettingsScreen.js b/src/containers/settings/EditSettingsScreen.js
index 698b5a3d9..ddee82e45 100644
--- a/src/containers/settings/EditSettingsScreen.js
+++ b/src/containers/settings/EditSettingsScreen.js
@@ -1,3 +1,4 @@
1import { ipcRenderer } from 'electron';
1import React, { Component } from 'react'; 2import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 3import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react'; 4import { inject, observer } from 'mobx-react';
@@ -9,7 +10,7 @@ import UserStore from '../../stores/UserStore';
9import TodosStore from '../../features/todos/store'; 10import TodosStore from '../../features/todos/store';
10import Form from '../../lib/Form'; 11import Form from '../../lib/Form';
11import { APP_LOCALES, SPELLCHECKER_LOCALES } from '../../i18n/languages'; 12import { APP_LOCALES, SPELLCHECKER_LOCALES } from '../../i18n/languages';
12import { DEFAULT_APP_SETTINGS } from '../../config'; 13import { DEFAULT_APP_SETTINGS, DEFAULT_LOCK_PASSWORD, HIBERNATION_STRATEGIES } from '../../config';
13import { config as spellcheckerConfig } from '../../features/spellchecker'; 14import { config as spellcheckerConfig } from '../../features/spellchecker';
14 15
15import { getSelectOptions } from '../../helpers/i18n-helpers'; 16import { getSelectOptions } from '../../helpers/i18n-helpers';
@@ -17,6 +18,8 @@ import { getSelectOptions } from '../../helpers/i18n-helpers';
17import EditSettingsForm from '../../components/settings/settings/EditSettingsForm'; 18import EditSettingsForm from '../../components/settings/settings/EditSettingsForm';
18import ErrorBoundary from '../../components/util/ErrorBoundary'; 19import ErrorBoundary from '../../components/util/ErrorBoundary';
19 20
21import { API, TODOS_FRONTEND } from '../../environment';
22
20import globalMessages from '../../i18n/globalMessages'; 23import globalMessages from '../../i18n/globalMessages';
21import { DEFAULT_IS_FEATURE_ENABLED_BY_USER } from '../../features/todos'; 24import { DEFAULT_IS_FEATURE_ENABLED_BY_USER } from '../../features/todos';
22import WorkspacesStore from '../../features/workspaces/store'; 25import WorkspacesStore from '../../features/workspaces/store';
@@ -25,7 +28,7 @@ import { DEFAULT_SETTING_KEEP_ALL_WORKSPACES_LOADED } from '../../features/works
25const messages = defineMessages({ 28const messages = defineMessages({
26 autoLaunchOnStart: { 29 autoLaunchOnStart: {
27 id: 'settings.app.form.autoLaunchOnStart', 30 id: 'settings.app.form.autoLaunchOnStart',
28 defaultMessage: '!!!Launch Franz on start', 31 defaultMessage: '!!!Launch Ferdi on start',
29 }, 32 },
30 autoLaunchInBackground: { 33 autoLaunchInBackground: {
31 id: 'settings.app.form.autoLaunchInBackground', 34 id: 'settings.app.form.autoLaunchInBackground',
@@ -33,15 +36,59 @@ const messages = defineMessages({
33 }, 36 },
34 runInBackground: { 37 runInBackground: {
35 id: 'settings.app.form.runInBackground', 38 id: 'settings.app.form.runInBackground',
36 defaultMessage: '!!!Keep Franz in background when closing the window', 39 defaultMessage: '!!!Keep Ferdi in background when closing the window',
37 }, 40 },
38 enableSystemTray: { 41 enableSystemTray: {
39 id: 'settings.app.form.enableSystemTray', 42 id: 'settings.app.form.enableSystemTray',
40 defaultMessage: '!!!Show Franz in system tray', 43 defaultMessage: '!!!Show Ferdi in system tray',
41 }, 44 },
42 minimizeToSystemTray: { 45 minimizeToSystemTray: {
43 id: 'settings.app.form.minimizeToSystemTray', 46 id: 'settings.app.form.minimizeToSystemTray',
44 defaultMessage: '!!!Minimize Franz to system tray', 47 defaultMessage: '!!!Minimize Ferdi to system tray',
48 },
49 privateNotifications: {
50 id: 'settings.app.form.privateNotifications',
51 defaultMessage: '!!!Don\'t show message content in notifications',
52 },
53 showServiceNavigationBar: {
54 id: 'settings.app.form.showServiceNavigationBar',
55 defaultMessage: '!!!Always show service navigation bar',
56 },
57 hibernate: {
58 id: 'settings.app.form.hibernate',
59 defaultMessage: '!!!Enable service hibernation',
60 },
61 hibernationStrategy: {
62 id: 'settings.app.form.hibernationStrategy',
63 defaultMessage: '!!!Hibernation strategy',
64 },
65 server: {
66 id: 'settings.app.form.server',
67 defaultMessage: '!!!Server',
68 },
69 todoServer: {
70 id: 'settings.app.form.todoServer',
71 defaultMessage: '!!!Todo Server',
72 },
73 enableLock: {
74 id: 'settings.app.form.enableLock',
75 defaultMessage: '!!!Enable Ferdi password lock',
76 },
77 lockPassword: {
78 id: 'settings.app.form.lockPassword',
79 defaultMessage: '!!!Ferdi Lock password',
80 },
81 scheduledDNDEnabled: {
82 id: 'settings.app.form.scheduledDNDEnabled',
83 defaultMessage: '!!!Enable scheduled Do-not-Disturb',
84 },
85 scheduledDNDStart: {
86 id: 'settings.app.form.scheduledDNDStart',
87 defaultMessage: '!!!From',
88 },
89 scheduledDNDEnd: {
90 id: 'settings.app.form.scheduledDNDEnd',
91 defaultMessage: '!!!To',
45 }, 92 },
46 language: { 93 language: {
47 id: 'settings.app.form.language', 94 id: 'settings.app.form.language',
@@ -51,6 +98,14 @@ const messages = defineMessages({
51 id: 'settings.app.form.darkMode', 98 id: 'settings.app.form.darkMode',
52 defaultMessage: '!!!Dark Mode', 99 defaultMessage: '!!!Dark Mode',
53 }, 100 },
101 universalDarkMode: {
102 id: 'settings.app.form.universalDarkMode',
103 defaultMessage: '!!!Enable universal Dark Mode',
104 },
105 accentColor: {
106 id: 'settings.app.form.accentColor',
107 defaultMessage: '!!!Accent color',
108 },
54 showDisabledServices: { 109 showDisabledServices: {
55 id: 'settings.app.form.showDisabledServices', 110 id: 'settings.app.form.showDisabledServices',
56 defaultMessage: '!!!Display disabled services tabs', 111 defaultMessage: '!!!Display disabled services tabs',
@@ -71,6 +126,10 @@ const messages = defineMessages({
71 id: 'settings.app.form.beta', 126 id: 'settings.app.form.beta',
72 defaultMessage: '!!!Include beta versions', 127 defaultMessage: '!!!Include beta versions',
73 }, 128 },
129 noUpdates: {
130 id: 'settings.app.form.noUpdates',
131 defaultMessage: '!!!Disable updates',
132 },
74 enableTodos: { 133 enableTodos: {
75 id: 'settings.app.form.enableTodos', 134 id: 'settings.app.form.enableTodos',
76 defaultMessage: '!!!Enable Franz Todos', 135 defaultMessage: '!!!Enable Franz Todos',
@@ -107,19 +166,34 @@ export default @inject('stores', 'actions') @observer class EditSettingsScreen e
107 runInBackground: settingsData.runInBackground, 166 runInBackground: settingsData.runInBackground,
108 enableSystemTray: settingsData.enableSystemTray, 167 enableSystemTray: settingsData.enableSystemTray,
109 minimizeToSystemTray: settingsData.minimizeToSystemTray, 168 minimizeToSystemTray: settingsData.minimizeToSystemTray,
169 privateNotifications: settingsData.privateNotifications,
170 showServiceNavigationBar: settingsData.showServiceNavigationBar,
171 hibernate: settingsData.hibernate,
172 hibernationStrategy: settingsData.hibernationStrategy,
173 server: settingsData.server,
174 todoServer: settingsData.todoServer,
175 lockingFeatureEnabled: settingsData.lockingFeatureEnabled,
176 lockedPassword: settingsData.lockedPassword,
177 scheduledDNDEnabled: settingsData.scheduledDNDEnabled,
178 scheduledDNDStart: settingsData.scheduledDNDStart,
179 scheduledDNDEnd: settingsData.scheduledDNDEnd,
110 enableGPUAcceleration: settingsData.enableGPUAcceleration, 180 enableGPUAcceleration: settingsData.enableGPUAcceleration,
111 showDisabledServices: settingsData.showDisabledServices, 181 showDisabledServices: settingsData.showDisabledServices,
112 darkMode: settingsData.darkMode, 182 darkMode: settingsData.darkMode,
183 universalDarkMode: settingsData.universalDarkMode,
184 accentColor: settingsData.accentColor,
113 showMessageBadgeWhenMuted: settingsData.showMessageBadgeWhenMuted, 185 showMessageBadgeWhenMuted: settingsData.showMessageBadgeWhenMuted,
114 enableSpellchecking: settingsData.enableSpellchecking, 186 enableSpellchecking: settingsData.enableSpellchecking,
115 spellcheckerLanguage: settingsData.spellcheckerLanguage, 187 spellcheckerLanguage: settingsData.spellcheckerLanguage,
116 beta: settingsData.beta, // we need this info in the main process as well 188 beta: settingsData.beta, // we need this info in the main process as well
189 noUpdates: settingsData.noUpdates, // we need this info in the main process as well
117 locale: settingsData.locale, // we need this info in the main process as well 190 locale: settingsData.locale, // we need this info in the main process as well
118 }, 191 },
119 }); 192 });
120 193
121 user.update({ 194 user.update({
122 userData: { 195 userData: {
196 noUpdates: settingsData.noUpdates,
123 beta: settingsData.beta, 197 beta: settingsData.beta,
124 locale: settingsData.locale, 198 locale: settingsData.locale,
125 }, 199 },
@@ -140,6 +214,10 @@ export default @inject('stores', 'actions') @observer class EditSettingsScreen e
140 } 214 }
141 } 215 }
142 216
217 openProcessManager() {
218 ipcRenderer.send('openProcessManager');
219 }
220
143 prepareForm() { 221 prepareForm() {
144 const { 222 const {
145 app, settings, user, todos, workspaces, 223 app, settings, user, todos, workspaces,
@@ -150,6 +228,11 @@ export default @inject('stores', 'actions') @observer class EditSettingsScreen e
150 locales: APP_LOCALES, 228 locales: APP_LOCALES,
151 }); 229 });
152 230
231 const hibernationStrategies = getSelectOptions({
232 locales: HIBERNATION_STRATEGIES,
233 sort: false,
234 });
235
153 const spellcheckingLanguages = getSelectOptions({ 236 const spellcheckingLanguages = getSelectOptions({
154 locales: SPELLCHECKER_LOCALES, 237 locales: SPELLCHECKER_LOCALES,
155 automaticDetectionText: this.context.intl.formatMessage(globalMessages.spellcheckerAutomaticDetection), 238 automaticDetectionText: this.context.intl.formatMessage(globalMessages.spellcheckerAutomaticDetection),
@@ -182,6 +265,65 @@ export default @inject('stores', 'actions') @observer class EditSettingsScreen e
182 value: settings.all.app.minimizeToSystemTray, 265 value: settings.all.app.minimizeToSystemTray,
183 default: DEFAULT_APP_SETTINGS.minimizeToSystemTray, 266 default: DEFAULT_APP_SETTINGS.minimizeToSystemTray,
184 }, 267 },
268 privateNotifications: {
269 label: intl.formatMessage(messages.privateNotifications),
270 value: settings.all.app.privateNotifications,
271 default: DEFAULT_APP_SETTINGS.privateNotifications,
272 },
273 showServiceNavigationBar: {
274 label: intl.formatMessage(messages.showServiceNavigationBar),
275 value: settings.all.app.showServiceNavigationBar,
276 default: DEFAULT_APP_SETTINGS.showServiceNavigationBar,
277 },
278 hibernate: {
279 label: intl.formatMessage(messages.hibernate),
280 value: settings.all.app.hibernate,
281 default: DEFAULT_APP_SETTINGS.hibernate,
282 },
283 hibernationStrategy: {
284 label: intl.formatMessage(messages.hibernationStrategy),
285 value: settings.all.app.hibernationStrategy,
286 options: hibernationStrategies,
287 default: DEFAULT_APP_SETTINGS.hibernationStrategy,
288 },
289 server: {
290 label: intl.formatMessage(messages.server),
291 value: settings.all.app.server || API,
292 default: API,
293 },
294 todoServer: {
295 label: intl.formatMessage(messages.todoServer),
296 value: settings.all.app.todoServer,
297 default: TODOS_FRONTEND,
298 },
299 lockingFeatureEnabled: {
300 label: intl.formatMessage(messages.enableLock),
301 value: settings.all.app.lockingFeatureEnabled || false,
302 default: false,
303 },
304 lockedPassword: {
305 label: intl.formatMessage(messages.lockPassword),
306 value: settings.all.app.lockedPassword,
307 default: DEFAULT_LOCK_PASSWORD,
308 type: 'password',
309 },
310 scheduledDNDEnabled: {
311 label: intl.formatMessage(messages.scheduledDNDEnabled),
312 value: settings.all.app.scheduledDNDEnabled || false,
313 default: false,
314 },
315 scheduledDNDStart: {
316 label: intl.formatMessage(messages.scheduledDNDStart),
317 value: settings.all.app.scheduledDNDStart,
318 default: '17:00',
319 type: 'time',
320 },
321 scheduledDNDEnd: {
322 label: intl.formatMessage(messages.scheduledDNDEnd),
323 value: settings.all.app.scheduledDNDEnd,
324 default: '09:00',
325 type: 'time',
326 },
185 showDisabledServices: { 327 showDisabledServices: {
186 label: intl.formatMessage(messages.showDisabledServices), 328 label: intl.formatMessage(messages.showDisabledServices),
187 value: settings.all.app.showDisabledServices, 329 value: settings.all.app.showDisabledServices,
@@ -208,6 +350,16 @@ export default @inject('stores', 'actions') @observer class EditSettingsScreen e
208 value: settings.all.app.darkMode, 350 value: settings.all.app.darkMode,
209 default: DEFAULT_APP_SETTINGS.darkMode, 351 default: DEFAULT_APP_SETTINGS.darkMode,
210 }, 352 },
353 universalDarkMode: {
354 label: intl.formatMessage(messages.universalDarkMode),
355 value: settings.all.app.universalDarkMode,
356 default: DEFAULT_APP_SETTINGS.universalDarkMode,
357 },
358 accentColor: {
359 label: intl.formatMessage(messages.accentColor),
360 value: settings.all.app.accentColor,
361 default: DEFAULT_APP_SETTINGS.accentColor,
362 },
211 enableGPUAcceleration: { 363 enableGPUAcceleration: {
212 label: intl.formatMessage(messages.enableGPUAcceleration), 364 label: intl.formatMessage(messages.enableGPUAcceleration),
213 value: settings.all.app.enableGPUAcceleration, 365 value: settings.all.app.enableGPUAcceleration,
@@ -224,6 +376,11 @@ export default @inject('stores', 'actions') @observer class EditSettingsScreen e
224 value: user.data.beta, 376 value: user.data.beta,
225 default: DEFAULT_APP_SETTINGS.beta, 377 default: DEFAULT_APP_SETTINGS.beta,
226 }, 378 },
379 noUpdates: {
380 label: intl.formatMessage(messages.noUpdates),
381 value: settings.app.noUpdates,
382 default: DEFAULT_APP_SETTINGS.noUpdates,
383 },
227 }, 384 },
228 }; 385 };
229 386
@@ -257,6 +414,7 @@ export default @inject('stores', 'actions') @observer class EditSettingsScreen e
257 cacheSize, 414 cacheSize,
258 updateStatusTypes, 415 updateStatusTypes,
259 isClearingAllCache, 416 isClearingAllCache,
417 lockingFeatureEnabled,
260 } = app; 418 } = app;
261 const { 419 const {
262 checkForUpdates, 420 checkForUpdates,
@@ -282,6 +440,12 @@ export default @inject('stores', 'actions') @observer class EditSettingsScreen e
282 isSpellcheckerIncludedInCurrentPlan={spellcheckerConfig.isIncludedInCurrentPlan} 440 isSpellcheckerIncludedInCurrentPlan={spellcheckerConfig.isIncludedInCurrentPlan}
283 isTodosEnabled={todos.isFeatureActive} 441 isTodosEnabled={todos.isFeatureActive}
284 isWorkspaceEnabled={workspaces.isFeatureActive} 442 isWorkspaceEnabled={workspaces.isFeatureActive}
443 server={this.props.stores.settings.app.server}
444 lockingFeatureEnabled={lockingFeatureEnabled}
445 noUpdates={this.props.stores.settings.app.noUpdates}
446 hibernationEnabled={this.props.stores.settings.app.hibernate}
447 isDarkmodeEnabled={this.props.stores.settings.app.darkMode}
448 openProcessManager={() => this.openProcessManager()}
285 /> 449 />
286 </ErrorBoundary> 450 </ErrorBoundary>
287 ); 451 );
diff --git a/src/containers/settings/RecipesScreen.js b/src/containers/settings/RecipesScreen.js
index 132820b6f..70b599d9c 100644
--- a/src/containers/settings/RecipesScreen.js
+++ b/src/containers/settings/RecipesScreen.js
@@ -1,4 +1,5 @@
1import { remote, shell } from 'electron'; 1import { remote, shell } from 'electron';
2import fs from 'fs-extra';
2import React, { Component } from 'react'; 3import React, { Component } from 'react';
3import PropTypes from 'prop-types'; 4import PropTypes from 'prop-types';
4import { autorun } from 'mobx'; 5import { autorun } from 'mobx';
@@ -12,9 +13,9 @@ import UserStore from '../../stores/UserStore';
12 13
13import RecipesDashboard from '../../components/settings/recipes/RecipesDashboard'; 14import RecipesDashboard from '../../components/settings/recipes/RecipesDashboard';
14import ErrorBoundary from '../../components/util/ErrorBoundary'; 15import ErrorBoundary from '../../components/util/ErrorBoundary';
15import { FRANZ_DEV_DOCS } from '../../config'; 16import { FRANZ_DEV_DOCS, RECIPES_PATH } from '../../config';
16import { gaEvent } from '../../lib/analytics';
17import { communityRecipesStore } from '../../features/communityRecipes'; 17import { communityRecipesStore } from '../../features/communityRecipes';
18import RecipePreview from '../../models/RecipePreview';
18 19
19const { app } = remote; 20const { app } = remote;
20 21
@@ -38,6 +39,14 @@ export default @inject('stores', 'actions') @observer class RecipesScreen extend
38 39
39 autorunDisposer = null; 40 autorunDisposer = null;
40 41
42 customRecipes = [];
43
44 constructor(props) {
45 super(props);
46
47 this.customRecipes = fs.readJsonSync(path.join(RECIPES_PATH, 'all.json'));
48 }
49
41 componentDidMount() { 50 componentDidMount() {
42 this.autorunDisposer = autorun(() => { 51 this.autorunDisposer = autorun(() => {
43 const { filter } = this.props.params; 52 const { filter } = this.props.params;
@@ -68,6 +77,27 @@ export default @inject('stores', 'actions') @observer class RecipesScreen extend
68 } 77 }
69 } 78 }
70 79
80
81 prepareRecipes(recipes) {
82 return recipes
83 // Filter out duplicate recipes
84 .filter((recipe, index, self) => {
85 const ids = self.map(rec => rec.id);
86 return ids.indexOf(recipe.id) === index;
87
88 // Sort alphabetically
89 }).sort((a, b) => {
90 if (a.id < b.id) { return -1; }
91 if (a.id > b.id) { return 1; }
92 return 0;
93 });
94 }
95
96 // Create an array of RecipePreviews from an array of recipe objects
97 createPreviews(recipes) {
98 return recipes.map(recipe => new RecipePreview(recipe));
99 }
100
71 resetSearch() { 101 resetSearch() {
72 this.setState({ needle: null }); 102 this.setState({ needle: null });
73 } 103 }
@@ -89,14 +119,25 @@ export default @inject('stores', 'actions') @observer class RecipesScreen extend
89 let recipeFilter; 119 let recipeFilter;
90 120
91 if (filter === 'all') { 121 if (filter === 'all') {
92 recipeFilter = recipePreviews.all; 122 recipeFilter = this.prepareRecipes([
123 ...recipePreviews.all,
124 ...this.createPreviews(this.customRecipes),
125 ]);
93 } else if (filter === 'dev') { 126 } else if (filter === 'dev') {
94 recipeFilter = communityRecipesStore.communityRecipes; 127 recipeFilter = communityRecipesStore.communityRecipes;
95 } else { 128 } else {
96 recipeFilter = recipePreviews.featured; 129 recipeFilter = recipePreviews.featured;
97 } 130 }
98 131
99 const allRecipes = this.state.needle ? recipePreviews.searchResults : recipeFilter; 132 const allRecipes = this.state.needle ? this.prepareRecipes([
133 // All search recipes from server
134 ...recipePreviews.searchResults,
135 // All search recipes from local recipes
136 ...this.createPreviews(
137 this.customRecipes
138 .filter(service => service.name.toLowerCase().includes(this.state.needle.toLowerCase())),
139 ),
140 ]) : recipeFilter;
100 141
101 const isLoading = recipePreviews.featuredRecipePreviewsRequest.isExecuting 142 const isLoading = recipePreviews.featuredRecipePreviewsRequest.isExecuting
102 || recipePreviews.allRecipePreviewsRequest.isExecuting 143 || recipePreviews.allRecipePreviewsRequest.isExecuting
@@ -122,11 +163,9 @@ export default @inject('stores', 'actions') @observer class RecipesScreen extend
122 recipeDirectory={recipeDirectory} 163 recipeDirectory={recipeDirectory}
123 openRecipeDirectory={() => { 164 openRecipeDirectory={() => {
124 shell.openItem(recipeDirectory); 165 shell.openItem(recipeDirectory);
125 gaEvent('Recipe', 'open-recipe-folder', 'Open Folder');
126 }} 166 }}
127 openDevDocs={() => { 167 openDevDocs={() => {
128 appActions.openExternalUrl({ url: FRANZ_DEV_DOCS }); 168 appActions.openExternalUrl({ url: FRANZ_DEV_DOCS });
129 gaEvent('Recipe', 'open-dev-docs', 'Developer Documentation');
130 }} 169 }}
131 isCommunityRecipesIncludedInCurrentPlan={communityRecipesStore.isCommunityRecipesIncludedInCurrentPlan} 170 isCommunityRecipesIncludedInCurrentPlan={communityRecipesStore.isCommunityRecipesIncludedInCurrentPlan}
132 isUserPremiumUser={user.isPremium} 171 isUserPremiumUser={user.isPremium}
diff --git a/src/containers/settings/SupportScreen.js b/src/containers/settings/SupportScreen.js
new file mode 100644
index 000000000..34dce1dae
--- /dev/null
+++ b/src/containers/settings/SupportScreen.js
@@ -0,0 +1,36 @@
1import React, { Component } from 'react';
2import { inject } from 'mobx-react';
3import PropTypes from 'prop-types';
4
5import SupportFerdi from '../../components/settings/supportFerdi/SupportFerdiDashboard';
6import ErrorBoundary from '../../components/util/ErrorBoundary';
7
8export default @inject('actions') class SupportScreen extends Component {
9 constructor(props) {
10 super(props);
11
12 this.openLink = this.openLink.bind(this);
13 }
14
15 openLink(url) {
16 this.props.actions.app.openExternalUrl({ url });
17 }
18
19 render() {
20 return (
21 <ErrorBoundary>
22 <SupportFerdi
23 openLink={this.openLink}
24 />
25 </ErrorBoundary>
26 );
27 }
28}
29
30SupportScreen.wrappedComponent.propTypes = {
31 actions: PropTypes.shape({
32 app: PropTypes.shape({
33 openExternalUrl: PropTypes.func.isRequired,
34 }).isRequired,
35 }).isRequired,
36};
diff --git a/src/containers/settings/TeamScreen.js b/src/containers/settings/TeamScreen.js
index f600c9947..d0196923a 100644
--- a/src/containers/settings/TeamScreen.js
+++ b/src/containers/settings/TeamScreen.js
@@ -4,6 +4,7 @@ import { inject, observer } from 'mobx-react';
4 4
5import UserStore from '../../stores/UserStore'; 5import UserStore from '../../stores/UserStore';
6import AppStore from '../../stores/AppStore'; 6import AppStore from '../../stores/AppStore';
7import SettingsStore from '../../stores/SettingsStore';
7 8
8import TeamDashboard from '../../components/settings/team/TeamDashboard'; 9import TeamDashboard from '../../components/settings/team/TeamDashboard';
9import ErrorBoundary from '../../components/util/ErrorBoundary'; 10import ErrorBoundary from '../../components/util/ErrorBoundary';
@@ -19,9 +20,10 @@ export default @inject('stores', 'actions') @observer class TeamScreen extends C
19 } 20 }
20 21
21 render() { 22 render() {
22 const { user } = this.props.stores; 23 const { user, settings } = this.props.stores;
23 24
24 const isLoadingUserInfo = user.getUserInfoRequest.isExecuting; 25 const isLoadingUserInfo = user.getUserInfoRequest.isExecuting;
26 const { server } = settings.app;
25 27
26 return ( 28 return (
27 <ErrorBoundary> 29 <ErrorBoundary>
@@ -31,6 +33,7 @@ export default @inject('stores', 'actions') @observer class TeamScreen extends C
31 retryUserInfoRequest={() => this.reloadData()} 33 retryUserInfoRequest={() => this.reloadData()}
32 openTeamManagement={() => this.handleWebsiteLink('/user/team')} 34 openTeamManagement={() => this.handleWebsiteLink('/user/team')}
33 isProUser={user.isPro} 35 isProUser={user.isPro}
36 server={server}
34 /> 37 />
35 </ErrorBoundary> 38 </ErrorBoundary>
36 ); 39 );
@@ -41,6 +44,7 @@ TeamScreen.wrappedComponent.propTypes = {
41 stores: PropTypes.shape({ 44 stores: PropTypes.shape({
42 user: PropTypes.instanceOf(UserStore).isRequired, 45 user: PropTypes.instanceOf(UserStore).isRequired,
43 app: PropTypes.instanceOf(AppStore).isRequired, 46 app: PropTypes.instanceOf(AppStore).isRequired,
47 settings: PropTypes.instanceOf(SettingsStore).isRequired,
44 }).isRequired, 48 }).isRequired,
45 actions: PropTypes.shape({ 49 actions: PropTypes.shape({
46 payment: PropTypes.shape({ 50 payment: PropTypes.shape({