diff options
Diffstat (limited to 'src/features/todos/components/TodosWebview.js')
-rw-r--r-- | src/features/todos/components/TodosWebview.js | 237 |
1 files changed, 237 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..288c1906f --- /dev/null +++ b/src/features/todos/components/TodosWebview.js | |||
@@ -0,0 +1,237 @@ | |||
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 | |||
8 | import * as environment from '../../../environment'; | ||
9 | |||
10 | const OPEN_TODOS_BUTTON_SIZE = 45; | ||
11 | const CLOSE_TODOS_BUTTON_SIZE = 35; | ||
12 | |||
13 | const styles = theme => ({ | ||
14 | root: { | ||
15 | background: theme.colorBackground, | ||
16 | position: 'relative', | ||
17 | borderLeft: [1, 'solid', theme.todos.todosLayer.borderLeftColor], | ||
18 | zIndex: 300, | ||
19 | |||
20 | transform: ({ isVisible, width }) => `translateX(${isVisible ? 0 : width}px)`, | ||
21 | |||
22 | '&:hover $closeTodosButton': { | ||
23 | opacity: 1, | ||
24 | }, | ||
25 | }, | ||
26 | webview: { | ||
27 | height: '100%', | ||
28 | |||
29 | '& webview': { | ||
30 | height: '100%', | ||
31 | }, | ||
32 | }, | ||
33 | resizeHandler: { | ||
34 | position: 'absolute', | ||
35 | left: 0, | ||
36 | marginLeft: -5, | ||
37 | width: 10, | ||
38 | zIndex: 400, | ||
39 | cursor: 'col-resize', | ||
40 | }, | ||
41 | dragIndicator: { | ||
42 | position: 'absolute', | ||
43 | left: 0, | ||
44 | width: 5, | ||
45 | zIndex: 400, | ||
46 | background: theme.todos.dragIndicator.background, | ||
47 | |||
48 | }, | ||
49 | openTodosButton: { | ||
50 | width: OPEN_TODOS_BUTTON_SIZE, | ||
51 | height: OPEN_TODOS_BUTTON_SIZE, | ||
52 | background: theme.todos.toggleButton.background, | ||
53 | position: 'absolute', | ||
54 | bottom: 80, | ||
55 | right: props => (props.width + (props.isVisible ? -OPEN_TODOS_BUTTON_SIZE / 2 : 0)), | ||
56 | borderRadius: OPEN_TODOS_BUTTON_SIZE / 2, | ||
57 | opacity: props => (props.isVisible ? 0 : 1), | ||
58 | transition: 'right 0.5s', | ||
59 | zIndex: 600, | ||
60 | display: 'flex', | ||
61 | alignItems: 'center', | ||
62 | justifyContent: 'center', | ||
63 | boxShadow: [0, 0, 10, theme.todos.toggleButton.shadowColor], | ||
64 | |||
65 | borderTopRightRadius: props => (props.isVisible ? null : 0), | ||
66 | borderBottomRightRadius: props => (props.isVisible ? null : 0), | ||
67 | |||
68 | '& svg': { | ||
69 | fill: theme.todos.toggleButton.textColor, | ||
70 | transition: 'all 0.5s', | ||
71 | }, | ||
72 | }, | ||
73 | closeTodosButton: { | ||
74 | width: CLOSE_TODOS_BUTTON_SIZE, | ||
75 | height: CLOSE_TODOS_BUTTON_SIZE, | ||
76 | background: theme.todos.toggleButton.background, | ||
77 | position: 'absolute', | ||
78 | bottom: 80, | ||
79 | right: ({ width }) => (width + -CLOSE_TODOS_BUTTON_SIZE / 2), | ||
80 | borderRadius: CLOSE_TODOS_BUTTON_SIZE / 2, | ||
81 | opacity: 0, | ||
82 | transition: 'opacity 0.5s', | ||
83 | zIndex: 600, | ||
84 | display: 'flex', | ||
85 | alignItems: 'center', | ||
86 | justifyContent: 'center', | ||
87 | boxShadow: [0, 0, 10, theme.todos.toggleButton.shadowColor], | ||
88 | |||
89 | '& svg': { | ||
90 | fill: theme.todos.toggleButton.textColor, | ||
91 | }, | ||
92 | }, | ||
93 | }); | ||
94 | |||
95 | @injectSheet(styles) @observer | ||
96 | class TodosWebview extends Component { | ||
97 | static propTypes = { | ||
98 | classes: PropTypes.object.isRequired, | ||
99 | isVisible: PropTypes.bool.isRequired, | ||
100 | togglePanel: PropTypes.func.isRequired, | ||
101 | handleClientMessage: PropTypes.func.isRequired, | ||
102 | setTodosWebview: PropTypes.func.isRequired, | ||
103 | resize: PropTypes.func.isRequired, | ||
104 | width: PropTypes.number.isRequired, | ||
105 | minWidth: PropTypes.number.isRequired, | ||
106 | }; | ||
107 | |||
108 | state = { | ||
109 | isDragging: false, | ||
110 | width: 300, | ||
111 | }; | ||
112 | |||
113 | componentWillMount() { | ||
114 | const { width } = this.props; | ||
115 | |||
116 | this.setState({ | ||
117 | width, | ||
118 | }); | ||
119 | } | ||
120 | |||
121 | componentDidMount() { | ||
122 | this.node.addEventListener('mousemove', this.resizePanel.bind(this)); | ||
123 | this.node.addEventListener('mouseup', this.stopResize.bind(this)); | ||
124 | this.node.addEventListener('mouseleave', this.stopResize.bind(this)); | ||
125 | } | ||
126 | |||
127 | startResize = (event) => { | ||
128 | this.setState({ | ||
129 | isDragging: true, | ||
130 | initialPos: event.clientX, | ||
131 | delta: 0, | ||
132 | }); | ||
133 | }; | ||
134 | |||
135 | resizePanel(e) { | ||
136 | const { minWidth } = this.props; | ||
137 | |||
138 | const { | ||
139 | isDragging, | ||
140 | initialPos, | ||
141 | } = this.state; | ||
142 | |||
143 | if (isDragging && Math.abs(e.clientX - window.innerWidth) > minWidth) { | ||
144 | const delta = e.clientX - initialPos; | ||
145 | |||
146 | this.setState({ | ||
147 | delta, | ||
148 | }); | ||
149 | } | ||
150 | } | ||
151 | |||
152 | stopResize() { | ||
153 | const { | ||
154 | resize, | ||
155 | minWidth, | ||
156 | } = this.props; | ||
157 | |||
158 | const { | ||
159 | isDragging, | ||
160 | delta, | ||
161 | width, | ||
162 | } = this.state; | ||
163 | |||
164 | if (isDragging) { | ||
165 | let newWidth = width + (delta < 0 ? Math.abs(delta) : -Math.abs(delta)); | ||
166 | |||
167 | if (newWidth < minWidth) { | ||
168 | newWidth = minWidth; | ||
169 | } | ||
170 | |||
171 | this.setState({ | ||
172 | isDragging: false, | ||
173 | delta: 0, | ||
174 | width: newWidth, | ||
175 | }); | ||
176 | |||
177 | resize(newWidth); | ||
178 | } | ||
179 | } | ||
180 | |||
181 | startListeningToIpcMessages() { | ||
182 | const { handleClientMessage } = this.props; | ||
183 | if (!this.webview) return; | ||
184 | this.webview.addEventListener('ipc-message', e => handleClientMessage(e.args[0])); | ||
185 | } | ||
186 | |||
187 | render() { | ||
188 | const { | ||
189 | classes, isVisible, togglePanel, | ||
190 | } = this.props; | ||
191 | const { width, delta, isDragging } = this.state; | ||
192 | |||
193 | return ( | ||
194 | <> | ||
195 | <div | ||
196 | className={classes.root} | ||
197 | style={{ width: isVisible ? width : 0 }} | ||
198 | onMouseUp={() => this.stopResize()} | ||
199 | ref={(node) => { this.node = node; }} | ||
200 | > | ||
201 | <button | ||
202 | onClick={() => togglePanel()} | ||
203 | className={isVisible ? classes.closeTodosButton : classes.openTodosButton} | ||
204 | type="button" | ||
205 | > | ||
206 | <Icon icon={isVisible ? 'mdiChevronRight' : 'mdiCheckAll'} size={2} /> | ||
207 | </button> | ||
208 | <div | ||
209 | className={classes.resizeHandler} | ||
210 | style={Object.assign({ left: delta }, isDragging ? { width: 600, marginLeft: -200 } : {})} // This hack is required as resizing with webviews beneath behaves quite bad | ||
211 | onMouseDown={e => this.startResize(e)} | ||
212 | /> | ||
213 | {isDragging && ( | ||
214 | <div | ||
215 | className={classes.dragIndicator} | ||
216 | style={{ left: delta }} // This hack is required as resizing with webviews beneath behaves quite bad | ||
217 | /> | ||
218 | )} | ||
219 | <Webview | ||
220 | className={classes.webview} | ||
221 | onDidAttach={() => { | ||
222 | const { setTodosWebview } = this.props; | ||
223 | setTodosWebview(this.webview); | ||
224 | this.startListeningToIpcMessages(); | ||
225 | }} | ||
226 | partition="persist:todos" | ||
227 | preload="./features/todos/preload.js" | ||
228 | ref={(webview) => { this.webview = webview ? webview.view : null; }} | ||
229 | src={environment.TODOS_FRONTEND} | ||
230 | /> | ||
231 | </div> | ||
232 | </> | ||
233 | ); | ||
234 | } | ||
235 | } | ||
236 | |||
237 | export default TodosWebview; | ||