From 912bca432ebf005a1680e9cc28bf7ee92cb0d36b Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Thu, 10 Jan 2019 13:43:45 +0100 Subject: add workspaces menu item in settings dialog --- src/i18n/locales/en-US.json | 1 + 1 file changed, 1 insertion(+) (limited to 'src/i18n/locales') diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 99df6a0ca..1b869aff1 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -93,6 +93,7 @@ "settings.invite.headline": "Invite Friends", "settings.navigation.availableServices": "Available services", "settings.navigation.yourServices": "Your services", + "settings.navigation.yourWorkspaces": "Your workspaces", "settings.navigation.account": "Account", "settings.navigation.settings": "Settings", "settings.navigation.inviteFriends": "Invite Friends", -- cgit v1.2.3-70-g09d2 From 84755ddf8b8fb015ee2bfd70e9c4aa50d256f9d0 Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Mon, 14 Jan 2019 17:11:27 +0100 Subject: basic setup of workspaces settings screen --- src/api/utils/auth.js | 24 ++++++++++ src/app.js | 2 + .../settings/workspaces/WorkspaceItem.js | 41 ++++++++++++++++ .../settings/workspaces/WorkspacesDashboard.js | 56 ++++++++++++++++++++++ src/containers/settings/WorkspacesScreen.js | 29 +++++++++++ src/environment.js | 1 + src/features/workspaces/api.js | 16 ++++--- src/features/workspaces/index.js | 10 ++-- src/features/workspaces/state.js | 12 +++++ src/features/workspaces/store.js | 15 ++++-- src/i18n/locales/en-US.json | 1 + src/models/Workspace.js | 25 ++++++++++ 12 files changed, 216 insertions(+), 16 deletions(-) create mode 100644 src/api/utils/auth.js create mode 100644 src/components/settings/workspaces/WorkspaceItem.js create mode 100644 src/components/settings/workspaces/WorkspacesDashboard.js create mode 100644 src/containers/settings/WorkspacesScreen.js create mode 100644 src/features/workspaces/state.js create mode 100644 src/models/Workspace.js (limited to 'src/i18n/locales') diff --git a/src/api/utils/auth.js b/src/api/utils/auth.js new file mode 100644 index 000000000..47ac94c19 --- /dev/null +++ b/src/api/utils/auth.js @@ -0,0 +1,24 @@ +import { remote } from 'electron'; +import localStorage from 'mobx-localstorage'; + +const { app } = remote; + +export const prepareAuthRequest = (options, auth = true) => { + const request = Object.assign(options, { + mode: 'cors', + headers: Object.assign({ + 'Content-Type': 'application/json', + 'X-Franz-Source': 'desktop', + 'X-Franz-Version': app.getVersion(), + 'X-Franz-platform': process.platform, + 'X-Franz-Timezone-Offset': new Date().getTimezoneOffset(), + 'X-Franz-System-Locale': app.getLocale(), + }, options.headers), + }); + + if (auth) { + request.headers.Authorization = `Bearer ${localStorage.getItem('authToken')}`; + } + + return request; +}; diff --git a/src/app.js b/src/app.js index 6660feb46..ee1b12939 100644 --- a/src/app.js +++ b/src/app.js @@ -39,6 +39,7 @@ import PricingScreen from './containers/auth/PricingScreen'; import InviteScreen from './containers/auth/InviteScreen'; import AuthLayoutContainer from './containers/auth/AuthLayoutContainer'; import SubscriptionPopupScreen from './containers/subscription/SubscriptionPopupScreen'; +import WorkspacesScreen from './containers/settings/WorkspacesScreen'; // Add Polyfills smoothScroll.polyfill(); @@ -75,6 +76,7 @@ window.addEventListener('load', () => { + diff --git a/src/components/settings/workspaces/WorkspaceItem.js b/src/components/settings/workspaces/WorkspaceItem.js new file mode 100644 index 000000000..82a578ebd --- /dev/null +++ b/src/components/settings/workspaces/WorkspaceItem.js @@ -0,0 +1,41 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { defineMessages, intlShape } from 'react-intl'; +import { observer } from 'mobx-react'; +import classnames from 'classnames'; +import Workspace from '../../../models/Workspace'; + +// const messages = defineMessages({}); + +@observer +class WorkspaceItem extends Component { + static propTypes = { + workspace: PropTypes.instanceOf(Workspace).isRequired, + }; + + static contextTypes = { + intl: intlShape, + }; + + render() { + const { workspace } = this.props; + // const { intl } = this.context; + + return ( + + console.log('go to workspace', workspace.name)} + > + {workspace.name} + + + ); + } +} + +export default WorkspaceItem; diff --git a/src/components/settings/workspaces/WorkspacesDashboard.js b/src/components/settings/workspaces/WorkspacesDashboard.js new file mode 100644 index 000000000..830f32f08 --- /dev/null +++ b/src/components/settings/workspaces/WorkspacesDashboard.js @@ -0,0 +1,56 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; +import { defineMessages, intlShape } from 'react-intl'; + +import Loader from '../../ui/Loader'; +import WorkspaceItem from './WorkspaceItem'; + +const messages = defineMessages({ + headline: { + id: 'settings.workspaces.headline', + defaultMessage: '!!!Your workspaces', + }, + noServicesAdded: { + id: 'settings.workspaces.noWorkspacesAdded', + defaultMessage: '!!!You haven\'t added any workspaces yet.', + }, +}); + +@observer +class WorkspacesDashboard extends Component { + static propTypes = { + workspaces: MobxPropTypes.arrayOrObservableArray.isRequired, + isLoading: PropTypes.bool.isRequired, + }; + + static contextTypes = { + intl: intlShape, + }; + + render() { + const { workspaces, isLoading } = this.props; + const { intl } = this.context; + + return ( +
+
+

{intl.formatMessage(messages.headline)}

+
+
+ {isLoading ? ( + + ) : ( + + + {workspaces.map(workspace => )} + +
+ )} +
+
+ ); + } +} + +export default WorkspacesDashboard; diff --git a/src/containers/settings/WorkspacesScreen.js b/src/containers/settings/WorkspacesScreen.js new file mode 100644 index 000000000..e767fdfbe --- /dev/null +++ b/src/containers/settings/WorkspacesScreen.js @@ -0,0 +1,29 @@ +import React, { Component } from 'react'; +import { observer } from 'mobx-react'; +import { gaPage } from '../../lib/analytics'; +import { state } from '../../features/workspaces/state'; + +import WorkspacesDashboard from '../../components/settings/workspaces/WorkspacesDashboard'; +import ErrorBoundary from '../../components/util/ErrorBoundary'; + +@observer +class WorkspacesScreen extends Component { + static propTypes = {}; + + componentDidMount() { + gaPage('Settings/Workspaces Dashboard'); + } + + render() { + return ( + + + + ); + } +} + +export default WorkspacesScreen; diff --git a/src/environment.js b/src/environment.js index 73b1c7ab2..d67fd6adb 100644 --- a/src/environment.js +++ b/src/environment.js @@ -28,3 +28,4 @@ if (!isDevMode || (isDevMode && useLiveAPI)) { } export const API = api; +export const API_VERSION = 'v1'; diff --git a/src/features/workspaces/api.js b/src/features/workspaces/api.js index 1ee2440fe..97badbd01 100644 --- a/src/features/workspaces/api.js +++ b/src/features/workspaces/api.js @@ -1,9 +1,13 @@ -// TODO: use real server instead -const workspaces = [ - { id: 'workspace-1', name: 'Private' }, - { id: 'workspace-2', name: 'Office' }, -]; +import { prepareAuthRequest } from '../../api/utils/auth'; +import { API, API_VERSION } from '../../environment'; export default { - getUserWorkspaces: () => Promise.resolve(workspaces), + getUserWorkspaces: async () => { + const url = `${API}/${API_VERSION}/workspace`; + const request = await window.fetch(url, prepareAuthRequest({ + method: 'GET', + })); + if (!request.ok) throw request; + return request.json(); + }, }; diff --git a/src/features/workspaces/index.js b/src/features/workspaces/index.js index b4cfd3c2d..50ac3b414 100644 --- a/src/features/workspaces/index.js +++ b/src/features/workspaces/index.js @@ -1,14 +1,11 @@ -import { observable, reaction } from 'mobx'; -import { merge } from 'lodash'; +import { reaction } from 'mobx'; import WorkspacesStore from './store'; import api from './api'; +import { state, resetState } from './state'; const debug = require('debug')('Franz:feature:workspaces'); let store = null; -const defaultState = { workspaces: [] }; - -export const state = observable(defaultState); export default function initWorkspaces(stores, actions) { const { features, user } = stores; @@ -27,8 +24,7 @@ export default function initWorkspaces(stores, actions) { debug('Disabling `workspaces` feature'); store.teardown(); store = null; - // Reset state to default - merge(state, defaultState); + resetState(); // Reset state to default } }, { diff --git a/src/features/workspaces/state.js b/src/features/workspaces/state.js new file mode 100644 index 000000000..ed3fe9f00 --- /dev/null +++ b/src/features/workspaces/state.js @@ -0,0 +1,12 @@ +import { observable } from 'mobx'; + +const defaultState = { + isLoading: false, + workspaces: [], +}; + +export const state = observable(defaultState); + +export function resetState() { + Object.assign(state, defaultState); +} diff --git a/src/features/workspaces/store.js b/src/features/workspaces/store.js index 4b4e729ed..2b6d55cc7 100644 --- a/src/features/workspaces/store.js +++ b/src/features/workspaces/store.js @@ -1,6 +1,7 @@ import { observable, reaction } from 'mobx'; import Store from '../../stores/lib/Store'; import CachedRequest from '../../stores/lib/CachedRequest'; +import Workspace from '../../models/Workspace'; const debug = require('debug')('Franz:feature:workspaces'); @@ -18,12 +19,20 @@ export default class WorkspacesStore extends Store { reaction( () => this.allWorkspacesRequest.result, - workspaces => this.setWorkspaces(workspaces), + workspaces => this._setWorkspaces(workspaces), + ); + reaction( + () => this.allWorkspacesRequest.isExecuting, + isExecuting => this._setIsLoading(isExecuting), ); } - setWorkspaces = (workspaces) => { + _setWorkspaces = (workspaces) => { debug('setting user workspaces', workspaces.slice()); - this.state.workspaces = workspaces; + this.state.workspaces = workspaces.map(data => new Workspace(data)); + }; + + _setIsLoading = (isLoading) => { + this.state.isLoading = isLoading; }; } diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 1b869aff1..1652b5585 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -196,6 +196,7 @@ "settings.user.form.accountType.individual": "Individual", "settings.user.form.accountType.non-profit": "Non-Profit", "settings.user.form.accountType.company": "Company", + "settings.workspaces.headline": "Your workspaces", "subscription.type.free": "free", "subscription.type.month": "month", "subscription.type.year": "year", diff --git a/src/models/Workspace.js b/src/models/Workspace.js new file mode 100644 index 000000000..ede2710dc --- /dev/null +++ b/src/models/Workspace.js @@ -0,0 +1,25 @@ +import { observable } from 'mobx'; + +export default class Workspace { + id = null; + + @observable name = null; + + @observable order = null; + + @observable services = []; + + @observable userId = null; + + constructor(data) { + if (!data.id) { + throw Error('Workspace requires Id'); + } + + this.id = data.id; + this.name = data.name; + this.order = data.order; + this.services = data.services; + this.userId = data.userId; + } +} -- cgit v1.2.3-70-g09d2 From 90399cc608b93cc185b0ee1c9b79e98cfafb8bc1 Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Tue, 12 Feb 2019 14:59:58 +0100 Subject: consolidate workspace feature for further development --- src/actions/index.js | 2 +- src/actions/workspace.js | 8 --- src/app.js | 4 +- .../settings/workspaces/WorkspaceItem.js | 42 ------------ .../settings/workspaces/WorkspacesDashboard.js | 63 ------------------ src/containers/settings/EditWorkspaceScreen.js | 54 ---------------- src/containers/settings/WorkspacesScreen.js | 37 ----------- src/features/workspaces/actions.js | 8 +++ .../workspaces/components/WorkspaceItem.js | 42 ++++++++++++ .../workspaces/components/WorkspacesDashboard.js | 63 ++++++++++++++++++ .../workspaces/containers/EditWorkspaceScreen.js | 75 ++++++++++++++++++++++ .../workspaces/containers/WorkspacesScreen.js | 37 +++++++++++ src/features/workspaces/models/Workspace.js | 25 ++++++++ src/features/workspaces/store.js | 2 +- .../workspaces/styles/workspaces-table.scss | 53 +++++++++++++++ src/i18n/locales/de.json | 3 + src/i18n/locales/en-US.json | 1 + src/models/Workspace.js | 25 -------- src/styles/main.scss | 2 +- src/styles/workspace-table.scss | 53 --------------- 20 files changed, 312 insertions(+), 287 deletions(-) delete mode 100644 src/actions/workspace.js delete mode 100644 src/components/settings/workspaces/WorkspaceItem.js delete mode 100644 src/components/settings/workspaces/WorkspacesDashboard.js delete mode 100644 src/containers/settings/EditWorkspaceScreen.js delete mode 100644 src/containers/settings/WorkspacesScreen.js create mode 100644 src/features/workspaces/actions.js create mode 100644 src/features/workspaces/components/WorkspaceItem.js create mode 100644 src/features/workspaces/components/WorkspacesDashboard.js create mode 100644 src/features/workspaces/containers/EditWorkspaceScreen.js create mode 100644 src/features/workspaces/containers/WorkspacesScreen.js create mode 100644 src/features/workspaces/models/Workspace.js create mode 100644 src/features/workspaces/styles/workspaces-table.scss delete mode 100644 src/models/Workspace.js delete mode 100644 src/styles/workspace-table.scss (limited to 'src/i18n/locales') diff --git a/src/actions/index.js b/src/actions/index.js index a406af50a..45e6da515 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -11,7 +11,7 @@ import payment from './payment'; import news from './news'; import settings from './settings'; import requests from './requests'; -import workspace from './workspace'; +import workspace from '../features/workspaces/actions'; const actions = Object.assign({}, { service, diff --git a/src/actions/workspace.js b/src/actions/workspace.js deleted file mode 100644 index ab07a96c0..000000000 --- a/src/actions/workspace.js +++ /dev/null @@ -1,8 +0,0 @@ -import PropTypes from 'prop-types'; -import Workspace from '../models/Workspace'; - -export default { - edit: { - workspace: PropTypes.instanceOf(Workspace).isRequired, - }, -}; diff --git a/src/app.js b/src/app.js index 320ba679f..d3b540f62 100644 --- a/src/app.js +++ b/src/app.js @@ -39,8 +39,8 @@ import PricingScreen from './containers/auth/PricingScreen'; import InviteScreen from './containers/auth/InviteScreen'; import AuthLayoutContainer from './containers/auth/AuthLayoutContainer'; import SubscriptionPopupScreen from './containers/subscription/SubscriptionPopupScreen'; -import WorkspacesScreen from './containers/settings/WorkspacesScreen'; -import EditWorkspaceScreen from './containers/settings/EditWorkspaceScreen'; +import WorkspacesScreen from './features/workspaces/containers/WorkspacesScreen'; +import EditWorkspaceScreen from './features/workspaces/containers/EditWorkspaceScreen'; // Add Polyfills smoothScroll.polyfill(); diff --git a/src/components/settings/workspaces/WorkspaceItem.js b/src/components/settings/workspaces/WorkspaceItem.js deleted file mode 100644 index 088d61433..000000000 --- a/src/components/settings/workspaces/WorkspaceItem.js +++ /dev/null @@ -1,42 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { intlShape } from 'react-intl'; -import { observer } from 'mobx-react'; -import classnames from 'classnames'; -import Workspace from '../../../models/Workspace'; - -// const messages = defineMessages({}); - -@observer -class WorkspaceItem extends Component { - static propTypes = { - workspace: PropTypes.instanceOf(Workspace).isRequired, - onItemClick: PropTypes.func.isRequired, - }; - - static contextTypes = { - intl: intlShape, - }; - - render() { - const { workspace, onItemClick } = this.props; - // const { intl } = this.context; - - return ( - - onItemClick(workspace)} - > - {workspace.name} - - - ); - } -} - -export default WorkspaceItem; diff --git a/src/components/settings/workspaces/WorkspacesDashboard.js b/src/components/settings/workspaces/WorkspacesDashboard.js deleted file mode 100644 index a5bb18cb7..000000000 --- a/src/components/settings/workspaces/WorkspacesDashboard.js +++ /dev/null @@ -1,63 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; -import { defineMessages, intlShape } from 'react-intl'; - -import Loader from '../../ui/Loader'; -import WorkspaceItem from './WorkspaceItem'; - -const messages = defineMessages({ - headline: { - id: 'settings.workspaces.headline', - defaultMessage: '!!!Your workspaces', - }, - noServicesAdded: { - id: 'settings.workspaces.noWorkspacesAdded', - defaultMessage: '!!!You haven\'t added any workspaces yet.', - }, -}); - -@observer -class WorkspacesDashboard extends Component { - static propTypes = { - workspaces: MobxPropTypes.arrayOrObservableArray.isRequired, - isLoading: PropTypes.bool.isRequired, - onWorkspaceClick: PropTypes.func.isRequired, - }; - - static contextTypes = { - intl: intlShape, - }; - - render() { - const { workspaces, isLoading, onWorkspaceClick } = this.props; - const { intl } = this.context; - - return ( -
-
-

