aboutsummaryrefslogtreecommitdiffstats
path: root/src/features/webControls
diff options
context:
space:
mode:
Diffstat (limited to 'src/features/webControls')
-rw-r--r--src/features/webControls/components/WebControls.js239
-rw-r--r--src/features/webControls/containers/WebControlsScreen.js139
2 files changed, 378 insertions, 0 deletions
diff --git a/src/features/webControls/components/WebControls.js b/src/features/webControls/components/WebControls.js
new file mode 100644
index 000000000..a39fcfe0e
--- /dev/null
+++ b/src/features/webControls/components/WebControls.js
@@ -0,0 +1,239 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import injectSheet from 'react-jss';
5import { Icon } from '@meetfranz/ui';
6import { defineMessages, intlShape } from 'react-intl';
7
8import {
9 mdiReload, mdiArrowRight, mdiArrowLeft, mdiHomeOutline, mdiEarth,
10} from '@mdi/js';
11
12const messages = defineMessages({
13 goHome: {
14 id: 'webControls.goHome',
15 defaultMessage: '!!!Home',
16 },
17 openInBrowser: {
18 id: 'webControls.openInBrowser',
19 defaultMessage: '!!!Open in Browser',
20 },
21 back: {
22 id: 'webControls.back',
23 defaultMessage: '!!!Back',
24 },
25 forward: {
26 id: 'webControls.forward',
27 defaultMessage: '!!!Forward',
28 },
29 reload: {
30 id: 'webControls.reload',
31 defaultMessage: '!!!Reload',
32 },
33});
34
35const styles = theme => ({
36 root: {
37 background: theme.colorBackground,
38 position: 'relative',
39 borderLeft: [1, 'solid', theme.todos.todosLayer.borderLeftColor],
40 zIndex: 300,
41 height: 50,
42 display: 'flex',
43 flexDirection: 'row',
44 alignItems: 'center',
45 padding: [0, 10],
46
47 '& + div': {
48 height: 'calc(100% - 50px)',
49 },
50 },
51 button: {
52 width: 30,
53 height: 50,
54 transition: 'opacity 0.25s',
55
56 '&:hover': {
57 opacity: 0.8,
58 },
59
60 '&:disabled': {
61 opacity: 0.5,
62 },
63 },
64 icon: {
65 width: '20px !important',
66 height: 20,
67 marginTop: 5,
68 },
69 input: {
70 marginBottom: 0,
71 height: 'auto',
72 margin: [0, 10],
73 flex: 1,
74 border: 0,
75 padding: [4, 10],
76 borderRadius: theme.borderRadius,
77 background: theme.inputBackground,
78 color: theme.inputColor,
79 },
80 inputButton: {
81 color: theme.colorText,
82 },
83});
84
85@injectSheet(styles) @observer
86class WebControls extends Component {
87 static propTypes = {
88 classes: PropTypes.object.isRequired,
89 goHome: PropTypes.func.isRequired,
90 canGoBack: PropTypes.bool.isRequired,
91 goBack: PropTypes.func.isRequired,
92 canGoForward: PropTypes.bool.isRequired,
93 goForward: PropTypes.func.isRequired,
94 reload: PropTypes.func.isRequired,
95 openInBrowser: PropTypes.func.isRequired,
96 url: PropTypes.string.isRequired,
97 navigate: PropTypes.func.isRequired,
98 }
99
100 static contextTypes = {
101 intl: intlShape,
102 };
103
104 static getDerivedStateFromProps(props, state) {
105 const { url } = props;
106 const { editUrl } = state;
107
108 if (!editUrl) {
109 return {
110 inputUrl: url,
111 editUrl: state.editUrl,
112 };
113 }
114 }
115
116 inputRef = React.createRef();
117
118 state = {
119 inputUrl: '',
120 editUrl: false,
121 }
122
123 render() {
124 const {
125 classes,
126 goHome,
127 canGoBack,
128 goBack,
129 canGoForward,
130 goForward,
131 reload,
132 openInBrowser,
133 url,
134 navigate,
135 } = this.props;
136
137 const {
138 inputUrl,
139 editUrl,
140 } = this.state;
141
142 const { intl } = this.context;
143
144 return (
145 <div className={classes.root}>
146 <button
147 onClick={goHome}
148 type="button"
149 className={classes.button}
150 data-tip={intl.formatMessage(messages.goHome)}
151 >
152 <Icon
153 icon={mdiHomeOutline}
154 className={classes.icon}
155 />
156 </button>
157 <button
158 onClick={goBack}
159 type="button"
160 className={classes.button}
161 disabled={!canGoBack}
162 data-tip={intl.formatMessage(messages.back)}
163 >
164 <Icon
165 icon={mdiArrowLeft}
166 className={classes.icon}
167 />
168 </button>
169 <button
170 onClick={goForward}
171 type="button"
172 className={classes.button}
173 disabled={!canGoForward}
174 data-tip={intl.formatMessage(messages.forward)}
175 >
176 <Icon
177 icon={mdiArrowRight}
178 className={classes.icon}
179 />
180 </button>
181 <button
182 onClick={reload}
183 type="button"
184 className={classes.button}
185 data-tip={intl.formatMessage(messages.reload)}
186 >
187 <Icon
188 icon={mdiReload}
189 className={classes.icon}
190 />
191 </button>
192 <input
193 value={editUrl ? inputUrl : url}
194 className={classes.input}
195 onChange={event => this.setState({
196 inputUrl: event.target.value,
197 })}
198 onFocus={(event) => {
199 event.target.select();
200 this.setState({
201 editUrl: true,
202 });
203 }}
204 onKeyDown={(event) => {
205 if (event.key === 'Enter') {
206 this.setState({
207 editUrl: false,
208 });
209 navigate(inputUrl);
210 this.inputRef.current.blur();
211 } else if (event.key === 'Escape') {
212 this.setState({
213 editUrl: false,
214 inputUrl: url,
215 });
216 event.target.blur();
217 }
218 }}
219 ref={this.inputRef}
220 />
221 <button
222 onClick={openInBrowser}
223 type="button"
224 className={classes.button}
225 data-tip={intl.formatMessage(messages.openInBrowser)}
226 data-place="left"
227 >
228 <Icon
229 icon={mdiEarth}
230 className={classes.icon}
231 />
232 </button>
233 {/* <ReactTooltip place="bottom" type="dark" effect="solid" /> */}
234 </div>
235 );
236 }
237}
238
239export default WebControls;
diff --git a/src/features/webControls/containers/WebControlsScreen.js b/src/features/webControls/containers/WebControlsScreen.js
new file mode 100644
index 000000000..cada01a6f
--- /dev/null
+++ b/src/features/webControls/containers/WebControlsScreen.js
@@ -0,0 +1,139 @@
1import React, { Component } from 'react';
2import { observer, inject } from 'mobx-react';
3import PropTypes from 'prop-types';
4
5import { autorun, observable } from 'mobx';
6import WebControls from '../components/WebControls';
7import ServicesStore from '../../../stores/ServicesStore';
8import Service from '../../../models/Service';
9
10const URL_EVENTS = [
11 'load-commit',
12 'will-navigate',
13 'did-navigate',
14 'did-navigate-in-page',
15];
16
17@inject('stores', 'actions') @observer
18class WebControlsScreen extends Component {
19 @observable url = '';
20
21 @observable canGoBack = false;
22
23 @observable canGoForward = false;
24
25 webview = null;
26
27 autorunDisposer = null;
28
29 componentDidMount() {
30 const { service } = this.props;
31
32 this.autorunDisposer = autorun(() => {
33 if (service.isAttached) {
34 this.webview = service.webview;
35
36 URL_EVENTS.forEach((event) => {
37 this.webview.addEventListener(event, (e) => {
38 if (!e.isMainFrame) return;
39
40 this.url = e.url;
41 this.canGoBack = this.webview.canGoBack();
42 this.canGoForward = this.webview.canGoForward();
43 });
44 });
45 }
46 });
47 }
48
49 componentWillUnmount() {
50 this.autorunDisposer();
51 }
52
53 goHome() {
54 const { reloadActive } = this.props.actions.service;
55
56 if (!this.webview) return;
57
58 reloadActive();
59 }
60
61 reload() {
62 if (!this.webview) return;
63
64 this.webview.reload();
65 }
66
67 goBack() {
68 if (!this.webview) return;
69
70 this.webview.goBack();
71 }
72
73 goForward() {
74 if (!this.webview) return;
75
76 this.webview.goForward();
77 }
78
79 navigate(newUrl) {
80 if (!this.webview) return;
81
82 let url = newUrl;
83
84 try {
85 url = new URL(url).toString();
86 } catch (err) {
87 // eslint-disable-next-line no-useless-escape
88 if (url.match(/^((?!-))(xn--)?[a-z0-9][a-z0-9-_]{0,61}[a-z0-9]{0,1}\.(xn--)?([a-z0-9\-]{1,61}|[a-z0-9-]{1,30}\.[a-z]{2,})$/)) {
89 url = `http://${url}`;
90 } else {
91 url = `https://www.google.com/search?query=${url}`;
92 }
93 }
94
95 this.webview.loadURL(url);
96 this.url = url;
97 }
98
99 openInBrowser() {
100 const { openExternalUrl } = this.props.actions.app;
101
102 if (!this.webview) return;
103
104 openExternalUrl({ url: this.url });
105 }
106
107 render() {
108 return (
109 <WebControls
110 goHome={() => this.goHome()}
111 reload={() => this.reload()}
112 openInBrowser={() => this.openInBrowser()}
113 canGoBack={this.canGoBack}
114 goBack={() => this.goBack()}
115 canGoForward={this.canGoForward}
116 goForward={() => this.goForward()}
117 navigate={url => this.navigate(url)}
118 url={this.url}
119 />
120 );
121 }
122}
123
124export default WebControlsScreen;
125
126WebControlsScreen.wrappedComponent.propTypes = {
127 service: PropTypes.instanceOf(Service).isRequired,
128 stores: PropTypes.shape({
129 services: PropTypes.instanceOf(ServicesStore).isRequired,
130 }).isRequired,
131 actions: PropTypes.shape({
132 app: PropTypes.shape({
133 openExternalUrl: PropTypes.func.isRequired,
134 }).isRequired,
135 service: PropTypes.shape({
136 reloadActive: PropTypes.func.isRequired,
137 }).isRequired,
138 }).isRequired,
139};