aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--package-lock.json5
-rw-r--r--package.json1
-rw-r--r--src/actions/index.js2
-rw-r--r--src/actions/service.js1
-rw-r--r--src/components/layout/AppLayout.js4
-rw-r--r--src/config.js1
-rw-r--r--src/containers/layout/AppLayoutContainer.js2
-rw-r--r--src/features/announcements/Component.js77
-rw-r--r--src/features/announcements/actions.js8
-rw-r--r--src/features/announcements/api.js19
-rw-r--r--src/features/announcements/index.js37
-rw-r--r--src/features/announcements/state.js17
-rw-r--r--src/features/announcements/store.js95
-rw-r--r--src/i18n/locales/defaultMessages.json115
-rw-r--r--src/i18n/locales/en-US.json2
-rw-r--r--src/i18n/messages/src/components/layout/AppLayout.json24
-rw-r--r--src/i18n/messages/src/features/announcements/Component.json15
-rw-r--r--src/i18n/messages/src/lib/Menu.json73
-rw-r--r--src/lib/Menu.js10
-rw-r--r--src/stores/FeaturesStore.js2
-rw-r--r--src/stores/ServicesStore.js6
21 files changed, 432 insertions, 84 deletions
diff --git a/package-lock.json b/package-lock.json
index 6b87d2432..600d4b7d7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12139,6 +12139,11 @@
12139 "object-visit": "^1.0.0" 12139 "object-visit": "^1.0.0"
12140 } 12140 }
12141 }, 12141 },
12142 "marked": {
12143 "version": "0.6.1",
12144 "resolved": "https://registry.npmjs.org/marked/-/marked-0.6.1.tgz",
12145 "integrity": "sha512-+H0L3ibcWhAZE02SKMqmvYsErLo4EAVJxu5h3bHBBDvvjeWXtl92rGUSBYHL2++5Y+RSNgl8dYOAXcYe7lp1fA=="
12146 },
12142 "matchdep": { 12147 "matchdep": {
12143 "version": "2.0.0", 12148 "version": "2.0.0",
12144 "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", 12149 "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz",
diff --git a/package.json b/package.json
index 8f5c2e8c8..7e926139d 100644
--- a/package.json
+++ b/package.json
@@ -54,6 +54,7 @@
54 "hex-to-rgba": "1.0.2", 54 "hex-to-rgba": "1.0.2",
55 "jsonwebtoken": "^7.4.1", 55 "jsonwebtoken": "^7.4.1",
56 "lodash": "^4.17.4", 56 "lodash": "^4.17.4",
57 "marked": "0.6.1",
57 "mdi": "^1.9.33", 58 "mdi": "^1.9.33",
58 "mime-types": "2.1.21", 59 "mime-types": "2.1.21",
59 "mobx": "5.7.0", 60 "mobx": "5.7.0",
diff --git a/src/actions/index.js b/src/actions/index.js
index 00f843cd6..fc525afeb 100644
--- a/src/actions/index.js
+++ b/src/actions/index.js
@@ -11,6 +11,7 @@ import payment from './payment';
11import news from './news'; 11import news from './news';
12import settings from './settings'; 12import settings from './settings';
13import requests from './requests'; 13import requests from './requests';
14import announcements from '../features/announcements/actions';
14import workspaces from '../features/workspaces/actions'; 15import workspaces from '../features/workspaces/actions';
15 16
16const actions = Object.assign({}, { 17const actions = Object.assign({}, {
@@ -28,5 +29,6 @@ const actions = Object.assign({}, {
28 29
29export default Object.assign( 30export default Object.assign(
30 defineActions(actions, PropTypes.checkPropTypes), 31 defineActions(actions, PropTypes.checkPropTypes),
32 { announcements },
31 { workspaces }, 33 { workspaces },
32); 34);
diff --git a/src/actions/service.js b/src/actions/service.js
index ceaabc31e..ce62560a9 100644
--- a/src/actions/service.js
+++ b/src/actions/service.js
@@ -5,6 +5,7 @@ export default {
5 setActive: { 5 setActive: {
6 serviceId: PropTypes.string.isRequired, 6 serviceId: PropTypes.string.isRequired,
7 }, 7 },
8 blurActive: {},
8 setActiveNext: {}, 9 setActiveNext: {},
9 setActivePrev: {}, 10 setActivePrev: {},
10 showAddServiceInterface: { 11 showAddServiceInterface: {
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js
index b7f7722dd..985475c8d 100644
--- a/src/components/layout/AppLayout.js
+++ b/src/components/layout/AppLayout.js
@@ -14,6 +14,7 @@ import ErrorBoundary from '../util/ErrorBoundary';
14// import globalMessages from '../../i18n/globalMessages'; 14// import globalMessages from '../../i18n/globalMessages';
15 15
16import { isWindows } from '../../environment'; 16import { isWindows } from '../../environment';
17import AnnouncementScreen from '../../features/announcements/Component';
17import WorkspaceSwitchingIndicator from '../../features/workspaces/components/WorkspaceSwitchingIndicator'; 18import WorkspaceSwitchingIndicator from '../../features/workspaces/components/WorkspaceSwitchingIndicator';
18import { workspaceStore } from '../../features/workspaces'; 19import { workspaceStore } from '../../features/workspaces';
19 20
@@ -80,6 +81,7 @@ class AppLayout extends Component {
80 areRequiredRequestsLoading: PropTypes.bool.isRequired, 81 areRequiredRequestsLoading: PropTypes.bool.isRequired,
81 darkMode: PropTypes.bool.isRequired, 82 darkMode: PropTypes.bool.isRequired,
82 isDelayAppScreenVisible: PropTypes.bool.isRequired, 83 isDelayAppScreenVisible: PropTypes.bool.isRequired,
84 isAnnouncementVisible: PropTypes.bool.isRequired,
83 }; 85 };
84 86
85 static defaultProps = { 87 static defaultProps = {
@@ -111,6 +113,7 @@ class AppLayout extends Component {
111 areRequiredRequestsLoading, 113 areRequiredRequestsLoading,
112 darkMode, 114 darkMode,
113 isDelayAppScreenVisible, 115 isDelayAppScreenVisible,
116 isAnnouncementVisible,
114 } = this.props; 117 } = this.props;
115 118
116 const { intl } = this.context; 119 const { intl } = this.context;
@@ -186,6 +189,7 @@ class AppLayout extends Component {
186 {isDelayAppScreenVisible && (<DelayApp />)} 189 {isDelayAppScreenVisible && (<DelayApp />)}
187 <BasicAuth /> 190 <BasicAuth />
188 <ShareFranz /> 191 <ShareFranz />
192 {isAnnouncementVisible && (<AnnouncementScreen />)}
189 {services} 193 {services}
190 </div> 194 </div>
191 </div> 195 </div>
diff --git a/src/config.js b/src/config.js
index 242675762..e7745b61d 100644
--- a/src/config.js
+++ b/src/config.js
@@ -41,6 +41,7 @@ export const DEFAULT_FEATURES_CONFIG = {
41 }, 41 },
42 isServiceProxyEnabled: false, 42 isServiceProxyEnabled: false,
43 isServiceProxyPremiumFeature: true, 43 isServiceProxyPremiumFeature: true,
44 isAnnouncementsEnabled: true,
44 isWorkspacePremiumFeature: true, 45 isWorkspacePremiumFeature: true,
45 isWorkspaceEnabled: false, 46 isWorkspaceEnabled: false,
46}; 47};
diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js
index 2d855c78f..0357f63bd 100644
--- a/src/containers/layout/AppLayoutContainer.js
+++ b/src/containers/layout/AppLayoutContainer.js
@@ -20,6 +20,7 @@ import Services from '../../components/services/content/Services';
20import AppLoader from '../../components/ui/AppLoader'; 20import AppLoader from '../../components/ui/AppLoader';
21 21
22import { state as delayAppState } from '../../features/delayApp'; 22import { state as delayAppState } from '../../features/delayApp';
23import { announcementsState } from '../../features/announcements/state';
23import { workspaceActions } from '../../features/workspaces/actions'; 24import { workspaceActions } from '../../features/workspaces/actions';
24import WorkspaceDrawer from '../../features/workspaces/components/WorkspaceDrawer'; 25import WorkspaceDrawer from '../../features/workspaces/components/WorkspaceDrawer';
25import { workspaceStore } from '../../features/workspaces'; 26import { workspaceStore } from '../../features/workspaces';
@@ -149,6 +150,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
149 areRequiredRequestsLoading={requests.areRequiredRequestsLoading} 150 areRequiredRequestsLoading={requests.areRequiredRequestsLoading}
150 darkMode={settings.all.app.darkMode} 151 darkMode={settings.all.app.darkMode}
151 isDelayAppScreenVisible={delayAppState.isDelayAppScreenVisible} 152 isDelayAppScreenVisible={delayAppState.isDelayAppScreenVisible}
153 isAnnouncementVisible={announcementsState.isAnnouncementVisible}
152 > 154 >
153 {React.Children.count(children) > 0 ? children : null} 155 {React.Children.count(children) > 0 ? children : null}
154 </AppLayout> 156 </AppLayout>
diff --git a/src/features/announcements/Component.js b/src/features/announcements/Component.js
new file mode 100644
index 000000000..5d95f5d84
--- /dev/null
+++ b/src/features/announcements/Component.js
@@ -0,0 +1,77 @@
1import React, { Component } from 'react';
2import marked from 'marked';
3import PropTypes from 'prop-types';
4import { inject, observer } from 'mobx-react';
5import { defineMessages, intlShape } from 'react-intl';
6import injectSheet from 'react-jss';
7import { themeSidebarWidth } from '@meetfranz/theme/lib/themes/legacy';
8import state from './state';
9
10const messages = defineMessages({
11 headline: {
12 id: 'feature.announcements.headline',
13 defaultMessage: '!!!What\'s new in Franz {version}?',
14 },
15});
16
17const styles = theme => ({
18 container: {
19 background: theme.colorBackground,
20 position: 'absolute',
21 top: 0,
22 zIndex: 140,
23 width: `calc(100% - ${themeSidebarWidth})`,
24 display: 'flex',
25 'flex-direction': 'column',
26 'align-items': 'center',
27 'justify-content': 'center',
28 },
29 headline: {
30 color: theme.colorHeadline,
31 margin: [25, 0, 40],
32 'max-width': 500,
33 'text-align': 'center',
34 'line-height': '1.3em',
35 },
36 body: {
37 '& h3': {
38 fontSize: '24px',
39 margin: '1.5em 0 1em 0',
40 },
41 '& li': {
42 marginBottom: '1em',
43 },
44 },
45});
46
47
48@inject('actions') @injectSheet(styles) @observer
49class AnnouncementScreen extends Component {
50 static propTypes = {
51 classes: PropTypes.object.isRequired,
52 };
53
54 static contextTypes = {
55 intl: intlShape,
56 };
57
58 render() {
59 const { classes } = this.props;
60 const { intl } = this.context;
61 return (
62 <div className={`${classes.container}`}>
63 <h1 className={classes.headline}>
64 {intl.formatMessage(messages.headline, { version: state.currentVersion })}
65 </h1>
66 <div
67 className={classes.body}
68 dangerouslySetInnerHTML={{
69 __html: marked(state.announcement, { sanitize: true }),
70 }}
71 />
72 </div>
73 );
74 }
75}
76
77export default AnnouncementScreen;
diff --git a/src/features/announcements/actions.js b/src/features/announcements/actions.js
new file mode 100644
index 000000000..68b262ded
--- /dev/null
+++ b/src/features/announcements/actions.js
@@ -0,0 +1,8 @@
1import PropTypes from 'prop-types';
2import { createActionsFromDefinitions } from '../../actions/lib/actions';
3
4export const announcementActions = createActionsFromDefinitions({
5 show: {},
6}, PropTypes.checkPropTypes);
7
8export default announcementActions;
diff --git a/src/features/announcements/api.js b/src/features/announcements/api.js
new file mode 100644
index 000000000..ec16066a6
--- /dev/null
+++ b/src/features/announcements/api.js
@@ -0,0 +1,19 @@
1import { remote } from 'electron';
2
3const debug = require('debug')('Franz:feature:announcements:api');
4
5export default {
6 async getCurrentVersion() {
7 debug('getting current version of electron app');
8 return Promise.resolve(remote.app.getVersion());
9 },
10
11 async getAnnouncementForVersion(version) {
12 debug('fetching release announcement from Github');
13 const url = `https://api.github.com/repos/meetfranz/franz/releases/tags/v${version}`;
14 const request = await window.fetch(url, { method: 'GET' });
15 if (!request.ok) throw request;
16 const data = await request.json();
17 return data.body;
18 },
19};
diff --git a/src/features/announcements/index.js b/src/features/announcements/index.js
new file mode 100644
index 000000000..5ea74e0af
--- /dev/null
+++ b/src/features/announcements/index.js
@@ -0,0 +1,37 @@
1import { reaction, runInAction } from 'mobx';
2import { AnnouncementsStore } from './store';
3import api from './api';
4import state, { resetState } from './state';
5
6const debug = require('debug')('Franz:feature:announcements');
7
8let store = null;
9
10export default function initAnnouncements(stores, actions) {
11 // const { features } = stores;
12
13 // Toggle workspace feature
14 reaction(
15 () => (
16 true
17 // features.features.isAnnouncementsEnabled
18 ),
19 (isEnabled) => {
20 if (isEnabled) {
21 debug('Initializing `announcements` feature');
22 store = new AnnouncementsStore(stores, api, actions, state);
23 store.initialize();
24 runInAction(() => { state.isFeatureActive = true; });
25 } else if (store) {
26 debug('Disabling `announcements` feature');
27 runInAction(() => { state.isFeatureActive = false; });
28 store.teardown();
29 store = null;
30 resetState(); // Reset state to default
31 }
32 },
33 {
34 fireImmediately: true,
35 },
36 );
37}
diff --git a/src/features/announcements/state.js b/src/features/announcements/state.js
new file mode 100644
index 000000000..81b632253
--- /dev/null
+++ b/src/features/announcements/state.js
@@ -0,0 +1,17 @@
1import { observable } from 'mobx';
2
3const defaultState = {
4 announcement: null,
5 currentVersion: null,
6 lastUsedVersion: null,
7 isAnnouncementVisible: false,
8 isFeatureActive: false,
9};
10
11export const announcementsState = observable(defaultState);
12
13export function resetState() {
14 Object.assign(announcementsState, defaultState);
15}
16
17export default announcementsState;
diff --git a/src/features/announcements/store.js b/src/features/announcements/store.js
new file mode 100644
index 000000000..004a44062
--- /dev/null
+++ b/src/features/announcements/store.js
@@ -0,0 +1,95 @@
1import { action, observable, reaction } from 'mobx';
2import semver from 'semver';
3
4import Request from '../../stores/lib/Request';
5import Store from '../../stores/lib/Store';
6
7const debug = require('debug')('Franz:feature:announcements:store');
8
9export class AnnouncementsStore extends Store {
10 @observable getCurrentVersion = new Request(this.api, 'getCurrentVersion');
11
12 @observable getAnnouncement = new Request(this.api, 'getAnnouncementForVersion');
13
14 constructor(stores, api, actions, state) {
15 super(stores, api, actions);
16 this.state = state;
17 }
18
19 async setup() {
20 await this.fetchLastUsedVersion();
21 await this.fetchCurrentVersion();
22 await this.fetchReleaseAnnouncement();
23 this.showAnnouncementIfNotSeenYet();
24
25 this.actions.announcements.show.listen(this._showAnnouncement.bind(this));
26 }
27
28 // ====== PUBLIC ======
29
30 async fetchLastUsedVersion() {
31 debug('getting last used version from local storage');
32 const lastUsedVersion = window.localStorage.getItem('lastUsedVersion');
33 this._setLastUsedVersion(lastUsedVersion == null ? '0.0.0' : lastUsedVersion);
34 }
35
36 async fetchCurrentVersion() {
37 debug('getting current version from api');
38 const version = await this.getCurrentVersion.execute();
39 this._setCurrentVersion(version);
40 }
41
42 async fetchReleaseAnnouncement() {
43 debug('getting release announcement from api');
44 try {
45 const announcement = await this.getAnnouncement.execute(this.state.currentVersion);
46 this._setAnnouncement(announcement);
47 } catch (error) {
48 this._setAnnouncement(null);
49 }
50 }
51
52 showAnnouncementIfNotSeenYet() {
53 const { announcement, currentVersion, lastUsedVersion } = this.state;
54 if (announcement && semver.gt(currentVersion, lastUsedVersion)) {
55 debug(`${currentVersion} < ${lastUsedVersion}: announcement is shown`);
56 this._showAnnouncement();
57 } else {
58 debug(`${currentVersion} >= ${lastUsedVersion}: announcement is hidden`);
59 this._hideAnnouncement();
60 }
61 }
62
63 // ====== PRIVATE ======
64
65 @action _setCurrentVersion(version) {
66 debug(`setting current version to ${version}`);
67 this.state.currentVersion = version;
68 }
69
70 @action _setLastUsedVersion(version) {
71 debug(`setting last used version to ${version}`);
72 this.state.lastUsedVersion = version;
73 }
74
75 @action _setAnnouncement(announcement) {
76 debug(`setting announcement to ${announcement}`);
77 this.state.announcement = announcement;
78 }
79
80 @action _showAnnouncement() {
81 this.state.isAnnouncementVisible = true;
82 this.actions.service.blurActive();
83 const dispose = reaction(
84 () => this.stores.services.active,
85 () => {
86 this._hideAnnouncement();
87 dispose();
88 },
89 );
90 }
91
92 @action _hideAnnouncement() {
93 this.state.isAnnouncementVisible = false;
94 }
95}
diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json
index 65799b614..698698515 100644
--- a/src/i18n/locales/defaultMessages.json
+++ b/src/i18n/locales/defaultMessages.json
@@ -625,78 +625,78 @@
625 "defaultMessage": "!!!Your services have been updated.", 625 "defaultMessage": "!!!Your services have been updated.",
626 "end": { 626 "end": {
627 "column": 3, 627 "column": 3,
628 "line": 28 628 "line": 29
629 }, 629 },
630 "file": "src/components/layout/AppLayout.js", 630 "file": "src/components/layout/AppLayout.js",
631 "id": "infobar.servicesUpdated", 631 "id": "infobar.servicesUpdated",
632 "start": { 632 "start": {
633 "column": 19, 633 "column": 19,
634 "line": 25 634 "line": 26
635 } 635 }
636 }, 636 },
637 { 637 {
638 "defaultMessage": "!!!A new update for Franz is available.", 638 "defaultMessage": "!!!A new update for Franz is available.",
639 "end": { 639 "end": {
640 "column": 3, 640 "column": 3,
641 "line": 32 641 "line": 33
642 }, 642 },
643 "file": "src/components/layout/AppLayout.js", 643 "file": "src/components/layout/AppLayout.js",
644 "id": "infobar.updateAvailable", 644 "id": "infobar.updateAvailable",
645 "start": { 645 "start": {
646 "column": 19, 646 "column": 19,
647 "line": 29 647 "line": 30
648 } 648 }
649 }, 649 },
650 { 650 {
651 "defaultMessage": "!!!Reload services", 651 "defaultMessage": "!!!Reload services",
652 "end": { 652 "end": {
653 "column": 3, 653 "column": 3,
654 "line": 36 654 "line": 37
655 }, 655 },
656 "file": "src/components/layout/AppLayout.js", 656 "file": "src/components/layout/AppLayout.js",
657 "id": "infobar.buttonReloadServices", 657 "id": "infobar.buttonReloadServices",
658 "start": { 658 "start": {
659 "column": 24, 659 "column": 24,
660 "line": 33 660 "line": 34
661 } 661 }
662 }, 662 },
663 { 663 {
664 "defaultMessage": "!!!Changelog", 664 "defaultMessage": "!!!Changelog",
665 "end": { 665 "end": {
666 "column": 3, 666 "column": 3,
667 "line": 40 667 "line": 41
668 }, 668 },
669 "file": "src/components/layout/AppLayout.js", 669 "file": "src/components/layout/AppLayout.js",
670 "id": "infobar.buttonChangelog", 670 "id": "infobar.buttonChangelog",
671 "start": { 671 "start": {
672 "column": 13, 672 "column": 13,
673 "line": 37 673 "line": 38
674 } 674 }
675 }, 675 },
676 { 676 {
677 "defaultMessage": "!!!Restart & install update", 677 "defaultMessage": "!!!Restart & install update",
678 "end": { 678 "end": {
679 "column": 3, 679 "column": 3,
680 "line": 44 680 "line": 45
681 }, 681 },
682 "file": "src/components/layout/AppLayout.js", 682 "file": "src/components/layout/AppLayout.js",
683 "id": "infobar.buttonInstallUpdate", 683 "id": "infobar.buttonInstallUpdate",
684 "start": { 684 "start": {
685 "column": 23, 685 "column": 23,
686 "line": 41 686 "line": 42
687 } 687 }
688 }, 688 },
689 { 689 {
690 "defaultMessage": "!!!Could not load services and user information", 690 "defaultMessage": "!!!Could not load services and user information",
691 "end": { 691 "end": {
692 "column": 3, 692 "column": 3,
693 "line": 48 693 "line": 49
694 }, 694 },
695 "file": "src/components/layout/AppLayout.js", 695 "file": "src/components/layout/AppLayout.js",
696 "id": "infobar.requiredRequestsFailed", 696 "id": "infobar.requiredRequestsFailed",
697 "start": { 697 "start": {
698 "column": 26, 698 "column": 26,
699 "line": 45 699 "line": 46
700 } 700 }
701 } 701 }
702 ], 702 ],
@@ -3082,6 +3082,24 @@
3082 { 3082 {
3083 "descriptors": [ 3083 "descriptors": [
3084 { 3084 {
3085 "defaultMessage": "!!!What's new in Franz {version}?",
3086 "end": {
3087 "column": 3,
3088 "line": 14
3089 },
3090 "file": "src/features/announcements/Component.js",
3091 "id": "feature.announcements.headline",
3092 "start": {
3093 "column": 12,
3094 "line": 11
3095 }
3096 }
3097 ],
3098 "path": "src/features/announcements/Component.json"
3099 },
3100 {
3101 "descriptors": [
3102 {
3085 "defaultMessage": "!!!Please purchase license to skip waiting", 3103 "defaultMessage": "!!!Please purchase license to skip waiting",
3086 "end": { 3104 "end": {
3087 "column": 3, 3105 "column": 3,
@@ -4224,198 +4242,211 @@
4224 } 4242 }
4225 }, 4243 },
4226 { 4244 {
4227 "defaultMessage": "!!!Settings", 4245 "defaultMessage": "!!!What's new in Franz?",
4228 "end": { 4246 "end": {
4229 "column": 3, 4247 "column": 3,
4230 "line": 164 4248 "line": 164
4231 }, 4249 },
4232 "file": "src/lib/Menu.js", 4250 "file": "src/lib/Menu.js",
4251 "id": "menu.app.announcement",
4252 "start": {
4253 "column": 16,
4254 "line": 161
4255 }
4256 },
4257 {
4258 "defaultMessage": "!!!Settings",
4259 "end": {
4260 "column": 3,
4261 "line": 168
4262 },
4263 "file": "src/lib/Menu.js",
4233 "id": "menu.app.settings", 4264 "id": "menu.app.settings",
4234 "start": { 4265 "start": {
4235 "column": 12, 4266 "column": 12,
4236 "line": 161 4267 "line": 165
4237 } 4268 }
4238 }, 4269 },
4239 { 4270 {
4240 "defaultMessage": "!!!Hide", 4271 "defaultMessage": "!!!Hide",
4241 "end": { 4272 "end": {
4242 "column": 3, 4273 "column": 3,
4243 "line": 168 4274 "line": 172
4244 }, 4275 },
4245 "file": "src/lib/Menu.js", 4276 "file": "src/lib/Menu.js",
4246 "id": "menu.app.hide", 4277 "id": "menu.app.hide",
4247 "start": { 4278 "start": {
4248 "column": 8, 4279 "column": 8,
4249 "line": 165 4280 "line": 169
4250 } 4281 }
4251 }, 4282 },
4252 { 4283 {
4253 "defaultMessage": "!!!Hide Others", 4284 "defaultMessage": "!!!Hide Others",
4254 "end": { 4285 "end": {
4255 "column": 3, 4286 "column": 3,
4256 "line": 172 4287 "line": 176
4257 }, 4288 },
4258 "file": "src/lib/Menu.js", 4289 "file": "src/lib/Menu.js",
4259 "id": "menu.app.hideOthers", 4290 "id": "menu.app.hideOthers",
4260 "start": { 4291 "start": {
4261 "column": 14, 4292 "column": 14,
4262 "line": 169 4293 "line": 173
4263 } 4294 }
4264 }, 4295 },
4265 { 4296 {
4266 "defaultMessage": "!!!Unhide", 4297 "defaultMessage": "!!!Unhide",
4267 "end": { 4298 "end": {
4268 "column": 3, 4299 "column": 3,
4269 "line": 176 4300 "line": 180
4270 }, 4301 },
4271 "file": "src/lib/Menu.js", 4302 "file": "src/lib/Menu.js",
4272 "id": "menu.app.unhide", 4303 "id": "menu.app.unhide",
4273 "start": { 4304 "start": {
4274 "column": 10, 4305 "column": 10,
4275 "line": 173 4306 "line": 177
4276 } 4307 }
4277 }, 4308 },
4278 { 4309 {
4279 "defaultMessage": "!!!Quit", 4310 "defaultMessage": "!!!Quit",
4280 "end": { 4311 "end": {
4281 "column": 3, 4312 "column": 3,
4282 "line": 180 4313 "line": 184
4283 }, 4314 },
4284 "file": "src/lib/Menu.js", 4315 "file": "src/lib/Menu.js",
4285 "id": "menu.app.quit", 4316 "id": "menu.app.quit",
4286 "start": { 4317 "start": {
4287 "column": 8, 4318 "column": 8,
4288 "line": 177 4319 "line": 181
4289 } 4320 }
4290 }, 4321 },
4291 { 4322 {
4292 "defaultMessage": "!!!Add New Service...", 4323 "defaultMessage": "!!!Add New Service...",
4293 "end": { 4324 "end": {
4294 "column": 3, 4325 "column": 3,
4295 "line": 184 4326 "line": 188
4296 }, 4327 },
4297 "file": "src/lib/Menu.js", 4328 "file": "src/lib/Menu.js",
4298 "id": "menu.services.addNewService", 4329 "id": "menu.services.addNewService",
4299 "start": { 4330 "start": {
4300 "column": 17, 4331 "column": 17,
4301 "line": 181 4332 "line": 185
4302 } 4333 }
4303 }, 4334 },
4304 { 4335 {
4305 "defaultMessage": "!!!Add New Workspace...", 4336 "defaultMessage": "!!!Add New Workspace...",
4306 "end": { 4337 "end": {
4307 "column": 3, 4338 "column": 3,
4308 "line": 188 4339 "line": 192
4309 }, 4340 },
4310 "file": "src/lib/Menu.js", 4341 "file": "src/lib/Menu.js",
4311 "id": "menu.workspaces.addNewWorkspace", 4342 "id": "menu.workspaces.addNewWorkspace",
4312 "start": { 4343 "start": {
4313 "column": 19, 4344 "column": 19,
4314 "line": 185 4345 "line": 189
4315 } 4346 }
4316 }, 4347 },
4317 { 4348 {
4318 "defaultMessage": "!!!Open workspace drawer", 4349 "defaultMessage": "!!!Open workspace drawer",
4319 "end": { 4350 "end": {
4320 "column": 3, 4351 "column": 3,
4321 "line": 192 4352 "line": 196
4322 }, 4353 },
4323 "file": "src/lib/Menu.js", 4354 "file": "src/lib/Menu.js",
4324 "id": "menu.workspaces.openWorkspaceDrawer", 4355 "id": "menu.workspaces.openWorkspaceDrawer",
4325 "start": { 4356 "start": {
4326 "column": 23, 4357 "column": 23,
4327 "line": 189 4358 "line": 193
4328 } 4359 }
4329 }, 4360 },
4330 { 4361 {
4331 "defaultMessage": "!!!Close workspace drawer", 4362 "defaultMessage": "!!!Close workspace drawer",
4332 "end": { 4363 "end": {
4333 "column": 3, 4364 "column": 3,
4334 "line": 196 4365 "line": 200
4335 }, 4366 },
4336 "file": "src/lib/Menu.js", 4367 "file": "src/lib/Menu.js",
4337 "id": "menu.workspaces.closeWorkspaceDrawer", 4368 "id": "menu.workspaces.closeWorkspaceDrawer",
4338 "start": { 4369 "start": {
4339 "column": 24, 4370 "column": 24,
4340 "line": 193 4371 "line": 197
4341 } 4372 }
4342 }, 4373 },
4343 { 4374 {
4344 "defaultMessage": "!!!Activate next service...", 4375 "defaultMessage": "!!!Activate next service...",
4345 "end": { 4376 "end": {
4346 "column": 3, 4377 "column": 3,
4347 "line": 200 4378 "line": 204
4348 }, 4379 },
4349 "file": "src/lib/Menu.js", 4380 "file": "src/lib/Menu.js",
4350 "id": "menu.services.setNextServiceActive", 4381 "id": "menu.services.setNextServiceActive",
4351 "start": { 4382 "start": {
4352 "column": 23, 4383 "column": 23,
4353 "line": 197 4384 "line": 201
4354 } 4385 }
4355 }, 4386 },
4356 { 4387 {
4357 "defaultMessage": "!!!Activate previous service...", 4388 "defaultMessage": "!!!Activate previous service...",
4358 "end": { 4389 "end": {
4359 "column": 3, 4390 "column": 3,
4360 "line": 204 4391 "line": 208
4361 }, 4392 },
4362 "file": "src/lib/Menu.js", 4393 "file": "src/lib/Menu.js",
4363 "id": "menu.services.activatePreviousService", 4394 "id": "menu.services.activatePreviousService",
4364 "start": { 4395 "start": {
4365 "column": 27, 4396 "column": 27,
4366 "line": 201 4397 "line": 205
4367 } 4398 }
4368 }, 4399 },
4369 { 4400 {
4370 "defaultMessage": "!!!Disable notifications & audio", 4401 "defaultMessage": "!!!Disable notifications & audio",
4371 "end": { 4402 "end": {
4372 "column": 3, 4403 "column": 3,
4373 "line": 208 4404 "line": 212
4374 }, 4405 },
4375 "file": "src/lib/Menu.js", 4406 "file": "src/lib/Menu.js",
4376 "id": "sidebar.muteApp", 4407 "id": "sidebar.muteApp",
4377 "start": { 4408 "start": {
4378 "column": 11, 4409 "column": 11,
4379 "line": 205 4410 "line": 209
4380 } 4411 }
4381 }, 4412 },
4382 { 4413 {
4383 "defaultMessage": "!!!Enable notifications & audio", 4414 "defaultMessage": "!!!Enable notifications & audio",
4384 "end": { 4415 "end": {
4385 "column": 3, 4416 "column": 3,
4386 "line": 212 4417 "line": 216
4387 }, 4418 },
4388 "file": "src/lib/Menu.js", 4419 "file": "src/lib/Menu.js",
4389 "id": "sidebar.unmuteApp", 4420 "id": "sidebar.unmuteApp",
4390 "start": { 4421 "start": {
4391 "column": 13, 4422 "column": 13,
4392 "line": 209 4423 "line": 213
4393 } 4424 }
4394 }, 4425 },
4395 { 4426 {
4396 "defaultMessage": "!!!Workspaces", 4427 "defaultMessage": "!!!Workspaces",
4397 "end": { 4428 "end": {
4398 "column": 3, 4429 "column": 3,
4399 "line": 216 4430 "line": 220
4400 }, 4431 },
4401 "file": "src/lib/Menu.js", 4432 "file": "src/lib/Menu.js",
4402 "id": "menu.workspaces", 4433 "id": "menu.workspaces",
4403 "start": { 4434 "start": {
4404 "column": 14, 4435 "column": 14,
4405 "line": 213 4436 "line": 217
4406 } 4437 }
4407 }, 4438 },
4408 { 4439 {
4409 "defaultMessage": "!!!Default", 4440 "defaultMessage": "!!!Default",
4410 "end": { 4441 "end": {
4411 "column": 3, 4442 "column": 3,
4412 "line": 220 4443 "line": 224
4413 }, 4444 },
4414 "file": "src/lib/Menu.js", 4445 "file": "src/lib/Menu.js",
4415 "id": "menu.workspaces.defaultWorkspace", 4446 "id": "menu.workspaces.defaultWorkspace",
4416 "start": { 4447 "start": {
4417 "column": 20, 4448 "column": 20,
4418 "line": 217 4449 "line": 221
4419 } 4450 }
4420 } 4451 }
4421 ], 4452 ],
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index 84a71117a..13775d758 100644
--- a/src/i18n/locales/en-US.json
+++ b/src/i18n/locales/en-US.json
@@ -1,6 +1,7 @@
1{ 1{
2 "app.errorHandler.action": "Reload", 2 "app.errorHandler.action": "Reload",
3 "app.errorHandler.headline": "Something went wrong", 3 "app.errorHandler.headline": "Something went wrong",
4 "feature.announcements.headline": "What's new in Franz {version}?",
4 "feature.delayApp.action": "Get a Franz Supporter License", 5 "feature.delayApp.action": "Get a Franz Supporter License",
5 "feature.delayApp.headline": "Please purchase a Franz Supporter License to skip waiting", 6 "feature.delayApp.headline": "Please purchase a Franz Supporter License to skip waiting",
6 "feature.delayApp.text": "Franz will continue in {seconds} seconds.", 7 "feature.delayApp.text": "Franz will continue in {seconds} seconds.",
@@ -43,6 +44,7 @@
43 "login.submit.label": "Sign in", 44 "login.submit.label": "Sign in",
44 "login.tokenExpired": "Your session expired, please login again.", 45 "login.tokenExpired": "Your session expired, please login again.",
45 "menu.app.about": "About Franz", 46 "menu.app.about": "About Franz",
47 "menu.app.announcement": "What's new in Franz?",
46 "menu.app.hide": "Hide", 48 "menu.app.hide": "Hide",
47 "menu.app.hideOthers": "Hide Others", 49 "menu.app.hideOthers": "Hide Others",
48 "menu.app.quit": "Quit", 50 "menu.app.quit": "Quit",
diff --git a/src/i18n/messages/src/components/layout/AppLayout.json b/src/i18n/messages/src/components/layout/AppLayout.json
index 92593ed5c..4dd354afc 100644
--- a/src/i18n/messages/src/components/layout/AppLayout.json
+++ b/src/i18n/messages/src/components/layout/AppLayout.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Your services have been updated.", 4 "defaultMessage": "!!!Your services have been updated.",
5 "file": "src/components/layout/AppLayout.js", 5 "file": "src/components/layout/AppLayout.js",
6 "start": { 6 "start": {
7 "line": 25, 7 "line": 26,
8 "column": 19 8 "column": 19
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 28, 11 "line": 29,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!A new update for Franz is available.", 17 "defaultMessage": "!!!A new update for Franz is available.",
18 "file": "src/components/layout/AppLayout.js", 18 "file": "src/components/layout/AppLayout.js",
19 "start": { 19 "start": {
20 "line": 29, 20 "line": 30,
21 "column": 19 21 "column": 19
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 32, 24 "line": 33,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Reload services", 30 "defaultMessage": "!!!Reload services",
31 "file": "src/components/layout/AppLayout.js", 31 "file": "src/components/layout/AppLayout.js",
32 "start": { 32 "start": {
33 "line": 33, 33 "line": 34,
34 "column": 24 34 "column": 24
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 36, 37 "line": 37,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,11 +43,11 @@
43 "defaultMessage": "!!!Changelog", 43 "defaultMessage": "!!!Changelog",
44 "file": "src/components/layout/AppLayout.js", 44 "file": "src/components/layout/AppLayout.js",
45 "start": { 45 "start": {
46 "line": 37, 46 "line": 38,
47 "column": 13 47 "column": 13
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 40, 50 "line": 41,
51 "column": 3 51 "column": 3
52 } 52 }
53 }, 53 },
@@ -56,11 +56,11 @@
56 "defaultMessage": "!!!Restart & install update", 56 "defaultMessage": "!!!Restart & install update",
57 "file": "src/components/layout/AppLayout.js", 57 "file": "src/components/layout/AppLayout.js",
58 "start": { 58 "start": {
59 "line": 41, 59 "line": 42,
60 "column": 23 60 "column": 23
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 44, 63 "line": 45,
64 "column": 3 64 "column": 3
65 } 65 }
66 }, 66 },
@@ -69,11 +69,11 @@
69 "defaultMessage": "!!!Could not load services and user information", 69 "defaultMessage": "!!!Could not load services and user information",
70 "file": "src/components/layout/AppLayout.js", 70 "file": "src/components/layout/AppLayout.js",
71 "start": { 71 "start": {
72 "line": 45, 72 "line": 46,
73 "column": 26 73 "column": 26
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 48, 76 "line": 49,
77 "column": 3 77 "column": 3
78 } 78 }
79 } 79 }
diff --git a/src/i18n/messages/src/features/announcements/Component.json b/src/i18n/messages/src/features/announcements/Component.json
new file mode 100644
index 000000000..18e1b84c5
--- /dev/null
+++ b/src/i18n/messages/src/features/announcements/Component.json
@@ -0,0 +1,15 @@
1[
2 {
3 "id": "feature.announcements.headline",
4 "defaultMessage": "!!!What's new in Franz {version}?",
5 "file": "src/features/announcements/Component.js",
6 "start": {
7 "line": 11,
8 "column": 12
9 },
10 "end": {
11 "line": 14,
12 "column": 3
13 }
14 }
15] \ No newline at end of file
diff --git a/src/i18n/messages/src/lib/Menu.json b/src/i18n/messages/src/lib/Menu.json
index 3889d39e0..f4cd35582 100644
--- a/src/i18n/messages/src/lib/Menu.json
+++ b/src/i18n/messages/src/lib/Menu.json
@@ -481,15 +481,28 @@
481 } 481 }
482 }, 482 },
483 { 483 {
484 "id": "menu.app.announcement",
485 "defaultMessage": "!!!What's new in Franz?",
486 "file": "src/lib/Menu.js",
487 "start": {
488 "line": 161,
489 "column": 16
490 },
491 "end": {
492 "line": 164,
493 "column": 3
494 }
495 },
496 {
484 "id": "menu.app.settings", 497 "id": "menu.app.settings",
485 "defaultMessage": "!!!Settings", 498 "defaultMessage": "!!!Settings",
486 "file": "src/lib/Menu.js", 499 "file": "src/lib/Menu.js",
487 "start": { 500 "start": {
488 "line": 161, 501 "line": 165,
489 "column": 12 502 "column": 12
490 }, 503 },
491 "end": { 504 "end": {
492 "line": 164, 505 "line": 168,
493 "column": 3 506 "column": 3
494 } 507 }
495 }, 508 },
@@ -498,11 +511,11 @@
498 "defaultMessage": "!!!Hide", 511 "defaultMessage": "!!!Hide",
499 "file": "src/lib/Menu.js", 512 "file": "src/lib/Menu.js",
500 "start": { 513 "start": {
501 "line": 165, 514 "line": 169,
502 "column": 8 515 "column": 8
503 }, 516 },
504 "end": { 517 "end": {
505 "line": 168, 518 "line": 172,
506 "column": 3 519 "column": 3
507 } 520 }
508 }, 521 },
@@ -511,11 +524,11 @@
511 "defaultMessage": "!!!Hide Others", 524 "defaultMessage": "!!!Hide Others",
512 "file": "src/lib/Menu.js", 525 "file": "src/lib/Menu.js",
513 "start": { 526 "start": {
514 "line": 169, 527 "line": 173,
515 "column": 14 528 "column": 14
516 }, 529 },
517 "end": { 530 "end": {
518 "line": 172, 531 "line": 176,
519 "column": 3 532 "column": 3
520 } 533 }
521 }, 534 },
@@ -524,11 +537,11 @@
524 "defaultMessage": "!!!Unhide", 537 "defaultMessage": "!!!Unhide",
525 "file": "src/lib/Menu.js", 538 "file": "src/lib/Menu.js",
526 "start": { 539 "start": {
527 "line": 173, 540 "line": 177,
528 "column": 10 541 "column": 10
529 }, 542 },
530 "end": { 543 "end": {
531 "line": 176, 544 "line": 180,
532 "column": 3 545 "column": 3
533 } 546 }
534 }, 547 },
@@ -537,11 +550,11 @@
537 "defaultMessage": "!!!Quit", 550 "defaultMessage": "!!!Quit",
538 "file": "src/lib/Menu.js", 551 "file": "src/lib/Menu.js",
539 "start": { 552 "start": {
540 "line": 177, 553 "line": 181,
541 "column": 8 554 "column": 8
542 }, 555 },
543 "end": { 556 "end": {
544 "line": 180, 557 "line": 184,
545 "column": 3 558 "column": 3
546 } 559 }
547 }, 560 },
@@ -550,11 +563,11 @@
550 "defaultMessage": "!!!Add New Service...", 563 "defaultMessage": "!!!Add New Service...",
551 "file": "src/lib/Menu.js", 564 "file": "src/lib/Menu.js",
552 "start": { 565 "start": {
553 "line": 181, 566 "line": 185,
554 "column": 17 567 "column": 17
555 }, 568 },
556 "end": { 569 "end": {
557 "line": 184, 570 "line": 188,
558 "column": 3 571 "column": 3
559 } 572 }
560 }, 573 },
@@ -563,11 +576,11 @@
563 "defaultMessage": "!!!Add New Workspace...", 576 "defaultMessage": "!!!Add New Workspace...",
564 "file": "src/lib/Menu.js", 577 "file": "src/lib/Menu.js",
565 "start": { 578 "start": {
566 "line": 185, 579 "line": 189,
567 "column": 19 580 "column": 19
568 }, 581 },
569 "end": { 582 "end": {
570 "line": 188, 583 "line": 192,
571 "column": 3 584 "column": 3
572 } 585 }
573 }, 586 },
@@ -576,11 +589,11 @@
576 "defaultMessage": "!!!Open workspace drawer", 589 "defaultMessage": "!!!Open workspace drawer",
577 "file": "src/lib/Menu.js", 590 "file": "src/lib/Menu.js",
578 "start": { 591 "start": {
579 "line": 189, 592 "line": 193,
580 "column": 23 593 "column": 23
581 }, 594 },
582 "end": { 595 "end": {
583 "line": 192, 596 "line": 196,
584 "column": 3 597 "column": 3
585 } 598 }
586 }, 599 },
@@ -589,11 +602,11 @@
589 "defaultMessage": "!!!Close workspace drawer", 602 "defaultMessage": "!!!Close workspace drawer",
590 "file": "src/lib/Menu.js", 603 "file": "src/lib/Menu.js",
591 "start": { 604 "start": {
592 "line": 193, 605 "line": 197,
593 "column": 24 606 "column": 24
594 }, 607 },
595 "end": { 608 "end": {
596 "line": 196, 609 "line": 200,
597 "column": 3 610 "column": 3
598 } 611 }
599 }, 612 },
@@ -602,11 +615,11 @@
602 "defaultMessage": "!!!Activate next service...", 615 "defaultMessage": "!!!Activate next service...",
603 "file": "src/lib/Menu.js", 616 "file": "src/lib/Menu.js",
604 "start": { 617 "start": {
605 "line": 197, 618 "line": 201,
606 "column": 23 619 "column": 23
607 }, 620 },
608 "end": { 621 "end": {
609 "line": 200, 622 "line": 204,
610 "column": 3 623 "column": 3
611 } 624 }
612 }, 625 },
@@ -615,11 +628,11 @@
615 "defaultMessage": "!!!Activate previous service...", 628 "defaultMessage": "!!!Activate previous service...",
616 "file": "src/lib/Menu.js", 629 "file": "src/lib/Menu.js",
617 "start": { 630 "start": {
618 "line": 201, 631 "line": 205,
619 "column": 27 632 "column": 27
620 }, 633 },
621 "end": { 634 "end": {
622 "line": 204, 635 "line": 208,
623 "column": 3 636 "column": 3
624 } 637 }
625 }, 638 },
@@ -628,11 +641,11 @@
628 "defaultMessage": "!!!Disable notifications & audio", 641 "defaultMessage": "!!!Disable notifications & audio",
629 "file": "src/lib/Menu.js", 642 "file": "src/lib/Menu.js",
630 "start": { 643 "start": {
631 "line": 205, 644 "line": 209,
632 "column": 11 645 "column": 11
633 }, 646 },
634 "end": { 647 "end": {
635 "line": 208, 648 "line": 212,
636 "column": 3 649 "column": 3
637 } 650 }
638 }, 651 },
@@ -641,11 +654,11 @@
641 "defaultMessage": "!!!Enable notifications & audio", 654 "defaultMessage": "!!!Enable notifications & audio",
642 "file": "src/lib/Menu.js", 655 "file": "src/lib/Menu.js",
643 "start": { 656 "start": {
644 "line": 209, 657 "line": 213,
645 "column": 13 658 "column": 13
646 }, 659 },
647 "end": { 660 "end": {
648 "line": 212, 661 "line": 216,
649 "column": 3 662 "column": 3
650 } 663 }
651 }, 664 },
@@ -654,11 +667,11 @@
654 "defaultMessage": "!!!Workspaces", 667 "defaultMessage": "!!!Workspaces",
655 "file": "src/lib/Menu.js", 668 "file": "src/lib/Menu.js",
656 "start": { 669 "start": {
657 "line": 213, 670 "line": 217,
658 "column": 14 671 "column": 14
659 }, 672 },
660 "end": { 673 "end": {
661 "line": 216, 674 "line": 220,
662 "column": 3 675 "column": 3
663 } 676 }
664 }, 677 },
@@ -667,11 +680,11 @@
667 "defaultMessage": "!!!Default", 680 "defaultMessage": "!!!Default",
668 "file": "src/lib/Menu.js", 681 "file": "src/lib/Menu.js",
669 "start": { 682 "start": {
670 "line": 217, 683 "line": 221,
671 "column": 20 684 "column": 20
672 }, 685 },
673 "end": { 686 "end": {
674 "line": 220, 687 "line": 224,
675 "column": 3 688 "column": 3
676 } 689 }
677 } 690 }
diff --git a/src/lib/Menu.js b/src/lib/Menu.js
index a4e41c17c..46a347237 100644
--- a/src/lib/Menu.js
+++ b/src/lib/Menu.js
@@ -158,6 +158,10 @@ const menuItems = defineMessages({
158 id: 'menu.app.about', 158 id: 'menu.app.about',
159 defaultMessage: '!!!About Franz', 159 defaultMessage: '!!!About Franz',
160 }, 160 },
161 announcement: {
162 id: 'menu.app.announcement',
163 defaultMessage: '!!!What\'s new in Franz?',
164 },
161 settings: { 165 settings: {
162 id: 'menu.app.settings', 166 id: 'menu.app.settings',
163 defaultMessage: '!!!Settings', 167 defaultMessage: '!!!Settings',
@@ -618,6 +622,12 @@ export default class FranzMenu {
618 role: 'about', 622 role: 'about',
619 }, 623 },
620 { 624 {
625 label: intl.formatMessage(menuItems.announcement),
626 click: () => {
627 this.actions.announcements.show();
628 },
629 },
630 {
621 type: 'separator', 631 type: 'separator',
622 }, 632 },
623 { 633 {
diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js
index 8fe576813..dcda021c1 100644
--- a/src/stores/FeaturesStore.js
+++ b/src/stores/FeaturesStore.js
@@ -14,6 +14,7 @@ import serviceProxy from '../features/serviceProxy';
14import basicAuth from '../features/basicAuth'; 14import basicAuth from '../features/basicAuth';
15import workspaces from '../features/workspaces'; 15import workspaces from '../features/workspaces';
16import shareFranz from '../features/shareFranz'; 16import shareFranz from '../features/shareFranz';
17import announcements from '../features/announcements';
17 18
18import { DEFAULT_FEATURES_CONFIG } from '../config'; 19import { DEFAULT_FEATURES_CONFIG } from '../config';
19 20
@@ -71,5 +72,6 @@ export default class FeaturesStore extends Store {
71 basicAuth(this.stores, this.actions); 72 basicAuth(this.stores, this.actions);
72 workspaces(this.stores, this.actions); 73 workspaces(this.stores, this.actions);
73 shareFranz(this.stores, this.actions); 74 shareFranz(this.stores, this.actions);
75 announcements(this.stores, this.actions);
74 } 76 }
75} 77}
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index 0ec6bf550..d04fdd0c5 100644
--- a/src/stores/ServicesStore.js
+++ b/src/stores/ServicesStore.js
@@ -36,6 +36,7 @@ export default class ServicesStore extends Store {
36 36
37 // Register action handlers 37 // Register action handlers
38 this.actions.service.setActive.listen(this._setActive.bind(this)); 38 this.actions.service.setActive.listen(this._setActive.bind(this));
39 this.actions.service.blurActive.listen(this._blurActive.bind(this));
39 this.actions.service.setActiveNext.listen(this._setActiveNext.bind(this)); 40 this.actions.service.setActiveNext.listen(this._setActiveNext.bind(this));
40 this.actions.service.setActivePrev.listen(this._setActivePrev.bind(this)); 41 this.actions.service.setActivePrev.listen(this._setActivePrev.bind(this));
41 this.actions.service.showAddServiceInterface.listen(this._showAddServiceInterface.bind(this)); 42 this.actions.service.showAddServiceInterface.listen(this._showAddServiceInterface.bind(this));
@@ -301,6 +302,11 @@ export default class ServicesStore extends Store {
301 this._focusActiveService(); 302 this._focusActiveService();
302 } 303 }
303 304
305 @action _blurActive() {
306 if (!this.active) return;
307 this.active.isActive = false;
308 }
309
304 @action _setActiveNext() { 310 @action _setActiveNext() {
305 const nextIndex = this._wrapIndex(this.allDisplayed.findIndex(service => service.isActive), 1, this.allDisplayed.length); 311 const nextIndex = this._wrapIndex(this.allDisplayed.findIndex(service => service.isActive), 1, this.allDisplayed.length);
306 312