diff options
Diffstat (limited to 'src/components/downloadManager')
-rw-r--r-- | src/components/downloadManager/DownloadManagerDashboard.tsx | 288 | ||||
-rw-r--r-- | src/components/downloadManager/DownloadManagerLayout.tsx | 81 |
2 files changed, 369 insertions, 0 deletions
diff --git a/src/components/downloadManager/DownloadManagerDashboard.tsx b/src/components/downloadManager/DownloadManagerDashboard.tsx new file mode 100644 index 000000000..86facc476 --- /dev/null +++ b/src/components/downloadManager/DownloadManagerDashboard.tsx | |||
@@ -0,0 +1,288 @@ | |||
1 | import { Component } from 'react'; | ||
2 | import { observer } from 'mobx-react'; | ||
3 | import { IntlShape, defineMessages, injectIntl } from 'react-intl'; | ||
4 | import { shell } from 'electron'; | ||
5 | import prettyBytes from 'pretty-bytes'; | ||
6 | import { | ||
7 | Typography, | ||
8 | Card, | ||
9 | CardContent, | ||
10 | LinearProgress, | ||
11 | Box, | ||
12 | IconButton, | ||
13 | ListItemButton, | ||
14 | ListItemIcon, | ||
15 | ListItemText, | ||
16 | } from '@mui/material'; | ||
17 | import { mdiDownload } from '@mdi/js'; | ||
18 | import PlayArrowIcon from '@mui/icons-material/PlayArrow'; | ||
19 | import PauseIcon from '@mui/icons-material/Pause'; | ||
20 | import CancelIcon from '@mui/icons-material/Cancel'; | ||
21 | import FolderIcon from '@mui/icons-material/Folder'; | ||
22 | import DeleteIcon from '@mui/icons-material/Delete'; | ||
23 | import ClearAllIcon from '@mui/icons-material/ClearAll'; | ||
24 | import { round } from 'lodash'; | ||
25 | import { RealStores } from '../../stores'; | ||
26 | import { Actions } from '../../actions/lib/actions'; | ||
27 | import Icon from '../ui/icon'; | ||
28 | |||
29 | const messages = defineMessages({ | ||
30 | headline: { | ||
31 | id: 'downloadManager.headline', | ||
32 | defaultMessage: 'Download Manager', | ||
33 | }, | ||
34 | empty: { | ||
35 | id: 'downloadManager.empty', | ||
36 | defaultMessage: 'Your download list is empty.', | ||
37 | }, | ||
38 | }); | ||
39 | |||
40 | interface IProps { | ||
41 | intl: IntlShape; | ||
42 | stores?: RealStores; | ||
43 | actions?: Actions; | ||
44 | } | ||
45 | |||
46 | interface IState { | ||
47 | data: string; | ||
48 | } | ||
49 | |||
50 | // eslint-disable-next-line react/prefer-stateless-function | ||
51 | class DownloadManagerDashboard extends Component<IProps, IState> { | ||
52 | render() { | ||
53 | const { intl, stores, actions } = this.props; | ||
54 | |||
55 | const downloads = stores?.app.downloads ?? []; | ||
56 | |||
57 | return ( | ||
58 | <div className="settings__main"> | ||
59 | <div className="settings__header"> | ||
60 | <span className="settings__header-item"> | ||
61 | <Box | ||
62 | sx={{ | ||
63 | display: 'flex', | ||
64 | justifyContent: 'center', | ||
65 | alignItems: 'center', | ||
66 | }} | ||
67 | gap={1.5} | ||
68 | > | ||
69 | <Icon icon={mdiDownload} size={1.5} /> | ||
70 | {intl.formatMessage(messages.headline)} | ||
71 | <span className="badge badge--success">beta</span> | ||
72 | </Box> | ||
73 | </span> | ||
74 | </div> | ||
75 | <div className="settings__body"> | ||
76 | {downloads.length === 0 ? ( | ||
77 | <Box | ||
78 | sx={{ | ||
79 | display: 'flex', | ||
80 | flexDirection: 'column', | ||
81 | justifyContent: 'center', | ||
82 | alignItems: 'center', | ||
83 | }} | ||
84 | gap={4} | ||
85 | > | ||
86 | <Icon icon={mdiDownload} size={1.8} /> | ||
87 | <Typography variant="h4"> | ||
88 | {intl.formatMessage(messages.empty)} | ||
89 | </Typography> | ||
90 | </Box> | ||
91 | ) : ( | ||
92 | <Box | ||
93 | sx={{ | ||
94 | display: 'flex', | ||
95 | flexDirection: 'row', | ||
96 | justifyContent: 'flex-end', | ||
97 | height: 'fit-content', | ||
98 | }} | ||
99 | > | ||
100 | <Box | ||
101 | sx={{ | ||
102 | maxWidth: '176px', | ||
103 | }} | ||
104 | > | ||
105 | <ListItemButton | ||
106 | onClick={() => { | ||
107 | actions?.app.removeDownload(null); | ||
108 | }} | ||
109 | > | ||
110 | <ListItemIcon> | ||
111 | <ClearAllIcon /> | ||
112 | </ListItemIcon> | ||
113 | <ListItemText primary="Clear all completed" /> | ||
114 | </ListItemButton> | ||
115 | </Box> | ||
116 | </Box> | ||
117 | )} | ||
118 | {downloads.map(download => { | ||
119 | const { | ||
120 | totalBytes, | ||
121 | receivedBytes, | ||
122 | filename, | ||
123 | url, | ||
124 | savePath, | ||
125 | state, | ||
126 | id, | ||
127 | paused, | ||
128 | } = download; | ||
129 | |||
130 | const downloadPercentage = | ||
131 | receivedBytes !== undefined && totalBytes !== undefined | ||
132 | ? round((receivedBytes / totalBytes) * 100, 2) | ||
133 | : null; | ||
134 | |||
135 | const stateParse = | ||
136 | state === 'progressing' | ||
137 | ? paused === false || paused === undefined | ||
138 | ? null | ||
139 | : 'Paused' | ||
140 | : state === 'cancelled' | ||
141 | ? 'Cancelled' | ||
142 | : state === 'completed' | ||
143 | ? null | ||
144 | : 'Error'; | ||
145 | |||
146 | return ( | ||
147 | <Card | ||
148 | key={id} | ||
149 | style={{ | ||
150 | marginBottom: '16px', | ||
151 | height: 'fit-content', | ||
152 | display: 'flex', | ||
153 | }} | ||
154 | > | ||
155 | <Box | ||
156 | sx={{ display: 'flex', flexDirection: 'column', flexGrow: 1 }} | ||
157 | > | ||
158 | <CardContent> | ||
159 | <Box | ||
160 | sx={{ | ||
161 | display: 'flex', | ||
162 | }} | ||
163 | gap={2} | ||
164 | > | ||
165 | <button | ||
166 | type="button" | ||
167 | disabled={state !== 'completed'} | ||
168 | style={{ | ||
169 | pointerEvents: | ||
170 | state === 'completed' ? undefined : 'none', | ||
171 | }} | ||
172 | onClick={() => { | ||
173 | if (savePath) shell.openPath(savePath); | ||
174 | }} | ||
175 | > | ||
176 | <Typography | ||
177 | variant="h6" | ||
178 | color={state === 'completed' ? 'primary' : undefined} | ||
179 | sx={{ | ||
180 | textDecoration: | ||
181 | stateParse !== null && stateParse !== 'Paused' | ||
182 | ? 'line-through' | ||
183 | : state === 'completed' | ||
184 | ? 'underline' | ||
185 | : null, | ||
186 | }} | ||
187 | > | ||
188 | {filename} | ||
189 | </Typography> | ||
190 | </button> | ||
191 | <Typography | ||
192 | variant="h6" | ||
193 | color={stateParse === 'Paused' ? '#ed6c02' : undefined} | ||
194 | > | ||
195 | {stateParse !== null && stateParse !== 'Paused' | ||
196 | ? stateParse | ||
197 | : stateParse === 'Paused' | ||
198 | ? stateParse | ||
199 | : null} | ||
200 | </Typography> | ||
201 | </Box> | ||
202 | <Typography variant="body2">{url}</Typography> | ||
203 | <LinearProgress | ||
204 | variant="determinate" | ||
205 | value={downloadPercentage || 0} | ||
206 | style={{ marginTop: '8px', marginBottom: '8px' }} | ||
207 | /> | ||
208 | <Typography variant="body2"> | ||
209 | {`${ | ||
210 | downloadPercentage ? `${downloadPercentage}% - ` : '' | ||
211 | }${ | ||
212 | receivedBytes ? `${prettyBytes(receivedBytes)} of ` : '' | ||
213 | }${totalBytes ? prettyBytes(totalBytes) : ''}`} | ||
214 | </Typography> | ||
215 | </CardContent> | ||
216 | </Box> | ||
217 | |||
218 | <Box | ||
219 | sx={{ | ||
220 | display: 'flex', | ||
221 | flexDirection: 'column', | ||
222 | justifyContent: 'center', | ||
223 | alignItems: 'center', | ||
224 | padding: '8px', | ||
225 | }} | ||
226 | > | ||
227 | {state !== 'completed' && state !== 'cancelled' && ( | ||
228 | <IconButton | ||
229 | color="error" | ||
230 | size="small" | ||
231 | onClick={() => { | ||
232 | actions?.app.stopDownload(id); | ||
233 | }} | ||
234 | > | ||
235 | <CancelIcon /> | ||
236 | </IconButton> | ||
237 | )} | ||
238 | {state === 'progressing' && ( | ||
239 | <IconButton | ||
240 | color={ | ||
241 | paused === false || paused === undefined | ||
242 | ? 'warning' | ||
243 | : 'success' | ||
244 | } | ||
245 | size="small" | ||
246 | onClick={() => { | ||
247 | actions?.app.togglePauseDownload(id); | ||
248 | }} | ||
249 | > | ||
250 | {(paused === false || paused === undefined) && ( | ||
251 | <PauseIcon /> | ||
252 | )} | ||
253 | {paused && <PlayArrowIcon />} | ||
254 | </IconButton> | ||
255 | )} | ||
256 | {(state === 'cancelled' || state === 'completed') && ( | ||
257 | <IconButton | ||
258 | color="error" | ||
259 | onClick={() => { | ||
260 | actions?.app.removeDownload(id); | ||
261 | }} | ||
262 | size="small" | ||
263 | > | ||
264 | <DeleteIcon /> | ||
265 | </IconButton> | ||
266 | )} | ||
267 | {state !== 'cancelled' && ( | ||
268 | <IconButton | ||
269 | color="primary" | ||
270 | onClick={() => { | ||
271 | if (savePath) shell.showItemInFolder(savePath); | ||
272 | }} | ||
273 | size="small" | ||
274 | > | ||
275 | <FolderIcon /> | ||
276 | </IconButton> | ||
277 | )} | ||
278 | </Box> | ||
279 | </Card> | ||
280 | ); | ||
281 | })} | ||
282 | </div> | ||
283 | </div> | ||
284 | ); | ||
285 | } | ||
286 | } | ||
287 | |||
288 | export default injectIntl(observer(DownloadManagerDashboard)); | ||
diff --git a/src/components/downloadManager/DownloadManagerLayout.tsx b/src/components/downloadManager/DownloadManagerLayout.tsx new file mode 100644 index 000000000..1e018cfb8 --- /dev/null +++ b/src/components/downloadManager/DownloadManagerLayout.tsx | |||
@@ -0,0 +1,81 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import { inject, observer } from 'mobx-react'; | ||
3 | import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; | ||
4 | |||
5 | import { mdiClose } from '@mdi/js'; | ||
6 | import { Outlet } from 'react-router-dom'; | ||
7 | import { Actions } from '../../actions/lib/actions'; | ||
8 | import { isEscKeyPress } from '../../jsUtils'; | ||
9 | import Appear from '../ui/effects/Appear'; | ||
10 | import ErrorBoundary from '../util/ErrorBoundary'; | ||
11 | import Icon from '../ui/icon'; | ||
12 | |||
13 | const messages = defineMessages({ | ||
14 | closeSettings: { | ||
15 | id: 'settings.app.closeSettings', | ||
16 | defaultMessage: 'Close settings', | ||
17 | }, | ||
18 | }); | ||
19 | |||
20 | interface IProps extends WrappedComponentProps { | ||
21 | actions?: Actions; | ||
22 | // eslint-disable-next-line react/no-unused-prop-types | ||
23 | children?: React.ReactNode; | ||
24 | } | ||
25 | |||
26 | @inject('stores', 'actions') | ||
27 | @observer | ||
28 | class DownloadManagerLayout extends Component<IProps> { | ||
29 | componentDidMount() { | ||
30 | document.addEventListener('keydown', this.handleKeyDown.bind(this), false); | ||
31 | } | ||
32 | |||
33 | componentWillUnmount() { | ||
34 | document.removeEventListener( | ||
35 | 'keydown', | ||
36 | // eslint-disable-next-line unicorn/no-invalid-remove-event-listener | ||
37 | this.handleKeyDown.bind(this), | ||
38 | false, | ||
39 | ); | ||
40 | } | ||
41 | |||
42 | handleKeyDown(e) { | ||
43 | if (isEscKeyPress(e.keyCode)) { | ||
44 | this.props.actions!.ui.closeSettings(); | ||
45 | } | ||
46 | } | ||
47 | |||
48 | render() { | ||
49 | const { closeSettings } = this.props.actions!.ui; | ||
50 | |||
51 | const { intl } = this.props; | ||
52 | |||
53 | return ( | ||
54 | <Appear transitionName="fadeIn-fast"> | ||
55 | <div className="settings-wrapper"> | ||
56 | <ErrorBoundary> | ||
57 | <button | ||
58 | type="button" | ||
59 | className="settings-wrapper__action" | ||
60 | onClick={closeSettings} | ||
61 | aria-label={intl.formatMessage(messages.closeSettings)} | ||
62 | /> | ||
63 | <div className="settings franz-form"> | ||
64 | <Outlet /> | ||
65 | <button | ||
66 | type="button" | ||
67 | className="settings__close" | ||
68 | onClick={closeSettings} | ||
69 | aria-label={intl.formatMessage(messages.closeSettings)} | ||
70 | > | ||
71 | <Icon icon={mdiClose} size={1.35} /> | ||
72 | </button> | ||
73 | </div> | ||
74 | </ErrorBoundary> | ||
75 | </div> | ||
76 | </Appear> | ||
77 | ); | ||
78 | } | ||
79 | } | ||
80 | |||
81 | export default injectIntl<'intl', IProps>(DownloadManagerLayout); | ||