diff options
author | Stefan Malzner <stefan@adlk.io> | 2018-11-22 11:36:11 +0100 |
---|---|---|
committer | Stefan Malzner <stefan@adlk.io> | 2018-11-22 11:36:11 +0100 |
commit | fd7954fef99d59ca0aa9f2b468afea3463ef2202 (patch) | |
tree | dd83cc2efd0e8ec54679b4db80ed1b5dd086feb6 | |
parent | Fix isFullScreen typo (diff) | |
download | ferdium-app-fd7954fef99d59ca0aa9f2b468afea3463ef2202.tar.gz ferdium-app-fd7954fef99d59ca0aa9f2b468afea3463ef2202.tar.zst ferdium-app-fd7954fef99d59ca0aa9f2b468afea3463ef2202.zip |
feat(App): Add option to enable dark mode for supported services
-rw-r--r-- | src/components/settings/services/EditServiceForm.js | 1 | ||||
-rw-r--r-- | src/containers/settings/EditServiceScreen.js | 11 | ||||
-rw-r--r-- | src/i18n/locales/en-US.json | 1 | ||||
-rw-r--r-- | src/models/Recipe.js | 6 | ||||
-rw-r--r-- | src/models/Service.js | 3 | ||||
-rw-r--r-- | src/stores/ServicesStore.js | 8 | ||||
-rw-r--r-- | src/webview/darkmode.js | 28 | ||||
-rw-r--r-- | src/webview/plugin.js | 33 |
8 files changed, 88 insertions, 3 deletions
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js index 7aa632f29..f9afe4c8d 100644 --- a/src/components/settings/services/EditServiceForm.js +++ b/src/components/settings/services/EditServiceForm.js | |||
@@ -304,6 +304,7 @@ export default @observer class EditServiceForm extends Component { | |||
304 | 304 | ||
305 | <div className="settings__settings-group"> | 305 | <div className="settings__settings-group"> |
306 | <h3>{intl.formatMessage(messages.headlineGeneral)}</h3> | 306 | <h3>{intl.formatMessage(messages.headlineGeneral)}</h3> |
307 | <Toggle field={form.$('isDarkModeEnabled')} /> | ||
307 | <Toggle field={form.$('isEnabled')} /> | 308 | <Toggle field={form.$('isEnabled')} /> |
308 | </div> | 309 | </div> |
309 | </div> | 310 | </div> |
diff --git a/src/containers/settings/EditServiceScreen.js b/src/containers/settings/EditServiceScreen.js index 1fc6bc85e..e69c2c2a8 100644 --- a/src/containers/settings/EditServiceScreen.js +++ b/src/containers/settings/EditServiceScreen.js | |||
@@ -6,6 +6,7 @@ import { defineMessages, intlShape } from 'react-intl'; | |||
6 | import UserStore from '../../stores/UserStore'; | 6 | import UserStore from '../../stores/UserStore'; |
7 | import RecipesStore from '../../stores/RecipesStore'; | 7 | import RecipesStore from '../../stores/RecipesStore'; |
8 | import ServicesStore from '../../stores/ServicesStore'; | 8 | import ServicesStore from '../../stores/ServicesStore'; |
9 | import SettingsStore from '../../stores/SettingsStore'; | ||
9 | import Form from '../../lib/Form'; | 10 | import Form from '../../lib/Form'; |
10 | import { gaPage } from '../../lib/analytics'; | 11 | import { gaPage } from '../../lib/analytics'; |
11 | 12 | ||
@@ -50,6 +51,10 @@ const messages = defineMessages({ | |||
50 | id: 'settings.service.form.icon', | 51 | id: 'settings.service.form.icon', |
51 | defaultMessage: '!!!Custom icon', | 52 | defaultMessage: '!!!Custom icon', |
52 | }, | 53 | }, |
54 | enableDarkMode: { | ||
55 | id: 'settings.service.form.enableDarkMode', | ||
56 | defaultMessage: '!!!Enable Dark Mode', | ||
57 | }, | ||
53 | }); | 58 | }); |
54 | 59 | ||
55 | export default @inject('stores', 'actions') @observer class EditServiceScreen extends Component { | 60 | export default @inject('stores', 'actions') @observer class EditServiceScreen extends Component { |
@@ -111,6 +116,11 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex | |||
111 | default: null, | 116 | default: null, |
112 | type: 'file', | 117 | type: 'file', |
113 | }, | 118 | }, |
119 | isDarkModeEnabled: { | ||
120 | label: intl.formatMessage(messages.enableDarkMode), | ||
121 | value: service.isDarkModeEnabled, | ||
122 | default: this.props.stores.settings.all.app.darkMode, | ||
123 | }, | ||
114 | }, | 124 | }, |
115 | }; | 125 | }; |
116 | 126 | ||
@@ -238,6 +248,7 @@ EditServiceScreen.wrappedComponent.propTypes = { | |||
238 | user: PropTypes.instanceOf(UserStore).isRequired, | 248 | user: PropTypes.instanceOf(UserStore).isRequired, |
239 | recipes: PropTypes.instanceOf(RecipesStore).isRequired, | 249 | recipes: PropTypes.instanceOf(RecipesStore).isRequired, |
240 | services: PropTypes.instanceOf(ServicesStore).isRequired, | 250 | services: PropTypes.instanceOf(ServicesStore).isRequired, |
251 | settings: PropTypes.instanceOf(SettingsStore).isRequired, | ||
241 | }).isRequired, | 252 | }).isRequired, |
242 | router: PropTypes.shape({ | 253 | router: PropTypes.shape({ |
243 | params: PropTypes.shape({ | 254 | params: PropTypes.shape({ |
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 8b2f763b5..66ec5af84 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json | |||
@@ -130,6 +130,7 @@ | |||
130 | "settings.service.form.icon": "Custom icon", | 130 | "settings.service.form.icon": "Custom icon", |
131 | "settings.service.form.iconDelete": "Delete", | 131 | "settings.service.form.iconDelete": "Delete", |
132 | "settings.service.form.iconUpload": "Drop your image, or click here", | 132 | "settings.service.form.iconUpload": "Drop your image, or click here", |
133 | "settings.service.form.enableDarkMode": "Enable Dark Mode", | ||
133 | "settings.service.error.headline": "Error", | 134 | "settings.service.error.headline": "Error", |
134 | "settings.service.error.goBack": "Back to services", | 135 | "settings.service.error.goBack": "Back to services", |
135 | "settings.service.error.message": "Could not load service recipe.", | 136 | "settings.service.error.message": "Could not load service recipe.", |
diff --git a/src/models/Recipe.js b/src/models/Recipe.js index 032a9aa19..43c44514c 100644 --- a/src/models/Recipe.js +++ b/src/models/Recipe.js | |||
@@ -1,5 +1,7 @@ | |||
1 | import emailParser from 'address-rfc2822'; | 1 | import emailParser from 'address-rfc2822'; |
2 | import semver from 'semver'; | 2 | import semver from 'semver'; |
3 | import fs from 'fs-extra'; | ||
4 | import path from 'path'; | ||
3 | 5 | ||
4 | export default class Recipe { | 6 | export default class Recipe { |
5 | id = ''; | 7 | id = ''; |
@@ -73,4 +75,8 @@ export default class Recipe { | |||
73 | 75 | ||
74 | return []; | 76 | return []; |
75 | } | 77 | } |
78 | |||
79 | get hasDarkMode() { | ||
80 | return fs.pathExistsSync(path.join(this.path, 'darkmode.css')); | ||
81 | } | ||
76 | } | 82 | } |
diff --git a/src/models/Service.js b/src/models/Service.js index 4f8767dbe..1bab8bd68 100644 --- a/src/models/Service.js +++ b/src/models/Service.js | |||
@@ -28,6 +28,7 @@ export default class Service { | |||
28 | @observable iconUrl = ''; | 28 | @observable iconUrl = ''; |
29 | @observable hasCustomUploadedIcon = false; | 29 | @observable hasCustomUploadedIcon = false; |
30 | @observable hasCrashed = false; | 30 | @observable hasCrashed = false; |
31 | @observable isDarkModeEnabled = false; | ||
31 | 32 | ||
32 | constructor(data, recipe) { | 33 | constructor(data, recipe) { |
33 | if (!data) { | 34 | if (!data) { |
@@ -64,6 +65,8 @@ export default class Service { | |||
64 | 65 | ||
65 | this.isMuted = data.isMuted !== undefined ? data.isMuted : this.isMuted; | 66 | this.isMuted = data.isMuted !== undefined ? data.isMuted : this.isMuted; |
66 | 67 | ||
68 | this.isDarkModeEnabled = data.isDarkModeEnabled !== undefined ? data.isDarkModeEnabled : this.isDarkModeEnabled; | ||
69 | |||
67 | this.hasCustomUploadedIcon = data.hasCustomIcon !== undefined ? data.hasCustomIcon : this.hasCustomUploadedIcon; | 70 | this.hasCustomUploadedIcon = data.hasCustomIcon !== undefined ? data.hasCustomIcon : this.hasCustomUploadedIcon; |
68 | 71 | ||
69 | this.recipe = recipe; | 72 | this.recipe = recipe; |
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index c5822968a..cdb2db142 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js | |||
@@ -214,6 +214,14 @@ export default class ServicesStore extends Store { | |||
214 | await request._promise; | 214 | await request._promise; |
215 | this.actionStatus = request.result.status; | 215 | this.actionStatus = request.result.status; |
216 | 216 | ||
217 | if (service.isEnabled) { | ||
218 | this._sendIPCMessage({ | ||
219 | serviceId, | ||
220 | channel: 'service-settings-update', | ||
221 | args: newData, | ||
222 | }); | ||
223 | } | ||
224 | |||
217 | if (redirect) { | 225 | if (redirect) { |
218 | this.stores.router.push('/settings/services'); | 226 | this.stores.router.push('/settings/services'); |
219 | gaEvent('Service', 'update', service.recipe.id); | 227 | gaEvent('Service', 'update', service.recipe.id); |
diff --git a/src/webview/darkmode.js b/src/webview/darkmode.js new file mode 100644 index 000000000..9830ef33c --- /dev/null +++ b/src/webview/darkmode.js | |||
@@ -0,0 +1,28 @@ | |||
1 | import path from 'path'; | ||
2 | import fs from 'fs-extra'; | ||
3 | |||
4 | const ID = 'franz-theme-dark-mode'; | ||
5 | |||
6 | export function injectDarkModeStyle(recipePath) { | ||
7 | const darkModeStyle = path.join(recipePath, 'darkmode.css'); | ||
8 | if (fs.pathExistsSync(darkModeStyle)) { | ||
9 | const data = fs.readFileSync(darkModeStyle); | ||
10 | const styles = document.createElement('style'); | ||
11 | styles.id = ID; | ||
12 | styles.innerHTML = data.toString(); | ||
13 | |||
14 | document.querySelector('head').appendChild(styles); | ||
15 | } | ||
16 | } | ||
17 | |||
18 | export function removeDarkModeStyle() { | ||
19 | const style = document.querySelector(`#${ID}`); | ||
20 | |||
21 | if (style) { | ||
22 | style.remove(); | ||
23 | } | ||
24 | } | ||
25 | |||
26 | export function isDarkModeStyleInjected() { | ||
27 | return !!document.querySelector(`#${ID}`); | ||
28 | } | ||
diff --git a/src/webview/plugin.js b/src/webview/plugin.js index ee8cedbab..e6fdc4efd 100644 --- a/src/webview/plugin.js +++ b/src/webview/plugin.js | |||
@@ -6,10 +6,14 @@ import { isDevMode } from '../environment'; | |||
6 | import RecipeWebview from './lib/RecipeWebview'; | 6 | import RecipeWebview from './lib/RecipeWebview'; |
7 | 7 | ||
8 | import Spellchecker from './spellchecker'; | 8 | import Spellchecker from './spellchecker'; |
9 | import { injectDarkModeStyle, isDarkModeStyleInjected, removeDarkModeStyle } from './darkmode'; | ||
9 | import './notifications'; | 10 | import './notifications'; |
10 | 11 | ||
11 | const debug = require('debug')('Franz:Plugin'); | 12 | const debug = require('debug')('Franz:Plugin'); |
12 | 13 | ||
14 | window.franzSettings = {}; | ||
15 | let serviceData; | ||
16 | |||
13 | ipcRenderer.on('initializeRecipe', (e, data) => { | 17 | ipcRenderer.on('initializeRecipe', (e, data) => { |
14 | const modulePath = path.join(data.recipe.path, 'webview.js'); | 18 | const modulePath = path.join(data.recipe.path, 'webview.js'); |
15 | // Delete module from cache | 19 | // Delete module from cache |
@@ -17,7 +21,14 @@ ipcRenderer.on('initializeRecipe', (e, data) => { | |||
17 | try { | 21 | try { |
18 | // eslint-disable-next-line | 22 | // eslint-disable-next-line |
19 | require(modulePath)(new RecipeWebview(), data); | 23 | require(modulePath)(new RecipeWebview(), data); |
20 | debug('Initialize Recipe'); | 24 | debug('Initialize Recipe', data); |
25 | |||
26 | serviceData = data; | ||
27 | |||
28 | if (data.isDarkModeEnabled) { | ||
29 | injectDarkModeStyle(data.recipe.path); | ||
30 | debug('Add dark theme styles'); | ||
31 | } | ||
21 | } catch (err) { | 32 | } catch (err) { |
22 | debug('Recipe initialization failed', err); | 33 | debug('Recipe initialization failed', err); |
23 | } | 34 | } |
@@ -33,11 +44,27 @@ new ContextMenuListener((info) => { // eslint-disable-line | |||
33 | }); | 44 | }); |
34 | 45 | ||
35 | ipcRenderer.on('settings-update', (e, data) => { | 46 | ipcRenderer.on('settings-update', (e, data) => { |
36 | spellchecker.toggleSpellchecker(data.enableSpellchecking); | ||
37 | debug('Settings update received', data); | 47 | debug('Settings update received', data); |
48 | |||
49 | spellchecker.toggleSpellchecker(data.enableSpellchecking); | ||
50 | window.franzSettings = data; | ||
51 | }); | ||
52 | |||
53 | ipcRenderer.on('service-settings-update', (e, data) => { | ||
54 | debug('Service settings update received', data); | ||
55 | |||
56 | if (data.isDarkModeEnabled && !isDarkModeStyleInjected()) { | ||
57 | injectDarkModeStyle(serviceData.recipe.path); | ||
58 | |||
59 | debug('Enable service dark mode'); | ||
60 | } else if (!data.isDarkModeEnabled && isDarkModeStyleInjected()) { | ||
61 | removeDarkModeStyle(); | ||
62 | |||
63 | debug('Disable service dark mode'); | ||
64 | } | ||
38 | }); | 65 | }); |
39 | 66 | ||
40 | // initSpellche | 67 | // initSpellchecker |
41 | 68 | ||
42 | document.addEventListener('DOMContentLoaded', () => { | 69 | document.addEventListener('DOMContentLoaded', () => { |
43 | ipcRenderer.sendToHost('hello'); | 70 | ipcRenderer.sendToHost('hello'); |