{intl.formatMessage(messages.headline)}

-
-
- {isLoading ? ( - - ) : ( - - - {workspaces.map(workspace => ( - onWorkspaceClick(w)} - /> - ))} - -
- )} -
-
- ); - } -} - -export default WorkspacesDashboard; diff --git a/src/containers/settings/EditWorkspaceScreen.js b/src/containers/settings/EditWorkspaceScreen.js deleted file mode 100644 index 665b405bd..000000000 --- a/src/containers/settings/EditWorkspaceScreen.js +++ /dev/null @@ -1,54 +0,0 @@ -import React, { Component } from 'react'; -import { inject, observer } from 'mobx-react'; -import { defineMessages, intlShape } from 'react-intl'; -import Form from '../../lib/Form'; -import ErrorBoundary from '../../components/util/ErrorBoundary'; -import { gaPage } from '../../lib/analytics'; -import { state } from '../../features/workspaces/state'; - -const messages = defineMessages({ - name: { - id: 'settings.workspace.form.name', - defaultMessage: '!!!Name', - }, -}); - -@inject('stores', 'actions') @observer -class EditWorkspaceScreen extends Component { - static contextTypes = { - intl: intlShape, - }; - - componentDidMount() { - gaPage('Settings/Workspace/Edit'); - } - - prepareForm(workspace) { - const { intl } = this.context; - const config = { - fields: { - name: { - label: intl.formatMessage(messages.name), - placeholder: intl.formatMessage(messages.name), - value: workspace.name, - }, - }, - }; - return new Form(config); - } - - render() { - const { workspaceBeingEdited } = state; - if (!workspaceBeingEdited) return null; - - // const form = this.prepareForm(workspaceBeingEdited); - - return ( - -
{workspaceBeingEdited.name}
-
- ); - } -} - -export default EditWorkspaceScreen; diff --git a/src/containers/settings/WorkspacesScreen.js b/src/containers/settings/WorkspacesScreen.js deleted file mode 100644 index 5e91f7673..000000000 --- a/src/containers/settings/WorkspacesScreen.js +++ /dev/null @@ -1,37 +0,0 @@ -import React, { Component } from 'react'; -import { inject, observer } from 'mobx-react'; -import PropTypes from 'prop-types'; -import { gaPage } from '../../lib/analytics'; -import { state } from '../../features/workspaces/state'; -import WorkspacesDashboard from '../../components/settings/workspaces/WorkspacesDashboard'; -import ErrorBoundary from '../../components/util/ErrorBoundary'; - -@inject('actions') @observer -class WorkspacesScreen extends Component { - static propTypes = { - actions: PropTypes.shape({ - workspace: PropTypes.shape({ - edit: PropTypes.func.isRequired, - }), - }).isRequired, - }; - - componentDidMount() { - gaPage('Settings/Workspaces Dashboard'); - } - - render() { - const { workspace } = this.props.actions; - return ( - - workspace.edit({ workspace: w })} - /> - - ); - } -} - -export default WorkspacesScreen; diff --git a/src/features/workspaces/actions.js b/src/features/workspaces/actions.js new file mode 100644 index 000000000..30866af96 --- /dev/null +++ b/src/features/workspaces/actions.js @@ -0,0 +1,8 @@ +import PropTypes from 'prop-types'; +import Workspace from './models/Workspace'; + +export default { + edit: { + workspace: PropTypes.instanceOf(Workspace).isRequired, + }, +}; diff --git a/src/features/workspaces/components/WorkspaceItem.js b/src/features/workspaces/components/WorkspaceItem.js new file mode 100644 index 000000000..b2c2a4830 --- /dev/null +++ b/src/features/workspaces/components/WorkspaceItem.js @@ -0,0 +1,42 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { intlShape } from 'react-intl'; +import { observer } from 'mobx-react'; +import classnames from 'classnames'; +import Workspace from '../models/Workspace'; + +// const messages = defineMessages({}); + +@observer +class WorkspaceItem extends Component { + static propTypes = { + workspace: PropTypes.instanceOf(Workspace).isRequired, + onItemClick: PropTypes.func.isRequired, + }; + + static contextTypes = { + intl: intlShape, + }; + + render() { + const { workspace, onItemClick } = this.props; + // const { intl } = this.context; + + return ( + + onItemClick(workspace)} + > + {workspace.name} + + + ); + } +} + +export default WorkspaceItem; diff --git a/src/features/workspaces/components/WorkspacesDashboard.js b/src/features/workspaces/components/WorkspacesDashboard.js new file mode 100644 index 000000000..2a8b3a5ee --- /dev/null +++ b/src/features/workspaces/components/WorkspacesDashboard.js @@ -0,0 +1,63 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; +import { defineMessages, intlShape } from 'react-intl'; + +import Loader from '../../../components/ui/Loader'; +import WorkspaceItem from './WorkspaceItem'; + +const messages = defineMessages({ + headline: { + id: 'settings.workspaces.headline', + defaultMessage: '!!!Your workspaces', + }, + noServicesAdded: { + id: 'settings.workspaces.noWorkspacesAdded', + defaultMessage: '!!!You haven\'t added any workspaces yet.', + }, +}); + +@observer +class WorkspacesDashboard extends Component { + static propTypes = { + workspaces: MobxPropTypes.arrayOrObservableArray.isRequired, + isLoading: PropTypes.bool.isRequired, + onWorkspaceClick: PropTypes.func.isRequired, + }; + + static contextTypes = { + intl: intlShape, + }; + + render() { + const { workspaces, isLoading, onWorkspaceClick } = this.props; + const { intl } = this.context; + + return ( +
+
+

{intl.formatMessage(messages.headline)}

