Skip to content

Commit 4811e0b

Browse files
committed
Added share functionality
1 parent 976647d commit 4811e0b

File tree

9 files changed

+287
-27
lines changed

9 files changed

+287
-27
lines changed

package-lock.json

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@
44
"private": true,
55
"dependencies": {
66
"@material-ui/core": "^4.11.2",
7+
"@material-ui/icons": "^4.11.2",
78
"@material-ui/styles": "^4.11.2",
89
"@testing-library/jest-dom": "^5.11.4",
910
"@testing-library/react": "^11.1.0",
1011
"@testing-library/user-event": "^12.1.10",
1112
"@types/jest": "^26.0.19",
13+
"@types/lodash": "^4.14.165",
1214
"@types/node": "^14.14.14",
1315
"@types/react": "^17.0.0",
1416
"@types/react-dom": "^17.0.0",
17+
"lodash": "^4.17.20",
1518
"magic-snowflakes": "^4.1.6",
1619
"react": "^17.0.1",
1720
"react-dom": "^17.0.1",
Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,43 @@
1-
.Player {
1+
.PlayerWrapper {
22
position: absolute;
33
bottom: 0;
44
width: 100%;
5+
box-sizing: border-box;
6+
}
7+
8+
.Player {
59
font-size: 32px;
6-
font-weight: 800;
710
padding: 24px;
11+
font-weight: 800;
812
z-index: 10000;
913
box-sizing: border-box;
14+
display: flex;
15+
flex-direction: column;
16+
max-width: 768px;
17+
margin: 0 auto;
18+
}
19+
20+
.Player__Title {
21+
display: flex;
22+
justify-content: space-between;
23+
font-size: 24px;
24+
font-weight: 300;
25+
margin-bottom: 16px;
26+
}
27+
28+
.Player__TrackTitle {
29+
cursor: pointer;
30+
}
31+
32+
.Player__Icon {
33+
cursor: pointer;
34+
}
35+
36+
.Player__Icon + .Player__Icon {
37+
margin-left: 8px;
38+
}
39+
40+
.Player__Controls {
1041
display: flex;
1142
justify-content: space-between;
1243
align-items: center;
@@ -17,11 +48,12 @@
1748
background: transparent;
1849
box-sizing: border-box;
1950
width: 0;
20-
height: 48px;
51+
height: 28px;
2152
transition: 100ms all ease;
2253
cursor: pointer;
2354
border-style: solid;
24-
border-width: 24px 0 24px 40px;
55+
border-width: 14px 0 14px 28px;
56+
text-align: center;
2557
}
2658

2759
.Player__Slider {
@@ -31,9 +63,26 @@
3163

3264
.Player__Control_Pause {
3365
border-style: double;
34-
border-width: 0px 0 0px 48px;
66+
border-width: 0px 0 0px 28px;
3567
}
3668

3769
.Player__Rate {
70+
user-select: none;
3871
cursor: pointer;
72+
text-align: center;
73+
font-size: 24px;
74+
}
75+
76+
@media (min-width: 768px) {
77+
.PlayerWrapper {
78+
padding: 24px 48px;
79+
}
80+
81+
.Player {
82+
border-radius: 12px;
83+
}
84+
85+
.Player__Rate {
86+
font-size: inherit;
87+
}
3988
}

src/components/Player/Player.tsx

Lines changed: 127 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
1-
import { useCallback, useContext, useMemo, ChangeEvent } from 'react';
1+
import { useCallback, useContext, useMemo, useState, ChangeEvent, useRef, useEffect } from 'react';
22
import { Slider } from '@material-ui/core';
33
import { createMuiTheme } from '@material-ui/core';
44
import { ThemeProvider } from '@material-ui/styles';
5+
import ShareIcon from '@material-ui/icons/Share';
6+
import CloseIcon from '@material-ui/icons/Close';
7+
import Button from '@material-ui/core/Button';
8+
import Dialog from '@material-ui/core/Dialog';
9+
import DialogActions from '@material-ui/core/DialogActions';
10+
import DialogContent from '@material-ui/core/DialogContent';
11+
import Checkbox from '@material-ui/core/Checkbox';
12+
import Input from '@material-ui/core/Input';
13+
import FormControlLabel from '@material-ui/core/FormControlLabel';
14+
import tap from 'lodash/tap';
515

616
import { default as Styles } from 'components/Player/Player.module.css';
717
import { PlayerContext } from 'components/PlayerContext/PlayerContext';
18+
import { useQueryParam } from 'hocs/useQueryParam';
819

920
export function Player() {
1021
const {
@@ -16,11 +27,54 @@ export function Player() {
1627
currentEndTime,
1728
increasePlaybackRate,
1829
changeCurrentTime,
30+
closeTrack,
31+
tracks
1932
} = useContext(PlayerContext);
33+
const [locationTrack, setLocationTrack] = useQueryParam('track');
34+
const shareInputRef = useRef<HTMLInputElement>(null)
35+
const [shareUrl, changeShareUrl] = useState<string>(() => {
36+
return tap(new URL(window.location.toString()), url => {
37+
url.searchParams.set('track', tracks.findIndex(track => track.order === currentTrack?.order).toString());
38+
url.searchParams.set('currentTime', '');
39+
}).toString();
40+
});
41+
const [isShareWithTime, setIsShareWithTime] = useState(false);
42+
const [isShareDialogOpen, setIsShareDialogOpen] = useState(false);
2043
const onClickControl = useCallback(() => {
2144
toggle();
2245
}, [toggle, isPlayed]);
2346

47+
const toggleShareDialog = useCallback(() => {
48+
setIsShareDialogOpen(state => !state);
49+
}, []);
50+
51+
const toggleShareWithTime = useCallback(() => {
52+
setIsShareWithTime(state => !state);
53+
}, [isShareWithTime]);
54+
55+
useEffect(() => {
56+
changeShareUrl(
57+
tap(new URL(window.location.toString()), url => {
58+
url.searchParams.set('track', tracks.findIndex(track => track.order === currentTrack?.order).toString());
59+
url.searchParams.set('currentTime', '');
60+
}).toString()
61+
);
62+
}, [currentTrack]);
63+
64+
useEffect(() => {
65+
const url = new URL(window.location.toString());
66+
if (isShareWithTime) {
67+
url.searchParams.set('track', tracks.findIndex(track => track.order === currentTrack?.order).toString());
68+
url.searchParams.set('currentTime', currentTime.toString());
69+
} else {
70+
url.searchParams.set('track', tracks.findIndex(track => track.order === currentTrack?.order).toString());
71+
url.searchParams.set('currentTime', '');
72+
}
73+
74+
changeShareUrl(url.toString());
75+
76+
}, [isShareWithTime])
77+
2478
const muiTheme = useMemo(() => createMuiTheme({
2579
overrides: {
2680
MuiSlider: {
@@ -39,24 +93,84 @@ export function Player() {
3993
changeCurrentTime(newTime);
4094
}, []);
4195

96+
const onCopy = useCallback(() => {
97+
if (!shareInputRef.current) {
98+
return;
99+
}
100+
101+
shareInputRef.current.focus();
102+
shareInputRef.current.setSelectionRange(0, shareInputRef.current.value.length);
103+
document.execCommand('copy');
104+
setTimeout(() => toggleShareDialog(), 0);
105+
}, [])
106+
107+
const onShareFocus = useCallback(() => {
108+
if (!shareInputRef.current) {
109+
return;
110+
}
111+
112+
shareInputRef.current.setSelectionRange(0, shareInputRef.current.value.length);
113+
}, []);
114+
115+
const onClickTitle = useCallback(() => {
116+
setLocationTrack(tracks.findIndex(track => track.order === currentTrack?.order).toString());
117+
}, []);
118+
42119
if (!currentTrack) {
43120
return null;
44121
}
45122

46-
const { backgroundColor, playColor, length } = currentTrack;
123+
const { backgroundColor, playColor } = currentTrack;
47124

48125

49126
return (
50-
<div className={Styles.Player} style={{ backgroundColor, color: playColor }}>
51-
<div
52-
className={[Styles.Player__Control, isPlayed && Styles.Player__Control_Pause].join(' ')}
53-
style={{ borderColor: `transparent transparent transparent ${playColor}`}}
54-
onClick={onClickControl}
55-
/>
56-
<ThemeProvider theme={muiTheme} >
57-
<Slider className={Styles.Player__Slider} min={0} max={currentEndTime} onChange={onChange} value={currentTime} />
58-
</ThemeProvider>
59-
<div className={Styles.Player__Rate} onClick={increasePlaybackRate}>x{playbackRate}</div>
60-
</div>
127+
<>
128+
<div className={Styles.PlayerWrapper}>
129+
<div className={Styles.Player} style={{ backgroundColor, color: playColor }}>
130+
<div className={Styles.Player__Title}>
131+
<span className={Styles.Player__TrackTitle} onClick={onClickTitle}>
132+
{currentTrack.order} {currentTrack.greeting}
133+
</span>
134+
<div>
135+
<span className={Styles.Player__Icon} onClick={toggleShareDialog}><ShareIcon /></span>
136+
<span className={Styles.Player__Icon} onClick={closeTrack}><CloseIcon /></span>
137+
</div>
138+
</div>
139+
<div className={Styles.Player__Controls}>
140+
<div
141+
className={[Styles.Player__Control, isPlayed && Styles.Player__Control_Pause].join(' ')}
142+
style={{ borderColor: `transparent transparent transparent ${playColor}`}}
143+
onClick={onClickControl}
144+
/>
145+
<ThemeProvider theme={muiTheme} >
146+
<Slider className={Styles.Player__Slider} min={0} max={currentEndTime} onChange={onChange} value={currentTime} />
147+
</ThemeProvider>
148+
<div className={Styles.Player__Rate} onClick={increasePlaybackRate}>x{playbackRate}</div>
149+
</div>
150+
</div>
151+
</div>
152+
<Dialog
153+
open={isShareDialogOpen}
154+
onClose={toggleShareDialog}
155+
aria-labelledby="alert-dialog-title"
156+
aria-describedby="alert-dialog-description"
157+
>
158+
<DialogContent>
159+
<Input value={shareUrl} fullWidth onClick={onShareFocus} inputRef={shareInputRef} />
160+
<FormControlLabel
161+
control={<Checkbox checked={isShareWithTime} onChange={toggleShareWithTime} />}
162+
label="С текущего момента"
163+
/>
164+
</DialogContent>
165+
<DialogActions>
166+
<Button onClick={toggleShareDialog} color='secondary'>
167+
Закрыть
168+
</Button>
169+
<Button onClick={onCopy} color='primary' autoFocus>
170+
Копировать
171+
</Button>
172+
</DialogActions>
173+
</Dialog>
174+
</>
61175
);
62176
}

0 commit comments

Comments
 (0)