diff options
Diffstat (limited to 'src/features/webControls/components/WebControls.tsx')
-rw-r--r-- | src/features/webControls/components/WebControls.tsx | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/src/features/webControls/components/WebControls.tsx b/src/features/webControls/components/WebControls.tsx new file mode 100644 index 000000000..ebcd93c9e --- /dev/null +++ b/src/features/webControls/components/WebControls.tsx | |||
@@ -0,0 +1,242 @@ | |||
1 | import { createRef, Component, ReactElement, RefObject } from 'react'; | ||
2 | import { observer } from 'mobx-react'; | ||
3 | import withStyles, { WithStylesProps } from 'react-jss'; | ||
4 | import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; | ||
5 | import { | ||
6 | mdiReload, | ||
7 | mdiArrowRight, | ||
8 | mdiArrowLeft, | ||
9 | mdiHomeOutline, | ||
10 | mdiEarth, | ||
11 | } from '@mdi/js'; | ||
12 | import Icon from '../../../components/ui/icon'; | ||
13 | |||
14 | const messages = defineMessages({ | ||
15 | goHome: { | ||
16 | id: 'webControls.goHome', | ||
17 | defaultMessage: 'Home', | ||
18 | }, | ||
19 | openInBrowser: { | ||
20 | id: 'webControls.openInBrowser', | ||
21 | defaultMessage: 'Open in Browser', | ||
22 | }, | ||
23 | back: { | ||
24 | id: 'webControls.back', | ||
25 | defaultMessage: 'Back', | ||
26 | }, | ||
27 | forward: { | ||
28 | id: 'webControls.forward', | ||
29 | defaultMessage: 'Forward', | ||
30 | }, | ||
31 | reload: { | ||
32 | id: 'webControls.reload', | ||
33 | defaultMessage: 'Reload', | ||
34 | }, | ||
35 | }); | ||
36 | |||
37 | const buttonTransition = | ||
38 | window && window.matchMedia('(prefers-reduced-motion: no-preference)') | ||
39 | ? 'opacity 0.25s' | ||
40 | : 'none'; | ||
41 | |||
42 | const styles = theme => ({ | ||
43 | root: { | ||
44 | background: theme.colorBackground, | ||
45 | position: 'relative', | ||
46 | borderLeft: [1, 'solid', theme.todos.todosLayer.borderLeftColor], | ||
47 | zIndex: 300, | ||
48 | height: 50, | ||
49 | display: 'flex', | ||
50 | flexDirection: 'row', | ||
51 | alignItems: 'center', | ||
52 | padding: [0, 10], | ||
53 | |||
54 | '& + div': { | ||
55 | height: 'calc(100% - 50px)', | ||
56 | }, | ||
57 | }, | ||
58 | button: { | ||
59 | width: 30, | ||
60 | height: 50, | ||
61 | transition: buttonTransition, | ||
62 | |||
63 | '&:hover': { | ||
64 | opacity: 0.8, | ||
65 | }, | ||
66 | |||
67 | '&:disabled': { | ||
68 | opacity: 0.5, | ||
69 | }, | ||
70 | }, | ||
71 | icon: { | ||
72 | width: '20px !important', | ||
73 | height: 20, | ||
74 | marginTop: 5, | ||
75 | color: theme.colorText, | ||
76 | }, | ||
77 | input: { | ||
78 | marginBottom: 0, | ||
79 | height: 'auto', | ||
80 | margin: [0, 10], | ||
81 | flex: 1, | ||
82 | border: 0, | ||
83 | padding: [4, 10], | ||
84 | borderRadius: theme.borderRadius, | ||
85 | background: theme.inputBackground, | ||
86 | color: theme.inputColor, | ||
87 | }, | ||
88 | inputButton: { | ||
89 | color: theme.colorText, | ||
90 | }, | ||
91 | }); | ||
92 | |||
93 | interface IProps extends WithStylesProps<typeof styles>, WrappedComponentProps { | ||
94 | goHome: () => void; | ||
95 | canGoBack: boolean; | ||
96 | goBack: () => void; | ||
97 | canGoForward: boolean; | ||
98 | goForward: () => void; | ||
99 | reload: () => void; | ||
100 | openInBrowser: () => void; | ||
101 | url: string; | ||
102 | navigate: (url: string) => void; | ||
103 | } | ||
104 | |||
105 | interface IState { | ||
106 | inputUrl: string; | ||
107 | editUrl: boolean; | ||
108 | } | ||
109 | |||
110 | @observer | ||
111 | class WebControls extends Component<IProps, IState> { | ||
112 | inputRef: RefObject<HTMLInputElement> = createRef(); | ||
113 | |||
114 | static getDerivedStateFromProps(props, state): IState | null { | ||
115 | const { url: inputUrl } = props; | ||
116 | const { editUrl } = state; | ||
117 | |||
118 | return !editUrl ? { inputUrl, editUrl } : null; | ||
119 | } | ||
120 | |||
121 | constructor(props: IProps) { | ||
122 | super(props); | ||
123 | |||
124 | this.state = { | ||
125 | inputUrl: '', | ||
126 | editUrl: false, | ||
127 | }; | ||
128 | } | ||
129 | |||
130 | render(): ReactElement { | ||
131 | const { | ||
132 | classes, | ||
133 | goHome, | ||
134 | canGoBack, | ||
135 | goBack, | ||
136 | canGoForward, | ||
137 | goForward, | ||
138 | reload, | ||
139 | openInBrowser, | ||
140 | url, | ||
141 | navigate, | ||
142 | intl, | ||
143 | } = this.props; | ||
144 | |||
145 | const { inputUrl, editUrl } = this.state; | ||
146 | |||
147 | return ( | ||
148 | <div className={classes.root}> | ||
149 | <button | ||
150 | onClick={goHome} | ||
151 | type="button" | ||
152 | className={classes.button} | ||
153 | data-tip={intl.formatMessage(messages.goHome)} | ||
154 | data-place="bottom" | ||
155 | > | ||
156 | <Icon icon={mdiHomeOutline} className={classes.icon} /> | ||
157 | </button> | ||
158 | <button | ||
159 | onClick={goBack} | ||
160 | type="button" | ||
161 | className={classes.button} | ||
162 | disabled={!canGoBack} | ||
163 | data-tip={intl.formatMessage(messages.back)} | ||
164 | data-place="bottom" | ||
165 | > | ||
166 | <Icon icon={mdiArrowLeft} className={classes.icon} /> | ||
167 | </button> | ||
168 | <button | ||
169 | onClick={goForward} | ||
170 | type="button" | ||
171 | className={classes.button} | ||
172 | disabled={!canGoForward} | ||
173 | data-tip={intl.formatMessage(messages.forward)} | ||
174 | data-place="bottom" | ||
175 | > | ||
176 | <Icon icon={mdiArrowRight} className={classes.icon} /> | ||
177 | </button> | ||
178 | <button | ||
179 | onClick={reload} | ||
180 | type="button" | ||
181 | className={classes.button} | ||
182 | data-tip={intl.formatMessage(messages.reload)} | ||
183 | data-place="bottom" | ||
184 | > | ||
185 | <Icon icon={mdiReload} className={classes.icon} /> | ||
186 | </button> | ||
187 | <input | ||
188 | value={editUrl ? inputUrl : url} | ||
189 | className={classes.input} | ||
190 | onChange={event => | ||
191 | this.setState({ | ||
192 | inputUrl: event.target.value, | ||
193 | }) | ||
194 | } | ||
195 | onFocus={event => { | ||
196 | event.target.select(); | ||
197 | this.setState({ | ||
198 | editUrl: true, | ||
199 | }); | ||
200 | }} | ||
201 | onBlur={event => { | ||
202 | event.target.blur(); | ||
203 | this.setState({ | ||
204 | editUrl: false, | ||
205 | }); | ||
206 | }} | ||
207 | onKeyDown={event => { | ||
208 | if (event.key === 'Enter') { | ||
209 | this.setState({ | ||
210 | editUrl: false, | ||
211 | }); | ||
212 | navigate(inputUrl); | ||
213 | } else if (event.key === 'Escape') { | ||
214 | this.setState({ | ||
215 | editUrl: false, | ||
216 | inputUrl: url, | ||
217 | }); | ||
218 | } | ||
219 | |||
220 | if (this.inputRef && this.inputRef.current) { | ||
221 | this.inputRef.current.blur(); | ||
222 | } | ||
223 | }} | ||
224 | ref={this.inputRef} | ||
225 | /> | ||
226 | <button | ||
227 | onClick={openInBrowser} | ||
228 | type="button" | ||
229 | className={classes.button} | ||
230 | data-tip={intl.formatMessage(messages.openInBrowser)} | ||
231 | data-place="bottom" | ||
232 | > | ||
233 | <Icon icon={mdiEarth} className={classes.icon} /> | ||
234 | </button> | ||
235 | </div> | ||
236 | ); | ||
237 | } | ||
238 | } | ||
239 | |||
240 | export default injectIntl( | ||
241 | withStyles(styles, { injectTheme: true })(WebControls), | ||
242 | ); | ||