aboutsummaryrefslogtreecommitdiffstats
path: root/src/features/todos
diff options
context:
space:
mode:
authorLibravatar kytwb <412895+kytwb@users.noreply.github.com>2021-06-12 19:51:28 +0200
committerLibravatar GitHub <noreply@github.com>2021-06-12 19:51:28 +0200
commitb0ecce5eab2a6d0eed3ade7c43e252ca9bac7edb (patch)
treeaed0253b61ca035f3388a7d6a369ffe75f195a58 /src/features/todos
parentBypassed code signing since that is also incorrect in GH settings. (diff)
downloadferdium-app-b0ecce5eab2a6d0eed3ade7c43e252ca9bac7edb.tar.gz
ferdium-app-b0ecce5eab2a6d0eed3ade7c43e252ca9bac7edb.tar.zst
ferdium-app-b0ecce5eab2a6d0eed3ade7c43e252ca9bac7edb.zip
Fix active Todos service behaviour (#1481)
* Return false instead of null in isTodosServiceAdded * Resolve from TODOS_RECIPES_ID instead of hardcoded TODOS_RECIPE_ID * Fix TodosWebview width toggling when isTodosServiceActive * Add more todo service recipe IDs * Refactor todos state management * Moved todos service URL and recipe ID computation logic to todos/store * Simplified TodosWebview by delegating to the store for the URL and removing the (unused) payment logic * Made the todos service computation logic in the Service model depend on the logic in todos/store * Made ServicesStore depend on the todos service logic from the Service model * Todos appearance fixes * Hide double horizontal rules if todo settings are hidden due to an added todo service * Hide todos panel border when the panel is hidden or expanded * Make expanded todos panel obey sidebar width and vertical style settings * Make todos/store use isValidExternalURL * Harden isValidExternalURL against malformed URLs * Reduce todo URL string duplication in config.js Co-authored-by: Kristóf Marussy <kristof@marussy.com>
Diffstat (limited to 'src/features/todos')
-rw-r--r--src/features/todos/components/TodosWebview.js96
-rw-r--r--src/features/todos/containers/TodosScreen.js3
-rw-r--r--src/features/todos/index.js1
-rw-r--r--src/features/todos/store.js29
4 files changed, 48 insertions, 81 deletions
diff --git a/src/features/todos/components/TodosWebview.js b/src/features/todos/components/TodosWebview.js
index 634ec4caa..03bb5efe8 100644
--- a/src/features/todos/components/TodosWebview.js
+++ b/src/features/todos/components/TodosWebview.js
@@ -1,47 +1,12 @@
1import React, { Component } from 'react'; 1import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer, inject } from 'mobx-react'; 3import { observer } from 'mobx-react';
4import injectSheet from 'react-jss'; 4import injectSheet from 'react-jss';
5import Webview from 'react-electron-web-view'; 5import Webview from 'react-electron-web-view';
6import { Icon } from '@meetfranz/ui';
7import { defineMessages, intlShape } from 'react-intl';
8import classnames from 'classnames'; 6import classnames from 'classnames';
9 7
10import { mdiCheckAll } from '@mdi/js';
11import SettingsStore from '../../../stores/SettingsStore';
12
13import Appear from '../../../components/ui/effects/Appear';
14import UpgradeButton from '../../../components/ui/UpgradeButton';
15import { TODOS_PARTITION_ID } from '..'; 8import { TODOS_PARTITION_ID } from '..';
16 9
17// NOTE: https://stackoverflow.com/questions/5717093/check-if-a-javascript-string-is-a-url
18function validURL(str) {
19 let url;
20
21 try {
22 url = new URL(str);
23 } catch (_) {
24 return false;
25 }
26
27 return url.protocol === 'http:' || url.protocol === 'https:';
28}
29
30const messages = defineMessages({
31 premiumInfo: {
32 id: 'feature.todos.premium.info',
33 defaultMessage: '!!!Franz Todos are available to premium users now!',
34 },
35 upgradeCTA: {
36 id: 'feature.todos.premium.upgrade',
37 defaultMessage: '!!!Upgrade Account',
38 },
39 rolloutInfo: {
40 id: 'feature.todos.premium.rollout',
41 defaultMessage: '!!!Everyone else will have to wait a little longer.',
42 },
43});
44
45const styles = theme => ({ 10const styles = theme => ({
46 root: { 11 root: {
47 background: theme.colorBackground, 12 background: theme.colorBackground,
@@ -96,10 +61,14 @@ const styles = theme => ({
96 position: 'absolute', 61 position: 'absolute',
97 right: 0, 62 right: 0,
98 zIndex: 0, 63 zIndex: 0,
64 borderLeftWidth: 0,
65 },
66 hidden: {
67 borderLeftWidth: 0,
99 }, 68 },
100}); 69});
101 70
102@injectSheet(styles) @inject('stores') @observer 71@injectSheet(styles) @observer
103class TodosWebview extends Component { 72class TodosWebview extends Component {
104 static propTypes = { 73 static propTypes = {
105 classes: PropTypes.object.isRequired, 74 classes: PropTypes.object.isRequired,
@@ -111,10 +80,8 @@ class TodosWebview extends Component {
111 width: PropTypes.number.isRequired, 80 width: PropTypes.number.isRequired,
112 minWidth: PropTypes.number.isRequired, 81 minWidth: PropTypes.number.isRequired,
113 userAgent: PropTypes.string.isRequired, 82 userAgent: PropTypes.string.isRequired,
114 isTodosIncludedInCurrentPlan: PropTypes.bool.isRequired, 83 todoUrl: PropTypes.string.isRequired,
115 stores: PropTypes.shape({ 84 isTodoUrlValid: PropTypes.bool.isRequired,
116 settings: PropTypes.instanceOf(SettingsStore).isRequired,
117 }).isRequired,
118 }; 85 };
119 86
120 state = { 87 state = {
@@ -122,10 +89,6 @@ class TodosWebview extends Component {
122 width: 300, 89 width: 300,
123 }; 90 };
124 91
125 static contextTypes = {
126 intl: intlShape,
127 };
128
129 componentWillMount() { 92 componentWillMount() {
130 const { width } = this.props; 93 const { width } = this.props;
131 94
@@ -209,8 +172,8 @@ class TodosWebview extends Component {
209 isTodosServiceActive, 172 isTodosServiceActive,
210 isVisible, 173 isVisible,
211 userAgent, 174 userAgent,
212 isTodosIncludedInCurrentPlan, 175 todoUrl,
213 stores, 176 isTodoUrlValid,
214 } = this.props; 177 } = this.props;
215 178
216 const { 179 const {
@@ -219,29 +182,20 @@ class TodosWebview extends Component {
219 isDragging, 182 isDragging,
220 } = this.state; 183 } = this.state;
221 184
222 const { intl } = this.context; 185 let displayedWidth = isVisible ? width : 0;
223 186 if (isTodosServiceActive) {
224 const isUsingPredefinedTodoServer = stores.settings.all.app.predefinedTodoServer !== 'isUsingCustomTodoService'; 187 displayedWidth = null;
225 const todoUrl = isUsingPredefinedTodoServer
226 ? stores.settings.all.app.predefinedTodoServer
227 : stores.settings.all.app.customTodoServer;
228 let isTodoUrlValid = true;
229 if (isUsingPredefinedTodoServer === false) {
230 isTodoUrlValid = validURL(todoUrl);
231 } 188 }
232 189
233 const todosPanelStyle = {
234 width: isVisible ? width : 0,
235 borderLeftWidth: isVisible ? '1px' : 0,
236 };
237
238 return ( 190 return (
239 <div 191 <div
240 className={classnames({ 192 className={classnames({
241 [classes.root]: true, 193 [classes.root]: true,
242 [classes.isTodosServiceActive]: isTodosServiceActive, 194 [classes.isTodosServiceActive]: isTodosServiceActive,
195 'todos__todos-panel--expanded': isTodosServiceActive,
196 [classes.hidden]: !isVisible,
243 })} 197 })}
244 style={todosPanelStyle} 198 style={{ width: displayedWidth }}
245 onMouseUp={() => this.stopResize()} 199 onMouseUp={() => this.stopResize()}
246 ref={(node) => { this.node = node; }} 200 ref={(node) => { this.node = node; }}
247 id="todos-panel" 201 id="todos-panel"
@@ -257,9 +211,7 @@ class TodosWebview extends Component {
257 style={{ left: delta }} // This hack is required as resizing with webviews beneath behaves quite bad 211 style={{ left: delta }} // This hack is required as resizing with webviews beneath behaves quite bad
258 /> 212 />
259 )} 213 )}
260 {isTodosIncludedInCurrentPlan ? ( 214 {isTodoUrlValid && (
261 isTodoUrlValid
262 && (
263 <Webview 215 <Webview
264 className={classes.webview} 216 className={classes.webview}
265 onDidAttach={() => { 217 onDidAttach={() => {
@@ -273,20 +225,6 @@ class TodosWebview extends Component {
273 useragent={userAgent} 225 useragent={userAgent}
274 src={todoUrl} 226 src={todoUrl}
275 /> 227 />
276 )
277 ) : (
278 <Appear>
279 <div className={classes.premiumContainer}>
280 <Icon icon={mdiCheckAll} className={classes.premiumIcon} size={4} />
281 <p>{intl.formatMessage(messages.premiumInfo)}</p>
282 <p>{intl.formatMessage(messages.rolloutInfo)}</p>
283 <UpgradeButton
284 className={classes.premiumCTA}
285 gaEventInfo={{ category: 'Todos', event: 'upgrade' }}
286 short
287 />
288 </div>
289 </Appear>
290 )} 228 )}
291 </div> 229 </div>
292 ); 230 );
diff --git a/src/features/todos/containers/TodosScreen.js b/src/features/todos/containers/TodosScreen.js
index 631893f93..96147d5ab 100644
--- a/src/features/todos/containers/TodosScreen.js
+++ b/src/features/todos/containers/TodosScreen.js
@@ -28,7 +28,8 @@ class TodosScreen extends Component {
28 minWidth={TODOS_MIN_WIDTH} 28 minWidth={TODOS_MIN_WIDTH}
29 resize={width => todoActions.resize({ width })} 29 resize={width => todoActions.resize({ width })}
30 userAgent={todosStore.userAgent} 30 userAgent={todosStore.userAgent}
31 isTodosIncludedInCurrentPlan 31 todoUrl={todosStore.todoUrl}
32 isTodoUrlValid={todosStore.isTodoUrlValid}
32 /> 33 />
33 </ErrorBoundary> 34 </ErrorBoundary>
34 ); 35 );
diff --git a/src/features/todos/index.js b/src/features/todos/index.js
index ebd2c60ef..b6d13e5e1 100644
--- a/src/features/todos/index.js
+++ b/src/features/todos/index.js
@@ -9,7 +9,6 @@ export const DEFAULT_TODOS_WIDTH = 300;
9export const TODOS_MIN_WIDTH = 200; 9export const TODOS_MIN_WIDTH = 200;
10export const DEFAULT_TODOS_VISIBLE = true; 10export const DEFAULT_TODOS_VISIBLE = true;
11export const DEFAULT_IS_FEATURE_ENABLED_BY_USER = true; 11export const DEFAULT_IS_FEATURE_ENABLED_BY_USER = true;
12export const TODOS_RECIPE_ID = 'franz-todos';
13export const TODOS_PARTITION_ID = 'persist:todos'; 12export const TODOS_PARTITION_ID = 'persist:todos';
14 13
15export const TODOS_ROUTES = { 14export const TODOS_ROUTES = {
diff --git a/src/features/todos/store.js b/src/features/todos/store.js
index af8519d9c..4febd7bb1 100644
--- a/src/features/todos/store.js
+++ b/src/features/todos/store.js
@@ -7,6 +7,8 @@ import {
7import localStorage from 'mobx-localstorage'; 7import localStorage from 'mobx-localstorage';
8 8
9import { todoActions } from './actions'; 9import { todoActions } from './actions';
10import { CUSTOM_TODO_SERVICE, TODO_SERVICE_RECIPE_IDS } from '../../config';
11import { isValidExternalURL } from '../../helpers/url-helpers';
10import { FeatureStore } from '../utils/FeatureStore'; 12import { FeatureStore } from '../utils/FeatureStore';
11import { createReactions } from '../../stores/lib/Reaction'; 13import { createReactions } from '../../stores/lib/Reaction';
12import { createActionBindings } from '../utils/ActionBinding'; 14import { createActionBindings } from '../utils/ActionBinding';
@@ -21,6 +23,8 @@ import UserAgent from '../../models/UserAgent';
21const debug = require('debug')('Ferdi:feature:todos:store'); 23const debug = require('debug')('Ferdi:feature:todos:store');
22 24
23export default class TodoStore extends FeatureStore { 25export default class TodoStore extends FeatureStore {
26 @observable stores = null;
27
24 @observable isFeatureEnabled = false; 28 @observable isFeatureEnabled = false;
25 29
26 @observable isFeatureActive = false; 30 @observable isFeatureActive = false;
@@ -59,6 +63,31 @@ export default class TodoStore extends FeatureStore {
59 return this.userAgentModel.userAgent; 63 return this.userAgentModel.userAgent;
60 } 64 }
61 65
66 @computed get isUsingPredefinedTodoServer() {
67 return this.stores && this.stores.settings.app.predefinedTodoServer !== CUSTOM_TODO_SERVICE;
68 }
69
70 @computed get todoUrl() {
71 if (!this.stores) {
72 return null;
73 }
74 return this.isUsingPredefinedTodoServer
75 ? this.stores.settings.app.predefinedTodoServer
76 : this.stores.settings.app.customTodoServer;
77 }
78
79 @computed get isTodoUrlValid() {
80 return !this.isUsingPredefinedTodoServer || isValidExternalURL(this.todoUrl);
81 }
82
83 @computed get todoRecipeId() {
84 if (this.isFeatureEnabledByUser && this.isUsingPredefinedTodoServer
85 && this.todoUrl in TODO_SERVICE_RECIPE_IDS) {
86 return TODO_SERVICE_RECIPE_IDS[this.todoUrl];
87 }
88 return null;
89 }
90
62 // ========== PUBLIC API ========= // 91 // ========== PUBLIC API ========= //
63 92
64 @action start(stores, actions) { 93 @action start(stores, actions) {