diff options
Diffstat (limited to 'src/features/todos/components/TodosWebview.js')
-rw-r--r-- | src/features/todos/components/TodosWebview.js | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/src/features/todos/components/TodosWebview.js b/src/features/todos/components/TodosWebview.js new file mode 100644 index 000000000..c06183e37 --- /dev/null +++ b/src/features/todos/components/TodosWebview.js | |||
@@ -0,0 +1,300 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import injectSheet from 'react-jss'; | ||
5 | import Webview from 'react-electron-web-view'; | ||
6 | import { Icon } from '@meetfranz/ui'; | ||
7 | import { defineMessages, intlShape } from 'react-intl'; | ||
8 | |||
9 | import { mdiChevronRight, mdiCheckAll } from '@mdi/js'; | ||
10 | import * as environment from '../../../environment'; | ||
11 | import Appear from '../../../components/ui/effects/Appear'; | ||
12 | import UpgradeButton from '../../../components/ui/UpgradeButton'; | ||
13 | |||
14 | const OPEN_TODOS_BUTTON_SIZE = 45; | ||
15 | const CLOSE_TODOS_BUTTON_SIZE = 35; | ||
16 | |||
17 | const messages = defineMessages({ | ||
18 | premiumInfo: { | ||
19 | id: 'feature.todos.premium.info', | ||
20 | defaultMessage: '!!!Franz Todos are available to premium users now!', | ||
21 | }, | ||
22 | upgradeCTA: { | ||
23 | id: 'feature.todos.premium.upgrade', | ||
24 | defaultMessage: '!!!Upgrade Account', | ||
25 | }, | ||
26 | rolloutInfo: { | ||
27 | id: 'feature.todos.premium.rollout', | ||
28 | defaultMessage: '!!!Everyone else will have to wait a little longer.', | ||
29 | }, | ||
30 | }); | ||
31 | |||
32 | const styles = theme => ({ | ||
33 | root: { | ||
34 | background: theme.colorBackground, | ||
35 | position: 'relative', | ||
36 | borderLeft: [1, 'solid', theme.todos.todosLayer.borderLeftColor], | ||
37 | zIndex: 300, | ||
38 | |||
39 | transform: ({ isVisible, width }) => `translateX(${isVisible ? 0 : width}px)`, | ||
40 | |||
41 | '&:hover $closeTodosButton': { | ||
42 | opacity: 1, | ||
43 | }, | ||
44 | '& webview': { | ||
45 | height: '100%', | ||
46 | }, | ||
47 | }, | ||
48 | resizeHandler: { | ||
49 | position: 'absolute', | ||
50 | left: 0, | ||
51 | marginLeft: -5, | ||
52 | width: 10, | ||
53 | zIndex: 400, | ||
54 | cursor: 'col-resize', | ||
55 | }, | ||
56 | dragIndicator: { | ||
57 | position: 'absolute', | ||
58 | left: 0, | ||
59 | width: 5, | ||
60 | zIndex: 400, | ||
61 | background: theme.todos.dragIndicator.background, | ||
62 | |||
63 | }, | ||
64 | openTodosButton: { | ||
65 | width: OPEN_TODOS_BUTTON_SIZE, | ||
66 | height: OPEN_TODOS_BUTTON_SIZE, | ||
67 | background: theme.todos.toggleButton.background, | ||
68 | position: 'absolute', | ||
69 | bottom: 120, | ||
70 | right: props => (props.width + (props.isVisible ? -OPEN_TODOS_BUTTON_SIZE / 2 : 0)), | ||
71 | borderRadius: OPEN_TODOS_BUTTON_SIZE / 2, | ||
72 | opacity: props => (props.isVisible ? 0 : 1), | ||
73 | transition: 'right 0.5s', | ||
74 | zIndex: 600, | ||
75 | display: 'flex', | ||
76 | alignItems: 'center', | ||
77 | justifyContent: 'center', | ||
78 | boxShadow: [0, 0, 10, theme.todos.toggleButton.shadowColor], | ||
79 | |||
80 | borderTopRightRadius: props => (props.isVisible ? null : 0), | ||
81 | borderBottomRightRadius: props => (props.isVisible ? null : 0), | ||
82 | |||
83 | '& svg': { | ||
84 | fill: theme.todos.toggleButton.textColor, | ||
85 | transition: 'all 0.5s', | ||
86 | }, | ||
87 | }, | ||
88 | closeTodosButton: { | ||
89 | width: CLOSE_TODOS_BUTTON_SIZE, | ||
90 | height: CLOSE_TODOS_BUTTON_SIZE, | ||
91 | background: theme.todos.toggleButton.background, | ||
92 | position: 'absolute', | ||
93 | bottom: 120, | ||
94 | right: ({ width }) => (width + -CLOSE_TODOS_BUTTON_SIZE / 2), | ||
95 | borderRadius: CLOSE_TODOS_BUTTON_SIZE / 2, | ||
96 | opacity: ({ isTodosIncludedInCurrentPlan }) => (!isTodosIncludedInCurrentPlan ? 1 : 0), | ||
97 | transition: 'opacity 0.5s', | ||
98 | zIndex: 600, | ||
99 | display: 'flex', | ||
100 | alignItems: 'center', | ||
101 | justifyContent: 'center', | ||
102 | boxShadow: [0, 0, 10, theme.todos.toggleButton.shadowColor], | ||
103 | |||
104 | '& svg': { | ||
105 | fill: theme.todos.toggleButton.textColor, | ||
106 | }, | ||
107 | }, | ||
108 | premiumContainer: { | ||
109 | display: 'flex', | ||
110 | flexDirection: 'column', | ||
111 | justifyContent: 'center', | ||
112 | alignItems: 'center', | ||
113 | width: '80%', | ||
114 | maxWidth: 300, | ||
115 | margin: [0, 'auto'], | ||
116 | textAlign: 'center', | ||
117 | }, | ||
118 | premiumIcon: { | ||
119 | marginBottom: 40, | ||
120 | background: theme.styleTypes.primary.accent, | ||
121 | fill: theme.styleTypes.primary.contrast, | ||
122 | padding: 10, | ||
123 | borderRadius: 10, | ||
124 | }, | ||
125 | premiumCTA: { | ||
126 | marginTop: 40, | ||
127 | }, | ||
128 | }); | ||
129 | |||
130 | @injectSheet(styles) @observer | ||
131 | class TodosWebview extends Component { | ||
132 | static propTypes = { | ||
133 | classes: PropTypes.object.isRequired, | ||
134 | isVisible: PropTypes.bool.isRequired, | ||
135 | togglePanel: PropTypes.func.isRequired, | ||
136 | handleClientMessage: PropTypes.func.isRequired, | ||
137 | setTodosWebview: PropTypes.func.isRequired, | ||
138 | resize: PropTypes.func.isRequired, | ||
139 | width: PropTypes.number.isRequired, | ||
140 | minWidth: PropTypes.number.isRequired, | ||
141 | isTodosIncludedInCurrentPlan: PropTypes.bool.isRequired, | ||
142 | }; | ||
143 | |||
144 | state = { | ||
145 | isDragging: false, | ||
146 | width: 300, | ||
147 | }; | ||
148 | |||
149 | static contextTypes = { | ||
150 | intl: intlShape, | ||
151 | }; | ||
152 | |||
153 | componentWillMount() { | ||
154 | const { width } = this.props; | ||
155 | |||
156 | this.setState({ | ||
157 | width, | ||
158 | }); | ||
159 | } | ||
160 | |||
161 | componentDidMount() { | ||
162 | this.node.addEventListener('mousemove', this.resizePanel.bind(this)); | ||
163 | this.node.addEventListener('mouseup', this.stopResize.bind(this)); | ||
164 | this.node.addEventListener('mouseleave', this.stopResize.bind(this)); | ||
165 | } | ||
166 | |||
167 | startResize = (event) => { | ||
168 | this.setState({ | ||
169 | isDragging: true, | ||
170 | initialPos: event.clientX, | ||
171 | delta: 0, | ||
172 | }); | ||
173 | }; | ||
174 | |||
175 | resizePanel(e) { | ||
176 | const { minWidth } = this.props; | ||
177 | |||
178 | const { | ||
179 | isDragging, | ||
180 | initialPos, | ||
181 | } = this.state; | ||
182 | |||
183 | if (isDragging && Math.abs(e.clientX - window.innerWidth) > minWidth) { | ||
184 | const delta = e.clientX - initialPos; | ||
185 | |||
186 | this.setState({ | ||
187 | delta, | ||
188 | }); | ||
189 | } | ||
190 | } | ||
191 | |||
192 | stopResize() { | ||
193 | const { | ||
194 | resize, | ||
195 | minWidth, | ||
196 | } = this.props; | ||
197 | |||
198 | const { | ||
199 | isDragging, | ||
200 | delta, | ||
201 | width, | ||
202 | } = this.state; | ||
203 | |||
204 | if (isDragging) { | ||
205 | let newWidth = width + (delta < 0 ? Math.abs(delta) : -Math.abs(delta)); | ||
206 | |||
207 | if (newWidth < minWidth) { | ||
208 | newWidth = minWidth; | ||
209 | } | ||
210 | |||
211 | this.setState({ | ||
212 | isDragging: false, | ||
213 | delta: 0, | ||
214 | width: newWidth, | ||
215 | }); | ||
216 | |||
217 | resize(newWidth); | ||
218 | } | ||
219 | } | ||
220 | |||
221 | startListeningToIpcMessages() { | ||
222 | const { handleClientMessage } = this.props; | ||
223 | if (!this.webview) return; | ||
224 | this.webview.addEventListener('ipc-message', e => handleClientMessage(e.args[0])); | ||
225 | } | ||
226 | |||
227 | render() { | ||
228 | const { | ||
229 | classes, | ||
230 | isVisible, | ||
231 | togglePanel, | ||
232 | isTodosIncludedInCurrentPlan, | ||
233 | } = this.props; | ||
234 | |||
235 | const { | ||
236 | width, | ||
237 | delta, | ||
238 | isDragging, | ||
239 | } = this.state; | ||
240 | |||
241 | const { intl } = this.context; | ||
242 | |||
243 | return ( | ||
244 | <div | ||
245 | className={classes.root} | ||
246 | style={{ width: isVisible ? width : 0 }} | ||
247 | onMouseUp={() => this.stopResize()} | ||
248 | ref={(node) => { this.node = node; }} | ||
249 | > | ||
250 | <button | ||
251 | onClick={() => togglePanel()} | ||
252 | className={isVisible ? classes.closeTodosButton : classes.openTodosButton} | ||
253 | type="button" | ||
254 | > | ||
255 | <Icon icon={isVisible ? mdiChevronRight : mdiCheckAll} size={2} /> | ||
256 | </button> | ||
257 | <div | ||
258 | className={classes.resizeHandler} | ||
259 | style={Object.assign({ left: delta }, isDragging ? { width: 600, marginLeft: -200 } : {})} // This hack is required as resizing with webviews beneath behaves quite bad | ||
260 | onMouseDown={e => this.startResize(e)} | ||
261 | /> | ||
262 | {isDragging && ( | ||
263 | <div | ||
264 | className={classes.dragIndicator} | ||
265 | style={{ left: delta }} // This hack is required as resizing with webviews beneath behaves quite bad | ||
266 | /> | ||
267 | )} | ||
268 | {isTodosIncludedInCurrentPlan ? ( | ||
269 | <Webview | ||
270 | className={classes.webview} | ||
271 | onDidAttach={() => { | ||
272 | const { setTodosWebview } = this.props; | ||
273 | setTodosWebview(this.webview); | ||
274 | this.startListeningToIpcMessages(); | ||
275 | }} | ||
276 | partition="persist:todos" | ||
277 | preload="./features/todos/preload.js" | ||
278 | ref={(webview) => { this.webview = webview ? webview.view : null; }} | ||
279 | src={environment.TODOS_FRONTEND} | ||
280 | /> | ||
281 | ) : ( | ||
282 | <Appear> | ||
283 | <div className={classes.premiumContainer}> | ||
284 | <Icon icon={mdiCheckAll} className={classes.premiumIcon} size={4} /> | ||
285 | <p>{intl.formatMessage(messages.premiumInfo)}</p> | ||
286 | <p>{intl.formatMessage(messages.rolloutInfo)}</p> | ||
287 | <UpgradeButton | ||
288 | className={classes.premiumCTA} | ||
289 | gaEventInfo={{ category: 'Todos', event: 'upgrade' }} | ||
290 | short | ||
291 | /> | ||
292 | </div> | ||
293 | </Appear> | ||
294 | )} | ||
295 | </div> | ||
296 | ); | ||
297 | } | ||
298 | } | ||
299 | |||
300 | export default TodosWebview; | ||