+
+
+ {isLoading ? ( + + ) : ( + + + {workspaces.map(workspace => ( + onWorkspaceClick(w)} + /> + ))} + +
+ )} +
+
+ ); + } +} + +export default WorkspacesDashboard; diff --git a/src/features/workspaces/containers/EditWorkspaceScreen.js b/src/features/workspaces/containers/EditWorkspaceScreen.js new file mode 100644 index 000000000..d8c52f586 --- /dev/null +++ b/src/features/workspaces/containers/EditWorkspaceScreen.js @@ -0,0 +1,75 @@ +import React, { Component } from 'react'; +import { inject, observer } from 'mobx-react'; +import { defineMessages, intlShape } from 'react-intl'; +import { Link } from 'react-router'; +import Form from '../../../lib/Form'; +import ErrorBoundary from '../../../components/util/ErrorBoundary'; +import { gaPage } from '../../../lib/analytics'; +import { state } from '../state'; + +const messages = defineMessages({ + name: { + id: 'settings.workspace.form.name', + defaultMessage: '!!!Name', + }, + yourWorkspaces: { + id: 'settings.workspace.form.yourWorkspaces', + defaultMessage: '!!!Your workspaces', + }, +}); + +@inject('stores', 'actions') @observer +class EditWorkspaceScreen extends Component { + static contextTypes = { + intl: intlShape, + }; + + componentDidMount() { + gaPage('Settings/Workspace/Edit'); + } + + prepareForm(workspace) { + const { intl } = this.context; + const config = { + fields: { + name: { + label: intl.formatMessage(messages.name), + placeholder: intl.formatMessage(messages.name), + value: workspace.name, + }, + }, + }; + return new Form(config); + } + + render() { + const { intl } = this.context; + const { workspaceBeingEdited } = state; + if (!workspaceBeingEdited) return null; + + // const form = this.prepareForm(workspaceBeingEdited); + + return ( + +
+
+ + + {intl.formatMessage(messages.yourWorkspaces)} + + + + + {workspaceBeingEdited.name} + +
+
+ test +
+
+
+ ); + } +} + +export default EditWorkspaceScreen; diff --git a/src/features/workspaces/containers/WorkspacesScreen.js b/src/features/workspaces/containers/WorkspacesScreen.js new file mode 100644 index 000000000..f129edec5 --- /dev/null +++ b/src/features/workspaces/containers/WorkspacesScreen.js @@ -0,0 +1,37 @@ +import React, { Component } from 'react'; +import { inject, observer } from 'mobx-react'; +import PropTypes from 'prop-types'; +import { gaPage } from '../../../lib/analytics'; +import { state } from '../state'; +import WorkspacesDashboard from '../components/WorkspacesDashboard'; +import ErrorBoundary from '../../../components/util/ErrorBoundary'; + +@inject('actions') @observer +class WorkspacesScreen extends Component { + static propTypes = { + actions: PropTypes.shape({ + workspace: PropTypes.shape({ + edit: PropTypes.func.isRequired, + }), + }).isRequired, + }; + + componentDidMount() { + gaPage('Settings/Workspaces Dashboard'); + } + + render() { + const { workspace } = this.props.actions; + return ( + + workspace.edit({ workspace: w })} + /> + + ); + } +} + +export default WorkspacesScreen; diff --git a/src/features/workspaces/models/Workspace.js b/src/features/workspaces/models/Workspace.js new file mode 100644 index 000000000..ede2710dc --- /dev/null +++ b/src/features/workspaces/models/Workspace.js @@ -0,0 +1,25 @@ +import { observable } from 'mobx'; + +export default class Workspace { + id = null; + + @observable name = null; + + @observable order = null; + + @observable services = []; + + @observable userId = null; + + constructor(data) { + if (!data.id) { + throw Error('Workspace requires Id'); + } + + this.id = data.id; + this.name = data.name; + this.order = data.order; + this.services = data.services; + this.userId = data.userId; + } +} diff --git a/src/features/workspaces/store.js b/src/features/workspaces/store.js index aab66708b..ea61cec31 100644 --- a/src/features/workspaces/store.js +++ b/src/features/workspaces/store.js @@ -1,7 +1,7 @@ import { observable, reaction } from 'mobx'; import Store from '../../stores/lib/Store'; import CachedRequest from '../../stores/lib/CachedRequest'; -import Workspace from '../../models/Workspace'; +import Workspace from './models/Workspace'; import { matchRoute } from '../../helpers/routing-helpers'; const debug = require('debug')('Franz:feature:workspaces'); diff --git a/src/features/workspaces/styles/workspaces-table.scss b/src/features/workspaces/styles/workspaces-table.scss new file mode 100644 index 000000000..6d0e7b4f5 --- /dev/null +++ b/src/features/workspaces/styles/workspaces-table.scss @@ -0,0 +1,53 @@ +@import '../../../styles/config'; + +.theme__dark .workspace-table { + .workspace-table__column-info .mdi { color: $dark-theme-gray-lightest; } + + .workspace-table__row { + border-bottom: 1px solid $dark-theme-gray-darker; + + &:hover { background: $dark-theme-gray-darker; } + &.workspace-table__row--disabled { color: $dark-theme-gray; } + } +} + +.workspace-table { + width: 100%; + + .workspace-table__toggle { + width: 60px; + + .franz-form__field { + margin-bottom: 0; + } + } + + .workspace-table__column-action { + width: 40px + } + + .workspace-table__column-info { + width: 40px; + + .mdi { + color: $theme-gray-light; + display: block; + font-size: 18px; + } + } + + .workspace-table__row { + border-bottom: 1px solid $theme-gray-lightest; + + &:hover { + cursor: initial; + background: $theme-gray-lightest; + } + + &.workspace-table__row--disabled { + color: $theme-gray-light; + } + } + + td { padding: 10px; } +} diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json index b5abb56d4..5cb8c6bd3 100644 --- a/src/i18n/locales/de.json +++ b/src/i18n/locales/de.json @@ -158,6 +158,7 @@ "settings.navigation.logout" : "Abmelden", "settings.navigation.settings" : "Einstellungen", "settings.navigation.yourServices" : "Deine Dienste", + "settings.navigation.yourWorkspaces": "Deine Workspaces", "settings.recipes.all" : "Alle Dienste", "settings.recipes.dev" : "Entwicklung", "settings.recipes.headline" : "Verfügbare Dienste", @@ -216,6 +217,8 @@ "settings.services.tooltip.isMuted" : "Alle Töne sind deaktiviert", "settings.services.tooltip.notificationsDisabled" : "Benachrichtigungen deaktiviert", "settings.services.updatedInfo" : "Deine Änderungen wurden gespeichert", + "settings.workspaces.headline": "Deine Workspaces", + "settings.workspace.form.yourWorkspaces": "Deine Workspaces", "settings.user.form.accountType.company" : "Firma", "settings.user.form.accountType.individual" : "Einzelperson", "settings.user.form.accountType.label" : "Konto-Typ", diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 1652b5585..9b323e323 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -197,6 +197,7 @@ "settings.user.form.accountType.non-profit": "Non-Profit", "settings.user.form.accountType.company": "Company", "settings.workspaces.headline": "Your workspaces", + "settings.workspace.form.yourWorkspaces": "Your workspaces", "subscription.type.free": "free", "subscription.type.month": "month", "subscription.type.year": "year", diff --git a/src/models/Workspace.js b/src/models/Workspace.js deleted file mode 100644 index ede2710dc..000000000 --- a/src/models/Workspace.js +++ /dev/null @@ -1,25 +0,0 @@ -import { observable } from 'mobx'; - -export default class Workspace { - id = null; - - @observable name = null; - - @observable order = null; - - @observable services = []; - - @observable userId = null; - - constructor(data) { - if (!data.id) { - throw Error('Workspace requires Id'); - } - - this.id = data.id; - this.name = data.name; - this.order = data.order; - this.services = data.services; - this.userId = data.userId; - } -} diff --git a/src/styles/main.scss b/src/styles/main.scss index 30f43532f..a941d89d0 100644 --- a/src/styles/main.scss +++ b/src/styles/main.scss @@ -30,7 +30,7 @@ $mdi-font-path: '../node_modules/mdi/fonts'; @import './content-tabs.scss'; @import './invite.scss'; @import './title-bar.scss'; -@import './workspace-table.scss'; +@import '../features/workspaces/styles/workspaces-table'; // form @import './input.scss'; diff --git a/src/styles/workspace-table.scss b/src/styles/workspace-table.scss deleted file mode 100644 index 05ebfa629..000000000 --- a/src/styles/workspace-table.scss +++ /dev/null @@ -1,53 +0,0 @@ -@import './config.scss'; - -.theme__dark .workspace-table { - .workspace-table__column-info .mdi { color: $dark-theme-gray-lightest; } - - .workspace-table__row { - border-bottom: 1px solid $dark-theme-gray-darker; - - &:hover { background: $dark-theme-gray-darker; } - &.workspace-table__row--disabled { color: $dark-theme-gray; } - } -} - -.workspace-table { - width: 100%; - - .workspace-table__toggle { - width: 60px; - - .franz-form__field { - margin-bottom: 0; - } - } - - .workspace-table__column-action { - width: 40px - } - - .workspace-table__column-info { - width: 40px; - - .mdi { - color: $theme-gray-light; - display: block; - font-size: 18px; - } - } - - .workspace-table__row { - border-bottom: 1px solid $theme-gray-lightest; - - &:hover { - cursor: initial; - background: $theme-gray-lightest; - } - - &.workspace-table__row--disabled { - color: $theme-gray-light; - } - } - - td { padding: 10px; } -} -- cgit v1.2.3-70-g09d2 From a421ba151f40695f54a4890219c5ec8af0a24a45 Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Tue, 19 Feb 2019 15:02:11 +0100 Subject: prepare basic workspace edit form --- .../workspaces/components/EditWorkspaceForm.js | 142 +++++++++++++++++++++ .../workspaces/containers/EditWorkspaceScreen.js | 67 +++------- src/i18n/locales/de.json | 3 + src/i18n/locales/en-US.json | 3 + 4 files changed, 164 insertions(+), 51 deletions(-) create mode 100644 src/features/workspaces/components/EditWorkspaceForm.js (limited to 'src/i18n/locales') diff --git a/src/features/workspaces/components/EditWorkspaceForm.js b/src/features/workspaces/components/EditWorkspaceForm.js new file mode 100644 index 000000000..9e87b56dd --- /dev/null +++ b/src/features/workspaces/components/EditWorkspaceForm.js @@ -0,0 +1,142 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer } from 'mobx-react'; +import { defineMessages, intlShape } from 'react-intl'; +import { Link } from 'react-router'; +import { Input, Button } from '@meetfranz/forms'; + +import Form from '../../../lib/Form'; +import Workspace from '../models/Workspace'; + +const messages = defineMessages({ + buttonDelete: { + id: 'settings.workspace.form.buttonDelete', + defaultMessage: '!!!Delete workspace', + }, + buttonSave: { + id: 'settings.workspace.form.buttonSave', + defaultMessage: '!!!Save workspace', + }, + name: { + id: 'settings.workspace.form.name', + defaultMessage: '!!!Name', + }, + yourWorkspaces: { + id: 'settings.workspace.form.yourWorkspaces', + defaultMessage: '!!!Your workspaces', + }, +}); + +@observer +class EditWorkspaceForm extends Component { + static contextTypes = { + intl: intlShape, + }; + + static propTypes = { + workspace: PropTypes.instanceOf(Workspace).isRequired, + onSave: PropTypes.func.isRequired, + onDelete: PropTypes.func.isRequired, + isSaving: PropTypes.bool.isRequired, + isDeleting: PropTypes.bool.isRequired, + }; + + prepareForm(workspace) { + const { intl } = this.context; + const config = { + fields: { + name: { + label: intl.formatMessage(messages.name), + placeholder: intl.formatMessage(messages.name), + value: workspace.name, + }, + }, + }; + return new Form(config); + } + + submitForm(submitEvent, form) { + submitEvent.preventDefault(); + form.submit({ + onSuccess: async (f) => { + const { onSave } = this.props; + const values = f.values(); + onSave(values); + }, + onError: async () => {}, + }); + } + + render() { + const { intl } = this.context; + const { + workspace, + isDeleting, + isSaving, + onDelete, + } = this.props; + if (!workspace) return null; + + const form = this.prepareForm(workspace); + + return ( +
+
+ + + {intl.formatMessage(messages.yourWorkspaces)} + + + + + {workspace.name} + +
+
+
this.submitForm(e, form)} id="form"> +
+ +
+
+
+
+ {/* ===== Delete Button ===== */} + {isDeleting ? ( +
+
+ ); + } +} + +export default EditWorkspaceForm; diff --git a/src/features/workspaces/containers/EditWorkspaceScreen.js b/src/features/workspaces/containers/EditWorkspaceScreen.js index d8c52f586..ed54b194e 100644 --- a/src/features/workspaces/containers/EditWorkspaceScreen.js +++ b/src/features/workspaces/containers/EditWorkspaceScreen.js @@ -1,72 +1,37 @@ import React, { Component } from 'react'; import { inject, observer } from 'mobx-react'; -import { defineMessages, intlShape } from 'react-intl'; -import { Link } from 'react-router'; -import Form from '../../../lib/Form'; + import ErrorBoundary from '../../../components/util/ErrorBoundary'; import { gaPage } from '../../../lib/analytics'; import { state } from '../state'; - -const messages = defineMessages({ - name: { - id: 'settings.workspace.form.name', - defaultMessage: '!!!Name', - }, - yourWorkspaces: { - id: 'settings.workspace.form.yourWorkspaces', - defaultMessage: '!!!Your workspaces', - }, -}); +import EditWorkspaceForm from '../components/EditWorkspaceForm'; @inject('stores', 'actions') @observer class EditWorkspaceScreen extends Component { - static contextTypes = { - intl: intlShape, - }; - componentDidMount() { gaPage('Settings/Workspace/Edit'); } - prepareForm(workspace) { - const { intl } = this.context; - const config = { - fields: { - name: { - label: intl.formatMessage(messages.name), - placeholder: intl.formatMessage(messages.name), - value: workspace.name, - }, - }, - }; - return new Form(config); - } + onDelete = () => { + console.log('delete workspace'); + }; + + onSave = (values) => { + console.log('save workspace', values); + }; render() { - const { intl } = this.context; const { workspaceBeingEdited } = state; if (!workspaceBeingEdited) return null; - - // const form = this.prepareForm(workspaceBeingEdited); - return ( -
-
- - - {intl.formatMessage(messages.yourWorkspaces)} - - - - - {workspaceBeingEdited.name} - -
-
- test -
-
+
); } diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json index 5cb8c6bd3..ffad8b835 100644 --- a/src/i18n/locales/de.json +++ b/src/i18n/locales/de.json @@ -219,6 +219,9 @@ "settings.services.updatedInfo" : "Deine Änderungen wurden gespeichert", "settings.workspaces.headline": "Deine Workspaces", "settings.workspace.form.yourWorkspaces": "Deine Workspaces", + "settings.workspace.form.name": "Name", + "settings.workspace.form.buttonDelete": "Workspace löschen", + "settings.workspace.form.buttonSave": "Workspace speichern", "settings.user.form.accountType.company" : "Firma", "settings.user.form.accountType.individual" : "Einzelperson", "settings.user.form.accountType.label" : "Konto-Typ", diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 9b323e323..899c62651 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -198,6 +198,9 @@ "settings.user.form.accountType.company": "Company", "settings.workspaces.headline": "Your workspaces", "settings.workspace.form.yourWorkspaces": "Your workspaces", + "settings.workspace.form.name": "Name", + "settings.workspace.form.buttonDelete": "Delete Workspace", + "settings.workspace.form.buttonSave": "Save Workspace", "subscription.type.free": "free", "subscription.type.month": "month", "subscription.type.year": "year", -- cgit v1.2.3-70-g09d2 From 7e40b08e2f8ab2448dcbe2ce7b4e38ca66764b9c Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Fri, 22 Feb 2019 12:34:27 +0100 Subject: add form for creating workspaces --- .eslintrc | 2 +- src/features/delayApp/Component.js | 2 +- src/features/workspaces/actions.js | 3 + src/features/workspaces/api.js | 9 +++ .../workspaces/components/CreateWorkspaceForm.js | 94 ++++++++++++++++++++++ .../workspaces/components/WorkspacesDashboard.js | 58 ++++++++----- .../workspaces/containers/WorkspacesScreen.js | 1 + src/features/workspaces/store.js | 14 +++- src/i18n/locales/de.json | 6 +- src/i18n/locales/en-US.json | 2 + src/styles/main.scss | 2 + 11 files changed, 170 insertions(+), 23 deletions(-) create mode 100644 src/features/workspaces/components/CreateWorkspaceForm.js (limited to 'src/i18n/locales') diff --git a/.eslintrc b/.eslintrc index e15148e96..48fb21250 100644 --- a/.eslintrc +++ b/.eslintrc @@ -13,7 +13,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/src/features/delayApp/Component.js b/src/features/delayApp/Component.js index ff84510e8..ff0f1f2f8 100644 --- a/src/features/delayApp/Component.js +++ b/src/features/delayApp/Component.js @@ -38,7 +38,7 @@ export default @inject('actions') @injectSheet(styles) @observer class DelayApp state = { countdown: config.delayDuration, - } + }; countdownInterval = null; diff --git a/src/features/workspaces/actions.js b/src/features/workspaces/actions.js index 30866af96..390af0696 100644 --- a/src/features/workspaces/actions.js +++ b/src/features/workspaces/actions.js @@ -5,4 +5,7 @@ export default { edit: { workspace: PropTypes.instanceOf(Workspace).isRequired, }, + create: { + name: PropTypes.string.isRequired, + }, }; diff --git a/src/features/workspaces/api.js b/src/features/workspaces/api.js index 97badbd01..65108a077 100644 --- a/src/features/workspaces/api.js +++ b/src/features/workspaces/api.js @@ -10,4 +10,13 @@ export default { if (!request.ok) throw request; return request.json(); }, + createWorkspace: async (name) => { + const url = `${API}/${API_VERSION}/workspace`; + const request = await window.fetch(url, prepareAuthRequest({ + method: 'POST', + body: JSON.stringify({ name }), + })); + if (!request.ok) throw request; + return request.json(); + }, }; diff --git a/src/features/workspaces/components/CreateWorkspaceForm.js b/src/features/workspaces/components/CreateWorkspaceForm.js new file mode 100644 index 000000000..d440b9bae --- /dev/null +++ b/src/features/workspaces/components/CreateWorkspaceForm.js @@ -0,0 +1,94 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer } from 'mobx-react'; +import { defineMessages, intlShape } from 'react-intl'; +import { Input, Button } from '@meetfranz/forms'; +import injectSheet from 'react-jss'; + +import Form from '../../../lib/Form'; + +const messages = defineMessages({ + submitButton: { + id: 'settings.workspace.add.form.submitButton', + defaultMessage: '!!!Save workspace', + }, + name: { + id: 'settings.workspace.add.form.name', + defaultMessage: '!!!Name', + }, +}); + +const styles = () => ({ + form: { + display: 'flex', + }, + input: { + flexGrow: 1, + marginRight: '10px', + }, + submitButton: { + height: 'inherit', + marginTop: '17px', + }, +}); + +@observer @injectSheet(styles) +class CreateWorkspaceForm extends Component { + static contextTypes = { + intl: intlShape, + }; + + static propTypes = { + classes: PropTypes.object.isRequired, + onSubmit: PropTypes.func.isRequired, + }; + + prepareForm() { + const { intl } = this.context; + const config = { + fields: { + name: { + label: intl.formatMessage(messages.name), + placeholder: intl.formatMessage(messages.name), + value: '', + }, + }, + }; + return new Form(config); + } + + submitForm(form) { + form.submit({ + onSuccess: async (f) => { + const { onSubmit } = this.props; + const values = f.values(); + onSubmit(values); + }, + onError: async () => {}, + }); + } + + render() { + const { intl } = this.context; + const { classes } = this.props; + const form = this.prepareForm(); + + return ( +
+ +
+ ); + } +} + +export default CreateWorkspaceForm; diff --git a/src/features/workspaces/components/WorkspacesDashboard.js b/src/features/workspaces/components/WorkspacesDashboard.js index 2a8b3a5ee..917807302 100644 --- a/src/features/workspaces/components/WorkspacesDashboard.js +++ b/src/features/workspaces/components/WorkspacesDashboard.js @@ -2,9 +2,11 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; import { defineMessages, intlShape } from 'react-intl'; +import injectSheet from 'react-jss'; import Loader from '../../../components/ui/Loader'; import WorkspaceItem from './WorkspaceItem'; +import CreateWorkspaceForm from './CreateWorkspaceForm'; const messages = defineMessages({ headline: { @@ -17,12 +19,21 @@ const messages = defineMessages({ }, }); -@observer +const styles = () => ({ + createForm: { + height: 'auto', + marginBottom: '20px', + }, +}); + +@observer @injectSheet(styles) class WorkspacesDashboard extends Component { static propTypes = { - workspaces: MobxPropTypes.arrayOrObservableArray.isRequired, + classes: PropTypes.object.isRequired, isLoading: PropTypes.bool.isRequired, + onCreateWorkspaceSubmit: PropTypes.func.isRequired, onWorkspaceClick: PropTypes.func.isRequired, + workspaces: MobxPropTypes.arrayOrObservableArray.isRequired, }; static contextTypes = { @@ -30,7 +41,13 @@ class WorkspacesDashboard extends Component { }; render() { - const { workspaces, isLoading, onWorkspaceClick } = this.props; + const { + workspaces, + isLoading, + onCreateWorkspaceSubmit, + onWorkspaceClick, + classes, + } = this.props; const { intl } = this.context; return ( @@ -39,21 +56,26 @@ class WorkspacesDashboard extends Component {

{intl.formatMessage(messages.headline)}

- {isLoading ? ( - - ) : ( - - - {workspaces.map(workspace => ( - onWorkspaceClick(w)} - /> - ))} - -
- )} +
+
+ +
+ {isLoading ? ( + + ) : ( + + + {workspaces.map(workspace => ( + onWorkspaceClick(w)} + /> + ))} + +
+ )} +
); diff --git a/src/features/workspaces/containers/WorkspacesScreen.js b/src/features/workspaces/containers/WorkspacesScreen.js index f129edec5..eb3287952 100644 --- a/src/features/workspaces/containers/WorkspacesScreen.js +++ b/src/features/workspaces/containers/WorkspacesScreen.js @@ -27,6 +27,7 @@ class WorkspacesScreen extends Component { workspace.create(data)} onWorkspaceClick={w => workspace.edit({ workspace: w })} /> diff --git a/src/features/workspaces/store.js b/src/features/workspaces/store.js index ea61cec31..d90f9caac 100644 --- a/src/features/workspaces/store.js +++ b/src/features/workspaces/store.js @@ -49,6 +49,7 @@ export default class WorkspacesStore extends Store { ); this.actions.workspace.edit.listen(this._edit); + this.actions.workspace.create.listen(this._create); } _setWorkspaces = (workspaces) => { @@ -64,5 +65,16 @@ export default class WorkspacesStore extends Store { _edit = ({ workspace }) => { this.stores.router.push(`/settings/workspaces/edit/${workspace.id}`); - } + }; + + _create = async ({ name }) => { + try { + const result = await this.api.createWorkspace(name); + const workspace = new Workspace(result); + this.state.workspaces.push(workspace); + this._edit({ workspace }); + } catch (error) { + throw error; + } + }; } diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json index ffad8b835..b795b90f8 100644 --- a/src/i18n/locales/de.json +++ b/src/i18n/locales/de.json @@ -218,10 +218,12 @@ "settings.services.tooltip.notificationsDisabled" : "Benachrichtigungen deaktiviert", "settings.services.updatedInfo" : "Deine Änderungen wurden gespeichert", "settings.workspaces.headline": "Deine Workspaces", + "settings.workspace.add.form.submitButton": "Create Workspace", + "settings.workspace.add.form.name": "Name", "settings.workspace.form.yourWorkspaces": "Deine Workspaces", "settings.workspace.form.name": "Name", - "settings.workspace.form.buttonDelete": "Workspace löschen", - "settings.workspace.form.buttonSave": "Workspace speichern", + "settings.workspace.form.buttonDelete": "Workspace löschen", + "settings.workspace.form.buttonSave": "Workspace speichern", "settings.user.form.accountType.company" : "Firma", "settings.user.form.accountType.individual" : "Einzelperson", "settings.user.form.accountType.label" : "Konto-Typ", diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 899c62651..24c96cb26 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -197,6 +197,8 @@ "settings.user.form.accountType.non-profit": "Non-Profit", "settings.user.form.accountType.company": "Company", "settings.workspaces.headline": "Your workspaces", + "settings.workspace.add.form.submitButton": "Create Workspace", + "settings.workspace.add.form.name": "Name", "settings.workspace.form.yourWorkspaces": "Your workspaces", "settings.workspace.form.name": "Name", "settings.workspace.form.buttonDelete": "Delete Workspace", diff --git a/src/styles/main.scss b/src/styles/main.scss index a941d89d0..9ba7f5827 100644 --- a/src/styles/main.scss +++ b/src/styles/main.scss @@ -30,6 +30,8 @@ $mdi-font-path: '../node_modules/mdi/fonts'; @import './content-tabs.scss'; @import './invite.scss'; @import './title-bar.scss'; + +// Workspaces legacy css @import '../features/workspaces/styles/workspaces-table'; // form -- cgit v1.2.3-70-g09d2 From 18d2b0f988792632d8b9395d06613d08c582ad7b Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Fri, 22 Feb 2019 13:03:40 +0100 Subject: small fixes --- .eslintrc | 1 + src/i18n/locales/de.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'src/i18n/locales') diff --git a/.eslintrc b/.eslintrc index 48fb21250..f1051723d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,6 +2,7 @@ "parser": "babel-eslint", "extends": "eslint-config-airbnb", "rules": { + "consistent-return": 0, "import/extensions": 0, "import/no-extraneous-dependencies": 0, "import/no-unresolved": [2, { diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json index b795b90f8..e1a955176 100644 --- a/src/i18n/locales/de.json +++ b/src/i18n/locales/de.json @@ -218,7 +218,7 @@ "settings.services.tooltip.notificationsDisabled" : "Benachrichtigungen deaktiviert", "settings.services.updatedInfo" : "Deine Änderungen wurden gespeichert", "settings.workspaces.headline": "Deine Workspaces", - "settings.workspace.add.form.submitButton": "Create Workspace", + "settings.workspace.add.form.submitButton": "Workspace erstellen", "settings.workspace.add.form.name": "Name", "settings.workspace.form.yourWorkspaces": "Deine Workspaces", "settings.workspace.form.name": "Name", -- cgit v1.2.3-70-g09d2 From dca7437b45c8eb67692a1df563fb4e969826b1cc Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Tue, 26 Feb 2019 15:29:34 +0100 Subject: finish basic workspace settings --- src/features/workspaces/actions.js | 3 ++ src/features/workspaces/api.js | 11 +++++ .../workspaces/components/EditWorkspaceForm.js | 46 +++++++++++++++++++-- .../workspaces/components/ServiceListItem.js | 48 ++++++++++++++++++++++ .../workspaces/containers/EditWorkspaceScreen.js | 14 ++++++- src/features/workspaces/models/Workspace.js | 2 +- src/features/workspaces/store.js | 12 ++++++ src/i18n/locales/de.json | 1 + src/i18n/locales/en-US.json | 1 + 9 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 src/features/workspaces/components/ServiceListItem.js (limited to 'src/i18n/locales') diff --git a/src/features/workspaces/actions.js b/src/features/workspaces/actions.js index 83d3447c3..84de2b011 100644 --- a/src/features/workspaces/actions.js +++ b/src/features/workspaces/actions.js @@ -11,4 +11,7 @@ export default { delete: { workspace: PropTypes.instanceOf(Workspace).isRequired, }, + update: { + workspace: PropTypes.instanceOf(Workspace).isRequired, + }, }; diff --git a/src/features/workspaces/api.js b/src/features/workspaces/api.js index fabc12455..733cb5593 100644 --- a/src/features/workspaces/api.js +++ b/src/features/workspaces/api.js @@ -1,3 +1,4 @@ +import { pick } from 'lodash'; import { sendAuthRequest } from '../../api/utils/auth'; import { API, API_VERSION } from '../../environment'; @@ -25,4 +26,14 @@ export default { if (!request.ok) throw request; return request.json(); }, + + updateWorkspace: async (workspace) => { + const url = `${API}/${API_VERSION}/workspace/${workspace.id}`; + const request = await sendAuthRequest(url, { + method: 'PUT', + body: JSON.stringify(pick(workspace, ['name', 'services'])), + }); + if (!request.ok) throw request; + return request.json(); + }, }; diff --git a/src/features/workspaces/components/EditWorkspaceForm.js b/src/features/workspaces/components/EditWorkspaceForm.js index 05ca65403..48090f608 100644 --- a/src/features/workspaces/components/EditWorkspaceForm.js +++ b/src/features/workspaces/components/EditWorkspaceForm.js @@ -7,8 +7,10 @@ import { Input, Button } from '@meetfranz/forms'; import injectSheet from 'react-jss'; import Workspace from '../models/Workspace'; +import Service from '../../../models/Service'; import Form from '../../../lib/Form'; import { required } from '../../../helpers/validation-helpers'; +import ServiceListItem from './ServiceListItem'; const messages = defineMessages({ buttonDelete: { @@ -27,12 +29,19 @@ const messages = defineMessages({ id: 'settings.workspace.form.yourWorkspaces', defaultMessage: '!!!Your workspaces', }, + servicesInWorkspaceHeadline: { + id: 'settings.workspace.form.servicesInWorkspaceHeadline', + defaultMessage: '!!!Services in this Workspace', + }, }); const styles = () => ({ nameInput: { height: 'auto', }, + serviceList: { + height: 'auto', + }, }); @injectSheet(styles) @observer @@ -42,11 +51,13 @@ class EditWorkspaceForm extends Component { }; static propTypes = { - workspace: PropTypes.instanceOf(Workspace).isRequired, - onSave: PropTypes.func.isRequired, - onDelete: PropTypes.func.isRequired, - isSaving: PropTypes.bool.isRequired, + classes: PropTypes.object.isRequired, isDeleting: PropTypes.bool.isRequired, + isSaving: PropTypes.bool.isRequired, + onDelete: PropTypes.func.isRequired, + onSave: PropTypes.func.isRequired, + services: PropTypes.arrayOf(PropTypes.instanceOf(Service)).isRequired, + workspace: PropTypes.instanceOf(Workspace).isRequired, }; form = this.prepareWorkspaceForm(this.props.workspace); @@ -68,6 +79,9 @@ class EditWorkspaceForm extends Component { value: workspace.name, validators: [required], }, + services: { + value: workspace.services.slice(), + }, }, }); } @@ -83,6 +97,17 @@ class EditWorkspaceForm extends Component { }); } + toggleService(service) { + const servicesField = this.form.$('services'); + const serviceIds = servicesField.value; + if (serviceIds.includes(service.id)) { + serviceIds.splice(serviceIds.indexOf(service.id), 1); + } else { + serviceIds.push(service.id); + } + servicesField.set(serviceIds); + } + render() { const { intl } = this.context; const { @@ -91,8 +116,10 @@ class EditWorkspaceForm extends Component { isSaving, onDelete, workspace, + services, } = this.props; const { form } = this; + const workspaceServices = form.$('services').value; return (
@@ -110,6 +137,17 @@ class EditWorkspaceForm extends Component {
+

{intl.formatMessage(messages.servicesInWorkspaceHeadline)}

+
+ {services.map(s => ( + this.toggleService(s)} + /> + ))} +
{/* ===== Delete Button ===== */} diff --git a/src/features/workspaces/components/ServiceListItem.js b/src/features/workspaces/components/ServiceListItem.js new file mode 100644 index 000000000..146cc5a36 --- /dev/null +++ b/src/features/workspaces/components/ServiceListItem.js @@ -0,0 +1,48 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer } from 'mobx-react'; +import injectSheet from 'react-jss'; +import { Toggle } from '@meetfranz/forms'; + +import Service from '../../../models/Service'; + +const styles = () => ({ + service: { + height: 'auto', + display: 'flex', + }, + name: { + marginTop: '4px', + }, +}); + +@injectSheet(styles) @observer +class ServiceListItem extends Component { + static propTypes = { + classes: PropTypes.object.isRequired, + isInWorkspace: PropTypes.bool.isRequired, + onToggle: PropTypes.func.isRequired, + service: PropTypes.instanceOf(Service).isRequired, + }; + + render() { + const { + classes, + isInWorkspace, + onToggle, + service, + } = this.props; + + return ( +
+ +
+ ); + } +} + +export default ServiceListItem; diff --git a/src/features/workspaces/containers/EditWorkspaceScreen.js b/src/features/workspaces/containers/EditWorkspaceScreen.js index 17b723303..790b8a0fe 100644 --- a/src/features/workspaces/containers/EditWorkspaceScreen.js +++ b/src/features/workspaces/containers/EditWorkspaceScreen.js @@ -5,6 +5,8 @@ import PropTypes from 'prop-types'; import ErrorBoundary from '../../../components/util/ErrorBoundary'; import EditWorkspaceForm from '../components/EditWorkspaceForm'; import { state } from '../state'; +import ServicesStore from '../../../stores/ServicesStore'; +import Workspace from '../models/Workspace'; @inject('stores', 'actions') @observer class EditWorkspaceScreen extends Component { @@ -14,6 +16,9 @@ class EditWorkspaceScreen extends Component { delete: PropTypes.func.isRequired, }), }).isRequired, + stores: PropTypes.shape({ + services: PropTypes.instanceOf(ServicesStore).isRequired, + }).isRequired, }; onDelete = () => { @@ -24,16 +29,23 @@ class EditWorkspaceScreen extends Component { }; onSave = (values) => { - console.log('save workspace', values); + const { workspaceBeingEdited } = state; + const { actions } = this.props; + const workspace = new Workspace( + Object.assign({}, workspaceBeingEdited, values), + ); + actions.workspace.update({ workspace }); }; render() { const { workspaceBeingEdited } = state; + const { stores } = this.props; if (!workspaceBeingEdited) return null; return ( { @@ -88,4 +89,15 @@ export default class WorkspacesStore extends Store { throw error; } }; + + _update = async ({ workspace }) => { + try { + await this.api.updateWorkspace(workspace); + const localWorkspace = this.state.workspaces.find(ws => ws.id === workspace.id); + Object.assign(localWorkspace, workspace); + this.stores.router.push('/settings/workspaces'); + } catch (error) { + throw error; + } + }; } diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json index e1a955176..4906070a3 100644 --- a/src/i18n/locales/de.json +++ b/src/i18n/locales/de.json @@ -224,6 +224,7 @@ "settings.workspace.form.name": "Name", "settings.workspace.form.buttonDelete": "Workspace löschen", "settings.workspace.form.buttonSave": "Workspace speichern", + "settings.workspace.form.servicesInWorkspaceHeadline": "Services in diesem Workspace", "settings.user.form.accountType.company" : "Firma", "settings.user.form.accountType.individual" : "Einzelperson", "settings.user.form.accountType.label" : "Konto-Typ", diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index fe16e916f..cd5c417e3 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -207,6 +207,7 @@ "settings.workspace.form.name": "Name", "settings.workspace.form.buttonDelete": "Delete Workspace", "settings.workspace.form.buttonSave": "Save Workspace", + "settings.workspace.form.servicesInWorkspaceHeadline": "Services in this Workspace", "subscription.type.free": "free", "subscription.type.month": "month", "subscription.type.year": "year", -- cgit v1.2.3-70-g09d2 From 739ef2e8a2dec94c3e10c3d26d797fe759fac7aa Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Fri, 1 Mar 2019 14:25:44 +0100 Subject: finish workspaces mvp --- src/actions/index.js | 8 ++- src/actions/lib/actions.js | 29 +++++---- src/components/layout/Sidebar.js | 2 +- src/components/services/tabs/Tabbar.js | 4 +- src/features/workspaces/actions.js | 9 ++- .../workspaces/containers/EditWorkspaceScreen.js | 12 ++-- .../workspaces/containers/WorkspacesScreen.js | 10 +-- src/features/workspaces/index.js | 40 +++++++++++- src/features/workspaces/state.js | 6 +- src/features/workspaces/store.js | 37 +++++++---- src/i18n/locales/de.json | 5 +- src/i18n/locales/en-US.json | 5 +- src/lib/Menu.js | 71 +++++++++++++++++++++- src/stores/ServicesStore.js | 12 ++-- 14 files changed, 193 insertions(+), 57 deletions(-) (limited to 'src/i18n/locales') diff --git a/src/actions/index.js b/src/actions/index.js index 45e6da515..00f843cd6 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -11,7 +11,7 @@ import payment from './payment'; import news from './news'; import settings from './settings'; import requests from './requests'; -import workspace from '../features/workspaces/actions'; +import workspaces from '../features/workspaces/actions'; const actions = Object.assign({}, { service, @@ -24,7 +24,9 @@ const actions = Object.assign({}, { news, settings, requests, - workspace, }); -export default defineActions(actions, PropTypes.checkPropTypes); +export default Object.assign( + defineActions(actions, PropTypes.checkPropTypes), + { workspaces }, +); diff --git a/src/actions/lib/actions.js b/src/actions/lib/actions.js index 499018d70..6571e9441 100644 --- a/src/actions/lib/actions.js +++ b/src/actions/lib/actions.js @@ -1,18 +1,23 @@ +export const createActionsFromDefinitions = (actionDefinitions, validate) => { + const actions = {}; + Object.keys(actionDefinitions).forEach((actionName) => { + const action = (params) => { + const schema = actionDefinitions[actionName]; + validate(schema, params, actionName); + action.notify(params); + }; + actions[actionName] = action; + action.listeners = []; + action.listen = listener => action.listeners.push(listener); + action.notify = params => action.listeners.forEach(listener => listener(params)); + }); + return actions; +}; + export default (definitions, validate) => { const newActions = {}; Object.keys(definitions).forEach((scopeName) => { - newActions[scopeName] = {}; - Object.keys(definitions[scopeName]).forEach((actionName) => { - const action = (params) => { - const schema = definitions[scopeName][actionName]; - validate(schema, params, actionName); - action.notify(params); - }; - newActions[scopeName][actionName] = action; - action.listeners = []; - action.listen = listener => action.listeners.push(listener); - action.notify = params => action.listeners.forEach(listener => listener(params)); - }); + newActions[scopeName] = createActionsFromDefinitions(definitions[scopeName], validate); }); return newActions; }; diff --git a/src/components/layout/Sidebar.js b/src/components/layout/Sidebar.js index 609a3b604..fcc5b0001 100644 --- a/src/components/layout/Sidebar.js +++ b/src/components/layout/Sidebar.js @@ -31,7 +31,7 @@ export default @observer class Sidebar extends Component { openSettings: PropTypes.func.isRequired, toggleMuteApp: PropTypes.func.isRequired, isAppMuted: PropTypes.bool.isRequired, - } + }; static contextTypes = { intl: intlShape, diff --git a/src/components/services/tabs/Tabbar.js b/src/components/services/tabs/Tabbar.js index dd5c2140f..5e8260ad0 100644 --- a/src/components/services/tabs/Tabbar.js +++ b/src/components/services/tabs/Tabbar.js @@ -19,7 +19,7 @@ export default @observer class TabBar extends Component { updateService: PropTypes.func.isRequired, showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, - } + }; onSortEnd = ({ oldIndex, newIndex }) => { const { @@ -45,7 +45,7 @@ export default @observer class TabBar extends Component { redirect: false, }); } - } + }; disableService({ serviceId }) { this.toggleService({ serviceId, isEnabled: false }); diff --git a/src/features/workspaces/actions.js b/src/features/workspaces/actions.js index 84de2b011..25246de09 100644 --- a/src/features/workspaces/actions.js +++ b/src/features/workspaces/actions.js @@ -1,7 +1,8 @@ import PropTypes from 'prop-types'; import Workspace from './models/Workspace'; +import { createActionsFromDefinitions } from '../../actions/lib/actions'; -export default { +export default createActionsFromDefinitions({ edit: { workspace: PropTypes.instanceOf(Workspace).isRequired, }, @@ -14,4 +15,8 @@ export default { update: { workspace: PropTypes.instanceOf(Workspace).isRequired, }, -}; + activate: { + workspace: PropTypes.instanceOf(Workspace).isRequired, + }, + deactivate: {}, +}, PropTypes.checkPropTypes); diff --git a/src/features/workspaces/containers/EditWorkspaceScreen.js b/src/features/workspaces/containers/EditWorkspaceScreen.js index 790b8a0fe..1b13bc2d4 100644 --- a/src/features/workspaces/containers/EditWorkspaceScreen.js +++ b/src/features/workspaces/containers/EditWorkspaceScreen.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import ErrorBoundary from '../../../components/util/ErrorBoundary'; import EditWorkspaceForm from '../components/EditWorkspaceForm'; -import { state } from '../state'; +import { workspacesState } from '../state'; import ServicesStore from '../../../stores/ServicesStore'; import Workspace from '../models/Workspace'; @@ -22,23 +22,23 @@ class EditWorkspaceScreen extends Component { }; onDelete = () => { - const { workspaceBeingEdited } = state; + const { workspaceBeingEdited } = workspacesState; const { actions } = this.props; if (!workspaceBeingEdited) return null; - actions.workspace.delete({ workspace: workspaceBeingEdited }); + actions.workspaces.delete({ workspace: workspaceBeingEdited }); }; onSave = (values) => { - const { workspaceBeingEdited } = state; + const { workspaceBeingEdited } = workspacesState; const { actions } = this.props; const workspace = new Workspace( Object.assign({}, workspaceBeingEdited, values), ); - actions.workspace.update({ workspace }); + actions.workspaces.update({ workspace }); }; render() { - const { workspaceBeingEdited } = state; + const { workspaceBeingEdited } = workspacesState; const { stores } = this.props; if (!workspaceBeingEdited) return null; return ( diff --git a/src/features/workspaces/containers/WorkspacesScreen.js b/src/features/workspaces/containers/WorkspacesScreen.js index b89cbcf67..94e714255 100644 --- a/src/features/workspaces/containers/WorkspacesScreen.js +++ b/src/features/workspaces/containers/WorkspacesScreen.js @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import { inject, observer } from 'mobx-react'; import PropTypes from 'prop-types'; -import { state } from '../state'; +import { workspacesState } from '../state'; import WorkspacesDashboard from '../components/WorkspacesDashboard'; import ErrorBoundary from '../../../components/util/ErrorBoundary'; @@ -20,10 +20,10 @@ class WorkspacesScreen extends Component { return ( actions.workspace.create(data)} - onWorkspaceClick={w => actions.workspace.edit({ workspace: w })} + workspaces={workspacesState.workspaces} + isLoading={workspacesState.isLoading} + onCreateWorkspaceSubmit={data => actions.workspaces.create(data)} + onWorkspaceClick={w => actions.workspaces.edit({ workspace: w })} /> ); diff --git a/src/features/workspaces/index.js b/src/features/workspaces/index.js index 50ac3b414..8091f49fc 100644 --- a/src/features/workspaces/index.js +++ b/src/features/workspaces/index.js @@ -1,14 +1,28 @@ -import { reaction } from 'mobx'; +import { reaction, runInAction } from 'mobx'; import WorkspacesStore from './store'; import api from './api'; -import { state, resetState } from './state'; +import { workspacesState, resetState } from './state'; const debug = require('debug')('Franz:feature:workspaces'); let store = null; +export const filterServicesByActiveWorkspace = (services) => { + const { isFeatureActive, activeWorkspace } = workspacesState; + if (isFeatureActive && activeWorkspace) { + return services.filter(s => activeWorkspace.services.includes(s.id)); + } + return services; +}; + +export const getActiveWorkspaceServices = (services) => { + return filterServicesByActiveWorkspace(services); +}; + export default function initWorkspaces(stores, actions) { const { features, user } = stores; + + // Toggle workspace feature reaction( () => ( features.features.isWorkspaceEnabled && ( @@ -18,10 +32,12 @@ export default function initWorkspaces(stores, actions) { (isEnabled) => { if (isEnabled) { debug('Initializing `workspaces` feature'); - store = new WorkspacesStore(stores, api, actions, state); + store = new WorkspacesStore(stores, api, actions, workspacesState); store.initialize(); + runInAction(() => { workspacesState.isFeatureActive = true; }); } else if (store) { debug('Disabling `workspaces` feature'); + runInAction(() => { workspacesState.isFeatureActive = false; }); store.teardown(); store = null; resetState(); // Reset state to default @@ -31,4 +47,22 @@ export default function initWorkspaces(stores, actions) { fireImmediately: true, }, ); + + // Update active service on workspace switches + reaction(() => ({ + isFeatureActive: workspacesState.isFeatureActive, + activeWorkspace: workspacesState.activeWorkspace, + }), ({ isFeatureActive, activeWorkspace }) => { + if (!isFeatureActive) return; + if (activeWorkspace) { + const services = stores.services.allDisplayed; + const activeService = services.find(s => s.isActive); + const workspaceServices = filterServicesByActiveWorkspace(services); + const isActiveServiceInWorkspace = workspaceServices.includes(activeService); + if (!isActiveServiceInWorkspace) { + console.log(workspaceServices[0].id); + actions.service.setActive({ serviceId: workspaceServices[0].id }); + } + } + }); } diff --git a/src/features/workspaces/state.js b/src/features/workspaces/state.js index f938c1470..963b96f81 100644 --- a/src/features/workspaces/state.js +++ b/src/features/workspaces/state.js @@ -1,13 +1,15 @@ import { observable } from 'mobx'; const defaultState = { + activeWorkspace: null, isLoading: false, + isFeatureActive: false, workspaces: [], workspaceBeingEdited: null, }; -export const state = observable(defaultState); +export const workspacesState = observable(defaultState); export function resetState() { - Object.assign(state, defaultState); + Object.assign(workspacesState, defaultState); } diff --git a/src/features/workspaces/store.js b/src/features/workspaces/store.js index 5cccb2ab7..a2997a0d2 100644 --- a/src/features/workspaces/store.js +++ b/src/features/workspaces/store.js @@ -1,8 +1,9 @@ -import { observable, reaction } from 'mobx'; +import { observable, reaction, action } from 'mobx'; import Store from '../../stores/lib/Store'; import CachedRequest from '../../stores/lib/CachedRequest'; import Workspace from './models/Workspace'; import { matchRoute } from '../../helpers/routing-helpers'; +import workspaceActions from './actions'; const debug = require('debug')('Franz:feature:workspaces'); @@ -48,28 +49,30 @@ export default class WorkspacesStore extends Store { }, ); - this.actions.workspace.edit.listen(this._edit); - this.actions.workspace.create.listen(this._create); - this.actions.workspace.delete.listen(this._delete); - this.actions.workspace.update.listen(this._update); + workspaceActions.edit.listen(this._edit); + workspaceActions.create.listen(this._create); + workspaceActions.delete.listen(this._delete); + workspaceActions.update.listen(this._update); + workspaceActions.activate.listen(this._setActiveWorkspace); + workspaceActions.deactivate.listen(this._deactivateActiveWorkspace); } - _setWorkspaces = (workspaces) => { + _getWorkspaceById = id => this.state.workspaces.find(w => w.id === id); + + @action _setWorkspaces = (workspaces) => { debug('setting user workspaces', workspaces.slice()); this.state.workspaces = workspaces.map(data => new Workspace(data)); }; - _setIsLoading = (isLoading) => { + @action _setIsLoading = (isLoading) => { this.state.isLoading = isLoading; }; - _getWorkspaceById = id => this.state.workspaces.find(w => w.id === id); - - _edit = ({ workspace }) => { + @action _edit = ({ workspace }) => { this.stores.router.push(`/settings/workspaces/edit/${workspace.id}`); }; - _create = async ({ name }) => { + @action _create = async ({ name }) => { try { const result = await this.api.createWorkspace(name); const workspace = new Workspace(result); @@ -80,7 +83,7 @@ export default class WorkspacesStore extends Store { } }; - _delete = async ({ workspace }) => { + @action _delete = async ({ workspace }) => { try { await this.api.deleteWorkspace(workspace); this.state.workspaces.remove(workspace); @@ -90,7 +93,7 @@ export default class WorkspacesStore extends Store { } }; - _update = async ({ workspace }) => { + @action _update = async ({ workspace }) => { try { await this.api.updateWorkspace(workspace); const localWorkspace = this.state.workspaces.find(ws => ws.id === workspace.id); @@ -100,4 +103,12 @@ export default class WorkspacesStore extends Store { throw error; } }; + + @action _setActiveWorkspace = ({ workspace }) => { + this.state.activeWorkspace = workspace; + }; + + @action _deactivateActiveWorkspace = () => { + this.state.activeWorkspace = null; + }; } diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json index 4906070a3..0c1fb8aa6 100644 --- a/src/i18n/locales/de.json +++ b/src/i18n/locales/de.json @@ -74,6 +74,9 @@ "menu.window" : "Fenster", "menu.window.close" : "Schließen", "menu.window.minimize" : "Minimieren", + "menu.workspaces": "Workspaces", + "menu.workspaces.defaultWorkspace": "All services", + "menu.workspaces.addNewWorkspace": "Add New Workspace", "password.email.label" : "E-Mail Adresse", "password.headline" : "Passwort zurücksetzen", "password.link.login" : "An Deinem Konto anmelden", @@ -224,7 +227,7 @@ "settings.workspace.form.name": "Name", "settings.workspace.form.buttonDelete": "Workspace löschen", "settings.workspace.form.buttonSave": "Workspace speichern", - "settings.workspace.form.servicesInWorkspaceHeadline": "Services in diesem Workspace", + "settings.workspace.form.servicesInWorkspaceHeadline": "Services in diesem Workspace", "settings.user.form.accountType.company" : "Firma", "settings.user.form.accountType.individual" : "Einzelperson", "settings.user.form.accountType.label" : "Konto-Typ", diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 988ac46f2..2a51662a2 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -276,7 +276,10 @@ "menu.app.hideOthers": "Hide Others", "menu.app.unhide": "Unhide", "menu.app.quit": "Quit", - "menu.services.addNewService": "Add New Service...", + "menu.services.addNewService": "Add New Service", + "menu.workspaces": "Workspaces", + "menu.workspaces.defaultWorkspace": "All services", + "menu.workspaces.addNewWorkspace": "Add New Workspace", "validation.required": "{field} is required", "validation.email": "{field} is not valid", "validation.url": "{field} is not a valid URL", diff --git a/src/lib/Menu.js b/src/lib/Menu.js index c378619ad..1560dd285 100644 --- a/src/lib/Menu.js +++ b/src/lib/Menu.js @@ -3,6 +3,8 @@ import { observable, autorun, computed } from 'mobx'; import { defineMessages } from 'react-intl'; import { isMac, ctrlKey, cmdKey } from '../environment'; +import { workspacesState } from '../features/workspaces/state'; +import workspaceActions from '../features/workspaces/actions'; const { app, Menu, dialog } = remote; @@ -179,6 +181,18 @@ const menuItems = defineMessages({ id: 'menu.services.addNewService', defaultMessage: '!!!Add New Service...', }, + workspaces: { + id: 'menu.workspaces', + defaultMessage: '!!!Workspaces', + }, + defaultWorkspace: { + id: 'menu.workspaces.defaultWorkspace', + defaultMessage: '!!!Default', + }, + addNewWorkspace: { + id: 'menu.workspaces.addNewWorkspace', + defaultMessage: '!!!Add New Workspace...', + }, }); function getActiveWebview() { @@ -265,6 +279,10 @@ const _templateFactory = intl => [ label: intl.formatMessage(menuItems.services), submenu: [], }, + { + label: intl.formatMessage(menuItems.workspaces), + submenu: [], + }, { label: intl.formatMessage(menuItems.window), role: 'window', @@ -499,7 +517,9 @@ export default class FranzMenu { } _build() { - const serviceTpl = Object.assign([], this.serviceTpl); // need to clone object so we don't modify computed (cached) object + // need to clone object so we don't modify computed (cached) object + const serviceTpl = Object.assign([], this.serviceTpl); + const workspacesMenu = Object.assign([], this.workspacesMenu); if (window.franz === undefined) { return; @@ -632,7 +652,7 @@ export default class FranzMenu { }, ); - tpl[4].submenu.unshift(about, { + tpl[5].submenu.unshift(about, { type: 'separator', }); } else { @@ -678,6 +698,8 @@ export default class FranzMenu { tpl[3].submenu = serviceTpl; } + tpl[4].submenu = workspacesMenu; + this.currentTemplate = tpl; const menu = Menu.buildFromTemplate(tpl); Menu.setApplicationMenu(menu); @@ -701,6 +723,51 @@ export default class FranzMenu { return []; } + @computed get workspacesMenu() { + const { workspaces, activeWorkspace } = workspacesState; + const { intl } = window.franz; + const menu = []; + + // Add new workspace item: + menu.push({ + label: intl.formatMessage(menuItems.addNewWorkspace), + accelerator: `${cmdKey}+Shift+N`, + click: () => { + this.actions.ui.openSettings({ path: 'workspaces' }); + }, + enabled: this.stores.user.isLoggedIn, + }, { + type: 'separator', + }); + + // Default workspace + menu.push({ + label: intl.formatMessage(menuItems.defaultWorkspace), + accelerator: `${cmdKey}+Alt+1`, + type: 'radio', + checked: !activeWorkspace, + click: () => { + workspaceActions.deactivate(); + }, + }); + + // Workspace items + if (this.stores.user.isLoggedIn) { + workspaces.forEach((workspace, i) => menu.push({ + label: workspace.name, + accelerator: i < 9 ? `${cmdKey}+Alt+${i + 2}` : null, + type: 'radio', + checked: activeWorkspace ? workspace.id === activeWorkspace.id : false, + click: () => { + workspaceActions.activate({ workspace }); + }, + })); + } + + console.log(menu); + return menu; + } + _getServiceName(service) { if (service.name) { return service.name; diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index c63bef196..a86db8103 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js @@ -2,7 +2,7 @@ import { action, reaction, computed, - observable, + observable, runInAction, } from 'mobx'; import { debounce, remove } from 'lodash'; import ms from 'ms'; @@ -12,6 +12,8 @@ import Request from './lib/Request'; import CachedRequest from './lib/CachedRequest'; import { matchRoute } from '../helpers/routing-helpers'; import { gaEvent } from '../lib/analytics'; +import { workspacesState } from '../features/workspaces/state'; +import { filterServicesByActiveWorkspace, getActiveWorkspaceServices } from '../features/workspaces'; const debug = require('debug')('Franz:ServiceStore'); @@ -98,7 +100,6 @@ export default class ServicesStore extends Store { return observable(services.slice().slice().sort((a, b) => a.order - b.order)); } } - return []; } @@ -107,13 +108,16 @@ export default class ServicesStore extends Store { } @computed get allDisplayed() { - return this.stores.settings.all.app.showDisabledServices ? this.all : this.enabled; + const services = this.stores.settings.all.app.showDisabledServices ? this.all : this.enabled; + return filterServicesByActiveWorkspace(services); } // This is just used to avoid unnecessary rerendering of resource-heavy webviews @computed get allDisplayedUnordered() { + const { showDisabledServices } = this.stores.settings.all.app; const services = this.allServicesRequest.execute().result || []; - return this.stores.settings.all.app.showDisabledServices ? services : services.filter(service => service.isEnabled); + const filteredServices = showDisabledServices ? services : services.filter(service => service.isEnabled); + return getActiveWorkspaceServices(filteredServices); } @computed get filtered() { -- 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/i18n/locales') 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/i18n/locales') 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/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index c1634df8e..d4d2a55cc 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json @@ -3326,26 +3326,39 @@ "defaultMessage": "!!!Workspaces", "end": { "column": 3, - "line": 15 + "line": 18 }, "file": "src/features/workspaces/components/WorkspaceDrawer.js", "id": "workspaceDrawer.headline", "start": { "column": 12, - "line": 12 + "line": 15 } }, { "defaultMessage": "!!!All services", "end": { "column": 3, - "line": 19 + "line": 22 }, "file": "src/features/workspaces/components/WorkspaceDrawer.js", "id": "workspaceDrawer.allServices", "start": { "column": 15, - "line": 16 + "line": 19 + } + }, + { + "defaultMessage": "!!!Add workspace", + "end": { + "column": 3, + "line": 26 + }, + "file": "src/features/workspaces/components/WorkspaceDrawer.js", + "id": "workspaceDrawer.addWorkspaceTooltip", + "start": { + "column": 23, + "line": 23 } } ], diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index f37237ac2..f7b5fe2b8 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -300,6 +300,7 @@ "validation.url": "{field} is not a valid URL", "welcome.loginButton": "Login to your account", "welcome.signupButton": "Create a free account", + "workspaceDrawer.addWorkspaceTooltip": "Add workspace", "workspaceDrawer.allServices": "All services", "workspaceDrawer.headline": "Workspaces", "workspaceDrawer.item.noServicesAddedYet": "No services added yet", diff --git a/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json b/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json index c875b82cb..7026708e2 100644 --- a/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json +++ b/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json @@ -4,11 +4,11 @@ "defaultMessage": "!!!Workspaces", "file": "src/features/workspaces/components/WorkspaceDrawer.js", "start": { - "line": 12, + "line": 15, "column": 12 }, "end": { - "line": 15, + "line": 18, "column": 3 } }, @@ -17,11 +17,24 @@ "defaultMessage": "!!!All services", "file": "src/features/workspaces/components/WorkspaceDrawer.js", "start": { - "line": 16, + "line": 19, "column": 15 }, "end": { - "line": 19, + "line": 22, + "column": 3 + } + }, + { + "id": "workspaceDrawer.addWorkspaceTooltip", + "defaultMessage": "!!!Add workspace", + "file": "src/features/workspaces/components/WorkspaceDrawer.js", + "start": { + "line": 23, + "column": 23 + }, + "end": { + "line": 26, "column": 3 } } -- cgit v1.2.3-70-g09d2 From af55b33800eeed36b50b1967da694acbb3991ecb Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Thu, 21 Mar 2019 16:30:13 +0100 Subject: add workspaces count badge in settings navigation --- src/components/settings/navigation/SettingsNavigation.js | 4 +++- src/containers/settings/SettingsWindow.js | 2 ++ src/features/workspaces/components/WorkspaceDrawer.js | 1 - src/i18n/locales/defaultMessages.json | 12 ++++++------ src/i18n/locales/en-US.json | 2 +- .../src/features/workspaces/components/WorkspaceDrawer.json | 12 ++++++------ src/styles/settings.scss | 5 +++-- 7 files changed, 21 insertions(+), 17 deletions(-) (limited to 'src/i18n/locales') diff --git a/src/components/settings/navigation/SettingsNavigation.js b/src/components/settings/navigation/SettingsNavigation.js index 4a80bb126..3f570f3b6 100644 --- a/src/components/settings/navigation/SettingsNavigation.js +++ b/src/components/settings/navigation/SettingsNavigation.js @@ -39,6 +39,7 @@ const messages = defineMessages({ export default @inject('stores') @observer class SettingsNavigation extends Component { static propTypes = { serviceCount: PropTypes.number.isRequired, + workspaceCount: PropTypes.number.isRequired, }; static contextTypes = { @@ -46,7 +47,7 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp }; render() { - const { serviceCount } = this.props; + const { serviceCount, workspaceCount } = this.props; const { intl } = this.context; return ( @@ -74,6 +75,7 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp > {intl.formatMessage(messages.yourWorkspaces)} {' '} + {workspaceCount} ); diff --git a/src/features/workspaces/components/WorkspaceDrawer.js b/src/features/workspaces/components/WorkspaceDrawer.js index 5a8d5d854..c18eb0e11 100644 --- a/src/features/workspaces/components/WorkspaceDrawer.js +++ b/src/features/workspaces/components/WorkspaceDrawer.js @@ -9,7 +9,6 @@ import ReactTooltip from 'react-tooltip'; import { workspacesState } from '../state'; import WorkspaceDrawerItem from './WorkspaceDrawerItem'; import { workspaceActions } from '../actions'; -import { ctrlKey } from '../../../environment'; const messages = defineMessages({ headline: { diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index d4d2a55cc..480f5c6cb 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json @@ -3326,39 +3326,39 @@ "defaultMessage": "!!!Workspaces", "end": { "column": 3, - "line": 18 + "line": 17 }, "file": "src/features/workspaces/components/WorkspaceDrawer.js", "id": "workspaceDrawer.headline", "start": { "column": 12, - "line": 15 + "line": 14 } }, { "defaultMessage": "!!!All services", "end": { "column": 3, - "line": 22 + "line": 21 }, "file": "src/features/workspaces/components/WorkspaceDrawer.js", "id": "workspaceDrawer.allServices", "start": { "column": 15, - "line": 19 + "line": 18 } }, { "defaultMessage": "!!!Add workspace", "end": { "column": 3, - "line": 26 + "line": 25 }, "file": "src/features/workspaces/components/WorkspaceDrawer.js", "id": "workspaceDrawer.addWorkspaceTooltip", "start": { "column": 23, - "line": 23 + "line": 22 } } ], diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index f7b5fe2b8..4206d4358 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -305,4 +305,4 @@ "workspaceDrawer.headline": "Workspaces", "workspaceDrawer.item.noServicesAddedYet": "No services added yet", "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 7026708e2..4e3237164 100644 --- a/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json +++ b/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json @@ -4,11 +4,11 @@ "defaultMessage": "!!!Workspaces", "file": "src/features/workspaces/components/WorkspaceDrawer.js", "start": { - "line": 15, + "line": 14, "column": 12 }, "end": { - "line": 18, + "line": 17, "column": 3 } }, @@ -17,11 +17,11 @@ "defaultMessage": "!!!All services", "file": "src/features/workspaces/components/WorkspaceDrawer.js", "start": { - "line": 19, + "line": 18, "column": 15 }, "end": { - "line": 22, + "line": 21, "column": 3 } }, @@ -30,11 +30,11 @@ "defaultMessage": "!!!Add workspace", "file": "src/features/workspaces/components/WorkspaceDrawer.js", "start": { - "line": 23, + "line": 22, "column": 23 }, "end": { - "line": 26, + "line": 25, "column": 3 } } diff --git a/src/styles/settings.scss b/src/styles/settings.scss index 750b6bedd..9fde9a7bf 100644 --- a/src/styles/settings.scss +++ b/src/styles/settings.scss @@ -68,7 +68,7 @@ } } - .premium-info { + .premium-info { background: $dark-theme-gray-darker; border: 2px solid $theme-brand-primary; } @@ -414,6 +414,7 @@ .settings-navigation__link { align-items: center; + justify-content: space-between; color: $theme-text-color; display: flex; flex-shrink: 0; @@ -442,8 +443,8 @@ .settings-navigation__expander { flex: 1; } .badge { + display: initial; - margin-left: 5px; transition: background $theme-transition-time, color $theme-transition-time; } -- cgit v1.2.3-70-g09d2 From 93137229bf3b04e4589ae315a81ed2de7a171ded Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Thu, 21 Mar 2019 16:35:38 +0100 Subject: fix merge conflicts with latest develop --- CHANGELOG.md | 27 +++++++++++++++++++++ README.md | 5 +--- appveyor.yml | 2 +- package.json | 2 +- packages/forms/package.json | 6 ++--- packages/forms/src/button/index.tsx | 1 + packages/forms/src/input/index.tsx | 9 +++++-- packages/forms/src/label/index.tsx | 2 ++ packages/theme/package.json | 4 +-- packages/theme/src/themes/default/index.ts | 4 ++- packages/typings/package.json | 4 +-- packages/ui/package.json | 6 ++--- packages/ui/src/infobox/index.tsx | 7 +++++- src/i18n/locales/ca.json | 15 +++++------- src/i18n/locales/cs.json | 15 +++++------- src/i18n/locales/de.json | 33 ++++++++++++------------- src/i18n/locales/el.json | 15 +++++------- src/i18n/locales/es.json | 19 ++++++--------- src/i18n/locales/fr.json | 33 ++++++++++++------------- src/i18n/locales/ga.json | 15 +++++------- src/i18n/locales/hr.json | 15 +++++------- src/i18n/locales/hu.json | 15 +++++------- src/i18n/locales/id.json | 15 +++++------- src/i18n/locales/it.json | 15 +++++------- src/i18n/locales/ja.json | 15 +++++------- src/i18n/locales/ka.json | 15 +++++------- src/i18n/locales/nl-BE.json | 15 +++++------- src/i18n/locales/nl.json | 15 +++++------- src/i18n/locales/pl.json | 15 +++++------- src/i18n/locales/pt-BR.json | 33 ++++++++++++------------- src/i18n/locales/pt.json | 15 +++++------- src/i18n/locales/ru.json | 17 ++++++------- src/i18n/locales/sk.json | 15 +++++------- src/i18n/locales/sr.json | 15 +++++------- src/i18n/locales/tr.json | 39 ++++++++++++++---------------- src/i18n/locales/uk.json | 15 +++++------- src/i18n/locales/zh-TW.json | 15 +++++------- uidev/src/stories/button.stories.tsx | 5 ++++ uidev/src/stories/infobox.stories.tsx | 9 +++++++ uidev/src/stories/input.stories.tsx | 8 ++++++ 40 files changed, 267 insertions(+), 278 deletions(-) (limited to 'src/i18n/locales') diff --git a/CHANGELOG.md b/CHANGELOG.md index 641cd2b6b..998d40f4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,30 @@ +## [5.0.1-beta.1](https://github.com/meetfranz/franz/compare/v5.0.0...v5.0.1-beta.1) (2019-03-18) + + +### General + +* **Translations:** Improved translations. **[A million thanks to the amazing community. 🎉](http://i18n.meetfranz.com/)** + + +### Features + +* **App:** Add security checks for external URLs ([6e5531a](https://github.com/meetfranz/franz/commit/6e5531a)) +* **App:** Update electron to 4.0.8 ([8336d17](https://github.com/meetfranz/franz/commit/8336d17)) +* **Linux:** Add auto updater for Linux AppImage builds ([d641b4e](https://github.com/meetfranz/franz/commit/d641b4e)) +* **Spell check:** Add British English as spell check language ([#1306](https://github.com/meetfranz/franz/issues/1306)) ([67fa325](https://github.com/meetfranz/franz/commit/67fa325)) +* **Windows:** Add option to quit Franz from Taskbar icon ([952fc8b](https://github.com/meetfranz/franz/commit/952fc8b)) + +### Bug Fixes + +* **Linux:** Fix minimized window focusing ([#1304](https://github.com/meetfranz/franz/issues/1304)) ([@skoruppa](https://github.com/skoruppa)) ([5b02c4d](https://github.com/meetfranz/franz/commit/5b02c4d)) +* **Notifications:** Fix notifications & notification click when icon is blob ([03589f6](https://github.com/meetfranz/franz/commit/03589f6)) +* **Service:** Fix service zoom (cmd/ctrl+ & cmd/ctrl-) ([91a0f59](https://github.com/meetfranz/franz/commit/91a0f59)) +* **Service:** Fix shortcut for (un)muting notifications & audio ([1df3342](https://github.com/meetfranz/franz/commit/1df3342)) +* **Windows:** Fix copy & paste in service context menus ([e66fcaa](https://github.com/meetfranz/franz/commit/e66fcaa)), closes [#1316](https://github.com/meetfranz/franz/issues/1316) +* **Windows:** Fix losing window when "Keep Franz in background" is enabled ([78a3722](https://github.com/meetfranz/franz/commit/78a3722)) + + + # [5.0.0](https://github.com/meetfranz/franz/compare/5.0.0-beta.24...5.0.0) (2019-02-15) ### General diff --git a/README.md b/README.md index d44cfaa6c..5397189c6 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,6 @@ -**This repository is only for Franz 5 and later, previous versions are no longer maintained.** ---- - -# Franz 5 (beta) +# Franz 5 [![Build status Windows](https://ci.appveyor.com/api/projects/status/9yman4ye19x4274o/branch/master?svg=true)](https://ci.appveyor.com/project/adlk/franz/branch/master) [![Build Status Mac & Linux](https://travis-ci.com/meetfranz/franz.svg?branch=master)](https://travis-ci.com/meetfranz/franz) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://meetfranz.com/payment.html) diff --git a/appveyor.yml b/appveyor.yml index 70b8d6f1f..0ed8866a3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,7 +6,7 @@ environment: CSC_KEY_PASSWORD: secure: t8ypNTPKTmvRfd3hHA4aMOtC5KOFqOw3AsKhpU7140Q= -version: 5.0.0.{build} +version: 5.0.1.{build} install: - ps: Install-Product node 10 diff --git a/package.json b/package.json index df7fa4191..ec135ca77 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "franz", "productName": "Franz", "appId": "com.meetfranz.franz", - "version": "5.0.0", + "version": "5.0.1-beta.1", "description": "Messaging app for WhatsApp, Slack, Telegram, HipChat, Hangouts and many many more.", "copyright": "adlk x franz - Stefan Malzner", "main": "index.js", diff --git a/packages/forms/package.json b/packages/forms/package.json index e78929777..fe161a282 100644 --- a/packages/forms/package.json +++ b/packages/forms/package.json @@ -1,6 +1,6 @@ { "name": "@meetfranz/forms", - "version": "1.0.9", + "version": "1.0.13", "description": "React form components for Franz", "main": "lib/index.js", "scripts": { @@ -25,7 +25,7 @@ "dependencies": { "@mdi/js": "^3.3.92", "@mdi/react": "^1.1.0", - "@meetfranz/theme": "^1.0.7", + "@meetfranz/theme": "^1.0.11", "react-html-attributes": "^1.4.3", "react-loader": "^2.4.5" }, @@ -35,5 +35,5 @@ "react-dom": "16.7.0", "react-jss": "^8.6.1" }, - "gitHead": "14b151cad6a5a849bb476aaa3fc53bf1eead7f4b" + "gitHead": "27778954921365e4957eae964e28f68690f3825f" } diff --git a/packages/forms/src/button/index.tsx b/packages/forms/src/button/index.tsx index 7a7f83dab..6959cde73 100644 --- a/packages/forms/src/button/index.tsx +++ b/packages/forms/src/button/index.tsx @@ -44,6 +44,7 @@ const styles = (theme: Theme) => ({ width: (props: IProps) => (props.stretch ? '100%' : 'auto') as CSS.WidthProperty, fontSize: theme.uiFontSize, textDecoration: 'none', + height: theme.buttonHeight, '&:hover': { opacity: 0.8, diff --git a/packages/forms/src/input/index.tsx b/packages/forms/src/input/index.tsx index cc3709b1a..f89c91be5 100644 --- a/packages/forms/src/input/index.tsx +++ b/packages/forms/src/input/index.tsx @@ -34,7 +34,7 @@ interface IState { } class InputComponent extends Component { - public static defaultProps = { + static defaultProps = { focus: false, onChange: () => {}, onBlur: () => {}, @@ -109,8 +109,10 @@ class InputComponent extends Component { placeholder, spellCheck, onBlur, - onEnterKey, onFocus, + min, + max, + step, } = this.props; const { @@ -157,6 +159,9 @@ class InputComponent extends Component { onBlur={onBlur} disabled={disabled} onKeyPress={this.onInputKeyPress.bind(this)} + min={min} + max={max} + step={step} /> {suffix && ( diff --git a/packages/forms/src/label/index.tsx b/packages/forms/src/label/index.tsx index 36fcfbedf..590270a06 100644 --- a/packages/forms/src/label/index.tsx +++ b/packages/forms/src/label/index.tsx @@ -26,6 +26,8 @@ class LabelComponent extends Component { htmlFor, } = this.props; + if (!showLabel) return children; + return (