Skip to content

Commit f8fbaad

Browse files
committed
feat(Custom Websites): Add simple browser controls
1 parent 9f4f3e7 commit f8fbaad

File tree

3 files changed

+329
-5
lines changed

3 files changed

+329
-5
lines changed

src/components/services/content/ServiceView.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import WebviewErrorHandler from './ErrorHandlers/WebviewErrorHandler';
1212
import ServiceDisabled from './ServiceDisabled';
1313
import ServiceRestricted from './ServiceRestricted';
1414
import ServiceWebview from './ServiceWebview';
15+
import WebControlsScreen from '../../../features/webControls/containers/WebControlsScreen';
1516

1617
export default @observer class ServiceView extends Component {
1718
static propTypes = {
@@ -137,11 +138,16 @@ export default @observer class ServiceView extends Component {
137138
type={service.restrictionType}
138139
/>
139140
) : (
140-
<ServiceWebview
141-
service={service}
142-
setWebviewReference={setWebviewReference}
143-
detachService={detachService}
144-
/>
141+
<>
142+
{service.recipe.id === 'franz-custom-website' && (
143+
<WebControlsScreen service={service} />
144+
)}
145+
<ServiceWebview
146+
service={service}
147+
setWebviewReference={setWebviewReference}
148+
detachService={detachService}
149+
/>
150+
</>
145151
)}
146152
</>
147153
)}
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
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 { Icon } from '@meetfranz/ui';
6+
7+
import {
8+
mdiReload, mdiArrowRight, mdiArrowLeft, mdiHomeOutline,
9+
} from '@mdi/js';
10+
11+
const styles = theme => ({
12+
root: {
13+
background: theme.colorBackground,
14+
position: 'relative',
15+
borderLeft: [1, 'solid', theme.todos.todosLayer.borderLeftColor],
16+
zIndex: 300,
17+
height: 50,
18+
display: 'flex',
19+
flexDirection: 'row',
20+
alignItems: 'center',
21+
padding: [0, 20],
22+
23+
'& + div': {
24+
height: 'calc(100% - 50px)',
25+
},
26+
},
27+
button: {
28+
width: 30,
29+
height: 50,
30+
transition: 'opacity 0.25s',
31+
32+
'&:hover': {
33+
opacity: 0.8,
34+
},
35+
36+
'&:disabled': {
37+
opacity: 0.5,
38+
},
39+
},
40+
icon: {
41+
width: '20px !important',
42+
height: 20,
43+
marginTop: 5,
44+
},
45+
input: {
46+
marginBottom: 0,
47+
height: 'auto',
48+
marginLeft: 10,
49+
flex: 1,
50+
border: 0,
51+
padding: [4, 10],
52+
borderRadius: theme.borderRadius,
53+
background: theme.inputBackground,
54+
color: theme.inputColor,
55+
},
56+
inputButton: {
57+
color: theme.colorText,
58+
},
59+
});
60+
61+
@injectSheet(styles) @observer
62+
class WebControls extends Component {
63+
static propTypes = {
64+
classes: PropTypes.object.isRequired,
65+
goHome: PropTypes.func.isRequired,
66+
canGoBack: PropTypes.bool.isRequired,
67+
goBack: PropTypes.func.isRequired,
68+
canGoForward: PropTypes.bool.isRequired,
69+
goForward: PropTypes.func.isRequired,
70+
reload: PropTypes.func.isRequired,
71+
url: PropTypes.string.isRequired,
72+
navigate: PropTypes.func.isRequired,
73+
}
74+
75+
static getDerivedStateFromProps(props, state) {
76+
const { url } = props;
77+
const { editUrl } = state;
78+
79+
if (!editUrl) {
80+
return {
81+
inputUrl: url,
82+
editUrl: state.editUrl,
83+
};
84+
}
85+
}
86+
87+
inputRef = React.createRef();
88+
89+
state = {
90+
inputUrl: '',
91+
editUrl: false,
92+
}
93+
94+
render() {
95+
const {
96+
classes,
97+
goHome,
98+
canGoBack,
99+
goBack,
100+
canGoForward,
101+
goForward,
102+
reload,
103+
url,
104+
navigate,
105+
} = this.props;
106+
107+
const {
108+
inputUrl,
109+
editUrl,
110+
} = this.state;
111+
112+
return (
113+
<div className={classes.root}>
114+
<button
115+
onClick={goHome}
116+
type="button"
117+
className={classes.button}
118+
>
119+
<Icon
120+
icon={mdiHomeOutline}
121+
className={classes.icon}
122+
/>
123+
</button>
124+
<button
125+
onClick={goBack}
126+
type="button"
127+
className={classes.button}
128+
disabled={!canGoBack}
129+
>
130+
<Icon
131+
icon={mdiArrowLeft}
132+
className={classes.icon}
133+
/>
134+
</button>
135+
<button
136+
onClick={goForward}
137+
type="button"
138+
className={classes.button}
139+
disabled={!canGoForward}
140+
>
141+
<Icon
142+
icon={mdiArrowRight}
143+
className={classes.icon}
144+
/>
145+
</button>
146+
<button
147+
onClick={reload}
148+
type="button"
149+
className={classes.button}
150+
>
151+
<Icon
152+
icon={mdiReload}
153+
className={classes.icon}
154+
/>
155+
</button>
156+
<input
157+
value={editUrl ? inputUrl : url}
158+
className={classes.input}
159+
onChange={event => this.setState({
160+
inputUrl: event.target.value,
161+
})}
162+
onFocus={(event) => {
163+
event.target.select();
164+
this.setState({
165+
editUrl: true,
166+
});
167+
}}
168+
onKeyDown={(event) => {
169+
if (event.key === 'Enter') {
170+
this.setState({
171+
editUrl: false,
172+
});
173+
navigate(inputUrl);
174+
this.inputRef.current.blur();
175+
} else if (event.key === 'Escape') {
176+
this.setState({
177+
editUrl: false,
178+
inputUrl: url,
179+
});
180+
event.target.blur();
181+
}
182+
}}
183+
ref={this.inputRef}
184+
/>
185+
</div>
186+
);
187+
}
188+
}
189+
190+
export default WebControls;
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import React, { Component } from 'react';
2+
import { observer, inject } from 'mobx-react';
3+
import PropTypes from 'prop-types';
4+
5+
import { autorun, observable } from 'mobx';
6+
import WebControls from '../components/WebControls';
7+
import ServicesStore from '../../../stores/ServicesStore';
8+
import Service from '../../../models/Service';
9+
10+
const URL_EVENTS = [
11+
'load-commit',
12+
// 'dom-ready',
13+
'will-navigate',
14+
'did-navigate',
15+
'did-navigate-in-page',
16+
];
17+
18+
@inject('stores', 'actions') @observer
19+
class WebControlsScreen extends Component {
20+
@observable url = '';
21+
22+
@observable canGoBack = false;
23+
24+
@observable canGoForward = false;
25+
26+
webview = null;
27+
28+
autorunDisposer = null;
29+
30+
componentDidMount() {
31+
const { service } = this.props;
32+
33+
this.autorunDisposer = autorun(() => {
34+
if (service.isAttached) {
35+
this.webview = service.webview;
36+
37+
URL_EVENTS.forEach((event) => {
38+
this.webview.addEventListener(event, (e) => {
39+
if (!e.isMainFrame) return;
40+
41+
this.url = e.url;
42+
this.canGoBack = this.webview.canGoBack();
43+
this.canGoForward = this.webview.canGoForward();
44+
});
45+
});
46+
}
47+
});
48+
}
49+
50+
componentWillUnmount() {
51+
this.autorunDisposer();
52+
}
53+
54+
goHome() {
55+
const { reloadActive } = this.props.actions.service;
56+
57+
if (!this.webview) return;
58+
59+
reloadActive();
60+
}
61+
62+
reload() {
63+
if (!this.webview) return;
64+
65+
this.webview.reload();
66+
}
67+
68+
goBack() {
69+
if (!this.webview) return;
70+
71+
this.webview.goBack();
72+
}
73+
74+
goForward() {
75+
if (!this.webview) return;
76+
77+
this.webview.goForward();
78+
}
79+
80+
navigate(newUrl) {
81+
if (!this.webview) return;
82+
83+
let url = newUrl;
84+
85+
try {
86+
url = new URL(url).toString();
87+
} catch (err) {
88+
// eslint-disable-next-line no-useless-escape
89+
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,})$/)) {
90+
url = `http://${url}`;
91+
} else {
92+
url = `https://www.google.com/search?query=${url}`;
93+
}
94+
}
95+
96+
this.webview.loadURL(url);
97+
this.url = url;
98+
}
99+
100+
render() {
101+
return (
102+
<WebControls
103+
goHome={() => this.goHome()}
104+
reload={() => this.reload()}
105+
canGoBack={this.canGoBack}
106+
goBack={() => this.goBack()}
107+
canGoForward={this.canGoForward}
108+
goForward={() => this.goForward()}
109+
navigate={url => this.navigate(url)}
110+
url={this.url}
111+
/>
112+
);
113+
}
114+
}
115+
116+
export default WebControlsScreen;
117+
118+
WebControlsScreen.wrappedComponent.propTypes = {
119+
service: PropTypes.instanceOf(Service).isRequired,
120+
stores: PropTypes.shape({
121+
services: PropTypes.instanceOf(ServicesStore).isRequired,
122+
}).isRequired,
123+
actions: PropTypes.shape({
124+
service: PropTypes.shape({
125+
reloadActive: PropTypes.func.isRequired,
126+
}).isRequired,
127+
}).isRequired,
128+
};

0 commit comments

Comments
 (0)