aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/theme/src/themes/default/index.ts7
-rw-r--r--src/actions/index.js2
-rw-r--r--src/components/layout/AppLayout.js16
-rw-r--r--src/containers/layout/AppLayoutContainer.js4
-rw-r--r--src/features/todos/actions.js10
-rw-r--r--src/features/todos/components/TodosWebview.js129
-rw-r--r--src/features/todos/containers/TodosScreen.js45
-rw-r--r--src/features/todos/index.js33
-rw-r--r--src/features/todos/store.js86
-rw-r--r--src/stores/FeaturesStore.js2
-rw-r--r--src/styles/layout.scss7
11 files changed, 311 insertions, 30 deletions
diff --git a/packages/theme/src/themes/default/index.ts b/packages/theme/src/themes/default/index.ts
index 0f02fa3c8..4a49a4de0 100644
--- a/packages/theme/src/themes/default/index.ts
+++ b/packages/theme/src/themes/default/index.ts
@@ -207,3 +207,10 @@ export const announcements = {
207 background: legacyStyles.themeGrayLightest, 207 background: legacyStyles.themeGrayLightest,
208 }, 208 },
209}; 209};
210
211// Todos
212export const todos = {
213 dragIndicator: {
214 background: legacyStyles.themeGrayLight,
215 },
216};
diff --git a/src/actions/index.js b/src/actions/index.js
index fc525afeb..336344d76 100644
--- a/src/actions/index.js
+++ b/src/actions/index.js
@@ -13,6 +13,7 @@ import settings from './settings';
13import requests from './requests'; 13import requests from './requests';
14import announcements from '../features/announcements/actions'; 14import announcements from '../features/announcements/actions';
15import workspaces from '../features/workspaces/actions'; 15import workspaces from '../features/workspaces/actions';
16import todos from '../features/todos/actions';
16 17
17const actions = Object.assign({}, { 18const actions = Object.assign({}, {
18 service, 19 service,
@@ -31,4 +32,5 @@ export default Object.assign(
31 defineActions(actions, PropTypes.checkPropTypes), 32 defineActions(actions, PropTypes.checkPropTypes),
32 { announcements }, 33 { announcements },
33 { workspaces }, 34 { workspaces },
35 { todos },
34); 36);
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js
index 7f2f707fb..dbf7d3c21 100644
--- a/src/components/layout/AppLayout.js
+++ b/src/components/layout/AppLayout.js
@@ -17,7 +17,7 @@ import { isWindows } from '../../environment';
17import WorkspaceSwitchingIndicator from '../../features/workspaces/components/WorkspaceSwitchingIndicator'; 17import WorkspaceSwitchingIndicator from '../../features/workspaces/components/WorkspaceSwitchingIndicator';
18import { workspaceStore } from '../../features/workspaces'; 18import { workspaceStore } from '../../features/workspaces';
19import AppUpdateInfoBar from '../AppUpdateInfoBar'; 19import AppUpdateInfoBar from '../AppUpdateInfoBar';
20import TodosWebview from '../../features/todos/components/TodosWebview'; 20import Todos from '../../features/todos/containers/TodosScreen';
21 21
22function createMarkup(HTMLString) { 22function createMarkup(HTMLString) {
23 return { __html: HTMLString }; 23 return { __html: HTMLString };
@@ -52,7 +52,6 @@ const styles = theme => ({
52@injectSheet(styles) @observer 52@injectSheet(styles) @observer
53class AppLayout extends Component { 53class AppLayout extends Component {
54 static propTypes = { 54 static propTypes = {
55 authToken: PropTypes.string.isRequired,
56 classes: PropTypes.object.isRequired, 55 classes: PropTypes.object.isRequired,
57 isFullScreen: PropTypes.bool.isRequired, 56 isFullScreen: PropTypes.bool.isRequired,
58 sidebar: PropTypes.element.isRequired, 57 sidebar: PropTypes.element.isRequired,
@@ -60,7 +59,6 @@ class AppLayout extends Component {
60 services: PropTypes.element.isRequired, 59 services: PropTypes.element.isRequired,
61 children: PropTypes.element, 60 children: PropTypes.element,
62 news: MobxPropTypes.arrayOrObservableArray.isRequired, 61 news: MobxPropTypes.arrayOrObservableArray.isRequired,
63 // isOnline: PropTypes.bool.isRequired,
64 showServicesUpdatedInfoBar: PropTypes.bool.isRequired, 62 showServicesUpdatedInfoBar: PropTypes.bool.isRequired,
65 appUpdateIsDownloaded: PropTypes.bool.isRequired, 63 appUpdateIsDownloaded: PropTypes.bool.isRequired,
66 nextAppReleaseVersion: PropTypes.string, 64 nextAppReleaseVersion: PropTypes.string,
@@ -85,7 +83,6 @@ class AppLayout extends Component {
85 83
86 render() { 84 render() {
87 const { 85 const {
88 authToken,
89 classes, 86 classes,
90 isFullScreen, 87 isFullScreen,
91 workspacesDrawer, 88 workspacesDrawer,
@@ -129,15 +126,6 @@ class AppLayout extends Component {
129 <span dangerouslySetInnerHTML={createMarkup(item.message)} /> 126 <span dangerouslySetInnerHTML={createMarkup(item.message)} />
130 </InfoBar> 127 </InfoBar>
131 ))} 128 ))}
132 {/* {!isOnline && (
133 <InfoBar
134 type="danger"
135 sticky
136 >
137 <span className="mdi mdi-flash" />
138 {intl.formatMessage(globalMessages.notConnectedToTheInternet)}
139 </InfoBar>
140 )} */}
141 {!areRequiredRequestsSuccessful && showRequiredRequestsError && ( 129 {!areRequiredRequestsSuccessful && showRequiredRequestsError && (
142 <InfoBar 130 <InfoBar
143 type="danger" 131 type="danger"
@@ -173,7 +161,7 @@ class AppLayout extends Component {
173 {services} 161 {services}
174 {children} 162 {children}
175 </div> 163 </div>
176 <TodosWebview authToken={authToken} /> 164 <Todos />
177 </div> 165 </div>
178 </div> 166 </div>
179 </ErrorBoundary> 167 </ErrorBoundary>
diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js
index 8a48f4924..cf3da71e8 100644
--- a/src/containers/layout/AppLayoutContainer.js
+++ b/src/containers/layout/AppLayoutContainer.js
@@ -12,7 +12,6 @@ import NewsStore from '../../stores/NewsStore';
12import SettingsStore from '../../stores/SettingsStore'; 12import SettingsStore from '../../stores/SettingsStore';
13import RequestStore from '../../stores/RequestStore'; 13import RequestStore from '../../stores/RequestStore';
14import GlobalErrorStore from '../../stores/GlobalErrorStore'; 14import GlobalErrorStore from '../../stores/GlobalErrorStore';
15import UserStore from '../../stores/UserStore';
16 15
17import { oneOrManyChildElements } from '../../prop-types'; 16import { oneOrManyChildElements } from '../../prop-types';
18import AppLayout from '../../components/layout/AppLayout'; 17import AppLayout from '../../components/layout/AppLayout';
@@ -40,7 +39,6 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
40 settings, 39 settings,
41 globalError, 40 globalError,
42 requests, 41 requests,
43 user,
44 } = this.props.stores; 42 } = this.props.stores;
45 43
46 const { 44 const {
@@ -133,7 +131,6 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
133 return ( 131 return (
134 <ThemeProvider theme={ui.theme}> 132 <ThemeProvider theme={ui.theme}>
135 <AppLayout 133 <AppLayout
136 authToken={user.authToken}
137 isFullScreen={app.isFullScreen} 134 isFullScreen={app.isFullScreen}
138 isOnline={app.isOnline} 135 isOnline={app.isOnline}
139 showServicesUpdatedInfoBar={ui.showServicesUpdatedInfoBar} 136 showServicesUpdatedInfoBar={ui.showServicesUpdatedInfoBar}
@@ -171,7 +168,6 @@ AppLayoutContainer.wrappedComponent.propTypes = {
171 settings: PropTypes.instanceOf(SettingsStore).isRequired, 168 settings: PropTypes.instanceOf(SettingsStore).isRequired,
172 requests: PropTypes.instanceOf(RequestStore).isRequired, 169 requests: PropTypes.instanceOf(RequestStore).isRequired,
173 globalError: PropTypes.instanceOf(GlobalErrorStore).isRequired, 170 globalError: PropTypes.instanceOf(GlobalErrorStore).isRequired,
174 user: PropTypes.instanceOf(UserStore).isRequired,
175 }).isRequired, 171 }).isRequired,
176 actions: PropTypes.shape({ 172 actions: PropTypes.shape({
177 service: PropTypes.shape({ 173 service: PropTypes.shape({
diff --git a/src/features/todos/actions.js b/src/features/todos/actions.js
new file mode 100644
index 000000000..673ce8531
--- /dev/null
+++ b/src/features/todos/actions.js
@@ -0,0 +1,10 @@
1import PropTypes from 'prop-types';
2import { createActionsFromDefinitions } from '../../actions/lib/actions';
3
4export const todoActions = createActionsFromDefinitions({
5 resize: {
6 width: PropTypes.number.isRequired,
7 },
8}, PropTypes.checkPropTypes);
9
10export default todoActions;
diff --git a/src/features/todos/components/TodosWebview.js b/src/features/todos/components/TodosWebview.js
index ca0b94ea1..1d99b9388 100644
--- a/src/features/todos/components/TodosWebview.js
+++ b/src/features/todos/components/TodosWebview.js
@@ -8,15 +8,26 @@ import * as environment from '../../../environment';
8const styles = theme => ({ 8const styles = theme => ({
9 root: { 9 root: {
10 background: theme.colorBackground, 10 background: theme.colorBackground,
11 height: '100%', 11 position: 'relative',
12 width: 300,
13 position: 'absolute',
14 top: 0,
15 right: -300,
16 }, 12 },
17 webview: { 13 webview: {
18 height: '100%', 14 height: '100%',
19 }, 15 },
16 resizeHandler: {
17 position: 'absolute',
18 left: 0,
19 marginLeft: -5,
20 width: 10,
21 zIndex: 400,
22 cursor: 'col-resize',
23 },
24 dragIndicator: {
25 position: 'absolute',
26 left: 0,
27 width: 5,
28 zIndex: 400,
29 background: theme.todos.dragIndicator.background,
30 },
20}); 31});
21 32
22@injectSheet(styles) @observer 33@injectSheet(styles) @observer
@@ -24,17 +35,113 @@ class TodosWebview extends Component {
24 static propTypes = { 35 static propTypes = {
25 classes: PropTypes.object.isRequired, 36 classes: PropTypes.object.isRequired,
26 authToken: PropTypes.string.isRequired, 37 authToken: PropTypes.string.isRequired,
38 resize: PropTypes.func.isRequired,
39 width: PropTypes.number.isRequired,
40 minWidth: PropTypes.number.isRequired,
27 }; 41 };
28 42
43 state = {
44 isDragging: false,
45 width: 300,
46 }
47
48 componentWillMount() {
49 const { width } = this.props;
50
51 this.setState({
52 width,
53 });
54 }
55
56 componentDidMount() {
57 this.node.addEventListener('mousemove', this.resizePanel.bind(this));
58 this.node.addEventListener('mouseup', this.stopResize.bind(this));
59 this.node.addEventListener('mouseleave', this.stopResize.bind(this));
60 }
61
62 startResize = (event) => {
63 this.setState({
64 isDragging: true,
65 initialPos: event.clientX,
66 delta: 0,
67 });
68 }
69
70 resizePanel(e) {
71 const { minWidth } = this.props;
72
73 const {
74 isDragging,
75 initialPos,
76 } = this.state;
77
78 if (isDragging && Math.abs(e.clientX - window.innerWidth) > minWidth) {
79 const delta = e.clientX - initialPos;
80
81 this.setState({
82 delta,
83 });
84 }
85 }
86
87 stopResize() {
88 const {
89 resize,
90 minWidth,
91 } = this.props;
92
93 const {
94 isDragging,
95 delta,
96 width,
97 } = this.state;
98
99 if (isDragging) {
100 let newWidth = width + (delta < 0 ? Math.abs(delta) : -Math.abs(delta));
101
102 if (newWidth < minWidth) {
103 newWidth = minWidth;
104 }
105
106 this.setState({
107 isDragging: false,
108 delta: 0,
109 width: newWidth,
110 });
111
112 resize(newWidth);
113 }
114 }
115
29 render() { 116 render() {
30 const { authToken, classes } = this.props; 117 const { authToken, classes } = this.props;
118 const { width, delta, isDragging } = this.state;
119
31 return ( 120 return (
32 <div className={classes.root}> 121 <>
33 <Webview 122 <div
34 className={classes.webview} 123 className={classes.root}
35 src={`${environment.TODOS_FRONTEND}?authToken=${authToken}`} 124 style={{ width }}
36 /> 125 onMouseUp={() => this.stopResize()}
37 </div> 126 ref={(node) => { this.node = node; }}
127 >
128 <div
129 className={classes.resizeHandler}
130 style={Object.assign({ left: delta }, isDragging ? { width: 600, marginLeft: -200 } : {})} // This hack is required as resizing with webviews beneath behaves quite bad
131 onMouseDown={e => this.startResize(e)}
132 />
133 {isDragging && (
134 <div
135 className={classes.dragIndicator}
136 style={{ left: delta }} // This hack is required as resizing with webviews beneath behaves quite bad
137 />
138 )}
139 <Webview
140 className={classes.webview}
141 src={`${environment.TODOS_FRONTEND}?authToken=${authToken}`}
142 />
143 </div>
144 </>
38 ); 145 );
39 } 146 }
40} 147}
diff --git a/src/features/todos/containers/TodosScreen.js b/src/features/todos/containers/TodosScreen.js
new file mode 100644
index 000000000..0759c22db
--- /dev/null
+++ b/src/features/todos/containers/TodosScreen.js
@@ -0,0 +1,45 @@
1import React, { Component } from 'react';
2import { inject, observer } from 'mobx-react';
3import PropTypes from 'prop-types';
4
5import TodosWebview from '../components/TodosWebview';
6import ErrorBoundary from '../../../components/util/ErrorBoundary';
7import UserStore from '../../../stores/UserStore';
8import TodoStore from '../store';
9import { TODOS_MIN_WIDTH } from '..';
10
11@inject('stores', 'actions') @observer
12class TodosScreen extends Component {
13 static propTypes = {
14 stores: PropTypes.shape({
15 user: PropTypes.instanceOf(UserStore).isRequired,
16 todos: PropTypes.instanceOf(TodoStore).isRequired,
17 }).isRequired,
18 actions: PropTypes.shape({
19 todos: PropTypes.shape({
20 resize: PropTypes.func.isRequired,
21 }),
22 }).isRequired,
23 };
24
25 render() {
26 const { stores, actions } = this.props;
27
28 if (!stores.todos || !stores.todos.isFeatureActive) {
29 return null;
30 }
31
32 return (
33 <ErrorBoundary>
34 <TodosWebview
35 authToken={stores.user.authToken}
36 width={stores.todos.width}
37 minWidth={TODOS_MIN_WIDTH}
38 resize={width => actions.todos.resize({ width })}
39 />
40 </ErrorBoundary>
41 );
42 }
43}
44
45export default TodosScreen;
diff --git a/src/features/todos/index.js b/src/features/todos/index.js
new file mode 100644
index 000000000..0dfd35c78
--- /dev/null
+++ b/src/features/todos/index.js
@@ -0,0 +1,33 @@
1import { reaction } from 'mobx';
2import TodoStore from './store';
3
4const debug = require('debug')('Franz:feature:todos');
5
6export const GA_CATEGORY_TODOS = 'Todos';
7
8export const DEFAULT_TODOS_WIDTH = 300;
9export const TODOS_MIN_WIDTH = 200;
10
11export const todoStore = new TodoStore();
12
13export default function initTodos(stores, actions) {
14 stores.todos = todoStore;
15 const { features } = stores;
16
17 // Toggle todos feature
18 reaction(
19 () => features.features.isTodosEnabled,
20 (isEnabled) => {
21 if (isEnabled) {
22 debug('Initializing `todos` feature');
23 todoStore.start(stores, actions);
24 } else if (todoStore.isFeatureActive) {
25 debug('Disabling `todos` feature');
26 todoStore.stop();
27 }
28 },
29 {
30 fireImmediately: true,
31 },
32 );
33}
diff --git a/src/features/todos/store.js b/src/features/todos/store.js
new file mode 100644
index 000000000..e7e13b37f
--- /dev/null
+++ b/src/features/todos/store.js
@@ -0,0 +1,86 @@
1import {
2 computed,
3 action,
4 observable,
5} from 'mobx';
6import localStorage from 'mobx-localstorage';
7
8import { todoActions } from './actions';
9import { FeatureStore } from '../utils/FeatureStore';
10import { createReactions } from '../../stores/lib/Reaction';
11import { createActionBindings } from '../utils/ActionBinding';
12import { DEFAULT_TODOS_WIDTH, TODOS_MIN_WIDTH } from '.';
13
14const debug = require('debug')('Franz:feature:todos:store');
15
16export default class TodoStore extends FeatureStore {
17 @observable isFeatureEnabled = false;
18
19 @observable isFeatureActive = false;
20
21 @computed get width() {
22 const width = this.settings.width || DEFAULT_TODOS_WIDTH;
23
24 return width < TODOS_MIN_WIDTH ? TODOS_MIN_WIDTH : width;
25 }
26
27 @computed get settings() {
28 return localStorage.getItem('todos') || {};
29 }
30
31 // ========== PUBLIC API ========= //
32
33 @action start(stores, actions) {
34 debug('TodoStore::start');
35 this.stores = stores;
36 this.actions = actions;
37
38 // ACTIONS
39
40 this._registerActions(createActionBindings([
41 [todoActions.resize, this._resize],
42 ]));
43
44 // REACTIONS
45
46 this._allReactions = createReactions([
47 this._setFeatureEnabledReaction,
48 ]);
49
50 this._registerReactions(this._allReactions);
51
52 this.isFeatureActive = true;
53 }
54
55 @action stop() {
56 super.stop();
57 debug('TodoStore::stop');
58 this.reset();
59 this.isFeatureActive = false;
60 }
61
62 // ========== PRIVATE METHODS ========= //
63
64 _updateSettings = (changes) => {
65 localStorage.setItem('todos', {
66 ...this.settings,
67 ...changes,
68 });
69 };
70
71 // Actions
72
73 @action _resize = ({ width }) => {
74 this._updateSettings({
75 width,
76 });
77 };
78
79 // Reactions
80
81 _setFeatureEnabledReaction = () => {
82 const { isTodosEnabled } = this.stores.features.features;
83
84 this.isFeatureEnabled = isTodosEnabled;
85 };
86}
diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js
index e7832088b..35a050c67 100644
--- a/src/stores/FeaturesStore.js
+++ b/src/stores/FeaturesStore.js
@@ -16,6 +16,7 @@ import workspaces from '../features/workspaces';
16import shareFranz from '../features/shareFranz'; 16import shareFranz from '../features/shareFranz';
17import announcements from '../features/announcements'; 17import announcements from '../features/announcements';
18import settingsWS from '../features/settingsWS'; 18import settingsWS from '../features/settingsWS';
19import todos from '../features/todos';
19 20
20import { DEFAULT_FEATURES_CONFIG } from '../config'; 21import { DEFAULT_FEATURES_CONFIG } from '../config';
21 22
@@ -75,5 +76,6 @@ export default class FeaturesStore extends Store {
75 shareFranz(this.stores, this.actions); 76 shareFranz(this.stores, this.actions);
76 announcements(this.stores, this.actions); 77 announcements(this.stores, this.actions);
77 settingsWS(this.stores, this.actions); 78 settingsWS(this.stores, this.actions);
79 todos(this.stores, this.actions);
78 } 80 }
79} 81}
diff --git a/src/styles/layout.scss b/src/styles/layout.scss
index 739082445..10027da60 100644
--- a/src/styles/layout.scss
+++ b/src/styles/layout.scss
@@ -37,10 +37,15 @@ html { overflow: hidden; }
37 37
38 .app__content { 38 .app__content {
39 display: flex; 39 display: flex;
40 width: calc(100% + 300px);
41 }
42
43 .app__main-content {
44 display: flex;
45 width: 100%;
40 } 46 }
41 47
42 .app__service { 48 .app__service {
43 // position: relative;
44 display: flex; 49 display: flex;
45 flex: 1; 50 flex: 1;
46 flex-direction: column; 51 flex-direction: column;