From 259d40c6aa150453671008a483754cb16d1da999 Mon Sep 17 00:00:00 2001 From: Danny Qiu Date: Mon, 30 Oct 2017 13:02:06 -0400 Subject: Add dialog to reload on service crash --- src/models/Service.js | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'src/models/Service.js') diff --git a/src/models/Service.js b/src/models/Service.js index 7a0310ebc..08dbc0b5f 100644 --- a/src/models/Service.js +++ b/src/models/Service.js @@ -112,6 +112,16 @@ export default class Service { frameName, options, })); + + this.webview.addEventListener('crashed', (e) => { + console.log(e); + let reload = confirm('Service crashed. Reload?'); + if (reload) { + store.actions.service.reload({ + serviceId: this.id + }); + } + }); } initializeWebViewListener() { -- cgit v1.2.3-70-g09d2 From 11fd7e7b06233cdba4adaf75f3cea19c7dbbdb8c Mon Sep 17 00:00:00 2001 From: Danny Qiu Date: Mon, 30 Oct 2017 17:39:48 -0400 Subject: Fix lint errors --- src/models/Service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/models/Service.js') diff --git a/src/models/Service.js b/src/models/Service.js index 08dbc0b5f..89d6748b5 100644 --- a/src/models/Service.js +++ b/src/models/Service.js @@ -115,10 +115,10 @@ export default class Service { this.webview.addEventListener('crashed', (e) => { console.log(e); - let reload = confirm('Service crashed. Reload?'); + const reload = window.confirm('Service crashed. Reload?'); // eslint-disable-line no-alert if (reload) { store.actions.service.reload({ - serviceId: this.id + serviceId: this.id, }); } }); -- cgit v1.2.3-70-g09d2 From dd307e3deb14f1738029ea38f7c1de7893455283 Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Tue, 7 Nov 2017 15:29:31 +0100 Subject: feature(Service): Add webview crash handler to display a user friendly message --- .vscode/launch.json | 13 ++++ .vscode/tasks.json | 18 +++++ src/components/services/content/ServiceWebview.js | 10 +++ src/components/services/content/Services.js | 3 + .../services/content/WebviewCrashHandler.js | 81 ++++++++++++++++++++++ src/containers/layout/AppLayoutContainer.js | 2 +- src/i18n/locales/en-US.json | 6 +- src/models/Service.js | 15 ++-- src/styles/services.scss | 19 +++-- src/styles/type.scss | 5 ++ 10 files changed, 156 insertions(+), 16 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 src/components/services/content/WebviewCrashHandler.js (limited to 'src/models/Service.js') diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..7d14571f5 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Electron Main", + "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron", + "program": "${workspaceFolder}/build/index.js", + "protocol": "inspector" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..a9fb57b06 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,18 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "dev", + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "type": "npm", + "script": "lint", + "group": "test" + } + ] +} \ No newline at end of file diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js index 3ee3155be..cd59e0a8a 100644 --- a/src/components/services/content/ServiceWebview.js +++ b/src/components/services/content/ServiceWebview.js @@ -7,12 +7,14 @@ import classnames from 'classnames'; import ServiceModel from '../../../models/Service'; import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl'; +import WebviewCrashHandler from './WebviewCrashHandler'; @observer export default class ServiceWebview extends Component { static propTypes = { service: PropTypes.instanceOf(ServiceModel).isRequired, setWebviewReference: PropTypes.func.isRequired, + reload: PropTypes.func.isRequired, }; static defaultProps = { @@ -53,6 +55,7 @@ export default class ServiceWebview extends Component { const { service, setWebviewReference, + reload, } = this.props; const webviewClasses = classnames({ @@ -70,6 +73,13 @@ export default class ServiceWebview extends Component { return (
+ {service.hasCrashed && ( + + )} { this.webview = element; }} diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js index 03c68b06f..bad525d22 100644 --- a/src/components/services/content/Services.js +++ b/src/components/services/content/Services.js @@ -25,6 +25,7 @@ export default class Services extends Component { setWebviewReference: PropTypes.func.isRequired, handleIPCMessage: PropTypes.func.isRequired, openWindow: PropTypes.func.isRequired, + reload: PropTypes.func.isRequired, }; static defaultProps = { @@ -42,6 +43,7 @@ export default class Services extends Component { handleIPCMessage, setWebviewReference, openWindow, + reload, } = this.props; const { intl } = this.context; @@ -73,6 +75,7 @@ export default class Services extends Component { handleIPCMessage={handleIPCMessage} setWebviewReference={setWebviewReference} openWindow={openWindow} + reload={() => reload({ serviceId: service.id })} /> ))}
diff --git a/src/components/services/content/WebviewCrashHandler.js b/src/components/services/content/WebviewCrashHandler.js new file mode 100644 index 000000000..24903f3c5 --- /dev/null +++ b/src/components/services/content/WebviewCrashHandler.js @@ -0,0 +1,81 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer } from 'mobx-react'; +import { defineMessages, intlShape } from 'react-intl'; + +import Button from '../../ui/Button'; + +const messages = defineMessages({ + headline: { + id: 'service.crashHandler.headline', + defaultMessage: '!!!Oh no!', + }, + text: { + id: 'service.crashHandler.text', + defaultMessage: '!!!{name} has caused an error.', + }, + action: { + id: 'service.crashHandler.action', + defaultMessage: '!!!Reload {name}', + }, + autoReload: { + id: 'service.crashHandler.autoReload', + defaultMessage: '!!!Trying to automatically restore {name} in {seconds} seconds', + }, +}); + +@observer +export default class ServiceWebview extends Component { + static propTypes = { + name: PropTypes.string.isRequired, + reload: PropTypes.func.isRequired, + }; + + static contextTypes = { + intl: intlShape, + }; + + state = { + countdown: 10000, + } + + componentDidMount() { + const { reload } = this.props; + + this.countdownInterval = setInterval(() => { + this.setState({ + countdown: this.state.countdown - this.countdownIntervalTimeout, + }); + + if (this.state.countdown <= 0) { + reload(); + clearInterval(this.countdownInterval); + } + }, this.countdownIntervalTimeout); + } + + countdownInterval = null; + countdownIntervalTimeout = 1000; + + render() { + const { name, reload } = this.props; + const { intl } = this.context; + + return ( +
+

{intl.formatMessage(messages.headline)}

+

{intl.formatMessage(messages.text, { name })}

+
+ ); + } +} diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js index aa7f7952a..68ad1039e 100644 --- a/src/containers/layout/AppLayoutContainer.js +++ b/src/containers/layout/AppLayoutContainer.js @@ -92,11 +92,11 @@ export default class AppLayoutContainer extends Component { const servicesContainer = ( ); diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index b1d260f0a..0493b547f 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -165,5 +165,9 @@ "tabs.item.disableNotifications": "Disable notifications", "tabs.item.enableNotification": "Enable notifications", "tabs.item.disableService": "Disable service", - "tabs.item.deleteService": "Delete service" + "tabs.item.deleteService": "Delete service", + "service.crashHandler.headline": "Oh no!", + "service.crashHandler.text": "{name} has caused an error.", + "service.crashHandler.action": "Reload {name}", + "service.crashHandler.autoReload": "Trying to automatically restore {name} in {seconds} seconds" } diff --git a/src/models/Service.js b/src/models/Service.js index c7276821a..dc53807f7 100644 --- a/src/models/Service.js +++ b/src/models/Service.js @@ -23,6 +23,7 @@ export default class Service { @observable isNotificationEnabled = true; @observable isIndirectMessageBadgeEnabled = true; @observable customIconUrl = ''; + @observable hasCrashed = false; constructor(data, recipe) { if (!data) { @@ -118,14 +119,12 @@ export default class Service { options, })); - this.webview.addEventListener('crashed', (e) => { - console.log(e); - const reload = window.confirm('Service crashed. Reload?'); // eslint-disable-line no-alert - if (reload) { - store.actions.service.reload({ - serviceId: this.id, - }); - } + this.webview.addEventListener('did-start-loading', () => { + this.hasCrashed = false; + }); + + this.webview.addEventListener('crashed', () => { + this.hasCrashed = true; }); } diff --git a/src/styles/services.scss b/src/styles/services.scss index 3347ea9d7..95738f123 100644 --- a/src/styles/services.scss +++ b/src/styles/services.scss @@ -8,7 +8,8 @@ background: #FFF; order: 5; - .services__webview { + .services__webview, + .services__crash-handler { position: absolute; width: 100%; top: 0; @@ -38,7 +39,8 @@ } } - .services__no-service { + .services__no-service, + .services__crash-handler { display: flex; flex-direction: column; justify-content: center; @@ -51,10 +53,15 @@ color: $theme-gray-dark; } - a.button { - margin-top: 40px; - // color: #FFF; - // border-color: #FFF; + a.button, + button { + margin: 40px 0 20px; } } + + .services__crash-handler { + position: absolut; + z-index: 110; + } + } diff --git a/src/styles/type.scss b/src/styles/type.scss index 935a36f4b..cacbec482 100644 --- a/src/styles/type.scss +++ b/src/styles/type.scss @@ -71,3 +71,8 @@ a { .label { @include formLabel(); } + +.footnote { + font-size: 12px; + color: $theme-gray-light; +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From b405ba11aa3c669a21831d016084e0c47bffdebc Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Thu, 9 Nov 2017 16:28:03 +0100 Subject: feat(Service): Add option to mute service --- src/components/services/content/ServiceWebview.js | 6 +----- .../settings/services/EditServiceForm.js | 12 ++++++++++-- src/components/settings/services/ServiceItem.js | 22 +++++++++++++++------- src/containers/settings/EditServiceScreen.js | 15 +++++++++++++-- src/i18n/locales/en-US.json | 15 +++++++++------ src/models/Service.js | 3 +++ src/stores/ServicesStore.js | 2 +- src/styles/settings.scss | 2 +- 8 files changed, 53 insertions(+), 24 deletions(-) (limited to 'src/models/Service.js') diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js index cd59e0a8a..d7e0a4f38 100644 --- a/src/components/services/content/ServiceWebview.js +++ b/src/components/services/content/ServiceWebview.js @@ -82,21 +82,17 @@ export default class ServiceWebview extends Component { )} { this.webview = element; }} - autosize src={service.url} preload="./webview/plugin.js" partition={`persist:service-${service.id}`} - onDidAttach={() => setWebviewReference({ serviceId: service.id, webview: this.webview.view, })} - onUpdateTargetUrl={this.updateTargetUrl} - useragent={service.userAgent} - + muted={service.isMuted} disablewebsecurity allowpopups /> diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js index 9b359a78e..753781507 100644 --- a/src/components/settings/services/EditServiceForm.js +++ b/src/components/settings/services/EditServiceForm.js @@ -61,7 +61,11 @@ const messages = defineMessages({ }, indirectMessageInfo: { id: 'settings.service.form.indirectMessageInfo', - defaultMessage: '!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...', // eslint-disable-line + defaultMessage: '!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...', + }, + isMutedInfo: { + id: 'settings.service.form.isMutedInfo', + defaultMessage: '!!!When disabled, all notification sounds and audio playback are muted', }, }); @@ -231,11 +235,15 @@ export default class EditServiceForm extends Component { {recipe.hasIndirectMessages && (
-

+

{intl.formatMessage(messages.indirectMessageInfo)}

)} + +

+ {intl.formatMessage(messages.isMutedInfo)} +

{recipe.message && ( diff --git a/src/components/settings/services/ServiceItem.js b/src/components/settings/services/ServiceItem.js index 20d8581d0..9743315b0 100644 --- a/src/components/settings/services/ServiceItem.js +++ b/src/components/settings/services/ServiceItem.js @@ -16,6 +16,10 @@ const messages = defineMessages({ id: 'settings.services.tooltip.notificationsDisabled', defaultMessage: '!!!Notifications are disabled', }, + tooltipIsMuted: { + id: 'settings.services.tooltip.isMuted', + defaultMessage: '!!!All sounds are muted', + }, }); @observer @@ -62,6 +66,17 @@ export default class ServiceItem extends Component { > {service.name !== '' ? service.name : service.recipe.name} + + {service.isMuted && ( + + )} + - {/* - - */} ); } diff --git a/src/containers/settings/EditServiceScreen.js b/src/containers/settings/EditServiceScreen.js index 6c614b941..191ef447b 100644 --- a/src/containers/settings/EditServiceScreen.js +++ b/src/containers/settings/EditServiceScreen.js @@ -9,7 +9,6 @@ import ServicesStore from '../../stores/ServicesStore'; import Form from '../../lib/Form'; import { gaPage } from '../../lib/analytics'; - import ServiceError from '../../components/settings/services/ServiceError'; import EditServiceForm from '../../components/settings/services/EditServiceForm'; import { required, url, oneRequired } from '../../helpers/validation-helpers'; @@ -27,6 +26,10 @@ const messages = defineMessages({ id: 'settings.service.form.enableNotification', defaultMessage: '!!!Enable Notifications', }, + enableAudio: { + id: 'settings.service.form.enableAudio', + defaultMessage: '!!!Enable audio', + }, team: { id: 'settings.service.form.team', defaultMessage: '!!!Team', @@ -51,11 +54,14 @@ export default class EditServiceScreen extends Component { gaPage('Settings/Service/Edit'); } - onSubmit(serviceData) { + onSubmit(data) { const { action } = this.props.router.params; const { recipes, services } = this.props.stores; const { createService, updateService } = this.props.actions.service; + const serviceData = data; + serviceData.isMuted = !serviceData.isMuted; + if (action === 'edit') { updateService({ serviceId: services.activeSettings.id, serviceData }); } else { @@ -82,6 +88,11 @@ export default class EditServiceScreen extends Component { value: service.isNotificationEnabled, default: true, }, + isMuted: { + label: intl.formatMessage(messages.enableAudio), + value: !service.isMuted, + default: true, + }, }, }; diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 0493b547f..e298728d1 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -106,11 +106,20 @@ "settings.service.form.customUrlPremiumInfo": "To add self hosted services, you need a Franz Premium Supporter Account.", "settings.service.form.customUrlUpgradeAccount": "Upgrade your account", "settings.service.form.indirectMessageInfo": "You will be notified about all new messages in a channel, not just @username, @channel, @here, ...", + "settings.service.form.name": "Name", + "settings.service.form.enableService": "Enable service", + "settings.service.form.enableNotification": "Enable notifications", + "settings.service.form.team": "Team", + "settings.service.form.customUrl": "Custom server", + "settings.service.form.indirectMessages": "Show message badge for all new messages", + "settings.service.form.enableAudio": "Enable audio", + "settings.service.form.isMutedInfo": "When disabled, all notification sounds and audio playback are muted", "settings.service.error.headline": "Error", "settings.service.error.goBack": "Back to services", "settings.service.error.message": "Could not load service recipe.", "settings.services.tooltip.isDisabled": "Service is disabled", "settings.services.tooltip.notificationsDisabled": "Notifications are disabled", + "settings.services.tooltip.isMuted": "All sounds are muted", "settings.services.headline": "Your services", "settings.services.noServicesAdded": "You haven't added any services yet.", "settings.services.discoverServices": "Discover services", @@ -133,12 +142,6 @@ "settings.app.form.language": "Language", "settings.app.form.beta": "Include beta versions", "settings.app.currentVersion": "Current version:", - "settings.service.form.name": "Name", - "settings.service.form.enableService": "Enable service", - "settings.service.form.enableNotification": "Enable notifications", - "settings.service.form.team": "Team", - "settings.service.form.customUrl": "Custom server", - "settings.service.form.indirectMessages": "Show message badge for all new messages", "settings.user.form.firstname": "Firstname", "settings.user.form.lastname": "Lastname", "settings.user.form.email": "Email", diff --git a/src/models/Service.js b/src/models/Service.js index dc53807f7..eb68493fe 100644 --- a/src/models/Service.js +++ b/src/models/Service.js @@ -18,6 +18,7 @@ export default class Service { @observable order = 99; @observable isEnabled = true; + @observable isMuted = false; @observable team = ''; @observable customUrl = ''; @observable isNotificationEnabled = true; @@ -54,6 +55,8 @@ export default class Service { this.isIndirectMessageBadgeEnabled = data.isIndirectMessageBadgeEnabled !== undefined ? data.isIndirectMessageBadgeEnabled : this.isIndirectMessageBadgeEnabled; + this.isMuted = data.isMuted !== undefined ? data.isMuted : this.isMuted; + this.recipe = recipe; } diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index 1d895d532..96c503510 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js @@ -287,7 +287,7 @@ export default class ServicesStore extends Store { }); } else if (channel === 'notification') { const options = args[0].options; - if (service.recipe.hasNotificationSound) { + if (service.recipe.hasNotificationSound || service.isMuted) { Object.assign(options, { silent: true, }); diff --git a/src/styles/settings.scss b/src/styles/settings.scss index 9b19deb4e..48d12a807 100644 --- a/src/styles/settings.scss +++ b/src/styles/settings.scss @@ -169,7 +169,7 @@ } } - .settings__indirect-message-help { + .settings__help { margin: -10px 0 20px 55px;; font-size: 12px; color: $theme-gray-light; -- cgit v1.2.3-70-g09d2 From f53b4fda62a3ff62ff13988b306a126d4cbd8cfe Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Fri, 17 Nov 2017 14:16:26 +0100 Subject: remove webview from memory when disabled --- src/models/Service.js | 10 +++++++++- src/stores/ServicesStore.js | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) (limited to 'src/models/Service.js') diff --git a/src/models/Service.js b/src/models/Service.js index dc53807f7..fc89f81b8 100644 --- a/src/models/Service.js +++ b/src/models/Service.js @@ -1,4 +1,4 @@ -import { computed, observable } from 'mobx'; +import { computed, observable, autorun } from 'mobx'; import path from 'path'; import normalizeUrl from 'normalize-url'; @@ -55,6 +55,14 @@ export default class Service { ? data.isIndirectMessageBadgeEnabled : this.isIndirectMessageBadgeEnabled; this.recipe = recipe; + + autorun(() => { + if (!this.isEnabled) { + this.webview = null; + this.unreadDirectMessageCount = 0; + this.unreadIndirectMessageCount = 0; + } + }); } @computed get url() { diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index 6c41c22cf..76e2e538b 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js @@ -512,6 +512,8 @@ export default class ServicesStore extends Store { if (service) { const loop = () => { + if (!service.webview) return; + service.webview.send('poll'); setTimeout(loop, delay); -- cgit v1.2.3-70-g09d2 From 20a7a0dab231059c5530b04662b4a5a34f920dbd Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Fri, 17 Nov 2017 14:25:02 +0100 Subject: allow service to be reattached properly after re-enabling service --- src/models/Service.js | 1 + 1 file changed, 1 insertion(+) (limited to 'src/models/Service.js') diff --git a/src/models/Service.js b/src/models/Service.js index fc89f81b8..41105ec85 100644 --- a/src/models/Service.js +++ b/src/models/Service.js @@ -59,6 +59,7 @@ export default class Service { autorun(() => { if (!this.isEnabled) { this.webview = null; + this.isAttached = false; this.unreadDirectMessageCount = 0; this.unreadIndirectMessageCount = 0; } -- cgit v1.2.3-70-g09d2 From a76432121ceca299a200d7fe30c4c4d644686d6b Mon Sep 17 00:00:00 2001 From: Benedikt Stemmildt Date: Wed, 22 Nov 2017 20:34:20 +0100 Subject: Do not strip www from custom urls Stripping www from the custom url causes some custom services to fail. When there is no domain without www loading is not possible. Resolves: #325 --- src/components/settings/services/EditServiceForm.js | 2 +- src/models/Service.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/models/Service.js') diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js index 9b359a78e..23f5399f7 100644 --- a/src/components/settings/services/EditServiceForm.js +++ b/src/components/settings/services/EditServiceForm.js @@ -110,7 +110,7 @@ export default class EditServiceForm extends Component { if (recipe.validateUrl && values.customUrl) { this.setState({ isValidatingCustomUrl: true }); try { - values.customUrl = normalizeUrl(values.customUrl); + values.customUrl = normalizeUrl(values.customUrl, { stripWWW: false }); isValid = await recipe.validateUrl(values.customUrl); } catch (err) { console.warn('ValidateURL', err); diff --git a/src/models/Service.js b/src/models/Service.js index 484252e7c..93061c2cf 100644 --- a/src/models/Service.js +++ b/src/models/Service.js @@ -60,7 +60,7 @@ export default class Service { if (this.recipe.hasCustomUrl && this.customUrl) { let url; try { - url = normalizeUrl(this.customUrl); + url = normalizeUrl(this.customUrl, { stripWWW: false }); } catch (err) { console.error(`Service (${this.recipe.name}): '${this.customUrl}' is not a valid Url.`); } -- cgit v1.2.3-70-g09d2