From cf9d7a30fed4fe50c346e652073464b07277a81e Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Fri, 8 Mar 2019 14:48:46 +0100 Subject: detach service when underlying webview unmounts --- src/containers/layout/AppLayoutContainer.js | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/containers/layout/AppLayoutContainer.js') diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js index 749912c59..5a05ce431 100644 --- a/src/containers/layout/AppLayoutContainer.js +++ b/src/containers/layout/AppLayoutContainer.js @@ -42,6 +42,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e setActive, handleIPCMessage, setWebviewReference, + detachService, openWindow, reorder, reload, @@ -105,6 +106,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e services={services.allDisplayedUnordered} handleIPCMessage={handleIPCMessage} setWebviewReference={setWebviewReference} + detachService={detachService} openWindow={openWindow} reload={reload} openSettings={openSettings} @@ -160,6 +162,7 @@ AppLayoutContainer.wrappedComponent.propTypes = { toggleAudio: PropTypes.func.isRequired, handleIPCMessage: PropTypes.func.isRequired, setWebviewReference: PropTypes.func.isRequired, + detachService: PropTypes.func.isRequired, openWindow: PropTypes.func.isRequired, reloadUpdatedServices: PropTypes.func.isRequired, updateService: PropTypes.func.isRequired, -- cgit v1.2.3-70-g09d2 From 6fb07bcb716af76ec2e96345f37624d12d0d1af0 Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Tue, 12 Mar 2019 21:36:10 +0100 Subject: implements basic release announcement feature --- .eslintrc | 2 +- package-lock.json | 5 ++ package.json | 1 + src/actions/index.js | 6 +- src/actions/service.js | 1 + src/components/layout/AppLayout.js | 4 + src/config.js | 1 + src/containers/layout/AppLayoutContainer.js | 2 + src/features/announcements/Component.js | 77 ++++++++++++++++++ src/features/announcements/actions.js | 8 ++ src/features/announcements/api.js | 19 +++++ src/features/announcements/index.js | 37 +++++++++ src/features/announcements/state.js | 17 ++++ src/features/announcements/store.js | 95 ++++++++++++++++++++++ src/i18n/locales/defaultMessages.json | 95 ++++++++++++++-------- src/i18n/locales/en-US.json | 2 + .../messages/src/components/layout/AppLayout.json | 24 +++--- .../src/features/announcements/Component.json | 15 ++++ src/i18n/messages/src/lib/Menu.json | 53 +++++++----- src/lib/Menu.js | 10 +++ src/stores/FeaturesStore.js | 2 + src/stores/ServicesStore.js | 6 ++ 22 files changed, 416 insertions(+), 66 deletions(-) create mode 100644 src/features/announcements/Component.js create mode 100644 src/features/announcements/actions.js create mode 100644 src/features/announcements/api.js create mode 100644 src/features/announcements/index.js create mode 100644 src/features/announcements/state.js create mode 100644 src/features/announcements/store.js create mode 100644 src/i18n/messages/src/features/announcements/Component.json (limited to 'src/containers/layout/AppLayoutContainer.js') diff --git a/.eslintrc b/.eslintrc index 743946d35..a4ffd505c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -14,7 +14,7 @@ "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], - "react/forbid-prop-types": 1, + "react/forbid-prop-types": 0, "react/destructuring-assignment": 1, "prefer-destructuring": 1, "no-underscore-dangle": 0, diff --git a/package-lock.json b/package-lock.json index 8499abda9..bc333ae50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12094,6 +12094,11 @@ "object-visit": "^1.0.0" } }, + "marked": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.6.1.tgz", + "integrity": "sha512-+H0L3ibcWhAZE02SKMqmvYsErLo4EAVJxu5h3bHBBDvvjeWXtl92rGUSBYHL2++5Y+RSNgl8dYOAXcYe7lp1fA==" + }, "matchdep": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", diff --git a/package.json b/package.json index 14e0df7ca..4ddc83777 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "hex-to-rgba": "1.0.2", "jsonwebtoken": "^7.4.1", "lodash": "^4.17.4", + "marked": "0.6.1", "mdi": "^1.9.33", "mime-types": "2.1.21", "mobx": "5.7.0", diff --git a/src/actions/index.js b/src/actions/index.js index 59acabb0b..dc1d3b6b2 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -11,6 +11,7 @@ import payment from './payment'; import news from './news'; import settings from './settings'; import requests from './requests'; +import announcements from '../features/announcements/actions'; const actions = Object.assign({}, { service, @@ -25,4 +26,7 @@ const actions = Object.assign({}, { requests, }); -export default defineActions(actions, PropTypes.checkPropTypes); +export default Object.assign( + defineActions(actions, PropTypes.checkPropTypes), + { announcements }, +); 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 { setActive: { serviceId: PropTypes.string.isRequired, }, + blurActive: {}, setActiveNext: {}, setActivePrev: {}, showAddServiceInterface: { diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js index 593149e72..2bda91f73 100644 --- a/src/components/layout/AppLayout.js +++ b/src/components/layout/AppLayout.js @@ -13,6 +13,7 @@ import ErrorBoundary from '../util/ErrorBoundary'; // import globalMessages from '../../i18n/globalMessages'; import { isWindows } from '../../environment'; +import AnnouncementScreen from '../../features/announcements/Component'; function createMarkup(HTMLString) { return { __html: HTMLString }; @@ -64,6 +65,7 @@ export default @observer class AppLayout extends Component { areRequiredRequestsLoading: PropTypes.bool.isRequired, darkMode: PropTypes.bool.isRequired, isDelayAppScreenVisible: PropTypes.bool.isRequired, + isAnnouncementVisible: PropTypes.bool.isRequired, }; static defaultProps = { @@ -93,6 +95,7 @@ export default @observer class AppLayout extends Component { areRequiredRequestsLoading, darkMode, isDelayAppScreenVisible, + isAnnouncementVisible, } = this.props; const { intl } = this.context; @@ -166,6 +169,7 @@ export default @observer class AppLayout extends Component { {isDelayAppScreenVisible && ()} + {isAnnouncementVisible && ()} {services} diff --git a/src/config.js b/src/config.js index 479572edb..47d22ca7d 100644 --- a/src/config.js +++ b/src/config.js @@ -41,6 +41,7 @@ export const DEFAULT_FEATURES_CONFIG = { }, isServiceProxyEnabled: false, isServiceProxyPremiumFeature: true, + isAnnouncementsEnabled: true, }; export const DEFAULT_WINDOW_OPTIONS = { diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js index 5a05ce431..f26e51517 100644 --- a/src/containers/layout/AppLayoutContainer.js +++ b/src/containers/layout/AppLayoutContainer.js @@ -20,6 +20,7 @@ import Services from '../../components/services/content/Services'; import AppLoader from '../../components/ui/AppLoader'; import { state as delayAppState } from '../../features/delayApp'; +import { announcementsState } from '../../features/announcements/state'; export default @inject('stores', 'actions') @observer class AppLayoutContainer extends Component { static defaultProps = { @@ -134,6 +135,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e areRequiredRequestsLoading={requests.areRequiredRequestsLoading} darkMode={settings.all.app.darkMode} isDelayAppScreenVisible={delayAppState.isDelayAppScreenVisible} + isAnnouncementVisible={announcementsState.isAnnouncementVisible} > {React.Children.count(children) > 0 ? children : null} 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 @@ +import React, { Component } from 'react'; +import marked from 'marked'; +import PropTypes from 'prop-types'; +import { inject, observer } from 'mobx-react'; +import { defineMessages, intlShape } from 'react-intl'; +import injectSheet from 'react-jss'; +import { themeSidebarWidth } from '@meetfranz/theme/lib/themes/legacy'; +import state from './state'; + +const messages = defineMessages({ + headline: { + id: 'feature.announcements.headline', + defaultMessage: '!!!What\'s new in Franz {version}?', + }, +}); + +const styles = theme => ({ + container: { + background: theme.colorBackground, + position: 'absolute', + top: 0, + zIndex: 140, + width: `calc(100% - ${themeSidebarWidth})`, + display: 'flex', + 'flex-direction': 'column', + 'align-items': 'center', + 'justify-content': 'center', + }, + headline: { + color: theme.colorHeadline, + margin: [25, 0, 40], + 'max-width': 500, + 'text-align': 'center', + 'line-height': '1.3em', + }, + body: { + '& h3': { + fontSize: '24px', + margin: '1.5em 0 1em 0', + }, + '& li': { + marginBottom: '1em', + }, + }, +}); + + +@inject('actions') @injectSheet(styles) @observer +class AnnouncementScreen extends Component { + static propTypes = { + classes: PropTypes.object.isRequired, + }; + + static contextTypes = { + intl: intlShape, + }; + + render() { + const { classes } = this.props; + const { intl } = this.context; + return ( +
+

+ {intl.formatMessage(messages.headline, { version: state.currentVersion })} +

+
+
+ ); + } +} + +export 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 @@ +import PropTypes from 'prop-types'; +import { createActionsFromDefinitions } from '../../actions/lib/actions'; + +export const announcementActions = createActionsFromDefinitions({ + show: {}, +}, PropTypes.checkPropTypes); + +export 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 @@ +import { remote } from 'electron'; + +const debug = require('debug')('Franz:feature:announcements:api'); + +export default { + async getCurrentVersion() { + debug('getting current version of electron app'); + return Promise.resolve(remote.app.getVersion()); + }, + + async getAnnouncementForVersion(version) { + debug('fetching release announcement from Github'); + const url = `https://api.github.com/repos/meetfranz/franz/releases/tags/v${version}`; + const request = await window.fetch(url, { method: 'GET' }); + if (!request.ok) throw request; + const data = await request.json(); + return data.body; + }, +}; 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 @@ +import { reaction, runInAction } from 'mobx'; +import { AnnouncementsStore } from './store'; +import api from './api'; +import state, { resetState } from './state'; + +const debug = require('debug')('Franz:feature:announcements'); + +let store = null; + +export default function initAnnouncements(stores, actions) { + // const { features } = stores; + + // Toggle workspace feature + reaction( + () => ( + true + // features.features.isAnnouncementsEnabled + ), + (isEnabled) => { + if (isEnabled) { + debug('Initializing `announcements` feature'); + store = new AnnouncementsStore(stores, api, actions, state); + store.initialize(); + runInAction(() => { state.isFeatureActive = true; }); + } else if (store) { + debug('Disabling `announcements` feature'); + runInAction(() => { state.isFeatureActive = false; }); + store.teardown(); + store = null; + resetState(); // Reset state to default + } + }, + { + fireImmediately: true, + }, + ); +} 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 @@ +import { observable } from 'mobx'; + +const defaultState = { + announcement: null, + currentVersion: null, + lastUsedVersion: null, + isAnnouncementVisible: false, + isFeatureActive: false, +}; + +export const announcementsState = observable(defaultState); + +export function resetState() { + Object.assign(announcementsState, defaultState); +} + +export 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 @@ +import { action, observable, reaction } from 'mobx'; +import semver from 'semver'; + +import Request from '../../stores/lib/Request'; +import Store from '../../stores/lib/Store'; + +const debug = require('debug')('Franz:feature:announcements:store'); + +export class AnnouncementsStore extends Store { + @observable getCurrentVersion = new Request(this.api, 'getCurrentVersion'); + + @observable getAnnouncement = new Request(this.api, 'getAnnouncementForVersion'); + + constructor(stores, api, actions, state) { + super(stores, api, actions); + this.state = state; + } + + async setup() { + await this.fetchLastUsedVersion(); + await this.fetchCurrentVersion(); + await this.fetchReleaseAnnouncement(); + this.showAnnouncementIfNotSeenYet(); + + this.actions.announcements.show.listen(this._showAnnouncement.bind(this)); + } + + // ====== PUBLIC ====== + + async fetchLastUsedVersion() { + debug('getting last used version from local storage'); + const lastUsedVersion = window.localStorage.getItem('lastUsedVersion'); + this._setLastUsedVersion(lastUsedVersion == null ? '0.0.0' : lastUsedVersion); + } + + async fetchCurrentVersion() { + debug('getting current version from api'); + const version = await this.getCurrentVersion.execute(); + this._setCurrentVersion(version); + } + + async fetchReleaseAnnouncement() { + debug('getting release announcement from api'); + try { + const announcement = await this.getAnnouncement.execute(this.state.currentVersion); + this._setAnnouncement(announcement); + } catch (error) { + this._setAnnouncement(null); + } + } + + showAnnouncementIfNotSeenYet() { + const { announcement, currentVersion, lastUsedVersion } = this.state; + if (announcement && semver.gt(currentVersion, lastUsedVersion)) { + debug(`${currentVersion} < ${lastUsedVersion}: announcement is shown`); + this._showAnnouncement(); + } else { + debug(`${currentVersion} >= ${lastUsedVersion}: announcement is hidden`); + this._hideAnnouncement(); + } + } + + // ====== PRIVATE ====== + + @action _setCurrentVersion(version) { + debug(`setting current version to ${version}`); + this.state.currentVersion = version; + } + + @action _setLastUsedVersion(version) { + debug(`setting last used version to ${version}`); + this.state.lastUsedVersion = version; + } + + @action _setAnnouncement(announcement) { + debug(`setting announcement to ${announcement}`); + this.state.announcement = announcement; + } + + @action _showAnnouncement() { + this.state.isAnnouncementVisible = true; + this.actions.service.blurActive(); + const dispose = reaction( + () => this.stores.services.active, + () => { + this._hideAnnouncement(); + dispose(); + }, + ); + } + + @action _hideAnnouncement() { + this.state.isAnnouncementVisible = false; + } +} diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index 0641c510c..fcd24c7ef 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json @@ -625,78 +625,78 @@ "defaultMessage": "!!!Your services have been updated.", "end": { "column": 3, - "line": 25 + "line": 26 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.servicesUpdated", "start": { "column": 19, - "line": 22 + "line": 23 } }, { "defaultMessage": "!!!A new update for Franz is available.", "end": { "column": 3, - "line": 29 + "line": 30 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.updateAvailable", "start": { "column": 19, - "line": 26 + "line": 27 } }, { "defaultMessage": "!!!Reload services", "end": { "column": 3, - "line": 33 + "line": 34 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.buttonReloadServices", "start": { "column": 24, - "line": 30 + "line": 31 } }, { "defaultMessage": "!!!Changelog", "end": { "column": 3, - "line": 37 + "line": 38 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.buttonChangelog", "start": { "column": 13, - "line": 34 + "line": 35 } }, { "defaultMessage": "!!!Restart & install update", "end": { "column": 3, - "line": 41 + "line": 42 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.buttonInstallUpdate", "start": { "column": 23, - "line": 38 + "line": 39 } }, { "defaultMessage": "!!!Could not load services and user information", "end": { "column": 3, - "line": 45 + "line": 46 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.requiredRequestsFailed", "start": { "column": 26, - "line": 42 + "line": 43 } } ], @@ -3022,6 +3022,24 @@ ], "path": "src/containers/settings/EditUserScreen.json" }, + { + "descriptors": [ + { + "defaultMessage": "!!!What's new in Franz {version}?", + "end": { + "column": 3, + "line": 14 + }, + "file": "src/features/announcements/Component.js", + "id": "feature.announcements.headline", + "start": { + "column": 12, + "line": 11 + } + } + ], + "path": "src/features/announcements/Component.json" + }, { "descriptors": [ { @@ -3799,133 +3817,146 @@ } }, { - "defaultMessage": "!!!Settings", + "defaultMessage": "!!!What's new in Franz?", "end": { "column": 3, "line": 161 }, "file": "src/lib/Menu.js", + "id": "menu.app.announcement", + "start": { + "column": 16, + "line": 158 + } + }, + { + "defaultMessage": "!!!Settings", + "end": { + "column": 3, + "line": 165 + }, + "file": "src/lib/Menu.js", "id": "menu.app.settings", "start": { "column": 12, - "line": 158 + "line": 162 } }, { "defaultMessage": "!!!Hide", "end": { "column": 3, - "line": 165 + "line": 169 }, "file": "src/lib/Menu.js", "id": "menu.app.hide", "start": { "column": 8, - "line": 162 + "line": 166 } }, { "defaultMessage": "!!!Hide Others", "end": { "column": 3, - "line": 169 + "line": 173 }, "file": "src/lib/Menu.js", "id": "menu.app.hideOthers", "start": { "column": 14, - "line": 166 + "line": 170 } }, { "defaultMessage": "!!!Unhide", "end": { "column": 3, - "line": 173 + "line": 177 }, "file": "src/lib/Menu.js", "id": "menu.app.unhide", "start": { "column": 10, - "line": 170 + "line": 174 } }, { "defaultMessage": "!!!Quit", "end": { "column": 3, - "line": 177 + "line": 181 }, "file": "src/lib/Menu.js", "id": "menu.app.quit", "start": { "column": 8, - "line": 174 + "line": 178 } }, { "defaultMessage": "!!!Add New Service...", "end": { "column": 3, - "line": 181 + "line": 185 }, "file": "src/lib/Menu.js", "id": "menu.services.addNewService", "start": { "column": 17, - "line": 178 + "line": 182 } }, { "defaultMessage": "!!!Activate next service...", "end": { "column": 3, - "line": 185 + "line": 189 }, "file": "src/lib/Menu.js", "id": "menu.services.setNextServiceActive", "start": { "column": 23, - "line": 182 + "line": 186 } }, { "defaultMessage": "!!!Activate previous service...", "end": { "column": 3, - "line": 189 + "line": 193 }, "file": "src/lib/Menu.js", "id": "menu.services.activatePreviousService", "start": { "column": 27, - "line": 186 + "line": 190 } }, { "defaultMessage": "!!!Disable notifications & audio", "end": { "column": 3, - "line": 193 + "line": 197 }, "file": "src/lib/Menu.js", "id": "sidebar.muteApp", "start": { "column": 11, - "line": 190 + "line": 194 } }, { "defaultMessage": "!!!Enable notifications & audio", "end": { "column": 3, - "line": 197 + "line": 201 }, "file": "src/lib/Menu.js", "id": "sidebar.unmuteApp", "start": { "column": 13, - "line": 194 + "line": 198 } } ], diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 7543d38bd..573231c45 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -1,6 +1,7 @@ { "app.errorHandler.action": "Reload", "app.errorHandler.headline": "Something went wrong", + "feature.announcements.headline": "What's new in Franz {version}?", "feature.delayApp.action": "Get a Franz Supporter License", "feature.delayApp.headline": "Please purchase a Franz Supporter License to skip waiting", "feature.delayApp.text": "Franz will continue in {seconds} seconds.", @@ -43,6 +44,7 @@ "login.submit.label": "Sign in", "login.tokenExpired": "Your session expired, please login again.", "menu.app.about": "About Franz", + "menu.app.announcement": "What's new in Franz?", "menu.app.hide": "Hide", "menu.app.hideOthers": "Hide Others", "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 07603d062..384d4b441 100644 --- a/src/i18n/messages/src/components/layout/AppLayout.json +++ b/src/i18n/messages/src/components/layout/AppLayout.json @@ -4,11 +4,11 @@ "defaultMessage": "!!!Your services have been updated.", "file": "src/components/layout/AppLayout.js", "start": { - "line": 22, + "line": 23, "column": 19 }, "end": { - "line": 25, + "line": 26, "column": 3 } }, @@ -17,11 +17,11 @@ "defaultMessage": "!!!A new update for Franz is available.", "file": "src/components/layout/AppLayout.js", "start": { - "line": 26, + "line": 27, "column": 19 }, "end": { - "line": 29, + "line": 30, "column": 3 } }, @@ -30,11 +30,11 @@ "defaultMessage": "!!!Reload services", "file": "src/components/layout/AppLayout.js", "start": { - "line": 30, + "line": 31, "column": 24 }, "end": { - "line": 33, + "line": 34, "column": 3 } }, @@ -43,11 +43,11 @@ "defaultMessage": "!!!Changelog", "file": "src/components/layout/AppLayout.js", "start": { - "line": 34, + "line": 35, "column": 13 }, "end": { - "line": 37, + "line": 38, "column": 3 } }, @@ -56,11 +56,11 @@ "defaultMessage": "!!!Restart & install update", "file": "src/components/layout/AppLayout.js", "start": { - "line": 38, + "line": 39, "column": 23 }, "end": { - "line": 41, + "line": 42, "column": 3 } }, @@ -69,11 +69,11 @@ "defaultMessage": "!!!Could not load services and user information", "file": "src/components/layout/AppLayout.js", "start": { - "line": 42, + "line": 43, "column": 26 }, "end": { - "line": 45, + "line": 46, "column": 3 } } 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 @@ +[ + { + "id": "feature.announcements.headline", + "defaultMessage": "!!!What's new in Franz {version}?", + "file": "src/features/announcements/Component.js", + "start": { + "line": 11, + "column": 12 + }, + "end": { + "line": 14, + "column": 3 + } + } +] \ 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 9314f5cce..0db994871 100644 --- a/src/i18n/messages/src/lib/Menu.json +++ b/src/i18n/messages/src/lib/Menu.json @@ -480,16 +480,29 @@ "column": 3 } }, + { + "id": "menu.app.announcement", + "defaultMessage": "!!!What's new in Franz?", + "file": "src/lib/Menu.js", + "start": { + "line": 158, + "column": 16 + }, + "end": { + "line": 161, + "column": 3 + } + }, { "id": "menu.app.settings", "defaultMessage": "!!!Settings", "file": "src/lib/Menu.js", "start": { - "line": 158, + "line": 162, "column": 12 }, "end": { - "line": 161, + "line": 165, "column": 3 } }, @@ -498,11 +511,11 @@ "defaultMessage": "!!!Hide", "file": "src/lib/Menu.js", "start": { - "line": 162, + "line": 166, "column": 8 }, "end": { - "line": 165, + "line": 169, "column": 3 } }, @@ -511,11 +524,11 @@ "defaultMessage": "!!!Hide Others", "file": "src/lib/Menu.js", "start": { - "line": 166, + "line": 170, "column": 14 }, "end": { - "line": 169, + "line": 173, "column": 3 } }, @@ -524,11 +537,11 @@ "defaultMessage": "!!!Unhide", "file": "src/lib/Menu.js", "start": { - "line": 170, + "line": 174, "column": 10 }, "end": { - "line": 173, + "line": 177, "column": 3 } }, @@ -537,11 +550,11 @@ "defaultMessage": "!!!Quit", "file": "src/lib/Menu.js", "start": { - "line": 174, + "line": 178, "column": 8 }, "end": { - "line": 177, + "line": 181, "column": 3 } }, @@ -550,11 +563,11 @@ "defaultMessage": "!!!Add New Service...", "file": "src/lib/Menu.js", "start": { - "line": 178, + "line": 182, "column": 17 }, "end": { - "line": 181, + "line": 185, "column": 3 } }, @@ -563,11 +576,11 @@ "defaultMessage": "!!!Activate next service...", "file": "src/lib/Menu.js", "start": { - "line": 182, + "line": 186, "column": 23 }, "end": { - "line": 185, + "line": 189, "column": 3 } }, @@ -576,11 +589,11 @@ "defaultMessage": "!!!Activate previous service...", "file": "src/lib/Menu.js", "start": { - "line": 186, + "line": 190, "column": 27 }, "end": { - "line": 189, + "line": 193, "column": 3 } }, @@ -589,11 +602,11 @@ "defaultMessage": "!!!Disable notifications & audio", "file": "src/lib/Menu.js", "start": { - "line": 190, + "line": 194, "column": 11 }, "end": { - "line": 193, + "line": 197, "column": 3 } }, @@ -602,11 +615,11 @@ "defaultMessage": "!!!Enable notifications & audio", "file": "src/lib/Menu.js", "start": { - "line": 194, + "line": 198, "column": 13 }, "end": { - "line": 197, + "line": 201, "column": 3 } } diff --git a/src/lib/Menu.js b/src/lib/Menu.js index 7a60c448f..70f3b2877 100644 --- a/src/lib/Menu.js +++ b/src/lib/Menu.js @@ -155,6 +155,10 @@ const menuItems = defineMessages({ id: 'menu.app.about', defaultMessage: '!!!About Franz', }, + announcement: { + id: 'menu.app.announcement', + defaultMessage: '!!!What\'s new in Franz?', + }, settings: { id: 'menu.app.settings', defaultMessage: '!!!Settings', @@ -589,6 +593,12 @@ export default class FranzMenu { label: intl.formatMessage(menuItems.about), role: 'about', }, + { + label: intl.formatMessage(menuItems.announcement), + click: () => { + this.actions.announcements.show(); + }, + }, { type: 'separator', }, diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js index d2842083c..1c9044b07 100644 --- a/src/stores/FeaturesStore.js +++ b/src/stores/FeaturesStore.js @@ -8,6 +8,7 @@ import spellchecker from '../features/spellchecker'; import serviceProxy from '../features/serviceProxy'; import basicAuth from '../features/basicAuth'; import shareFranz from '../features/shareFranz'; +import announcements from '../features/announcements'; import { DEFAULT_FEATURES_CONFIG } from '../config'; @@ -58,5 +59,6 @@ export default class FeaturesStore extends Store { serviceProxy(this.stores, this.actions); basicAuth(this.stores, this.actions); shareFranz(this.stores, this.actions); + announcements(this.stores, this.actions); } } diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index 69e616f0c..88b0331bf 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js @@ -35,6 +35,7 @@ export default class ServicesStore extends Store { // Register action handlers this.actions.service.setActive.listen(this._setActive.bind(this)); + this.actions.service.blurActive.listen(this._blurActive.bind(this)); this.actions.service.setActiveNext.listen(this._setActiveNext.bind(this)); this.actions.service.setActivePrev.listen(this._setActivePrev.bind(this)); this.actions.service.showAddServiceInterface.listen(this._showAddServiceInterface.bind(this)); @@ -298,6 +299,11 @@ export default class ServicesStore extends Store { this._focusActiveService(); } + @action _blurActive() { + if (!this.active) return; + this.active.isActive = false; + } + @action _setActiveNext() { const nextIndex = this._wrapIndex(this.allDisplayed.findIndex(service => service.isActive), 1, this.allDisplayed.length); -- cgit v1.2.3-70-g09d2 From e4f1862644d5921e2ee77078c10e16efa3e58c7b Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Tue, 19 Mar 2019 19:38:56 +0100 Subject: add workspace drawer --- packages/theme/src/themes/dark/index.ts | 9 +++ packages/theme/src/themes/default/index.ts | 10 +++ src/components/layout/AppLayout.js | 24 +++++- src/components/layout/Sidebar.js | 42 +++++++++- src/components/services/content/ServiceView.js | 1 + src/containers/layout/AppLayoutContainer.js | 14 ++++ src/features/workspaces/actions.js | 6 +- .../workspaces/components/WorkspaceDrawer.js | 94 ++++++++++++++++++++++ .../workspaces/components/WorkspaceDrawerItem.js | 88 ++++++++++++++++++++ src/features/workspaces/state.js | 1 + src/features/workspaces/store.js | 12 ++- src/i18n/locales/defaultMessages.json | 57 +++++++++++++ src/i18n/locales/en-US.json | 8 +- .../messages/src/components/layout/AppLayout.json | 24 +++--- .../messages/src/components/layout/Sidebar.json | 26 ++++++ .../workspaces/components/WorkspaceDrawer.json | 28 +++++++ src/lib/Menu.js | 4 +- src/styles/layout.scss | 12 ++- 18 files changed, 435 insertions(+), 25 deletions(-) create mode 100644 src/features/workspaces/components/WorkspaceDrawer.js create mode 100644 src/features/workspaces/components/WorkspaceDrawerItem.js create mode 100644 src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json (limited to 'src/containers/layout/AppLayoutContainer.js') diff --git a/packages/theme/src/themes/dark/index.ts b/packages/theme/src/themes/dark/index.ts index 3a56719b2..eaa552961 100644 --- a/packages/theme/src/themes/dark/index.ts +++ b/packages/theme/src/themes/dark/index.ts @@ -63,3 +63,12 @@ export const selectSearchColor = inputBackground; // Modal export const colorModalOverlayBackground = color(legacyStyles.darkThemeBlack).alpha(0.8).rgb().string(); + +// Workspace Drawer +export const workspaceDrawerBackground = color(colorBackground).lighten(0.3).hex(); +export const workspaceDrawerItemBorder = color(workspaceDrawerBackground).lighten(0.2).hex(); +export const workspaceDrawerItemActiveBackground = defaultStyles.brandPrimary; +export const workspaceDrawerNameColor = colorText; +export const workspaceDrawerNameActiveColor = 'white'; +export const workspaceDrawerServicesColor = color(colorText).darken(0.5).hex(); +export const workspaceDrawerServicesActiveColor = color(defaultStyles.brandPrimary).lighten(0.5).hex(); diff --git a/packages/theme/src/themes/default/index.ts b/packages/theme/src/themes/default/index.ts index 8a71e61cf..fc03b67de 100644 --- a/packages/theme/src/themes/default/index.ts +++ b/packages/theme/src/themes/default/index.ts @@ -140,3 +140,13 @@ export const badgeBorderRadius = 50; // Modal export const colorModalOverlayBackground = color('#000').alpha(0.5).rgb().string(); + +// Workspace Drawer +export const workspaceDrawerWidth = '220px'; +export const workspaceDrawerBackground = color(colorBackground).lighten(0.1).hex(); +export const workspaceDrawerItemActiveBackground = legacyStyles.themeGrayLightest; +export const workspaceDrawerItemBorder = color(workspaceDrawerBackground).darken(0.05).hex(); +export const workspaceDrawerNameColor = colorText; +export const workspaceDrawerNameActiveColor = colorText; +export const workspaceDrawerServicesColor = color(colorText).lighten(1.5).hex(); +export const workspaceDrawerServicesActiveColor = workspaceDrawerServicesColor; diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js index 593149e72..e06192f87 100644 --- a/src/components/layout/AppLayout.js +++ b/src/components/layout/AppLayout.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; import { defineMessages, intlShape } from 'react-intl'; import { TitleBar } from 'electron-react-titlebar'; +import injectSheet from 'react-jss'; import InfoBar from '../ui/InfoBar'; import { Component as DelayApp } from '../../features/delayApp'; @@ -13,6 +14,7 @@ import ErrorBoundary from '../util/ErrorBoundary'; // import globalMessages from '../../i18n/globalMessages'; import { isWindows } from '../../environment'; +import { workspacesState } from '../../features/workspaces/state'; function createMarkup(HTMLString) { return { __html: HTMLString }; @@ -45,10 +47,23 @@ const messages = defineMessages({ }, }); -export default @observer class AppLayout extends Component { +const styles = theme => ({ + appContent: { + width: `calc(100% + ${theme.workspaceDrawerWidth})`, + transition: 'transform 0.5s ease', + transform() { + return workspacesState.isWorkspaceDrawerOpen ? 'translateX(0)' : 'translateX(-220px)'; + }, + }, +}); + +@injectSheet(styles) @observer +class AppLayout extends Component { static propTypes = { + classes: PropTypes.object.isRequired, isFullScreen: PropTypes.bool.isRequired, sidebar: PropTypes.element.isRequired, + workspacesDrawer: PropTypes.element.isRequired, services: PropTypes.element.isRequired, children: PropTypes.element, news: MobxPropTypes.arrayOrObservableArray.isRequired, @@ -76,7 +91,9 @@ export default @observer class AppLayout extends Component { render() { const { + classes, isFullScreen, + workspacesDrawer, sidebar, services, children, @@ -102,7 +119,8 @@ export default @observer class AppLayout extends Component {
{isWindows && !isFullScreen && } -
+
+ {workspacesDrawer} {sidebar}
{news.length > 0 && news.map(item => ( @@ -176,3 +194,5 @@ export default @observer class AppLayout extends Component { ); } } + +export default AppLayout; diff --git a/src/components/layout/Sidebar.js b/src/components/layout/Sidebar.js index fcc5b0001..de379875e 100644 --- a/src/components/layout/Sidebar.js +++ b/src/components/layout/Sidebar.js @@ -24,6 +24,14 @@ const messages = defineMessages({ id: 'sidebar.unmuteApp', defaultMessage: '!!!Enable notifications & audio', }, + openWorkspaceDrawer: { + id: 'sidebar.openWorkspaceDrawer', + defaultMessage: '!!!Open workspace drawer', + }, + closeWorkspaceDrawer: { + id: 'sidebar.closeWorkspaceDrawer', + defaultMessage: '!!!Close workspace drawer', + }, }); export default @observer class Sidebar extends Component { @@ -31,6 +39,8 @@ export default @observer class Sidebar extends Component { openSettings: PropTypes.func.isRequired, toggleMuteApp: PropTypes.func.isRequired, isAppMuted: PropTypes.bool.isRequired, + isWorkspaceDrawerOpen: PropTypes.bool.isRequired, + toggleWorkspaceDrawer: PropTypes.func.isRequired, }; static contextTypes = { @@ -53,9 +63,23 @@ export default @observer class Sidebar extends Component { this.setState({ tooltipEnabled: false }); } + updateToolTip() { + this.disableToolTip(); + setTimeout(this.enableToolTip.bind(this)); + } + render() { - const { openSettings, toggleMuteApp, isAppMuted } = this.props; + const { + openSettings, + toggleMuteApp, + isAppMuted, + isWorkspaceDrawerOpen, + toggleWorkspaceDrawer, + } = this.props; const { intl } = this.context; + const workspaceToggleMessage = ( + isWorkspaceDrawerOpen ? messages.closeWorkspaceDrawer : messages.openWorkspaceDrawer + ); return (
@@ -66,7 +90,21 @@ export default @observer class Sidebar extends Component { /> +
) : (
diff --git a/src/features/workspaces/components/WorkspacesDashboard.js b/src/features/workspaces/components/WorkspacesDashboard.js index a0a34c778..18813e267 100644 --- a/src/features/workspaces/components/WorkspacesDashboard.js +++ b/src/features/workspaces/components/WorkspacesDashboard.js @@ -147,38 +147,38 @@ class WorkspacesDashboard extends Component { onSubmit={onCreateWorkspaceSubmit} />
+ {getUserWorkspacesRequest.isExecuting ? ( + + ) : ( + + {/* ===== Workspace could not be loaded error ===== */} + {getUserWorkspacesRequest.error ? ( + + {intl.formatMessage(messages.workspacesRequestFailed)} + + ) : ( + + {/* ===== Workspaces list ===== */} + + {workspaces.map(workspace => ( + onWorkspaceClick(w)} + /> + ))} + +
+ )} +
+ )} - {getUserWorkspacesRequest.isExecuting ? ( - - ) : ( - - {/* ===== Workspace could not be loaded error ===== */} - {getUserWorkspacesRequest.error ? ( - - {intl.formatMessage(messages.workspacesRequestFailed)} - - ) : ( - - {/* ===== Workspaces list ===== */} - - {workspaces.map(workspace => ( - onWorkspaceClick(w)} - /> - ))} - -
- )} -
- )}
); diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index 500053377..ec519bad5 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json @@ -3386,6 +3386,19 @@ "column": 25, "line": 32 } + }, + { + "defaultMessage": "!!!Reactivate your premium account!", + "end": { + "column": 3, + "line": 39 + }, + "file": "src/features/workspaces/components/WorkspaceDrawer.js", + "id": "workspaceDrawer.reactivatePremiumAccountLabel", + "start": { + "column": 28, + "line": 36 + } } ], "path": "src/features/workspaces/components/WorkspaceDrawer.json" diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 9770ff0d5..ed5d5e345 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -311,6 +311,7 @@ "workspaceDrawer.headline": "Workspaces", "workspaceDrawer.item.noServicesAddedYet": "No services added yet", "workspaceDrawer.premiumCtaButtonLabel": "Create your first workspace", + "workspaceDrawer.reactivatePremiumAccountLabel": "Reactivate premium account", "workspaceDrawer.workspaceFeatureInfo": "

Franz Workspaces let you focus on what’s important right now. Set up different sets of services and easily switch between them at any time.

You decide which services you need when and where, so we can help you stay on top of your game - or easily switch off from work whenever you want.

", "workspaces.switchingIndicator.switchingTo": "Switching to" -} \ No newline at end of file +} diff --git a/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json b/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json index acd304253..37bae262a 100644 --- a/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json +++ b/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json @@ -63,5 +63,18 @@ "line": 35, "column": 3 } + }, + { + "id": "workspaceDrawer.reactivatePremiumAccountLabel", + "defaultMessage": "!!!Reactivate premium account", + "file": "src/features/workspaces/components/WorkspaceDrawer.js", + "start": { + "line": 36, + "column": 28 + }, + "end": { + "line": 39, + "column": 3 + } } ] \ No newline at end of file -- cgit v1.2.3-70-g09d2 From adf5ac074626dac5bfec9d8fb54b28149e492e1b Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Wed, 10 Apr 2019 17:17:02 +0200 Subject: handle deleted services that are attached to workspaces --- src/containers/layout/AppLayoutContainer.js | 2 +- src/features/workspaces/store.js | 32 ++++++++++++++++++++++------- src/i18n/locales/en-US.json | 2 +- 3 files changed, 27 insertions(+), 9 deletions(-) (limited to 'src/containers/layout/AppLayoutContainer.js') diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js index 52d4e0c27..2d855c78f 100644 --- a/src/containers/layout/AppLayoutContainer.js +++ b/src/containers/layout/AppLayoutContainer.js @@ -88,7 +88,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e const workspacesDrawer = ( ( - workspace ? workspace.services.map(id => services.one(id).name) : services.all.map(s => s.name) + workspace ? workspaceStore.getWorkspaceServices(workspace).map(s => s.name) : services.all.map(s => s.name) )} onUpgradeAccountClick={() => openSettings({ path: 'user' })} /> diff --git a/src/features/workspaces/store.js b/src/features/workspaces/store.js index ba48022c2..ea601700e 100644 --- a/src/features/workspaces/store.js +++ b/src/features/workspaces/store.js @@ -74,6 +74,7 @@ export default class WorkspacesStore extends FeatureStore { this._setIsPremiumFeatureReaction, this._activateLastUsedWorkspaceReaction, this._openDrawerWithSettingsReaction, + this._cleanupInvalidServiceReferences, ]); getUserWorkspacesRequest.execute(); @@ -92,16 +93,17 @@ export default class WorkspacesStore extends FeatureStore { filterServicesByActiveWorkspace = (services) => { const { activeWorkspace, isFeatureActive } = this; - - if (!isFeatureActive) return services; - if (activeWorkspace) { - return services.filter(s => ( - activeWorkspace.services.includes(s.id) - )); + if (isFeatureActive && activeWorkspace) { + return this.getWorkspaceServices(activeWorkspace); } return services; }; + getWorkspaceServices(workspace) { + const { services } = this.stores; + return workspace.services.map(id => services.one(id)).filter(s => !!s); + } + // ========== PRIVATE ========= // _wasDrawerOpenBeforeSettingsRoute = null; @@ -218,7 +220,8 @@ export default class WorkspacesStore extends FeatureStore { if (this.activeWorkspace) { const services = this.stores.services.allDisplayed; const activeService = services.find(s => s.isActive); - const workspaceServices = this.filterServicesByActiveWorkspace(services); + const workspaceServices = this.getWorkspaceServices(this.activeWorkspace); + if (workspaceServices.length <= 0) return; const isActiveServiceInWorkspace = workspaceServices.includes(activeService); if (!isActiveServiceInWorkspace) { this.actions.service.setActive({ serviceId: workspaceServices[0].id }); @@ -255,4 +258,19 @@ export default class WorkspacesStore extends FeatureStore { } } }; + + _cleanupInvalidServiceReferences = () => { + const { services } = this.stores; + let invalidServiceReferencesExist = false; + this.workspaces.forEach((workspace) => { + workspace.services.forEach((serviceId) => { + if (!services.one(serviceId)) { + invalidServiceReferencesExist = true; + } + }); + }); + if (invalidServiceReferencesExist) { + getUserWorkspacesRequest.execute(); + } + }; } diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index da7649b90..84a71117a 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -317,4 +317,4 @@ "workspaceDrawer.workspaceFeatureInfo": "

Franz Workspaces let you focus on what’s important right now. Set up different sets of services and easily switch between them at any time.

You decide which services you need when and where, so we can help you stay on top of your game - or easily switch off from work whenever you want.

", "workspaceDrawer.workspacesSettingsTooltip": "Edit workspaces settings", "workspaces.switchingIndicator.switchingTo": "Switching to" -} +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From ca9d5863f9067f2a32498da763cd536e0bcc5c77 Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Thu, 11 Apr 2019 12:53:16 +0200 Subject: refactor announcements to newest feature pattern --- src/components/layout/AppLayout.js | 2 +- src/containers/layout/AppLayoutContainer.js | 5 +- src/features/announcements/Component.js | 77 --------------------- src/features/announcements/api.js | 6 +- .../announcements/components/AnnouncementScreen.js | 79 ++++++++++++++++++++++ src/features/announcements/index.js | 17 ++--- src/features/announcements/state.js | 17 ----- src/features/announcements/store.js | 49 ++++++++------ src/i18n/locales/defaultMessages.json | 18 +++++ .../components/AnnouncementScreen.json | 15 ++++ 10 files changed, 156 insertions(+), 129 deletions(-) delete mode 100644 src/features/announcements/Component.js create mode 100644 src/features/announcements/components/AnnouncementScreen.js delete mode 100644 src/features/announcements/state.js create mode 100644 src/i18n/messages/src/features/announcements/components/AnnouncementScreen.json (limited to 'src/containers/layout/AppLayoutContainer.js') diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js index 985475c8d..eb3f03f12 100644 --- a/src/components/layout/AppLayout.js +++ b/src/components/layout/AppLayout.js @@ -14,7 +14,7 @@ import ErrorBoundary from '../util/ErrorBoundary'; // import globalMessages from '../../i18n/globalMessages'; import { isWindows } from '../../environment'; -import AnnouncementScreen from '../../features/announcements/Component'; +import AnnouncementScreen from '../../features/announcements/components/AnnouncementScreen'; import WorkspaceSwitchingIndicator from '../../features/workspaces/components/WorkspaceSwitchingIndicator'; import { workspaceStore } from '../../features/workspaces'; diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js index 0357f63bd..8a0e105e7 100644 --- a/src/containers/layout/AppLayoutContainer.js +++ b/src/containers/layout/AppLayoutContainer.js @@ -20,10 +20,11 @@ import Services from '../../components/services/content/Services'; import AppLoader from '../../components/ui/AppLoader'; import { state as delayAppState } from '../../features/delayApp'; -import { announcementsState } from '../../features/announcements/state'; +import { } from '../../features/announcements/store'; import { workspaceActions } from '../../features/workspaces/actions'; import WorkspaceDrawer from '../../features/workspaces/components/WorkspaceDrawer'; import { workspaceStore } from '../../features/workspaces'; +import { announcementsStore } from '../../features/announcements'; export default @inject('stores', 'actions') @observer class AppLayoutContainer extends Component { static defaultProps = { @@ -150,7 +151,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e areRequiredRequestsLoading={requests.areRequiredRequestsLoading} darkMode={settings.all.app.darkMode} isDelayAppScreenVisible={delayAppState.isDelayAppScreenVisible} - isAnnouncementVisible={announcementsState.isAnnouncementVisible} + isAnnouncementVisible={announcementsStore.isAnnouncementVisible} > {React.Children.count(children) > 0 ? children : null} diff --git a/src/features/announcements/Component.js b/src/features/announcements/Component.js deleted file mode 100644 index 5d95f5d84..000000000 --- a/src/features/announcements/Component.js +++ /dev/null @@ -1,77 +0,0 @@ -import React, { Component } from 'react'; -import marked from 'marked'; -import PropTypes from 'prop-types'; -import { inject, observer } from 'mobx-react'; -import { defineMessages, intlShape } from 'react-intl'; -import injectSheet from 'react-jss'; -import { themeSidebarWidth } from '@meetfranz/theme/lib/themes/legacy'; -import state from './state'; - -const messages = defineMessages({ - headline: { - id: 'feature.announcements.headline', - defaultMessage: '!!!What\'s new in Franz {version}?', - }, -}); - -const styles = theme => ({ - container: { - background: theme.colorBackground, - position: 'absolute', - top: 0, - zIndex: 140, - width: `calc(100% - ${themeSidebarWidth})`, - display: 'flex', - 'flex-direction': 'column', - 'align-items': 'center', - 'justify-content': 'center', - }, - headline: { - color: theme.colorHeadline, - margin: [25, 0, 40], - 'max-width': 500, - 'text-align': 'center', - 'line-height': '1.3em', - }, - body: { - '& h3': { - fontSize: '24px', - margin: '1.5em 0 1em 0', - }, - '& li': { - marginBottom: '1em', - }, - }, -}); - - -@inject('actions') @injectSheet(styles) @observer -class AnnouncementScreen extends Component { - static propTypes = { - classes: PropTypes.object.isRequired, - }; - - static contextTypes = { - intl: intlShape, - }; - - render() { - const { classes } = this.props; - const { intl } = this.context; - return ( -
-

- {intl.formatMessage(messages.headline, { version: state.currentVersion })} -

-
-
- ); - } -} - -export default AnnouncementScreen; diff --git a/src/features/announcements/api.js b/src/features/announcements/api.js index ec16066a6..09fcb8235 100644 --- a/src/features/announcements/api.js +++ b/src/features/announcements/api.js @@ -1,8 +1,9 @@ import { remote } from 'electron'; +import Request from '../../stores/lib/Request'; const debug = require('debug')('Franz:feature:announcements:api'); -export default { +export const announcementsApi = { async getCurrentVersion() { debug('getting current version of electron app'); return Promise.resolve(remote.app.getVersion()); @@ -17,3 +18,6 @@ export default { return data.body; }, }; + +export const getCurrentVersionRequest = new Request(announcementsApi, 'getCurrentVersion'); +export const getAnnouncementRequest = new Request(announcementsApi, 'getAnnouncementForVersion'); diff --git a/src/features/announcements/components/AnnouncementScreen.js b/src/features/announcements/components/AnnouncementScreen.js new file mode 100644 index 000000000..5b3e7aeaa --- /dev/null +++ b/src/features/announcements/components/AnnouncementScreen.js @@ -0,0 +1,79 @@ +import React, { Component } from 'react'; +import marked from 'marked'; +import PropTypes from 'prop-types'; +import { inject, observer } from 'mobx-react'; +import { defineMessages, intlShape } from 'react-intl'; +import injectSheet from 'react-jss'; +import { themeSidebarWidth } from '../../../../packages/theme/lib/themes/legacy'; +import { announcementsStore } from '../index'; + +const messages = defineMessages({ + headline: { + id: 'feature.announcements.headline', + defaultMessage: '!!!What\'s new in Franz {version}?', + }, +}); + +const styles = theme => ({ + container: { + background: theme.colorBackground, + position: 'absolute', + top: 0, + zIndex: 140, + width: `calc(100% - ${themeSidebarWidth})`, + display: 'flex', + 'flex-direction': 'column', + 'align-items': 'center', + 'justify-content': 'center', + }, + headline: { + color: theme.colorHeadline, + margin: [25, 0, 40], + 'max-width': 500, + 'text-align': 'center', + 'line-height': '1.3em', + }, + body: { + '& h3': { + fontSize: '24px', + margin: '1.5em 0 1em 0', + }, + '& li': { + marginBottom: '1em', + }, + }, +}); + + +@inject('actions') @injectSheet(styles) @observer +class AnnouncementScreen extends Component { + static propTypes = { + classes: PropTypes.object.isRequired, + }; + + static contextTypes = { + intl: intlShape, + }; + + render() { + const { classes } = this.props; + const { intl } = this.context; + return ( +
+

+ {intl.formatMessage(messages.headline, { + version: announcementsStore.currentVersion, + })} +

+
+
+ ); + } +} + +export default AnnouncementScreen; diff --git a/src/features/announcements/index.js b/src/features/announcements/index.js index 5ea74e0af..c087689a7 100644 --- a/src/features/announcements/index.js +++ b/src/features/announcements/index.js @@ -1,11 +1,9 @@ -import { reaction, runInAction } from 'mobx'; +import { reaction } from 'mobx'; import { AnnouncementsStore } from './store'; -import api from './api'; -import state, { resetState } from './state'; const debug = require('debug')('Franz:feature:announcements'); -let store = null; +export const announcementsStore = new AnnouncementsStore(); export default function initAnnouncements(stores, actions) { // const { features } = stores; @@ -19,15 +17,10 @@ export default function initAnnouncements(stores, actions) { (isEnabled) => { if (isEnabled) { debug('Initializing `announcements` feature'); - store = new AnnouncementsStore(stores, api, actions, state); - store.initialize(); - runInAction(() => { state.isFeatureActive = true; }); - } else if (store) { + announcementsStore.start(stores, actions); + } else if (announcementsStore.isFeatureActive) { debug('Disabling `announcements` feature'); - runInAction(() => { state.isFeatureActive = false; }); - store.teardown(); - store = null; - resetState(); // Reset state to default + announcementsStore.stop(); } }, { diff --git a/src/features/announcements/state.js b/src/features/announcements/state.js deleted file mode 100644 index 81b632253..000000000 --- a/src/features/announcements/state.js +++ /dev/null @@ -1,17 +0,0 @@ -import { observable } from 'mobx'; - -const defaultState = { - announcement: null, - currentVersion: null, - lastUsedVersion: null, - isAnnouncementVisible: false, - isFeatureActive: false, -}; - -export const announcementsState = observable(defaultState); - -export function resetState() { - Object.assign(announcementsState, defaultState); -} - -export default announcementsState; diff --git a/src/features/announcements/store.js b/src/features/announcements/store.js index 004a44062..c59700926 100644 --- a/src/features/announcements/store.js +++ b/src/features/announcements/store.js @@ -1,28 +1,39 @@ import { action, observable, reaction } from 'mobx'; import semver from 'semver'; - -import Request from '../../stores/lib/Request'; -import Store from '../../stores/lib/Store'; +import { FeatureStore } from '../utils/FeatureStore'; +import { getAnnouncementRequest, getCurrentVersionRequest } from './api'; const debug = require('debug')('Franz:feature:announcements:store'); -export class AnnouncementsStore extends Store { - @observable getCurrentVersion = new Request(this.api, 'getCurrentVersion'); +export class AnnouncementsStore extends FeatureStore { - @observable getAnnouncement = new Request(this.api, 'getAnnouncementForVersion'); + @observable announcement = null; - constructor(stores, api, actions, state) { - super(stores, api, actions); - this.state = state; - } + @observable currentVersion = null; + + @observable lastUsedVersion = null; + + @observable isAnnouncementVisible = false; - async setup() { + @observable isFeatureActive = false; + + async start(stores, actions) { + debug('AnnouncementsStore::start'); + this.stores = stores; + this.actions = actions; await this.fetchLastUsedVersion(); await this.fetchCurrentVersion(); await this.fetchReleaseAnnouncement(); this.showAnnouncementIfNotSeenYet(); this.actions.announcements.show.listen(this._showAnnouncement.bind(this)); + this.isFeatureActive = true; + } + + stop() { + debug('AnnouncementsStore::stop'); + this.isFeatureActive = false; + this.isAnnouncementVisible = false; } // ====== PUBLIC ====== @@ -35,14 +46,14 @@ export class AnnouncementsStore extends Store { async fetchCurrentVersion() { debug('getting current version from api'); - const version = await this.getCurrentVersion.execute(); + const version = await getCurrentVersionRequest.execute(); this._setCurrentVersion(version); } async fetchReleaseAnnouncement() { debug('getting release announcement from api'); try { - const announcement = await this.getAnnouncement.execute(this.state.currentVersion); + const announcement = await getAnnouncementRequest.execute(this.currentVersion); this._setAnnouncement(announcement); } catch (error) { this._setAnnouncement(null); @@ -50,7 +61,7 @@ export class AnnouncementsStore extends Store { } showAnnouncementIfNotSeenYet() { - const { announcement, currentVersion, lastUsedVersion } = this.state; + const { announcement, currentVersion, lastUsedVersion } = this; if (announcement && semver.gt(currentVersion, lastUsedVersion)) { debug(`${currentVersion} < ${lastUsedVersion}: announcement is shown`); this._showAnnouncement(); @@ -64,21 +75,21 @@ export class AnnouncementsStore extends Store { @action _setCurrentVersion(version) { debug(`setting current version to ${version}`); - this.state.currentVersion = version; + this.currentVersion = version; } @action _setLastUsedVersion(version) { debug(`setting last used version to ${version}`); - this.state.lastUsedVersion = version; + this.lastUsedVersion = version; } @action _setAnnouncement(announcement) { debug(`setting announcement to ${announcement}`); - this.state.announcement = announcement; + this.announcement = announcement; } @action _showAnnouncement() { - this.state.isAnnouncementVisible = true; + this.isAnnouncementVisible = true; this.actions.service.blurActive(); const dispose = reaction( () => this.stores.services.active, @@ -90,6 +101,6 @@ export class AnnouncementsStore extends Store { } @action _hideAnnouncement() { - this.state.isAnnouncementVisible = false; + this.isAnnouncementVisible = false; } } diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index 698698515..1dd31324e 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json @@ -3097,6 +3097,24 @@ ], "path": "src/features/announcements/Component.json" }, + { + "descriptors": [ + { + "defaultMessage": "!!!What's new in Franz {version}?", + "end": { + "column": 3, + "line": 14 + }, + "file": "src/features/announcements/components/AnnouncementScreen.js", + "id": "feature.announcements.headline", + "start": { + "column": 12, + "line": 11 + } + } + ], + "path": "src/features/announcements/components/AnnouncementScreen.json" + }, { "descriptors": [ { diff --git a/src/i18n/messages/src/features/announcements/components/AnnouncementScreen.json b/src/i18n/messages/src/features/announcements/components/AnnouncementScreen.json new file mode 100644 index 000000000..225670ee2 --- /dev/null +++ b/src/i18n/messages/src/features/announcements/components/AnnouncementScreen.json @@ -0,0 +1,15 @@ +[ + { + "id": "feature.announcements.headline", + "defaultMessage": "!!!What's new in Franz {version}?", + "file": "src/features/announcements/components/AnnouncementScreen.js", + "start": { + "line": 11, + "column": 12 + }, + "end": { + "line": 14, + "column": 3 + } + } +] \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 2af294e00cd14e7ba26ab36bc1b8fe7680661739 Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Thu, 11 Apr 2019 16:47:28 +0200 Subject: fix lint issues --- src/containers/layout/AppLayoutContainer.js | 1 - .../announcements/components/AnnouncementScreen.js | 2 +- src/features/announcements/store.js | 16 ++++++++-------- src/lib/Menu.js | 2 +- 4 files changed, 10 insertions(+), 11 deletions(-) (limited to 'src/containers/layout/AppLayoutContainer.js') diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js index 8a0e105e7..8c1d2dfc1 100644 --- a/src/containers/layout/AppLayoutContainer.js +++ b/src/containers/layout/AppLayoutContainer.js @@ -20,7 +20,6 @@ import Services from '../../components/services/content/Services'; import AppLoader from '../../components/ui/AppLoader'; import { state as delayAppState } from '../../features/delayApp'; -import { } from '../../features/announcements/store'; import { workspaceActions } from '../../features/workspaces/actions'; import WorkspaceDrawer from '../../features/workspaces/components/WorkspaceDrawer'; import { workspaceStore } from '../../features/workspaces'; diff --git a/src/features/announcements/components/AnnouncementScreen.js b/src/features/announcements/components/AnnouncementScreen.js index 2d5efc396..2682b7890 100644 --- a/src/features/announcements/components/AnnouncementScreen.js +++ b/src/features/announcements/components/AnnouncementScreen.js @@ -150,7 +150,7 @@ class AnnouncementScreen extends Component {

diff --git a/src/features/announcements/store.js b/src/features/announcements/store.js index d4fb0a52c..3c46828bb 100644 --- a/src/features/announcements/store.js +++ b/src/features/announcements/store.js @@ -112,14 +112,14 @@ export class AnnouncementsStore extends FeatureStore { this._showAnnouncement(); // Check if the user has already used current version (= has seen the announcement) - // const { currentVersion, lastSeenAnnouncementVersion } = this; - // if (semver.gt(currentVersion, lastSeenAnnouncementVersion)) { - // debug(`${currentVersion} < ${lastSeenAnnouncementVersion}: announcement is shown`); - // this._showAnnouncement(); - // } else { - // debug(`${currentVersion} >= ${lastSeenAnnouncementVersion}: announcement is hidden`); - // this._hideAnnouncement(); - // } + const { currentVersion, lastSeenAnnouncementVersion } = this; + if (semver.gt(currentVersion, lastSeenAnnouncementVersion)) { + debug(`${currentVersion} < ${lastSeenAnnouncementVersion}: announcement is shown`); + this._showAnnouncement(); + } else { + debug(`${currentVersion} >= ${lastSeenAnnouncementVersion}: announcement is hidden`); + this._hideAnnouncement(); + } }; _fetchAnnouncements = () => { diff --git a/src/lib/Menu.js b/src/lib/Menu.js index 3df06e05a..6bea67860 100644 --- a/src/lib/Menu.js +++ b/src/lib/Menu.js @@ -6,7 +6,7 @@ import { isMac, ctrlKey, cmdKey } from '../environment'; import { GA_CATEGORY_WORKSPACES, workspaceStore } from '../features/workspaces/index'; import { workspaceActions } from '../features/workspaces/actions'; import { gaEvent } from './analytics'; -import announcementActions from '../features/announcements/actions'; +import { announcementActions } from '../features/announcements/actions'; const { app, Menu, dialog } = remote; -- cgit v1.2.3-70-g09d2 From 9620db3443c70d6f92b55725ed07a17adda8cea4 Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Fri, 12 Apr 2019 17:18:19 +0200 Subject: link to in-app changelog on new update notification --- src/components/layout/AppLayout.js | 12 +++++++++-- src/containers/layout/AppLayoutContainer.js | 1 + src/electron/ipc-api/autoUpdate.js | 7 +++++-- src/i18n/locales/defaultMessages.json | 24 +++++++++++----------- .../messages/src/components/layout/AppLayout.json | 24 +++++++++++----------- src/stores/AppStore.js | 4 +++- src/styles/info-bar.scss | 4 ++++ 7 files changed, 47 insertions(+), 29 deletions(-) (limited to 'src/containers/layout/AppLayoutContainer.js') diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js index eb3f03f12..d5febfaf4 100644 --- a/src/components/layout/AppLayout.js +++ b/src/components/layout/AppLayout.js @@ -17,6 +17,7 @@ import { isWindows } from '../../environment'; import AnnouncementScreen from '../../features/announcements/components/AnnouncementScreen'; import WorkspaceSwitchingIndicator from '../../features/workspaces/components/WorkspaceSwitchingIndicator'; import { workspaceStore } from '../../features/workspaces'; +import { announcementActions } from '../../features/announcements/actions'; function createMarkup(HTMLString) { return { __html: HTMLString }; @@ -72,6 +73,7 @@ class AppLayout extends Component { // isOnline: PropTypes.bool.isRequired, showServicesUpdatedInfoBar: PropTypes.bool.isRequired, appUpdateIsDownloaded: PropTypes.bool.isRequired, + nextAppReleaseVersion: PropTypes.string, removeNewsItem: PropTypes.func.isRequired, reloadServicesAfterUpdate: PropTypes.func.isRequired, installAppUpdate: PropTypes.func.isRequired, @@ -86,6 +88,7 @@ class AppLayout extends Component { static defaultProps = { children: [], + nextAppReleaseVersion: null, }; static contextTypes = { @@ -104,6 +107,7 @@ class AppLayout extends Component { news, showServicesUpdatedInfoBar, appUpdateIsDownloaded, + nextAppReleaseVersion, removeNewsItem, reloadServicesAfterUpdate, installAppUpdate, @@ -181,9 +185,13 @@ class AppLayout extends Component { {intl.formatMessage(messages.updateAvailable)} {' '} - + )} {isDelayAppScreenVisible && ()} diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js index 8c1d2dfc1..d2891a6a4 100644 --- a/src/containers/layout/AppLayoutContainer.js +++ b/src/containers/layout/AppLayoutContainer.js @@ -136,6 +136,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e isOnline={app.isOnline} showServicesUpdatedInfoBar={ui.showServicesUpdatedInfoBar} appUpdateIsDownloaded={app.updateStatus === app.updateStatusTypes.DOWNLOADED} + nextAppReleaseVersion={app.nextAppReleaseVersion} sidebar={sidebar} workspacesDrawer={workspacesDrawer} services={servicesContainer} diff --git a/src/electron/ipc-api/autoUpdate.js b/src/electron/ipc-api/autoUpdate.js index 74b718734..9a04c1958 100644 --- a/src/electron/ipc-api/autoUpdate.js +++ b/src/electron/ipc-api/autoUpdate.js @@ -30,9 +30,12 @@ export default (params) => { params.mainWindow.webContents.send('autoUpdate', { available: false }); }); - autoUpdater.on('update-available', () => { + autoUpdater.on('update-available', (event) => { debug('update-available'); - params.mainWindow.webContents.send('autoUpdate', { available: true }); + params.mainWindow.webContents.send('autoUpdate', { + version: event.version, + available: true, + }); }); autoUpdater.on('download-progress', (progressObj) => { diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index 3323aa310..df7f04a06 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json @@ -625,78 +625,78 @@ "defaultMessage": "!!!Your services have been updated.", "end": { "column": 3, - "line": 29 + "line": 30 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.servicesUpdated", "start": { "column": 19, - "line": 26 + "line": 27 } }, { "defaultMessage": "!!!A new update for Franz is available.", "end": { "column": 3, - "line": 33 + "line": 34 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.updateAvailable", "start": { "column": 19, - "line": 30 + "line": 31 } }, { "defaultMessage": "!!!Reload services", "end": { "column": 3, - "line": 37 + "line": 38 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.buttonReloadServices", "start": { "column": 24, - "line": 34 + "line": 35 } }, { "defaultMessage": "!!!Changelog", "end": { "column": 3, - "line": 41 + "line": 42 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.buttonChangelog", "start": { "column": 13, - "line": 38 + "line": 39 } }, { "defaultMessage": "!!!Restart & install update", "end": { "column": 3, - "line": 45 + "line": 46 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.buttonInstallUpdate", "start": { "column": 23, - "line": 42 + "line": 43 } }, { "defaultMessage": "!!!Could not load services and user information", "end": { "column": 3, - "line": 49 + "line": 50 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.requiredRequestsFailed", "start": { "column": 26, - "line": 46 + "line": 47 } } ], diff --git a/src/i18n/messages/src/components/layout/AppLayout.json b/src/i18n/messages/src/components/layout/AppLayout.json index 4dd354afc..26b8ce040 100644 --- a/src/i18n/messages/src/components/layout/AppLayout.json +++ b/src/i18n/messages/src/components/layout/AppLayout.json @@ -4,11 +4,11 @@ "defaultMessage": "!!!Your services have been updated.", "file": "src/components/layout/AppLayout.js", "start": { - "line": 26, + "line": 27, "column": 19 }, "end": { - "line": 29, + "line": 30, "column": 3 } }, @@ -17,11 +17,11 @@ "defaultMessage": "!!!A new update for Franz is available.", "file": "src/components/layout/AppLayout.js", "start": { - "line": 30, + "line": 31, "column": 19 }, "end": { - "line": 33, + "line": 34, "column": 3 } }, @@ -30,11 +30,11 @@ "defaultMessage": "!!!Reload services", "file": "src/components/layout/AppLayout.js", "start": { - "line": 34, + "line": 35, "column": 24 }, "end": { - "line": 37, + "line": 38, "column": 3 } }, @@ -43,11 +43,11 @@ "defaultMessage": "!!!Changelog", "file": "src/components/layout/AppLayout.js", "start": { - "line": 38, + "line": 39, "column": 13 }, "end": { - "line": 41, + "line": 42, "column": 3 } }, @@ -56,11 +56,11 @@ "defaultMessage": "!!!Restart & install update", "file": "src/components/layout/AppLayout.js", "start": { - "line": 42, + "line": 43, "column": 23 }, "end": { - "line": 45, + "line": 46, "column": 3 } }, @@ -69,11 +69,11 @@ "defaultMessage": "!!!Could not load services and user information", "file": "src/components/layout/AppLayout.js", "start": { - "line": 46, + "line": 47, "column": 26 }, "end": { - "line": 49, + "line": 50, "column": 3 } } diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js index ca0c9175b..e68e797ef 100644 --- a/src/stores/AppStore.js +++ b/src/stores/AppStore.js @@ -67,6 +67,8 @@ export default class AppStore extends Store { @observable isFocused = true; + @observable nextAppReleaseVersion = null; + dictionaries = []; constructor(...args) { @@ -123,7 +125,7 @@ export default class AppStore extends Store { ipcRenderer.on('autoUpdate', (event, data) => { if (data.available) { this.updateStatus = this.updateStatusTypes.AVAILABLE; - + this.nextAppReleaseVersion = data.version; if (isMac) { app.dock.bounce(); } diff --git a/src/styles/info-bar.scss b/src/styles/info-bar.scss index fb4917358..d3010942f 100644 --- a/src/styles/info-bar.scss +++ b/src/styles/info-bar.scss @@ -43,6 +43,10 @@ } } + .info-bar__inline-button { + color: white; + } + &.info-bar--bottom { order: 10; } &.info-bar--primary { -- cgit v1.2.3-70-g09d2