aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/auth/AuthLayout.js6
-rw-r--r--src/components/layout/AppLayout.js126
-rw-r--r--src/containers/auth/AuthLayoutContainer.js10
-rw-r--r--src/containers/layout/AppLayoutContainer.js1
-rw-r--r--src/containers/settings/SettingsWindow.js35
-rw-r--r--src/features/announcements/components/AnnouncementScreen.js2
-rw-r--r--src/features/delayApp/index.js5
-rw-r--r--src/features/delayApp/styles.js1
-rw-r--r--src/features/workspaces/store.js21
-rw-r--r--src/index.html1
-rw-r--r--src/stores/RecipesStore.js27
-rw-r--r--src/stores/ServicesStore.js18
-rw-r--r--src/stores/UIStore.js30
-rw-r--r--src/stores/lib/Reaction.js6
-rw-r--r--src/styles/layout.scss2
-rw-r--r--src/styles/reset.scss2
-rw-r--r--src/webview/contextMenu.js5
-rw-r--r--src/webview/spellchecker.js17
18 files changed, 179 insertions, 136 deletions
diff --git a/src/components/auth/AuthLayout.js b/src/components/auth/AuthLayout.js
index 3d43d4e5c..75a8cfc61 100644
--- a/src/components/auth/AuthLayout.js
+++ b/src/components/auth/AuthLayout.js
@@ -22,7 +22,6 @@ export default @observer class AuthLayout extends Component {
22 retryHealthCheck: PropTypes.func.isRequired, 22 retryHealthCheck: PropTypes.func.isRequired,
23 isHealthCheckLoading: PropTypes.bool.isRequired, 23 isHealthCheckLoading: PropTypes.bool.isRequired,
24 isFullScreen: PropTypes.bool.isRequired, 24 isFullScreen: PropTypes.bool.isRequired,
25 darkMode: PropTypes.bool.isRequired,
26 nextAppReleaseVersion: PropTypes.string, 25 nextAppReleaseVersion: PropTypes.string,
27 installAppUpdate: PropTypes.func.isRequired, 26 installAppUpdate: PropTypes.func.isRequired,
28 appUpdateIsDownloaded: PropTypes.bool.isRequired, 27 appUpdateIsDownloaded: PropTypes.bool.isRequired,
@@ -45,7 +44,6 @@ export default @observer class AuthLayout extends Component {
45 retryHealthCheck, 44 retryHealthCheck,
46 isHealthCheckLoading, 45 isHealthCheckLoading,
47 isFullScreen, 46 isFullScreen,
48 darkMode,
49 nextAppReleaseVersion, 47 nextAppReleaseVersion,
50 installAppUpdate, 48 installAppUpdate,
51 appUpdateIsDownloaded, 49 appUpdateIsDownloaded,
@@ -53,7 +51,7 @@ export default @observer class AuthLayout extends Component {
53 const { intl } = this.context; 51 const { intl } = this.context;
54 52
55 return ( 53 return (
56 <div className={darkMode ? 'theme__dark' : ''}> 54 <>
57 {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon="assets/images/logo.svg" />} 55 {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon="assets/images/logo.svg" />}
58 <div className="auth"> 56 <div className="auth">
59 {!isOnline && ( 57 {!isOnline && (
@@ -93,7 +91,7 @@ export default @observer class AuthLayout extends Component {
93 <img src="./assets/images/adlk.svg" alt="" /> 91 <img src="./assets/images/adlk.svg" alt="" />
94 </Link> 92 </Link>
95 </div> 93 </div>
96 </div> 94 </>
97 ); 95 );
98 } 96 }
99} 97}
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js
index 499bc097a..ebb9849ea 100644
--- a/src/components/layout/AppLayout.js
+++ b/src/components/layout/AppLayout.js
@@ -68,7 +68,6 @@ class AppLayout extends Component {
68 areRequiredRequestsSuccessful: PropTypes.bool.isRequired, 68 areRequiredRequestsSuccessful: PropTypes.bool.isRequired,
69 retryRequiredRequests: PropTypes.func.isRequired, 69 retryRequiredRequests: PropTypes.func.isRequired,
70 areRequiredRequestsLoading: PropTypes.bool.isRequired, 70 areRequiredRequestsLoading: PropTypes.bool.isRequired,
71 darkMode: PropTypes.bool.isRequired,
72 isDelayAppScreenVisible: PropTypes.bool.isRequired, 71 isDelayAppScreenVisible: PropTypes.bool.isRequired,
73 }; 72 };
74 73
@@ -101,7 +100,6 @@ class AppLayout extends Component {
101 areRequiredRequestsSuccessful, 100 areRequiredRequestsSuccessful,
102 retryRequiredRequests, 101 retryRequiredRequests,
103 areRequiredRequestsLoading, 102 areRequiredRequestsLoading,
104 darkMode,
105 isDelayAppScreenVisible, 103 isDelayAppScreenVisible,
106 } = this.props; 104 } = this.props;
107 105
@@ -109,69 +107,67 @@ class AppLayout extends Component {
109 107
110 return ( 108 return (
111 <ErrorBoundary> 109 <ErrorBoundary>
112 <div className={(darkMode ? 'theme__dark' : '')}> 110 <div className="app">
113 <div className="app"> 111 {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon="assets/images/logo.svg" />}
114 {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon="assets/images/logo.svg" />} 112 <div className={`app__content ${classes.appContent}`}>
115 <div className={`app__content ${classes.appContent}`}> 113 {workspacesDrawer}
116 {workspacesDrawer} 114 {sidebar}
117 {sidebar} 115 <div className="app__service">
118 <div className="app__service"> 116 <WorkspaceSwitchingIndicator />
119 <WorkspaceSwitchingIndicator /> 117 {news.length > 0 && news.map(item => (
120 {news.length > 0 && news.map(item => ( 118 <InfoBar
121 <InfoBar 119 key={item.id}
122 key={item.id} 120 position="top"
123 position="top" 121 type={item.type}
124 type={item.type} 122 sticky={item.sticky}
125 sticky={item.sticky} 123 onHide={() => removeNewsItem({ newsId: item.id })}
126 onHide={() => removeNewsItem({ newsId: item.id })} 124 >
127 > 125 <span dangerouslySetInnerHTML={createMarkup(item.message)} />
128 <span dangerouslySetInnerHTML={createMarkup(item.message)} /> 126 </InfoBar>
129 </InfoBar> 127 ))}
130 ))} 128 {/* {!isOnline && (
131 {/* {!isOnline && ( 129 <InfoBar
132 <InfoBar 130 type="danger"
133 type="danger" 131 sticky
134 sticky 132 >
135 > 133 <span className="mdi mdi-flash" />
136 <span className="mdi mdi-flash" /> 134 {intl.formatMessage(globalMessages.notConnectedToTheInternet)}
137 {intl.formatMessage(globalMessages.notConnectedToTheInternet)} 135 </InfoBar>
138 </InfoBar> 136 )} */}
139 )} */} 137 {!areRequiredRequestsSuccessful && showRequiredRequestsError && (
140 {!areRequiredRequestsSuccessful && showRequiredRequestsError && ( 138 <InfoBar
141 <InfoBar 139 type="danger"
142 type="danger" 140 ctaLabel="Try again"
143 ctaLabel="Try again" 141 ctaLoading={areRequiredRequestsLoading}
144 ctaLoading={areRequiredRequestsLoading} 142 sticky
145 sticky 143 onClick={retryRequiredRequests}
146 onClick={retryRequiredRequests} 144 >
147 > 145 <span className="mdi mdi-flash" />
148 <span className="mdi mdi-flash" /> 146 {intl.formatMessage(messages.requiredRequestsFailed)}
149 {intl.formatMessage(messages.requiredRequestsFailed)} 147 </InfoBar>
150 </InfoBar> 148 )}
151 )} 149 {showServicesUpdatedInfoBar && (
152 {showServicesUpdatedInfoBar && ( 150 <InfoBar
153 <InfoBar 151 type="primary"
154 type="primary" 152 ctaLabel={intl.formatMessage(messages.buttonReloadServices)}
155 ctaLabel={intl.formatMessage(messages.buttonReloadServices)} 153 onClick={reloadServicesAfterUpdate}
156 onClick={reloadServicesAfterUpdate} 154 sticky
157 sticky 155 >
158 > 156 <span className="mdi mdi-power-plug" />
159 <span className="mdi mdi-power-plug" /> 157 {intl.formatMessage(messages.servicesUpdated)}
160 {intl.formatMessage(messages.servicesUpdated)} 158 </InfoBar>
161 </InfoBar> 159 )}
162 )} 160 {appUpdateIsDownloaded && (
163 {appUpdateIsDownloaded && ( 161 <AppUpdateInfoBar
164 <AppUpdateInfoBar 162 nextAppReleaseVersion={nextAppReleaseVersion}
165 nextAppReleaseVersion={nextAppReleaseVersion} 163 onInstallUpdate={installAppUpdate}
166 onInstallUpdate={installAppUpdate} 164 />
167 /> 165 )}
168 )} 166 {isDelayAppScreenVisible && (<DelayApp />)}
169 {isDelayAppScreenVisible && (<DelayApp />)} 167 <BasicAuth />
170 <BasicAuth /> 168 <ShareFranz />
171 <ShareFranz /> 169 {services}
172 {services} 170 {children}
173 {children}
174 </div>
175 </div> 171 </div>
176 </div> 172 </div>
177 </div> 173 </div>
diff --git a/src/containers/auth/AuthLayoutContainer.js b/src/containers/auth/AuthLayoutContainer.js
index 1f9c1ea61..427054d3d 100644
--- a/src/containers/auth/AuthLayoutContainer.js
+++ b/src/containers/auth/AuthLayoutContainer.js
@@ -2,7 +2,6 @@ import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react'; 3import { inject, observer } from 'mobx-react';
4import { ThemeProvider } from 'react-jss'; 4import { ThemeProvider } from 'react-jss';
5import { theme } from '@meetfranz/theme';
6 5
7import AuthLayout from '../../components/auth/AuthLayout'; 6import AuthLayout from '../../components/auth/AuthLayout';
8import AppStore from '../../stores/AppStore'; 7import AppStore from '../../stores/AppStore';
@@ -24,24 +23,22 @@ export default @inject('stores', 'actions') @observer class AuthLayoutContainer
24 stores, actions, children, location, 23 stores, actions, children, location,
25 } = this.props; 24 } = this.props;
26 const { 25 const {
27 app, features, globalError, settings, 26 app, features, globalError,
28 } = stores; 27 } = stores;
29 28
30 const isLoadingBaseFeatures = features.defaultFeaturesRequest.isExecuting 29 const isLoadingBaseFeatures = features.defaultFeaturesRequest.isExecuting
31 && !features.defaultFeaturesRequest.wasExecuted; 30 && !features.defaultFeaturesRequest.wasExecuted;
32 31
33 const themeType = theme(settings.app.darkMode ? 'dark' : 'default');
34
35 if (isLoadingBaseFeatures) { 32 if (isLoadingBaseFeatures) {
36 return ( 33 return (
37 <ThemeProvider theme={theme(themeType)}> 34 <ThemeProvider theme={stores.ui.theme}>
38 <AppLoader /> 35 <AppLoader />
39 </ThemeProvider> 36 </ThemeProvider>
40 ); 37 );
41 } 38 }
42 39
43 return ( 40 return (
44 <ThemeProvider theme={theme(themeType)}> 41 <ThemeProvider theme={stores.ui.theme}>
45 <AuthLayout 42 <AuthLayout
46 error={globalError.response} 43 error={globalError.response}
47 pathname={location.pathname} 44 pathname={location.pathname}
@@ -50,7 +47,6 @@ export default @inject('stores', 'actions') @observer class AuthLayoutContainer
50 retryHealthCheck={actions.app.healthCheck} 47 retryHealthCheck={actions.app.healthCheck}
51 isHealthCheckLoading={app.healthCheckRequest.isExecuting} 48 isHealthCheckLoading={app.healthCheckRequest.isExecuting}
52 isFullScreen={app.isFullScreen} 49 isFullScreen={app.isFullScreen}
53 darkMode={app.isSystemDarkModeEnabled}
54 installAppUpdate={actions.app.installUpdate} 50 installAppUpdate={actions.app.installUpdate}
55 nextAppReleaseVersion={app.nextAppReleaseVersion} 51 nextAppReleaseVersion={app.nextAppReleaseVersion}
56 appUpdateIsDownloaded={app.updateStatus === app.updateStatusTypes.DOWNLOADED} 52 appUpdateIsDownloaded={app.updateStatus === app.updateStatusTypes.DOWNLOADED}
diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js
index d290a6094..cf3da71e8 100644
--- a/src/containers/layout/AppLayoutContainer.js
+++ b/src/containers/layout/AppLayoutContainer.js
@@ -148,7 +148,6 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
148 areRequiredRequestsSuccessful={requests.areRequiredRequestsSuccessful} 148 areRequiredRequestsSuccessful={requests.areRequiredRequestsSuccessful}
149 retryRequiredRequests={retryRequiredRequests} 149 retryRequiredRequests={retryRequiredRequests}
150 areRequiredRequestsLoading={requests.areRequiredRequestsLoading} 150 areRequiredRequestsLoading={requests.areRequiredRequestsLoading}
151 darkMode={settings.all.app.darkMode}
152 isDelayAppScreenVisible={delayAppState.isDelayAppScreenVisible} 151 isDelayAppScreenVisible={delayAppState.isDelayAppScreenVisible}
153 > 152 >
154 {React.Children.count(children) > 0 ? children : null} 153 {React.Children.count(children) > 0 ? children : null}
diff --git a/src/containers/settings/SettingsWindow.js b/src/containers/settings/SettingsWindow.js
index 663b9e2e4..440d32a46 100644
--- a/src/containers/settings/SettingsWindow.js
+++ b/src/containers/settings/SettingsWindow.js
@@ -1,4 +1,5 @@
1import React, { Component } from 'react'; 1import React, { Component } from 'react';
2import ReactDOM from 'react-dom';
2import PropTypes from 'prop-types'; 3import PropTypes from 'prop-types';
3import { observer, inject } from 'mobx-react'; 4import { observer, inject } from 'mobx-react';
4 5
@@ -10,10 +11,23 @@ import ErrorBoundary from '../../components/util/ErrorBoundary';
10import { workspaceStore } from '../../features/workspaces'; 11import { workspaceStore } from '../../features/workspaces';
11 12
12export default @inject('stores', 'actions') @observer class SettingsContainer extends Component { 13export default @inject('stores', 'actions') @observer class SettingsContainer extends Component {
14 portalRoot = document.querySelector('#portalContainer');
15
16 el = document.createElement('div');
17
18 componentDidMount() {
19 this.portalRoot.appendChild(this.el);
20 }
21
22 componentWillUnmount() {
23 this.portalRoot.removeChild(this.el);
24 }
25
13 render() { 26 render() {
14 const { children, stores } = this.props; 27 const { children, stores } = this.props;
15 const { closeSettings } = this.props.actions.ui; 28 const { closeSettings } = this.props.actions.ui;
16 29
30
17 const navigation = ( 31 const navigation = (
18 <Navigation 32 <Navigation
19 serviceCount={stores.services.all.length} 33 serviceCount={stores.services.all.length}
@@ -21,15 +35,18 @@ export default @inject('stores', 'actions') @observer class SettingsContainer ex
21 /> 35 />
22 ); 36 );
23 37
24 return ( 38 return ReactDOM.createPortal(
25 <ErrorBoundary> 39 (
26 <Layout 40 <ErrorBoundary>
27 navigation={navigation} 41 <Layout
28 closeSettings={closeSettings} 42 navigation={navigation}
29 > 43 closeSettings={closeSettings}
30 {children} 44 >
31 </Layout> 45 {children}
32 </ErrorBoundary> 46 </Layout>
47 </ErrorBoundary>
48 ),
49 this.el,
33 ); 50 );
34 } 51 }
35} 52}
diff --git a/src/features/announcements/components/AnnouncementScreen.js b/src/features/announcements/components/AnnouncementScreen.js
index e7c5fe395..03bd5ba41 100644
--- a/src/features/announcements/components/AnnouncementScreen.js
+++ b/src/features/announcements/components/AnnouncementScreen.js
@@ -28,7 +28,7 @@ const smallScreen = '1000px';
28const styles = theme => ({ 28const styles = theme => ({
29 container: { 29 container: {
30 background: theme.colorBackground, 30 background: theme.colorBackground,
31 position: 'absolute', 31 position: 'relative',
32 top: 0, 32 top: 0,
33 zIndex: 140, 33 zIndex: 140,
34 width: '100%', 34 width: '100%',
diff --git a/src/features/delayApp/index.js b/src/features/delayApp/index.js
index 67f0fc5e6..39fae3b20 100644
--- a/src/features/delayApp/index.js
+++ b/src/features/delayApp/index.js
@@ -33,7 +33,7 @@ export default function init(stores) {
33 }; 33 };
34 34
35 reaction( 35 reaction(
36 () => stores.user.isLoggedIn && stores.features.features.needToWaitToProceed && !stores.user.data.isPremium, 36 () => stores.user.isLoggedIn && stores.services.allServicesRequest.wasExecuted && stores.features.features.needToWaitToProceed && !stores.user.data.isPremium,
37 (isEnabled) => { 37 (isEnabled) => {
38 if (isEnabled) { 38 if (isEnabled) {
39 debug('Enabling `delayApp` feature'); 39 debug('Enabling `delayApp` feature');
@@ -45,6 +45,7 @@ export default function init(stores) {
45 45
46 autorun(() => { 46 autorun(() => {
47 if (stores.services.all.length === 0) { 47 if (stores.services.all.length === 0) {
48 debug('seas', stores.services.all.length);
48 shownAfterLaunch = true; 49 shownAfterLaunch = true;
49 return; 50 return;
50 } 51 }
@@ -64,7 +65,7 @@ export default function init(stores) {
64 debug('Resetting app delay'); 65 debug('Resetting app delay');
65 66
66 setVisibility(false); 67 setVisibility(false);
67 }, DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.wait + 1000); // timer needs to be able to hit 0 68 }, config.delayDuration + 1000); // timer needs to be able to hit 0
68 } 69 }
69 }); 70 });
70 } else { 71 } else {
diff --git a/src/features/delayApp/styles.js b/src/features/delayApp/styles.js
index 5c214cfdf..69c3c7a27 100644
--- a/src/features/delayApp/styles.js
+++ b/src/features/delayApp/styles.js
@@ -1,7 +1,6 @@
1export default theme => ({ 1export default theme => ({
2 container: { 2 container: {
3 background: theme.colorBackground, 3 background: theme.colorBackground,
4 position: 'absolute',
5 top: 0, 4 top: 0,
6 width: '100%', 5 width: '100%',
7 display: 'flex', 6 display: 'flex',
diff --git a/src/features/workspaces/store.js b/src/features/workspaces/store.js
index 07b16ff23..a82f6895c 100644
--- a/src/features/workspaces/store.js
+++ b/src/features/workspaces/store.js
@@ -79,7 +79,7 @@ export default class WorkspacesStore extends FeatureStore {
79 79
80 // ========== PUBLIC API ========= // 80 // ========== PUBLIC API ========= //
81 81
82 start(stores, actions) { 82 @action start(stores, actions) {
83 debug('WorkspacesStore::start'); 83 debug('WorkspacesStore::start');
84 this.stores = stores; 84 this.stores = stores;
85 this.actions = actions; 85 this.actions = actions;
@@ -104,7 +104,7 @@ export default class WorkspacesStore extends FeatureStore {
104 // REACTIONS 104 // REACTIONS
105 105
106 this._freeUserReactions = createReactions([ 106 this._freeUserReactions = createReactions([
107 this._stopPremiumActionsAndReactions, 107 this._disablePremiumFeatures,
108 this._openDrawerWithSettingsReaction, 108 this._openDrawerWithSettingsReaction,
109 this._setFeatureEnabledReaction, 109 this._setFeatureEnabledReaction,
110 this._setIsPremiumFeatureReaction, 110 this._setIsPremiumFeatureReaction,
@@ -123,10 +123,7 @@ export default class WorkspacesStore extends FeatureStore {
123 this.isFeatureActive = true; 123 this.isFeatureActive = true;
124 } 124 }
125 125
126 stop() { 126 @action reset() {
127 super.stop();
128 debug('WorkspacesStore::stop');
129 this.isFeatureActive = false;
130 this.activeWorkspace = null; 127 this.activeWorkspace = null;
131 this.nextWorkspace = null; 128 this.nextWorkspace = null;
132 this.workspaceBeingEdited = null; 129 this.workspaceBeingEdited = null;
@@ -134,6 +131,13 @@ export default class WorkspacesStore extends FeatureStore {
134 this.isWorkspaceDrawerOpen = false; 131 this.isWorkspaceDrawerOpen = false;
135 } 132 }
136 133
134 @action stop() {
135 super.stop();
136 debug('WorkspacesStore::stop');
137 this.reset();
138 this.isFeatureActive = false;
139 }
140
137 filterServicesByActiveWorkspace = (services) => { 141 filterServicesByActiveWorkspace = (services) => {
138 const { activeWorkspace, isFeatureActive } = this; 142 const { activeWorkspace, isFeatureActive } = this;
139 if (isFeatureActive && activeWorkspace) { 143 if (isFeatureActive && activeWorkspace) {
@@ -281,6 +285,7 @@ export default class WorkspacesStore extends FeatureStore {
281 }; 285 };
282 286
283 _activateLastUsedWorkspaceReaction = () => { 287 _activateLastUsedWorkspaceReaction = () => {
288 debug('_activateLastUsedWorkspaceReaction');
284 if (!this.activeWorkspace && this.userHasWorkspaces) { 289 if (!this.activeWorkspace && this.userHasWorkspaces) {
285 const { lastActiveWorkspace } = this.settings; 290 const { lastActiveWorkspace } = this.settings;
286 if (lastActiveWorkspace) { 291 if (lastActiveWorkspace) {
@@ -324,10 +329,12 @@ export default class WorkspacesStore extends FeatureStore {
324 }); 329 });
325 }; 330 };
326 331
327 _stopPremiumActionsAndReactions = () => { 332 _disablePremiumFeatures = () => {
328 if (!this.isUserAllowedToUseFeature) { 333 if (!this.isUserAllowedToUseFeature) {
334 debug('_disablePremiumFeatures');
329 this._stopActions(this._premiumUserActions); 335 this._stopActions(this._premiumUserActions);
330 this._stopReactions(this._premiumUserReactions); 336 this._stopReactions(this._premiumUserReactions);
337 this.reset();
331 } else { 338 } else {
332 this._startActions(this._premiumUserActions); 339 this._startActions(this._premiumUserActions);
333 this._startReactions(this._premiumUserReactions); 340 this._startReactions(this._premiumUserReactions);
diff --git a/src/index.html b/src/index.html
index bf15e2d4e..f29aa2686 100644
--- a/src/index.html
+++ b/src/index.html
@@ -10,6 +10,7 @@
10 <div class="window-draggable"></div> 10 <div class="window-draggable"></div>
11 <div class="dev-warning">DEV MODE</div> 11 <div class="dev-warning">DEV MODE</div>
12 <div id="root"></div> 12 <div id="root"></div>
13 <div id="portalContainer"></div>
13 <script> 14 <script>
14 document.querySelector('body').classList.add(process.env.OS_PLATFORM ? process.env.OS_PLATFORM : process.platform); 15 document.querySelector('body').classList.add(process.env.OS_PLATFORM ? process.env.OS_PLATFORM : process.platform);
15 16
diff --git a/src/stores/RecipesStore.js b/src/stores/RecipesStore.js
index ab64bf79c..d51192078 100644
--- a/src/stores/RecipesStore.js
+++ b/src/stores/RecipesStore.js
@@ -20,6 +20,11 @@ export default class RecipesStore extends Store {
20 // Register action handlers 20 // Register action handlers
21 this.actions.recipe.install.listen(this._install.bind(this)); 21 this.actions.recipe.install.listen(this._install.bind(this));
22 this.actions.recipe.update.listen(this._update.bind(this)); 22 this.actions.recipe.update.listen(this._update.bind(this));
23
24 // Reactions
25 this.registerReactions([
26 this._checkIfRecipeIsInstalled.bind(this),
27 ]);
23 } 28 }
24 29
25 setup() { 30 setup() {
@@ -99,4 +104,26 @@ export default class RecipesStore extends Store {
99 syncUpdate(0); 104 syncUpdate(0);
100 } 105 }
101 } 106 }
107
108 async _checkIfRecipeIsInstalled() {
109 const { router } = this.stores;
110
111 const match = matchRoute('/settings/services/add/:id', router.location.pathname);
112 if (match) {
113 const recipeId = match.id;
114
115 if (!this.stores.recipes.isInstalled(recipeId)) {
116 router.push('/settings/recipes');
117 debug(`Recipe ${recipeId} is not installed, trying to install it`);
118
119 const recipe = await this.installRecipeRequest.execute(recipeId)._promise;
120 if (recipe) {
121 await this.allRecipesRequest.invalidate({ immediately: true })._promise;
122 router.push(`/settings/services/add/${recipeId}`);
123 } else {
124 router.push('/settings/recipes');
125 }
126 }
127 }
128 }
102} 129}
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index d63302fce..109ac5cd7 100644
--- a/src/stores/ServicesStore.js
+++ b/src/stores/ServicesStore.js
@@ -148,18 +148,7 @@ export default class ServicesStore extends Store {
148 } 148 }
149 149
150 async _showAddServiceInterface({ recipeId }) { 150 async _showAddServiceInterface({ recipeId }) {
151 const recipesStore = this.stores.recipes; 151 this.stores.router.push(`/settings/services/add/${recipeId}`);
152
153 if (recipesStore.isInstalled(recipeId)) {
154 debug(`Recipe ${recipeId} is installed`);
155 this._redirectToAddServiceRoute(recipeId);
156 } else {
157 debug(`Recipe ${recipeId} is not installed`);
158 // We access the RecipeStore action directly
159 // returns Promise instead of action
160 await this.stores.recipes._install({ recipeId });
161 this._redirectToAddServiceRoute(recipeId);
162 }
163 } 152 }
164 153
165 // Actions 154 // Actions
@@ -690,11 +679,6 @@ export default class ServicesStore extends Store {
690 } 679 }
691 680
692 // Helper 681 // Helper
693 _redirectToAddServiceRoute(recipeId) {
694 const route = `/settings/services/add/${recipeId}`;
695 this.stores.router.push(route);
696 }
697
698 _initializeServiceRecipeInWebview(serviceId) { 682 _initializeServiceRecipeInWebview(serviceId) {
699 const service = this.one(serviceId); 683 const service = this.one(serviceId);
700 684
diff --git a/src/stores/UIStore.js b/src/stores/UIStore.js
index a95a8e1e0..9680c5bcc 100644
--- a/src/stores/UIStore.js
+++ b/src/stores/UIStore.js
@@ -1,4 +1,9 @@
1import { action, observable, computed } from 'mobx'; 1import {
2 action,
3 observable,
4 computed,
5 reaction,
6} from 'mobx';
2import { theme } from '@meetfranz/theme'; 7import { theme } from '@meetfranz/theme';
3 8
4import Store from './lib/Store'; 9import Store from './lib/Store';
@@ -15,10 +20,18 @@ export default class UIStore extends Store {
15 this.actions.ui.toggleServiceUpdatedInfoBar.listen(this._toggleServiceUpdatedInfoBar.bind(this)); 20 this.actions.ui.toggleServiceUpdatedInfoBar.listen(this._toggleServiceUpdatedInfoBar.bind(this));
16 } 21 }
17 22
23 setup() {
24 reaction(
25 () => this.isDarkThemeActive,
26 () => this._setupThemeInDOM(),
27 { fireImmediately: true },
28 );
29 }
30
18 @computed get showMessageBadgesEvenWhenMuted() { 31 @computed get showMessageBadgesEvenWhenMuted() {
19 const settings = this.stores.settings.all; 32 const settings = this.stores.settings.all;
20 33
21 return (settings.app.isAppMuted && settings.app.showMessageBadgeWhenMuted) || !settings.isAppMuted; 34 return (settings.app.isAppMuted && settings.app.showMessageBadgeWhenMuted) || !settings.app.isAppMuted;
22 } 35 }
23 36
24 @computed get isDarkThemeActive() { 37 @computed get isDarkThemeActive() {
@@ -26,7 +39,7 @@ export default class UIStore extends Store {
26 } 39 }
27 40
28 @computed get theme() { 41 @computed get theme() {
29 if (this.isDarkThemeActive) return theme('dark'); 42 if (this.isDarkThemeActive || this.stores.settings.app.darkMode) return theme('dark');
30 return theme('default'); 43 return theme('default');
31 } 44 }
32 45
@@ -47,4 +60,15 @@ export default class UIStore extends Store {
47 } 60 }
48 this.showServicesUpdatedInfoBar = visibility; 61 this.showServicesUpdatedInfoBar = visibility;
49 } 62 }
63
64 // Reactions
65 _setupThemeInDOM() {
66 const body = document.querySelector('body');
67
68 if (!this.isDarkThemeActive) {
69 body.classList.remove('theme__dark');
70 } else {
71 body.classList.add('theme__dark');
72 }
73 }
50} 74}
diff --git a/src/stores/lib/Reaction.js b/src/stores/lib/Reaction.js
index f2642908f..f8009b7f6 100644
--- a/src/stores/lib/Reaction.js
+++ b/src/stores/lib/Reaction.js
@@ -13,15 +13,15 @@ export default class Reaction {
13 13
14 start() { 14 start() {
15 if (!this.isRunning) { 15 if (!this.isRunning) {
16 this.dispose = autorun(() => this.reaction()); 16 this.dispose = autorun(this.reaction);
17 this.isActive = true; 17 this.isRunning = true;
18 } 18 }
19 } 19 }
20 20
21 stop() { 21 stop() {
22 if (this.isRunning) { 22 if (this.isRunning) {
23 this.dispose(); 23 this.dispose();
24 this.isActive = false; 24 this.isRunning = false;
25 } 25 }
26 } 26 }
27} 27}
diff --git a/src/styles/layout.scss b/src/styles/layout.scss
index e858b7904..9f226b61c 100644
--- a/src/styles/layout.scss
+++ b/src/styles/layout.scss
@@ -39,7 +39,7 @@ html { overflow: hidden; }
39 .app__content { display: flex; } 39 .app__content { display: flex; }
40 40
41 .app__service { 41 .app__service {
42 position: relative; 42 // position: relative;
43 display: flex; 43 display: flex;
44 flex: 1; 44 flex: 1;
45 flex-direction: column; 45 flex-direction: column;
diff --git a/src/styles/reset.scss b/src/styles/reset.scss
index 80328dcef..f46ede4a2 100644
--- a/src/styles/reset.scss
+++ b/src/styles/reset.scss
@@ -64,7 +64,7 @@ body {
64 font-size: 1.4rem; 64 font-size: 1.4rem;
65 line-height: 1; 65 line-height: 1;
66 66
67 .theme__dark { color: $dark-theme-gray-smoke; } 67 &.theme__dark { color: $dark-theme-gray-smoke; }
68} 68}
69 69
70* { 70* {
diff --git a/src/webview/contextMenu.js b/src/webview/contextMenu.js
index 967e8e667..a4a6ab899 100644
--- a/src/webview/contextMenu.js
+++ b/src/webview/contextMenu.js
@@ -280,13 +280,12 @@ const buildMenuTpl = (props, suggestions, isSpellcheckEnabled, defaultSpellcheck
280}; 280};
281 281
282export default function contextMenu(spellcheckProvider, isSpellcheckEnabled, getDefaultSpellcheckerLanguage, getSpellcheckerLanguage) { 282export default function contextMenu(spellcheckProvider, isSpellcheckEnabled, getDefaultSpellcheckerLanguage, getSpellcheckerLanguage) {
283 webContents.on('context-menu', async (e, props) => { 283 webContents.on('context-menu', (e, props) => {
284 e.preventDefault(); 284 e.preventDefault();
285 285
286 let suggestions = []; 286 let suggestions = [];
287 if (spellcheckProvider && props.misspelledWord) { 287 if (spellcheckProvider && props.misspelledWord) {
288 debug('Mispelled word', props.misspelledWord); 288 suggestions = spellcheckProvider.getSuggestion(props.misspelledWord);
289 suggestions = await spellcheckProvider.getSuggestion(props.misspelledWord);
290 289
291 debug('Suggestions', suggestions); 290 debug('Suggestions', suggestions);
292 } 291 }
diff --git a/src/webview/spellchecker.js b/src/webview/spellchecker.js
index 417d1ea1a..9158b3b94 100644
--- a/src/webview/spellchecker.js
+++ b/src/webview/spellchecker.js
@@ -1,8 +1,6 @@
1import { webFrame } from 'electron'; 1import { webFrame } from 'electron';
2import { attachSpellCheckProvider, SpellCheckerProvider } from 'electron-hunspell'; 2import { SpellCheckerProvider } from 'electron-hunspell';
3import { ENVIRONMENT } from 'hunspell-asm';
4import path from 'path'; 3import path from 'path';
5import { readFileSync } from 'fs';
6 4
7import { DICTIONARY_PATH } from '../config'; 5import { DICTIONARY_PATH } from '../config';
8import { SPELLCHECKER_LOCALES } from '../i18n/languages'; 6import { SPELLCHECKER_LOCALES } from '../i18n/languages';
@@ -12,12 +10,11 @@ const debug = require('debug')('Franz:spellchecker');
12let provider; 10let provider;
13let currentDict; 11let currentDict;
14let _isEnabled = false; 12let _isEnabled = false;
15let attached;
16 13
17async function loadDictionary(locale) { 14async function loadDictionary(locale) {
18 try { 15 try {
19 const fileLocation = path.join(DICTIONARY_PATH, `hunspell-dict-${locale}/${locale}`); 16 const fileLocation = path.join(DICTIONARY_PATH, `hunspell-dict-${locale}/${locale}`);
20 await provider.loadDictionary(locale, readFileSync(`${fileLocation}.dic`), readFileSync(`${fileLocation}.aff`)); 17 await provider.loadDictionary(locale, `${fileLocation}.dic`, `${fileLocation}.aff`);
21 debug('Loaded dictionary', locale, 'from', fileLocation); 18 debug('Loaded dictionary', locale, 'from', fileLocation);
22 } catch (err) { 19 } catch (err) {
23 console.error('Could not load dictionary', err); 20 console.error('Could not load dictionary', err);
@@ -44,7 +41,7 @@ export async function switchDict(locale) {
44 provider.unloadDictionary(locale); 41 provider.unloadDictionary(locale);
45 } 42 }
46 loadDictionary(locale); 43 loadDictionary(locale);
47 attached.switchLanguage(locale); 44 provider.switchDictionary(locale);
48 45
49 debug('Switched dictionary to', locale); 46 debug('Switched dictionary to', locale);
50 47
@@ -61,14 +58,12 @@ export default async function initialize(languageCode = 'en-us') {
61 const locale = languageCode.toLowerCase(); 58 const locale = languageCode.toLowerCase();
62 59
63 debug('Init spellchecker'); 60 debug('Init spellchecker');
64 await provider.initialize({ environment: ENVIRONMENT.NODE }); 61 await provider.initialize();
65 62 // await loadDictionaries();
66 debug('Attaching spellcheck provider');
67 attached = await attachSpellCheckProvider(provider);
68 63
69 debug('Available spellchecker dictionaries', provider.availableDictionaries); 64 debug('Available spellchecker dictionaries', provider.availableDictionaries);
70 65
71 attached.switchLanguage(locale); 66 switchDict(locale);
72 67
73 return provider; 68 return provider;
74 } catch (err) { 69 } catch (err) {