diff options
author | 2019-03-23 14:15:57 +0100 | |
---|---|---|
committer | 2019-03-23 14:15:57 +0100 | |
commit | 0af622e6e81a5aee64f839eeadd23b4a62b3cf62 (patch) | |
tree | 0e2264b83c3f57f2850062f6b7293bf06416bb49 | |
parent | fix merge conflicts with latest develop (diff) | |
download | ferdium-app-0af622e6e81a5aee64f839eeadd23b4a62b3cf62.tar.gz ferdium-app-0af622e6e81a5aee64f839eeadd23b4a62b3cf62.tar.zst ferdium-app-0af622e6e81a5aee64f839eeadd23b4a62b3cf62.zip |
refactor state management for workspace feature
-rw-r--r-- | src/components/layout/AppLayout.js | 4 | ||||
-rw-r--r-- | src/components/services/content/ServiceView.js | 6 | ||||
-rw-r--r-- | src/components/ui/AppLoader/index.js | 4 | ||||
-rw-r--r-- | src/containers/layout/AppLayoutContainer.js | 4 | ||||
-rw-r--r-- | src/containers/settings/SettingsWindow.js | 4 | ||||
-rw-r--r-- | src/features/workspaces/api.js | 51 | ||||
-rw-r--r-- | src/features/workspaces/components/WorkspaceDrawer.js | 11 | ||||
-rw-r--r-- | src/features/workspaces/components/WorkspaceSwitchingIndicator.js | 4 | ||||
-rw-r--r-- | src/features/workspaces/containers/EditWorkspaceScreen.js | 8 | ||||
-rw-r--r-- | src/features/workspaces/containers/WorkspacesScreen.js | 7 | ||||
-rw-r--r-- | src/features/workspaces/index.js | 51 | ||||
-rw-r--r-- | src/features/workspaces/state.js | 18 | ||||
-rw-r--r-- | src/features/workspaces/store.js | 196 | ||||
-rw-r--r-- | src/lib/Menu.js | 6 | ||||
-rw-r--r-- | src/stores/ServicesStore.js | 6 |
15 files changed, 199 insertions, 181 deletions
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js index 4dd5ff686..0c72c1413 100644 --- a/src/components/layout/AppLayout.js +++ b/src/components/layout/AppLayout.js | |||
@@ -14,8 +14,8 @@ import ErrorBoundary from '../util/ErrorBoundary'; | |||
14 | // import globalMessages from '../../i18n/globalMessages'; | 14 | // import globalMessages from '../../i18n/globalMessages'; |
15 | 15 | ||
16 | import { isWindows } from '../../environment'; | 16 | import { isWindows } from '../../environment'; |
17 | import { workspacesState } from '../../features/workspaces/state'; | ||
18 | import WorkspaceSwitchingIndicator from '../../features/workspaces/components/WorkspaceSwitchingIndicator'; | 17 | import WorkspaceSwitchingIndicator from '../../features/workspaces/components/WorkspaceSwitchingIndicator'; |
18 | import { workspaceStore } from '../../features/workspaces'; | ||
19 | 19 | ||
20 | function createMarkup(HTMLString) { | 20 | function createMarkup(HTMLString) { |
21 | return { __html: HTMLString }; | 21 | return { __html: HTMLString }; |
@@ -53,7 +53,7 @@ const styles = theme => ({ | |||
53 | width: `calc(100% + ${theme.workspaceDrawerWidth}px)`, | 53 | width: `calc(100% + ${theme.workspaceDrawerWidth}px)`, |
54 | transition: 'transform 0.5s ease', | 54 | transition: 'transform 0.5s ease', |
55 | transform() { | 55 | transform() { |
56 | return workspacesState.isWorkspaceDrawerOpen ? 'translateX(0)' : `translateX(-${theme.workspaceDrawerWidth}px)`; | 56 | return workspaceStore.isWorkspaceDrawerOpen ? 'translateX(0)' : `translateX(-${theme.workspaceDrawerWidth}px)`; |
57 | }, | 57 | }, |
58 | }, | 58 | }, |
59 | }); | 59 | }); |
diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js index ada920cb6..13148b9b3 100644 --- a/src/components/services/content/ServiceView.js +++ b/src/components/services/content/ServiceView.js | |||
@@ -35,12 +35,13 @@ export default @observer class ServiceView extends Component { | |||
35 | 35 | ||
36 | autorunDisposer = null; | 36 | autorunDisposer = null; |
37 | 37 | ||
38 | forceRepaintTimeout = null; | ||
39 | |||
38 | componentDidMount() { | 40 | componentDidMount() { |
39 | this.autorunDisposer = autorun(() => { | 41 | this.autorunDisposer = autorun(() => { |
40 | if (!this.isMounted) return; | ||
41 | if (this.props.service.isActive) { | 42 | if (this.props.service.isActive) { |
42 | this.setState({ forceRepaint: true }); | 43 | this.setState({ forceRepaint: true }); |
43 | setTimeout(() => { | 44 | this.forceRepaintTimeout = setTimeout(() => { |
44 | this.setState({ forceRepaint: false }); | 45 | this.setState({ forceRepaint: false }); |
45 | }, 100); | 46 | }, 100); |
46 | } | 47 | } |
@@ -49,6 +50,7 @@ export default @observer class ServiceView extends Component { | |||
49 | 50 | ||
50 | componentWillUnmount() { | 51 | componentWillUnmount() { |
51 | this.autorunDisposer(); | 52 | this.autorunDisposer(); |
53 | clearTimeout(this.forceRepaintTimeout); | ||
52 | } | 54 | } |
53 | 55 | ||
54 | updateTargetUrl = (event) => { | 56 | updateTargetUrl = (event) => { |
diff --git a/src/components/ui/AppLoader/index.js b/src/components/ui/AppLoader/index.js index 61053f6d1..b0c7fed7b 100644 --- a/src/components/ui/AppLoader/index.js +++ b/src/components/ui/AppLoader/index.js | |||
@@ -23,11 +23,11 @@ export default @injectSheet(styles) @withTheme class AppLoader extends Component | |||
23 | static propTypes = { | 23 | static propTypes = { |
24 | classes: PropTypes.object.isRequired, | 24 | classes: PropTypes.object.isRequired, |
25 | theme: PropTypes.object.isRequired, | 25 | theme: PropTypes.object.isRequired, |
26 | } | 26 | }; |
27 | 27 | ||
28 | state = { | 28 | state = { |
29 | step: 0, | 29 | step: 0, |
30 | } | 30 | }; |
31 | 31 | ||
32 | interval = null; | 32 | interval = null; |
33 | 33 | ||
diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js index 772458eab..4329c3097 100644 --- a/src/containers/layout/AppLayoutContainer.js +++ b/src/containers/layout/AppLayoutContainer.js | |||
@@ -20,9 +20,9 @@ import Services from '../../components/services/content/Services'; | |||
20 | import AppLoader from '../../components/ui/AppLoader'; | 20 | import AppLoader from '../../components/ui/AppLoader'; |
21 | 21 | ||
22 | import { state as delayAppState } from '../../features/delayApp'; | 22 | import { state as delayAppState } from '../../features/delayApp'; |
23 | import { workspacesState } from '../../features/workspaces/state'; | ||
24 | import { workspaceActions } from '../../features/workspaces/actions'; | 23 | import { workspaceActions } from '../../features/workspaces/actions'; |
25 | import WorkspaceDrawer from '../../features/workspaces/components/WorkspaceDrawer'; | 24 | import WorkspaceDrawer from '../../features/workspaces/components/WorkspaceDrawer'; |
25 | import { workspaceStore } from '../../features/workspaces'; | ||
26 | 26 | ||
27 | export default @inject('stores', 'actions') @observer class AppLayoutContainer extends Component { | 27 | export default @inject('stores', 'actions') @observer class AppLayoutContainer extends Component { |
28 | static defaultProps = { | 28 | static defaultProps = { |
@@ -108,7 +108,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e | |||
108 | updateService={updateService} | 108 | updateService={updateService} |
109 | toggleMuteApp={toggleMuteApp} | 109 | toggleMuteApp={toggleMuteApp} |
110 | toggleWorkspaceDrawer={workspaceActions.toggleWorkspaceDrawer} | 110 | toggleWorkspaceDrawer={workspaceActions.toggleWorkspaceDrawer} |
111 | isWorkspaceDrawerOpen={workspacesState.isWorkspaceDrawerOpen} | 111 | isWorkspaceDrawerOpen={workspaceStore.isWorkspaceDrawerOpen} |
112 | showMessageBadgeWhenMutedSetting={settings.all.app.showMessageBadgeWhenMuted} | 112 | showMessageBadgeWhenMutedSetting={settings.all.app.showMessageBadgeWhenMuted} |
113 | showMessageBadgesEvenWhenMuted={ui.showMessageBadgesEvenWhenMuted} | 113 | showMessageBadgesEvenWhenMuted={ui.showMessageBadgesEvenWhenMuted} |
114 | /> | 114 | /> |
diff --git a/src/containers/settings/SettingsWindow.js b/src/containers/settings/SettingsWindow.js index 8cbde24c1..663b9e2e4 100644 --- a/src/containers/settings/SettingsWindow.js +++ b/src/containers/settings/SettingsWindow.js | |||
@@ -7,7 +7,7 @@ import ServicesStore from '../../stores/ServicesStore'; | |||
7 | import Layout from '../../components/settings/SettingsLayout'; | 7 | import Layout from '../../components/settings/SettingsLayout'; |
8 | import Navigation from '../../components/settings/navigation/SettingsNavigation'; | 8 | import Navigation from '../../components/settings/navigation/SettingsNavigation'; |
9 | import ErrorBoundary from '../../components/util/ErrorBoundary'; | 9 | import ErrorBoundary from '../../components/util/ErrorBoundary'; |
10 | import { workspacesState } from '../../features/workspaces/state'; | 10 | import { workspaceStore } from '../../features/workspaces'; |
11 | 11 | ||
12 | export default @inject('stores', 'actions') @observer class SettingsContainer extends Component { | 12 | export default @inject('stores', 'actions') @observer class SettingsContainer extends Component { |
13 | render() { | 13 | render() { |
@@ -17,7 +17,7 @@ export default @inject('stores', 'actions') @observer class SettingsContainer ex | |||
17 | const navigation = ( | 17 | const navigation = ( |
18 | <Navigation | 18 | <Navigation |
19 | serviceCount={stores.services.all.length} | 19 | serviceCount={stores.services.all.length} |
20 | workspaceCount={workspacesState.workspaces.length} | 20 | workspaceCount={workspaceStore.workspaces.length} |
21 | /> | 21 | /> |
22 | ); | 22 | ); |
23 | 23 | ||
diff --git a/src/features/workspaces/api.js b/src/features/workspaces/api.js index 733cb5593..4e076d233 100644 --- a/src/features/workspaces/api.js +++ b/src/features/workspaces/api.js | |||
@@ -1,39 +1,60 @@ | |||
1 | import { pick } from 'lodash'; | 1 | import { pick } from 'lodash'; |
2 | import { sendAuthRequest } from '../../api/utils/auth'; | 2 | import { sendAuthRequest } from '../../api/utils/auth'; |
3 | import { API, API_VERSION } from '../../environment'; | 3 | import { API, API_VERSION } from '../../environment'; |
4 | import Request from '../../stores/lib/Request'; | ||
5 | import CachedRequest from '../../stores/lib/CachedRequest'; | ||
6 | import Workspace from './models/Workspace'; | ||
4 | 7 | ||
5 | export default { | 8 | const debug = require('debug')('Franz:feature:workspaces:api'); |
9 | |||
10 | export const workspaceApi = { | ||
6 | getUserWorkspaces: async () => { | 11 | getUserWorkspaces: async () => { |
7 | const url = `${API}/${API_VERSION}/workspace`; | 12 | const url = `${API}/${API_VERSION}/workspace`; |
8 | const request = await sendAuthRequest(url, { method: 'GET' }); | 13 | debug('getUserWorkspaces GET', url); |
9 | if (!request.ok) throw request; | 14 | const result = await sendAuthRequest(url, { method: 'GET' }); |
10 | return request.json(); | 15 | debug('getUserWorkspaces RESULT', result); |
16 | if (!result.ok) throw result; | ||
17 | const workspaces = await result.json(); | ||
18 | return workspaces.map(data => new Workspace(data)); | ||
11 | }, | 19 | }, |
12 | 20 | ||
13 | createWorkspace: async (name) => { | 21 | createWorkspace: async (name) => { |
14 | const url = `${API}/${API_VERSION}/workspace`; | 22 | const url = `${API}/${API_VERSION}/workspace`; |
15 | const request = await sendAuthRequest(url, { | 23 | const options = { |
16 | method: 'POST', | 24 | method: 'POST', |
17 | body: JSON.stringify({ name }), | 25 | body: JSON.stringify({ name }), |
18 | }); | 26 | }; |
19 | if (!request.ok) throw request; | 27 | debug('createWorkspace POST', url, options); |
20 | return request.json(); | 28 | const result = await sendAuthRequest(url, options); |
29 | debug('createWorkspace RESULT', result); | ||
30 | if (!result.ok) throw result; | ||
31 | return new Workspace(await result.json()); | ||
21 | }, | 32 | }, |
22 | 33 | ||
23 | deleteWorkspace: async (workspace) => { | 34 | deleteWorkspace: async (workspace) => { |
24 | const url = `${API}/${API_VERSION}/workspace/${workspace.id}`; | 35 | const url = `${API}/${API_VERSION}/workspace/${workspace.id}`; |
25 | const request = await sendAuthRequest(url, { method: 'DELETE' }); | 36 | debug('deleteWorkspace DELETE', url); |
26 | if (!request.ok) throw request; | 37 | const result = await sendAuthRequest(url, { method: 'DELETE' }); |
27 | return request.json(); | 38 | debug('deleteWorkspace RESULT', result); |
39 | if (!result.ok) throw result; | ||
40 | return (await result.json()).deleted; | ||
28 | }, | 41 | }, |
29 | 42 | ||
30 | updateWorkspace: async (workspace) => { | 43 | updateWorkspace: async (workspace) => { |
31 | const url = `${API}/${API_VERSION}/workspace/${workspace.id}`; | 44 | const url = `${API}/${API_VERSION}/workspace/${workspace.id}`; |
32 | const request = await sendAuthRequest(url, { | 45 | const options = { |
33 | method: 'PUT', | 46 | method: 'PUT', |
34 | body: JSON.stringify(pick(workspace, ['name', 'services'])), | 47 | body: JSON.stringify(pick(workspace, ['name', 'services'])), |
35 | }); | 48 | }; |
36 | if (!request.ok) throw request; | 49 | debug('updateWorkspace UPDATE', url, options); |
37 | return request.json(); | 50 | const result = await sendAuthRequest(url, options); |
51 | debug('updateWorkspace RESULT', result); | ||
52 | if (!result.ok) throw result; | ||
53 | return new Workspace(await result.json()); | ||
38 | }, | 54 | }, |
39 | }; | 55 | }; |
56 | |||
57 | export const getUserWorkspacesRequest = new CachedRequest(workspaceApi, 'getUserWorkspaces'); | ||
58 | export const createWorkspaceRequest = new Request(workspaceApi, 'createWorkspace'); | ||
59 | export const deleteWorkspaceRequest = new Request(workspaceApi, 'deleteWorkspace'); | ||
60 | export const updateWorkspaceRequest = new Request(workspaceApi, 'updateWorkspace'); | ||
diff --git a/src/features/workspaces/components/WorkspaceDrawer.js b/src/features/workspaces/components/WorkspaceDrawer.js index c18eb0e11..6dc779be9 100644 --- a/src/features/workspaces/components/WorkspaceDrawer.js +++ b/src/features/workspaces/components/WorkspaceDrawer.js | |||
@@ -6,9 +6,9 @@ import { defineMessages, intlShape } from 'react-intl'; | |||
6 | import { H1, Icon } from '@meetfranz/ui'; | 6 | import { H1, Icon } from '@meetfranz/ui'; |
7 | import ReactTooltip from 'react-tooltip'; | 7 | import ReactTooltip from 'react-tooltip'; |
8 | 8 | ||
9 | import { workspacesState } from '../state'; | ||
10 | import WorkspaceDrawerItem from './WorkspaceDrawerItem'; | 9 | import WorkspaceDrawerItem from './WorkspaceDrawerItem'; |
11 | import { workspaceActions } from '../actions'; | 10 | import { workspaceActions } from '../actions'; |
11 | import { workspaceStore } from '../index'; | ||
12 | 12 | ||
13 | const messages = defineMessages({ | 13 | const messages = defineMessages({ |
14 | headline: { | 14 | headline: { |
@@ -70,7 +70,12 @@ class WorkspaceDrawer extends Component { | |||
70 | getServicesForWorkspace, | 70 | getServicesForWorkspace, |
71 | } = this.props; | 71 | } = this.props; |
72 | const { intl } = this.context; | 72 | const { intl } = this.context; |
73 | const { activeWorkspace, isSwitchingWorkspace, nextWorkspace } = workspacesState; | 73 | const { |
74 | activeWorkspace, | ||
75 | isSwitchingWorkspace, | ||
76 | nextWorkspace, | ||
77 | workspaces, | ||
78 | } = workspaceStore; | ||
74 | const actualWorkspace = isSwitchingWorkspace ? nextWorkspace : activeWorkspace; | 79 | const actualWorkspace = isSwitchingWorkspace ? nextWorkspace : activeWorkspace; |
75 | return ( | 80 | return ( |
76 | <div className={classes.drawer}> | 81 | <div className={classes.drawer}> |
@@ -95,7 +100,7 @@ class WorkspaceDrawer extends Component { | |||
95 | services={getServicesForWorkspace(null)} | 100 | services={getServicesForWorkspace(null)} |
96 | isActive={actualWorkspace == null} | 101 | isActive={actualWorkspace == null} |
97 | /> | 102 | /> |
98 | {workspacesState.workspaces.map(workspace => ( | 103 | {workspaces.map(workspace => ( |
99 | <WorkspaceDrawerItem | 104 | <WorkspaceDrawerItem |
100 | key={workspace.id} | 105 | key={workspace.id} |
101 | name={workspace.name} | 106 | name={workspace.name} |
diff --git a/src/features/workspaces/components/WorkspaceSwitchingIndicator.js b/src/features/workspaces/components/WorkspaceSwitchingIndicator.js index 68ce6fd87..c012ab008 100644 --- a/src/features/workspaces/components/WorkspaceSwitchingIndicator.js +++ b/src/features/workspaces/components/WorkspaceSwitchingIndicator.js | |||
@@ -5,8 +5,8 @@ import injectSheet from 'react-jss'; | |||
5 | import classnames from 'classnames'; | 5 | import classnames from 'classnames'; |
6 | import { defineMessages, intlShape } from 'react-intl'; | 6 | import { defineMessages, intlShape } from 'react-intl'; |
7 | 7 | ||
8 | import { workspacesState } from '../state'; | ||
9 | import LoaderComponent from '../../../components/ui/Loader'; | 8 | import LoaderComponent from '../../../components/ui/Loader'; |
9 | import { workspaceStore } from '../index'; | ||
10 | 10 | ||
11 | const messages = defineMessages({ | 11 | const messages = defineMessages({ |
12 | switchingTo: { | 12 | switchingTo: { |
@@ -56,7 +56,7 @@ class WorkspaceSwitchingIndicator extends Component { | |||
56 | render() { | 56 | render() { |
57 | const { classes } = this.props; | 57 | const { classes } = this.props; |
58 | const { intl } = this.context; | 58 | const { intl } = this.context; |
59 | const { isSwitchingWorkspace, isWorkspaceDrawerOpen, nextWorkspace } = workspacesState; | 59 | const { isSwitchingWorkspace, isWorkspaceDrawerOpen, nextWorkspace } = workspaceStore; |
60 | if (!isSwitchingWorkspace) return null; | 60 | if (!isSwitchingWorkspace) return null; |
61 | const nextWorkspaceName = nextWorkspace ? nextWorkspace.name : 'All services'; | 61 | const nextWorkspaceName = nextWorkspace ? nextWorkspace.name : 'All services'; |
62 | return ( | 62 | return ( |
diff --git a/src/features/workspaces/containers/EditWorkspaceScreen.js b/src/features/workspaces/containers/EditWorkspaceScreen.js index 1b13bc2d4..1c4633e71 100644 --- a/src/features/workspaces/containers/EditWorkspaceScreen.js +++ b/src/features/workspaces/containers/EditWorkspaceScreen.js | |||
@@ -4,9 +4,9 @@ import PropTypes from 'prop-types'; | |||
4 | 4 | ||
5 | import ErrorBoundary from '../../../components/util/ErrorBoundary'; | 5 | import ErrorBoundary from '../../../components/util/ErrorBoundary'; |
6 | import EditWorkspaceForm from '../components/EditWorkspaceForm'; | 6 | import EditWorkspaceForm from '../components/EditWorkspaceForm'; |
7 | import { workspacesState } from '../state'; | ||
8 | import ServicesStore from '../../../stores/ServicesStore'; | 7 | import ServicesStore from '../../../stores/ServicesStore'; |
9 | import Workspace from '../models/Workspace'; | 8 | import Workspace from '../models/Workspace'; |
9 | import { workspaceStore } from '../index'; | ||
10 | 10 | ||
11 | @inject('stores', 'actions') @observer | 11 | @inject('stores', 'actions') @observer |
12 | class EditWorkspaceScreen extends Component { | 12 | class EditWorkspaceScreen extends Component { |
@@ -22,14 +22,14 @@ class EditWorkspaceScreen extends Component { | |||
22 | }; | 22 | }; |
23 | 23 | ||
24 | onDelete = () => { | 24 | onDelete = () => { |
25 | const { workspaceBeingEdited } = workspacesState; | 25 | const { workspaceBeingEdited } = workspaceStore; |
26 | const { actions } = this.props; | 26 | const { actions } = this.props; |
27 | if (!workspaceBeingEdited) return null; | 27 | if (!workspaceBeingEdited) return null; |
28 | actions.workspaces.delete({ workspace: workspaceBeingEdited }); | 28 | actions.workspaces.delete({ workspace: workspaceBeingEdited }); |
29 | }; | 29 | }; |
30 | 30 | ||
31 | onSave = (values) => { | 31 | onSave = (values) => { |
32 | const { workspaceBeingEdited } = workspacesState; | 32 | const { workspaceBeingEdited } = workspaceStore; |
33 | const { actions } = this.props; | 33 | const { actions } = this.props; |
34 | const workspace = new Workspace( | 34 | const workspace = new Workspace( |
35 | Object.assign({}, workspaceBeingEdited, values), | 35 | Object.assign({}, workspaceBeingEdited, values), |
@@ -38,7 +38,7 @@ class EditWorkspaceScreen extends Component { | |||
38 | }; | 38 | }; |
39 | 39 | ||
40 | render() { | 40 | render() { |
41 | const { workspaceBeingEdited } = workspacesState; | 41 | const { workspaceBeingEdited } = workspaceStore; |
42 | const { stores } = this.props; | 42 | const { stores } = this.props; |
43 | if (!workspaceBeingEdited) return null; | 43 | if (!workspaceBeingEdited) return null; |
44 | return ( | 44 | return ( |
diff --git a/src/features/workspaces/containers/WorkspacesScreen.js b/src/features/workspaces/containers/WorkspacesScreen.js index bd1ddcd43..5fdea217e 100644 --- a/src/features/workspaces/containers/WorkspacesScreen.js +++ b/src/features/workspaces/containers/WorkspacesScreen.js | |||
@@ -1,9 +1,10 @@ | |||
1 | import React, { Component } from 'react'; | 1 | import React, { Component } from 'react'; |
2 | import { inject, observer } from 'mobx-react'; | 2 | import { inject, observer } from 'mobx-react'; |
3 | import PropTypes from 'prop-types'; | 3 | import PropTypes from 'prop-types'; |
4 | import { workspacesState } from '../state'; | ||
5 | import WorkspacesDashboard from '../components/WorkspacesDashboard'; | 4 | import WorkspacesDashboard from '../components/WorkspacesDashboard'; |
6 | import ErrorBoundary from '../../../components/util/ErrorBoundary'; | 5 | import ErrorBoundary from '../../../components/util/ErrorBoundary'; |
6 | import { workspaceStore } from '../index'; | ||
7 | import { getUserWorkspacesRequest } from '../api'; | ||
7 | 8 | ||
8 | @inject('actions') @observer | 9 | @inject('actions') @observer |
9 | class WorkspacesScreen extends Component { | 10 | class WorkspacesScreen extends Component { |
@@ -20,8 +21,8 @@ class WorkspacesScreen extends Component { | |||
20 | return ( | 21 | return ( |
21 | <ErrorBoundary> | 22 | <ErrorBoundary> |
22 | <WorkspacesDashboard | 23 | <WorkspacesDashboard |
23 | workspaces={workspacesState.workspaces} | 24 | workspaces={workspaceStore.workspaces} |
24 | isLoadingWorkspaces={workspacesState.isLoadingWorkspaces} | 25 | isLoadingWorkspaces={getUserWorkspacesRequest.isExecuting} |
25 | onCreateWorkspaceSubmit={data => actions.workspaces.create(data)} | 26 | onCreateWorkspaceSubmit={data => actions.workspaces.create(data)} |
26 | onWorkspaceClick={w => actions.workspaces.edit({ workspace: w })} | 27 | onWorkspaceClick={w => actions.workspaces.edit({ workspace: w })} |
27 | /> | 28 | /> |
diff --git a/src/features/workspaces/index.js b/src/features/workspaces/index.js index 1644c0e2f..68f82bdee 100644 --- a/src/features/workspaces/index.js +++ b/src/features/workspaces/index.js | |||
@@ -1,26 +1,9 @@ | |||
1 | import { reaction, runInAction } from 'mobx'; | 1 | import { reaction } from 'mobx'; |
2 | import WorkspacesStore from './store'; | 2 | import WorkspacesStore from './store'; |
3 | import api from './api'; | ||
4 | import { workspacesState, resetState } from './state'; | ||
5 | 3 | ||
6 | const debug = require('debug')('Franz:feature:workspaces'); | 4 | const debug = require('debug')('Franz:feature:workspaces'); |
7 | 5 | ||
8 | let store = null; | 6 | export const workspaceStore = new WorkspacesStore(); |
9 | |||
10 | export const filterServicesByActiveWorkspace = (services) => { | ||
11 | const { | ||
12 | activeWorkspace, | ||
13 | isFeatureActive, | ||
14 | } = workspacesState; | ||
15 | |||
16 | if (!isFeatureActive) return services; | ||
17 | if (activeWorkspace) return services.filter(s => activeWorkspace.services.includes(s.id)); | ||
18 | return services; | ||
19 | }; | ||
20 | |||
21 | export const getActiveWorkspaceServices = services => ( | ||
22 | filterServicesByActiveWorkspace(services) | ||
23 | ); | ||
24 | 7 | ||
25 | export default function initWorkspaces(stores, actions) { | 8 | export default function initWorkspaces(stores, actions) { |
26 | const { features, user } = stores; | 9 | const { features, user } = stores; |
@@ -33,38 +16,16 @@ export default function initWorkspaces(stores, actions) { | |||
33 | ) | 16 | ) |
34 | ), | 17 | ), |
35 | (isEnabled) => { | 18 | (isEnabled) => { |
36 | if (isEnabled) { | 19 | if (isEnabled && !workspaceStore.isFeatureActive) { |
37 | debug('Initializing `workspaces` feature'); | 20 | debug('Initializing `workspaces` feature'); |
38 | store = new WorkspacesStore(stores, api, actions, workspacesState); | 21 | workspaceStore.start(stores, actions); |
39 | store.initialize(); | 22 | } else if (workspaceStore.isFeatureActive) { |
40 | runInAction(() => { workspacesState.isFeatureActive = true; }); | ||
41 | } else if (store) { | ||
42 | debug('Disabling `workspaces` feature'); | 23 | debug('Disabling `workspaces` feature'); |
43 | runInAction(() => { workspacesState.isFeatureActive = false; }); | 24 | workspaceStore.stop(); |
44 | store.teardown(); | ||
45 | store = null; | ||
46 | resetState(); // Reset state to default | ||
47 | } | 25 | } |
48 | }, | 26 | }, |
49 | { | 27 | { |
50 | fireImmediately: true, | 28 | fireImmediately: true, |
51 | }, | 29 | }, |
52 | ); | 30 | ); |
53 | |||
54 | // Update active service on workspace switches | ||
55 | reaction(() => ({ | ||
56 | isFeatureActive: workspacesState.isFeatureActive, | ||
57 | activeWorkspace: workspacesState.activeWorkspace, | ||
58 | }), ({ isFeatureActive, activeWorkspace }) => { | ||
59 | if (!isFeatureActive) return; | ||
60 | if (activeWorkspace) { | ||
61 | const services = stores.services.allDisplayed; | ||
62 | const activeService = services.find(s => s.isActive); | ||
63 | const workspaceServices = filterServicesByActiveWorkspace(services); | ||
64 | const isActiveServiceInWorkspace = workspaceServices.includes(activeService); | ||
65 | if (!isActiveServiceInWorkspace) { | ||
66 | actions.service.setActive({ serviceId: workspaceServices[0].id }); | ||
67 | } | ||
68 | } | ||
69 | }); | ||
70 | } | 31 | } |
diff --git a/src/features/workspaces/state.js b/src/features/workspaces/state.js deleted file mode 100644 index c916480c0..000000000 --- a/src/features/workspaces/state.js +++ /dev/null | |||
@@ -1,18 +0,0 @@ | |||
1 | import { observable } from 'mobx'; | ||
2 | |||
3 | const defaultState = { | ||
4 | activeWorkspace: null, | ||
5 | nextWorkspace: null, | ||
6 | isLoadingWorkspaces: false, | ||
7 | isFeatureActive: false, | ||
8 | isSwitchingWorkspace: false, | ||
9 | isWorkspaceDrawerOpen: false, | ||
10 | workspaces: [], | ||
11 | workspaceBeingEdited: null, | ||
12 | }; | ||
13 | |||
14 | export const workspacesState = observable(defaultState); | ||
15 | |||
16 | export function resetState() { | ||
17 | Object.assign(workspacesState, defaultState); | ||
18 | } | ||
diff --git a/src/features/workspaces/store.js b/src/features/workspaces/store.js index f6b9b2ff4..883f36ffb 100644 --- a/src/features/workspaces/store.js +++ b/src/features/workspaces/store.js | |||
@@ -1,54 +1,39 @@ | |||
1 | import { observable, reaction, action } from 'mobx'; | 1 | import { |
2 | import Store from '../../stores/lib/Store'; | 2 | computed, |
3 | import CachedRequest from '../../stores/lib/CachedRequest'; | 3 | observable, |
4 | import Workspace from './models/Workspace'; | 4 | action, |
5 | } from 'mobx'; | ||
6 | import Reaction from '../../stores/lib/Reaction'; | ||
5 | import { matchRoute } from '../../helpers/routing-helpers'; | 7 | import { matchRoute } from '../../helpers/routing-helpers'; |
6 | import { workspaceActions } from './actions'; | 8 | import { workspaceActions } from './actions'; |
9 | import { | ||
10 | createWorkspaceRequest, | ||
11 | deleteWorkspaceRequest, | ||
12 | getUserWorkspacesRequest, | ||
13 | updateWorkspaceRequest, | ||
14 | } from './api'; | ||
7 | 15 | ||
8 | const debug = require('debug')('Franz:feature:workspaces'); | 16 | const debug = require('debug')('Franz:feature:workspaces:store'); |
9 | 17 | ||
10 | export default class WorkspacesStore extends Store { | 18 | export default class WorkspacesStore { |
11 | @observable allWorkspacesRequest = new CachedRequest(this.api, 'getUserWorkspaces'); | 19 | @observable isFeatureActive = false; |
12 | 20 | ||
13 | constructor(stores, api, actions, state) { | 21 | @observable activeWorkspace = null; |
14 | super(stores, api, actions); | 22 | |
15 | this.state = state; | 23 | @observable nextWorkspace = null; |
16 | } | 24 | |
25 | @observable workspaceBeingEdited = null; | ||
26 | |||
27 | @observable isSwitchingWorkspace = false; | ||
17 | 28 | ||
18 | setup() { | 29 | @observable isWorkspaceDrawerOpen = false; |
19 | debug('fetching workspaces'); | ||
20 | this.allWorkspacesRequest.execute(); | ||
21 | |||
22 | /** | ||
23 | * Update the state workspaces array when workspaces request has results. | ||
24 | */ | ||
25 | reaction( | ||
26 | () => this.allWorkspacesRequest.result, | ||
27 | workspaces => this._setWorkspaces(workspaces), | ||
28 | ); | ||
29 | /** | ||
30 | * Update the loading state when workspace request is executing. | ||
31 | */ | ||
32 | reaction( | ||
33 | () => this.allWorkspacesRequest.isExecuting, | ||
34 | isExecuting => this._setIsLoadingWorkspaces(isExecuting), | ||
35 | ); | ||
36 | /** | ||
37 | * Update the state with the workspace to be edited when route matches. | ||
38 | */ | ||
39 | reaction( | ||
40 | () => ({ | ||
41 | pathname: this.stores.router.location.pathname, | ||
42 | workspaces: this.state.workspaces, | ||
43 | }), | ||
44 | ({ pathname }) => { | ||
45 | const match = matchRoute('/settings/workspaces/edit/:id', pathname); | ||
46 | if (match) { | ||
47 | this.state.workspaceBeingEdited = this._getWorkspaceById(match.id); | ||
48 | } | ||
49 | }, | ||
50 | ); | ||
51 | 30 | ||
31 | @computed get workspaces() { | ||
32 | return getUserWorkspacesRequest.execute().result || []; | ||
33 | } | ||
34 | |||
35 | constructor() { | ||
36 | // Wire-up action handlers | ||
52 | workspaceActions.edit.listen(this._edit); | 37 | workspaceActions.edit.listen(this._edit); |
53 | workspaceActions.create.listen(this._create); | 38 | workspaceActions.create.listen(this._create); |
54 | workspaceActions.delete.listen(this._delete); | 39 | workspaceActions.delete.listen(this._delete); |
@@ -57,28 +42,62 @@ export default class WorkspacesStore extends Store { | |||
57 | workspaceActions.deactivate.listen(this._deactivateActiveWorkspace); | 42 | workspaceActions.deactivate.listen(this._deactivateActiveWorkspace); |
58 | workspaceActions.toggleWorkspaceDrawer.listen(this._toggleWorkspaceDrawer); | 43 | workspaceActions.toggleWorkspaceDrawer.listen(this._toggleWorkspaceDrawer); |
59 | workspaceActions.openWorkspaceSettings.listen(this._openWorkspaceSettings); | 44 | workspaceActions.openWorkspaceSettings.listen(this._openWorkspaceSettings); |
45 | |||
46 | // Register and start reactions | ||
47 | this._registerReactions([ | ||
48 | this._updateWorkspaceBeingEdited, | ||
49 | this._updateActiveServiceOnWorkspaceSwitch, | ||
50 | ]); | ||
60 | } | 51 | } |
61 | 52 | ||
62 | _getWorkspaceById = id => this.state.workspaces.find(w => w.id === id); | 53 | start(stores, actions) { |
54 | debug('WorkspacesStore::start'); | ||
55 | this.stores = stores; | ||
56 | this.actions = actions; | ||
57 | this._reactions.forEach(r => r.start()); | ||
58 | this.isFeatureActive = true; | ||
59 | } | ||
63 | 60 | ||
64 | @action _setWorkspaces = (workspaces) => { | 61 | stop() { |
65 | debug('setting user workspaces', workspaces.slice()); | 62 | debug('WorkspacesStore::stop'); |
66 | this.state.workspaces = workspaces.map(data => new Workspace(data)); | 63 | this._reactions.forEach(r => r.stop()); |
67 | }; | 64 | this.isFeatureActive = false; |
65 | } | ||
68 | 66 | ||
69 | @action _setIsLoadingWorkspaces = (isLoading) => { | 67 | filterServicesByActiveWorkspace = (services) => { |
70 | this.state.isLoadingWorkspaces = isLoading; | 68 | const { activeWorkspace, isFeatureActive } = this; |
69 | |||
70 | if (!isFeatureActive) return services; | ||
71 | if (activeWorkspace) { | ||
72 | return services.filter(s => ( | ||
73 | activeWorkspace.services.includes(s.id) | ||
74 | )); | ||
75 | } | ||
76 | return services; | ||
71 | }; | 77 | }; |
72 | 78 | ||
79 | // ========== PRIVATE ========= // | ||
80 | |||
81 | _reactions = []; | ||
82 | |||
83 | _registerReactions(reactions) { | ||
84 | reactions.forEach(r => this._reactions.push(new Reaction(r))); | ||
85 | } | ||
86 | |||
87 | _getWorkspaceById = id => this.workspaces.find(w => w.id === id); | ||
88 | |||
89 | // Actions | ||
90 | |||
73 | @action _edit = ({ workspace }) => { | 91 | @action _edit = ({ workspace }) => { |
74 | this.stores.router.push(`/settings/workspaces/edit/${workspace.id}`); | 92 | this.stores.router.push(`/settings/workspaces/edit/${workspace.id}`); |
75 | }; | 93 | }; |
76 | 94 | ||
77 | @action _create = async ({ name }) => { | 95 | @action _create = async ({ name }) => { |
78 | try { | 96 | try { |
79 | const result = await this.api.createWorkspace(name); | 97 | const workspace = await createWorkspaceRequest.execute(name); |
80 | const workspace = new Workspace(result); | 98 | await getUserWorkspacesRequest.patch((result) => { |
81 | this.state.workspaces.push(workspace); | 99 | result.push(workspace); |
100 | }); | ||
82 | this._edit({ workspace }); | 101 | this._edit({ workspace }); |
83 | } catch (error) { | 102 | } catch (error) { |
84 | throw error; | 103 | throw error; |
@@ -87,8 +106,10 @@ export default class WorkspacesStore extends Store { | |||
87 | 106 | ||
88 | @action _delete = async ({ workspace }) => { | 107 | @action _delete = async ({ workspace }) => { |
89 | try { | 108 | try { |
90 | await this.api.deleteWorkspace(workspace); | 109 | await deleteWorkspaceRequest.execute(workspace); |
91 | this.state.workspaces.remove(workspace); | 110 | await getUserWorkspacesRequest.patch((result) => { |
111 | result.remove(workspace); | ||
112 | }); | ||
92 | this.stores.router.push('/settings/workspaces'); | 113 | this.stores.router.push('/settings/workspaces'); |
93 | } catch (error) { | 114 | } catch (error) { |
94 | throw error; | 115 | throw error; |
@@ -97,9 +118,11 @@ export default class WorkspacesStore extends Store { | |||
97 | 118 | ||
98 | @action _update = async ({ workspace }) => { | 119 | @action _update = async ({ workspace }) => { |
99 | try { | 120 | try { |
100 | await this.api.updateWorkspace(workspace); | 121 | await updateWorkspaceRequest.execute(workspace); |
101 | const localWorkspace = this.state.workspaces.find(ws => ws.id === workspace.id); | 122 | await getUserWorkspacesRequest.patch((result) => { |
102 | Object.assign(localWorkspace, workspace); | 123 | const localWorkspace = result.find(ws => ws.id === workspace.id); |
124 | Object.assign(localWorkspace, workspace); | ||
125 | }); | ||
103 | this.stores.router.push('/settings/workspaces'); | 126 | this.stores.router.push('/settings/workspaces'); |
104 | } catch (error) { | 127 | } catch (error) { |
105 | throw error; | 128 | throw error; |
@@ -107,33 +130,56 @@ export default class WorkspacesStore extends Store { | |||
107 | }; | 130 | }; |
108 | 131 | ||
109 | @action _setActiveWorkspace = ({ workspace }) => { | 132 | @action _setActiveWorkspace = ({ workspace }) => { |
110 | Object.assign(this.state, { | 133 | // Indicate that we are switching to another workspace |
111 | isSwitchingWorkspace: true, | 134 | this.isSwitchingWorkspace = true; |
112 | nextWorkspace: workspace, | 135 | this.nextWorkspace = workspace; |
113 | }); | 136 | // Delay switching to next workspace so that the services loading does not drag down UI |
114 | setTimeout(() => { this.state.activeWorkspace = workspace; }, 100); | 137 | setTimeout(() => { this.activeWorkspace = workspace; }, 100); |
138 | // Indicate that we are done switching to the next workspace | ||
115 | setTimeout(() => { | 139 | setTimeout(() => { |
116 | Object.assign(this.state, { | 140 | this.isSwitchingWorkspace = false; |
117 | isSwitchingWorkspace: false, | 141 | this.nextWorkspace = null; |
118 | nextWorkspace: null, | ||
119 | }); | ||
120 | }, 1000); | 142 | }, 1000); |
121 | }; | 143 | }; |
122 | 144 | ||
123 | @action _deactivateActiveWorkspace = () => { | 145 | @action _deactivateActiveWorkspace = () => { |
124 | Object.assign(this.state, { | 146 | // Indicate that we are switching to default workspace |
125 | isSwitchingWorkspace: true, | 147 | this.isSwitchingWorkspace = true; |
126 | nextWorkspace: null, | 148 | this.nextWorkspace = null; |
127 | }); | 149 | // Delay switching to next workspace so that the services loading does not drag down UI |
128 | setTimeout(() => { this.state.activeWorkspace = null; }, 100); | 150 | setTimeout(() => { this.activeWorkspace = null; }, 100); |
129 | setTimeout(() => { this.state.isSwitchingWorkspace = false; }, 1000); | 151 | // Indicate that we are done switching to the default workspace |
152 | setTimeout(() => { this.isSwitchingWorkspace = false; }, 1000); | ||
130 | }; | 153 | }; |
131 | 154 | ||
132 | @action _toggleWorkspaceDrawer = () => { | 155 | @action _toggleWorkspaceDrawer = () => { |
133 | this.state.isWorkspaceDrawerOpen = !this.state.isWorkspaceDrawerOpen; | 156 | this.isWorkspaceDrawerOpen = !this.isWorkspaceDrawerOpen; |
134 | }; | 157 | }; |
135 | 158 | ||
136 | @action _openWorkspaceSettings = () => { | 159 | @action _openWorkspaceSettings = () => { |
137 | this.actions.ui.openSettings({ path: 'workspaces' }); | 160 | this.actions.ui.openSettings({ path: 'workspaces' }); |
138 | }; | 161 | }; |
162 | |||
163 | // Reactions | ||
164 | |||
165 | _updateWorkspaceBeingEdited = () => { | ||
166 | const { pathname } = this.stores.router.location; | ||
167 | const match = matchRoute('/settings/workspaces/edit/:id', pathname); | ||
168 | if (match) { | ||
169 | this.workspaceBeingEdited = this._getWorkspaceById(match.id); | ||
170 | } | ||
171 | }; | ||
172 | |||
173 | _updateActiveServiceOnWorkspaceSwitch = () => { | ||
174 | if (!this.isFeatureActive) return; | ||
175 | if (this.activeWorkspace) { | ||
176 | const services = this.stores.services.allDisplayed; | ||
177 | const activeService = services.find(s => s.isActive); | ||
178 | const workspaceServices = this.filterServicesByActiveWorkspace(services); | ||
179 | const isActiveServiceInWorkspace = workspaceServices.includes(activeService); | ||
180 | if (!isActiveServiceInWorkspace) { | ||
181 | this.actions.service.setActive({ serviceId: workspaceServices[0].id }); | ||
182 | } | ||
183 | } | ||
184 | }; | ||
139 | } | 185 | } |
diff --git a/src/lib/Menu.js b/src/lib/Menu.js index 2a88515f4..3435e04f7 100644 --- a/src/lib/Menu.js +++ b/src/lib/Menu.js | |||
@@ -3,7 +3,7 @@ import { observable, autorun } from 'mobx'; | |||
3 | import { defineMessages } from 'react-intl'; | 3 | import { defineMessages } from 'react-intl'; |
4 | 4 | ||
5 | import { isMac, ctrlKey, cmdKey } from '../environment'; | 5 | import { isMac, ctrlKey, cmdKey } from '../environment'; |
6 | import { workspacesState } from '../features/workspaces/state'; | 6 | import { workspaceStore } from '../features/workspaces/index'; |
7 | import { workspaceActions } from '../features/workspaces/actions'; | 7 | import { workspaceActions } from '../features/workspaces/actions'; |
8 | 8 | ||
9 | const { app, Menu, dialog } = remote; | 9 | const { app, Menu, dialog } = remote; |
@@ -784,7 +784,7 @@ export default class FranzMenu { | |||
784 | } | 784 | } |
785 | 785 | ||
786 | workspacesMenu() { | 786 | workspacesMenu() { |
787 | const { workspaces, activeWorkspace } = workspacesState; | 787 | const { workspaces, activeWorkspace, isWorkspaceDrawerOpen } = workspaceStore; |
788 | const { intl } = window.franz; | 788 | const { intl } = window.franz; |
789 | const menu = []; | 789 | const menu = []; |
790 | 790 | ||
@@ -800,7 +800,7 @@ export default class FranzMenu { | |||
800 | 800 | ||
801 | // Open workspace drawer: | 801 | // Open workspace drawer: |
802 | const drawerLabel = ( | 802 | const drawerLabel = ( |
803 | workspacesState.isWorkspaceDrawerOpen ? menuItems.closeWorkspaceDrawer : menuItems.openWorkspaceDrawer | 803 | isWorkspaceDrawerOpen ? menuItems.closeWorkspaceDrawer : menuItems.openWorkspaceDrawer |
804 | ); | 804 | ); |
805 | menu.push({ | 805 | menu.push({ |
806 | label: intl.formatMessage(drawerLabel), | 806 | label: intl.formatMessage(drawerLabel), |
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index cc8eed65b..0ec6bf550 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js | |||
@@ -12,7 +12,7 @@ import Request from './lib/Request'; | |||
12 | import CachedRequest from './lib/CachedRequest'; | 12 | import CachedRequest from './lib/CachedRequest'; |
13 | import { matchRoute } from '../helpers/routing-helpers'; | 13 | import { matchRoute } from '../helpers/routing-helpers'; |
14 | import { gaEvent } from '../lib/analytics'; | 14 | import { gaEvent } from '../lib/analytics'; |
15 | import { filterServicesByActiveWorkspace, getActiveWorkspaceServices } from '../features/workspaces'; | 15 | import { workspaceStore } from '../features/workspaces'; |
16 | 16 | ||
17 | const debug = require('debug')('Franz:ServiceStore'); | 17 | const debug = require('debug')('Franz:ServiceStore'); |
18 | 18 | ||
@@ -109,7 +109,7 @@ export default class ServicesStore extends Store { | |||
109 | 109 | ||
110 | @computed get allDisplayed() { | 110 | @computed get allDisplayed() { |
111 | const services = this.stores.settings.all.app.showDisabledServices ? this.all : this.enabled; | 111 | const services = this.stores.settings.all.app.showDisabledServices ? this.all : this.enabled; |
112 | return filterServicesByActiveWorkspace(services); | 112 | return workspaceStore.filterServicesByActiveWorkspace(services); |
113 | } | 113 | } |
114 | 114 | ||
115 | // This is just used to avoid unnecessary rerendering of resource-heavy webviews | 115 | // This is just used to avoid unnecessary rerendering of resource-heavy webviews |
@@ -117,7 +117,7 @@ export default class ServicesStore extends Store { | |||
117 | const { showDisabledServices } = this.stores.settings.all.app; | 117 | const { showDisabledServices } = this.stores.settings.all.app; |
118 | const services = this.allServicesRequest.execute().result || []; | 118 | const services = this.allServicesRequest.execute().result || []; |
119 | const filteredServices = showDisabledServices ? services : services.filter(service => service.isEnabled); | 119 | const filteredServices = showDisabledServices ? services : services.filter(service => service.isEnabled); |
120 | return getActiveWorkspaceServices(filteredServices); | 120 | return workspaceStore.filterServicesByActiveWorkspace(filteredServices); |
121 | } | 121 | } |
122 | 122 | ||
123 | @computed get filtered() { | 123 | @computed get filtered() { |