fix(#2639): bug fixing for match page mobile

keep-around/31934c87741a585cc6b3c9552489ffa62de670b6
Rakov Roman 3 years ago committed by Gitea
parent d5c7379b1b
commit 7d3185dc1d
  1. 28
      public/images/rewind-left.svg
  2. 28
      public/images/rewind-right.svg
  3. 12
      src/features/HeaderFilters/components/DateFilter/styled.tsx
  4. 2
      src/features/MatchCard/helpers/index.tsx
  5. 6
      src/features/MatchPage/components/FinishedMatch/hooks/index.tsx
  6. 6
      src/features/MatchPage/components/FinishedMatch/hooks/useEpisodes.tsx
  7. 30
      src/features/MatchPage/components/FinishedMatch/index.tsx
  8. 26
      src/features/MatchPage/components/LiveMatch/index.tsx
  9. 60
      src/features/MatchPage/components/MatchDescription/index.tsx
  10. 58
      src/features/MatchPage/components/MatchDescription/styled.tsx
  11. 34
      src/features/MatchPage/components/MatchProfileCardMobile/components/MatchDate/index.tsx
  12. 10
      src/features/MatchPage/components/MatchProfileCardMobile/components/MatchDate/styled.tsx
  13. 73
      src/features/MatchPage/components/MatchProfileCardMobile/components/TeamsDetails/index.tsx
  14. 70
      src/features/MatchPage/components/MatchProfileCardMobile/components/TeamsDetails/styled.tsx
  15. 28
      src/features/MatchPage/components/MatchProfileCardMobile/index.tsx
  16. 19
      src/features/MatchPage/components/MatchProfileCardMobile/styled.tsx
  17. 15
      src/features/MatchPage/hooks/index.tsx
  18. 28
      src/features/MatchPage/hooks/useEvents.tsx
  19. 25
      src/features/MatchPage/hooks/useEventsLexics.tsx
  20. 64
      src/features/MatchPage/hooks/useMatchData.tsx
  21. 76
      src/features/MatchPage/hooks/useMatchProfile.tsx
  22. 20
      src/features/MatchPage/index.tsx
  23. 5
      src/features/MatchPage/store/hooks/index.tsx
  24. 9
      src/features/MatchPage/store/hooks/useMatchPlaylists.tsx
  25. 2
      src/features/MatchPage/store/hooks/useTournamentData.tsx
  26. 6
      src/features/MatchPage/styled.tsx
  27. 10
      src/features/MatchSidePlaylists/components/TabVideo/components/VideoDate/styled.tsx
  28. 12
      src/features/MatchSidePlaylists/components/TabVideo/styled.tsx
  29. 17
      src/features/MatchSidePlaylists/hooks.tsx
  30. 59
      src/features/MatchSidePlaylists/index.tsx
  31. 41
      src/features/MatchSidePlaylists/styled.tsx
  32. 4
      src/features/MultiSourcePlayer/components/ProgressBar/index.tsx
  33. 4
      src/features/MultiSourcePlayer/config.tsx
  34. 2
      src/features/MultiSourcePlayer/hooks/index.tsx
  35. 31
      src/features/MultiSourcePlayer/index.tsx
  36. 1
      src/features/SportsFilter/components/SelectSport/styled.tsx
  37. 2
      src/features/SportsFilter/components/SelectSportPopup/styled.tsx
  38. 16
      src/features/StreamPlayer/components/Chapters/styled.tsx
  39. 3
      src/features/StreamPlayer/components/Controls/Components/ControlsMobile/styled.tsx
  40. 46
      src/features/StreamPlayer/components/Controls/index.tsx
  41. 8
      src/features/StreamPlayer/components/ProgressBar/index.tsx
  42. 27
      src/features/StreamPlayer/components/ProgressBar/styled.tsx
  43. 67
      src/features/StreamPlayer/components/RewindMobile/index.tsx
  44. 41
      src/features/StreamPlayer/components/RewindMobile/styled.tsx
  45. 4
      src/features/StreamPlayer/components/YoutubePlayer/index.tsx
  46. 4
      src/features/StreamPlayer/config.tsx
  47. 7
      src/features/StreamPlayer/hooks/index.tsx
  48. 56
      src/features/StreamPlayer/hooks/useControlsVisibility.tsx
  49. 163
      src/features/StreamPlayer/index.tsx
  50. 21
      src/features/StreamPlayer/styled.tsx
  51. 7
      src/features/TournamentList/components/TournamentMobile/styled.tsx
  52. 24
      src/features/TournamentSubtitle/index.tsx
  53. 30
      src/features/TournamentSubtitle/styled.tsx

@ -0,0 +1,28 @@
<svg width="46" height="33" viewBox="0 0 46 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_31723_269496)">
<path d="M21.8175 14.3968C21.5834 14.5488 21.5834 14.8915 21.8175 15.0436L39.2343 26.3561C39.4908 26.5227 39.8299 26.3386 39.8299 26.0327V3.40766C39.8299 3.10174 39.4908 2.91762 39.2343 3.08425L21.8175 14.3968Z" fill="white"/>
</g>
<g filter="url(#filter1_d_31723_269496)">
<path d="M3.56651 14.3967C3.33241 14.5487 3.33241 14.8914 3.56651 15.0435L20.9833 26.356C21.2398 26.5226 21.579 26.3385 21.579 26.0326V3.40753C21.579 3.10162 21.2398 2.9175 20.9833 3.08413L3.56651 14.3967Z" fill="white"/>
</g>
<defs>
<filter id="filter0_d_31723_269496" x="18.5568" y="3.02136" width="24.3581" height="29.5677" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="3.08506"/>
<feGaussianBlur stdDeviation="1.54253"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_31723_269496"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_31723_269496" result="shape"/>
</filter>
<filter id="filter1_d_31723_269496" x="0.305808" y="3.02124" width="24.3581" height="29.5677" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="3.08506"/>
<feGaussianBlur stdDeviation="1.54253"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_31723_269496"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_31723_269496" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@ -0,0 +1,28 @@
<svg width="46" height="33" viewBox="0 0 46 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_31723_268653)">
<path d="M24.1825 14.3968C24.4166 14.5488 24.4166 14.8915 24.1825 15.0436L6.76574 26.3561C6.50919 26.5227 6.17005 26.3386 6.17005 26.0327V3.40766C6.17005 3.10174 6.50919 2.91762 6.76574 3.08425L24.1825 14.3968Z" fill="white"/>
</g>
<g filter="url(#filter1_d_31723_268653)">
<path d="M42.4335 14.3967C42.6676 14.5487 42.6676 14.8914 42.4335 15.0435L25.0167 26.356C24.7602 26.5226 24.421 26.3385 24.421 26.0326V3.40753C24.421 3.10162 24.7602 2.9175 25.0167 3.08413L42.4335 14.3967Z" fill="white"/>
</g>
<defs>
<filter id="filter0_d_31723_268653" x="3.08511" y="3.02136" width="24.3581" height="29.5677" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="3.08506"/>
<feGaussianBlur stdDeviation="1.54253"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_31723_268653"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_31723_268653" result="shape"/>
</filter>
<filter id="filter1_d_31723_268653" x="21.3361" y="3.02124" width="24.3581" height="29.5677" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="3.08506"/>
<feGaussianBlur stdDeviation="1.54253"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_31723_268653"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_31723_268653" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@ -84,6 +84,7 @@ export const DateButton = styled(BaseButton) <DateButtonProps>`
position: static; position: static;
width: 9px; width: 9px;
height: 9px; height: 9px;
margin-left: 10px;
` `
: ''}; : ''};
@ -96,17 +97,6 @@ export const DateButton = styled(BaseButton) <DateButtonProps>`
? 'opacity: 1;' ? 'opacity: 1;'
: '' : ''
)} )}
${isMobileDevice
? css`
/* right: 14px;
width: 16px;
height: 16px; */
@media screen and (orientation: landscape){
/* right: 20px; */
}
`
: ''};
` `
export const WeekDaysWrapper = styled.div` export const WeekDaysWrapper = styled.div`

@ -12,6 +12,6 @@ export const getPrepareTimeFormat = ({
time, time,
}: prepareTimeFormat) => ( }: prepareTimeFormat) => (
isNeedFormatTimeChanged isNeedFormatTimeChanged
? format(date, 'h:mm aaa') ? format(date, 'h:mm a')
: time : time
) )

@ -4,19 +4,19 @@ import { useToggle } from 'hooks/useToggle'
import type { Settings } from 'features/MatchPopup' import type { Settings } from 'features/MatchPopup'
import { useMatchPopupStore } from 'features/MatchPopup' import { useMatchPopupStore } from 'features/MatchPopup'
import { useMatchPageStore } from 'features/MatchPage/store'
import { usePlayerLogger } from './usePlayerLogger' import { usePlayerLogger } from './usePlayerLogger'
import { useEpisodes } from './useEpisodes' import { useEpisodes } from './useEpisodes'
import { useChapters } from './useChapters' import { useChapters } from './useChapters'
export const useFinishedMatch = () => { export const useFinishedMatch = () => {
const { setChapters, setSettings } = useMatchPopupStore()
const { const {
handlePlaylistClick, handlePlaylistClick,
matchPlaylists, matchPlaylists,
selectedPlaylist, selectedPlaylist,
setChapters, } = useMatchPageStore()
setSettings,
} = useMatchPopupStore()
const { const {
close: closeSettingsPopup, close: closeSettingsPopup,
isOpen: isSettingsPopupOpen, isOpen: isSettingsPopupOpen,

@ -12,6 +12,8 @@ import { getPlayerPlaylists } from 'requests'
import { usePageParams } from 'hooks/usePageParams' import { usePageParams } from 'hooks/usePageParams'
import { PlaylistOption, PlaylistTypes } from 'features/MatchPage/types' import { PlaylistOption, PlaylistTypes } from 'features/MatchPage/types'
import { useMatchPageStore } from 'features/MatchPage/store'
import { import {
defaultSettings, defaultSettings,
Settings, Settings,
@ -19,12 +21,12 @@ import {
} from 'features/MatchPopup' } from 'features/MatchPopup'
export const useEpisodes = () => { export const useEpisodes = () => {
const { settings } = useMatchPopupStore()
const { const {
handlePlaylistClick, handlePlaylistClick,
matchPlaylists: playlists, matchPlaylists: playlists,
selectedPlaylist, selectedPlaylist,
settings, } = useMatchPageStore()
} = useMatchPopupStore()
const [episodes, setEpisodes] = useState<Episodes>([]) const [episodes, setEpisodes] = useState<Episodes>([])
const { profileId: matchId, sportType } = usePageParams() const { profileId: matchId, sportType } = usePageParams()

@ -2,11 +2,6 @@ import { Fragment } from 'react'
import isEmpty from 'lodash/isEmpty' import isEmpty from 'lodash/isEmpty'
import { isMobileDevice } from 'config/userAgent'
import type { Events } from 'requests/getMatchEvents'
import type { MatchInfo } from 'requests/getMatchInfo'
import { MatchSidePlaylists } from 'features/MatchSidePlaylists' import { MatchSidePlaylists } from 'features/MatchSidePlaylists'
import { MultiSourcePlayer } from 'features/MultiSourcePlayer' import { MultiSourcePlayer } from 'features/MultiSourcePlayer'
@ -15,28 +10,17 @@ import { SettingsPopup } from '../SettingsPopup'
import { useFinishedMatch } from './hooks' import { useFinishedMatch } from './hooks'
import { Container } from '../../styled' import { Container } from '../../styled'
import { Modal } from './styled' import { Modal } from './styled'
import { TournamentData } from '../../types'
import { MatchDescription } from '../MatchDescription' import { MatchDescription } from '../MatchDescription'
import { MatchProfileCardMobile } from '../MatchProfileCardMobile' import { useMatchPageStore } from '../../store'
type Props = { export const FinishedMatch = () => {
events: Events, const { profile } = useMatchPageStore()
profile: MatchInfo,
tournamentData: TournamentData,
}
export const FinishedMatch = ({
events,
profile,
tournamentData,
}: Props) => {
const { const {
chapters, chapters,
closeSettingsPopup, closeSettingsPopup,
isSettingsPopupOpen, isSettingsPopupOpen,
onPlayingChange, onPlayingChange,
onPlaylistSelect, onPlaylistSelect,
playlists,
selectedPlaylist, selectedPlaylist,
setEpisodesSettings, setEpisodesSettings,
} = useFinishedMatch() } = useFinishedMatch()
@ -64,20 +48,14 @@ export const FinishedMatch = ({
onPlayingChange={onPlayingChange} onPlayingChange={onPlayingChange}
profile={profile} profile={profile}
/> />
{isMobileDevice <MatchDescription />
? <MatchProfileCardMobile profile={profile} /> : (
<MatchDescription profile={profile} />)}
</Fragment> </Fragment>
)} )}
</Container> </Container>
<MatchSidePlaylists <MatchSidePlaylists
events={events}
playlists={playlists}
selectedPlaylist={selectedPlaylist} selectedPlaylist={selectedPlaylist}
onSelect={onPlaylistSelect} onSelect={onPlaylistSelect}
profile={profile}
tournamentData={tournamentData}
/> />
</Fragment> </Fragment>
) )

@ -7,25 +7,13 @@ import { StreamPlayer } from 'features/StreamPlayer'
import { YoutubePlayer } from 'features/StreamPlayer/components/YoutubePlayer' import { YoutubePlayer } from 'features/StreamPlayer/components/YoutubePlayer'
import { MatchSidePlaylists } from 'features/MatchSidePlaylists' import { MatchSidePlaylists } from 'features/MatchSidePlaylists'
import { isMobileDevice } from 'config/userAgent'
import { Container } from '../../styled' import { Container } from '../../styled'
import { useLiveMatch } from './hooks' import { useLiveMatch } from './hooks'
import { TournamentData } from '../../types'
import { MatchDescription } from '../MatchDescription' import { MatchDescription } from '../MatchDescription'
import { MatchProfileCardMobile } from '../MatchProfileCardMobile'
type Props = {
tournamentData: TournamentData,
}
export const LiveMatch = ({ export const LiveMatch = () => {
tournamentData,
}: Props) => {
const { const {
events,
matchPlaylists,
profile, profile,
selectedPlaylist, selectedPlaylist,
} = useMatchPageStore() } = useMatchPageStore()
@ -47,8 +35,8 @@ export const LiveMatch = ({
<YoutubePlayer <YoutubePlayer
chapters={chapters} chapters={chapters}
onPlayingChange={onPlayingChange} onPlayingChange={onPlayingChange}
isLive={profile.live}
onProgressChange={onPlayerProgressChange} onProgressChange={onPlayerProgressChange}
profile={profile}
resumeFrom={resume} resumeFrom={resume}
url={streamUrl} url={streamUrl}
/> />
@ -58,24 +46,18 @@ export const LiveMatch = ({
onDurationChange={onDurationChange} onDurationChange={onDurationChange}
onPlayingChange={onPlayingChange} onPlayingChange={onPlayingChange}
onProgressChange={onPlayerProgressChange} onProgressChange={onPlayerProgressChange}
isLive={profile?.live} isLive={profile?.live ?? false}
resumeFrom={resume} resumeFrom={resume}
chapters={chapters} chapters={chapters}
profile={profile} profile={profile}
/> />
) )
)} )}
{isMobileDevice <MatchDescription />
? <MatchProfileCardMobile profile={profile} /> : (
<MatchDescription profile={profile} />)}
</Container> </Container>
<MatchSidePlaylists <MatchSidePlaylists
tournamentData={tournamentData}
events={events}
onSelect={onPlaylistSelect} onSelect={onPlaylistSelect}
playlists={matchPlaylists}
profile={profile}
selectedPlaylist={selectedPlaylist} selectedPlaylist={selectedPlaylist}
/> />
</Fragment> </Fragment>

@ -1,43 +1,54 @@
import { useCallback } from 'react'
import { format } from 'date-fns' import { format } from 'date-fns'
import includes from 'lodash/includes' import includes from 'lodash/includes'
import type { MatchInfo } from 'requests/getMatchInfo' import type { Team } from 'requests/getMatchInfo'
import { Name } from 'features/Name' import { getName, Name } from 'features/Name'
import { SportIcon } from 'components/SportIcon/SportIcon'
import { useAuthStore } from 'features/AuthStore' import { useAuthStore } from 'features/AuthStore'
import { useMatchSwitchesStore } from 'features/MatchSwitches' import { useMatchSwitchesStore } from 'features/MatchSwitches'
import { TournamentSubtitle } from 'features/TournamentSubtitle'
import { useLexicsStore } from 'features/LexicsStore'
import { useMatchPageStore } from 'features/MatchPage/store'
import { parseDate } from 'helpers/parseDate' import { parseDate } from 'helpers/parseDate'
import { ProfileTypes } from 'config' import { ProfileTypes } from 'config'
import { isMobileDevice } from 'config/userAgent'
import { usePageParams } from 'hooks/usePageParams' import { usePageParams } from 'hooks/usePageParams'
import { import {
CountryFlag,
Description, Description,
DescriptionInnerBlock, DescriptionInnerBlock,
MatchDate, MatchDate,
StyledLink, StyledLink,
Score, Score,
Title, Title,
Tournament,
Views, Views,
Time, Time,
} from './styled' } from './styled'
type Props = { export const MatchDescription = () => {
profile: MatchInfo,
}
export const MatchDescription = ({
profile,
}: Props) => {
const { sportType } = usePageParams() const { sportType } = usePageParams()
const { user } = useAuthStore() const { user } = useAuthStore()
const { isScoreHidden } = useMatchSwitchesStore() const { isScoreHidden } = useMatchSwitchesStore()
const { suffix } = useLexicsStore()
const { profile, profileCardShown } = useMatchPageStore()
const getTeamName = useCallback((team: Team) => {
if (isMobileDevice) {
const teamNameLimit = 14
let name = getName({ nameObj: team, suffix })
if (name.length > teamNameLimit) {
name = `${name.substring(0, teamNameLimit)}...`
}
return name
}
return <Name nameObj={team} />
}, [suffix])
if (!profile) return <Description /> if (!profile) return <Description />
@ -50,12 +61,12 @@ export const MatchDescription = ({
} = profile } = profile
const isChangedTimeFormat = includes(['US', 'CA'], user?.profile.country_code) const isChangedTimeFormat = includes(['US', 'CA'], user?.profile.country_code)
const localDate = format(parseDate(date), 'MMMM d, y') const localDate = format(parseDate(date), 'MMM d, y')
const changedTimeFormat = format(parseDate(date), const changedTimeFormat = format(parseDate(date),
isChangedTimeFormat ? 'h:mm aaa' : 'HH:mm') isChangedTimeFormat ? 'h:mm a' : 'HH:mm')
return ( return (
<Description> <Description isHidden={!profileCardShown}>
<DescriptionInnerBlock> <DescriptionInnerBlock>
<Title> <Title>
<StyledLink <StyledLink
@ -63,7 +74,7 @@ export const MatchDescription = ({
profileType={ProfileTypes.TEAMS} profileType={ProfileTypes.TEAMS}
sportType={sportType} sportType={sportType}
> >
<Name nameObj={team1} /> {getTeamName(team1)}
</StyledLink> </StyledLink>
<Score> <Score>
{ {
@ -77,22 +88,13 @@ export const MatchDescription = ({
profileType={ProfileTypes.TEAMS} profileType={ProfileTypes.TEAMS}
sportType={sportType} sportType={sportType}
> >
<Name nameObj={team2} /> {getTeamName(team2)}
</StyledLink> </StyledLink>
</Title> </Title>
<Tournament> <TournamentSubtitle
<SportIcon sport={sportType} /> countryId={country_id}
<CountryFlag tournament={tournament}
src={`https://instatscout.com/images/flags/48/${country_id}.png`}
/> />
<StyledLink
id={tournament.id}
profileType={ProfileTypes.TOURNAMENTS}
sportType={sportType}
>
<Name nameObj={tournament} />
</StyledLink>
</Tournament>
</DescriptionInnerBlock> </DescriptionInnerBlock>
<Views> <Views>
<Time>{changedTimeFormat}</Time> <Time>{changedTimeFormat}</Time>

@ -3,7 +3,7 @@ import { isMobileDevice } from 'config/userAgent'
import { ProfileLink } from 'features/ProfileLink' import { ProfileLink } from 'features/ProfileLink'
export const Description = styled.div` export const Description = styled.div<{isHidden?: boolean}>`
padding: 22px 0 24px; padding: 22px 0 24px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -11,24 +11,37 @@ export const Description = styled.div`
${isMobileDevice ${isMobileDevice
? css` ? css`
@media (orientation: portrait) { padding: 0 5px;
padding-left: 14px;
}
` `
: ''}; : ''};
${({ isHidden }) => (isHidden && isMobileDevice ? css`
height: 0;
opacity: 0;
margin-bottom: 0;
` : '')}
` `
export const DescriptionInnerBlock = styled.div`` export const DescriptionInnerBlock = styled.div`
margin-right: 10px;
`
export const Score = styled.span` export const Score = styled.span`
margin: 0 10px; margin: 0 10px;
color: inherit; color: inherit;
${isMobileDevice
? css`
margin: 0 5px;
`
: ''};
` `
export const StyledLink = styled(ProfileLink)` export const StyledLink = styled(ProfileLink)`
display: flex; display: flex;
align-items: center; align-items: center;
color: #fff; color: #fff;
white-space: nowrap;
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;
@ -37,11 +50,8 @@ export const StyledLink = styled(ProfileLink)`
export const Title = styled.div` export const Title = styled.div`
display: flex; display: flex;
flex-direction: row;
font-weight: 500; font-weight: 500;
font-size: 20px; font-size: 20px;
line-height: 24px;
margin-bottom: 1px;
&:hover > ${StyledLink}:not(:hover) { &:hover > ${StyledLink}:not(:hover) {
opacity: 0.7; opacity: 0.7;
@ -51,34 +61,28 @@ export const Title = styled.div`
opacity: 0.7; opacity: 0.7;
pointer-events: none; pointer-events: none;
} }
`
export const Tournament = styled.span` ${isMobileDevice
display: flex; ? css`
flex-direction: row;
align-items: center;
font-size: 14px; font-size: 14px;
line-height: 16px; margin-bottom: 5px;
margin-bottom: 1px;
opacity: 0.7;
&:hover {
opacity: 1;
text-decoration: underline;
}
` `
: ''};
export const CountryFlag = styled.img`
height: 12px;
margin: 0 6px;
` `
export const Views = styled.div` export const Views = styled.div`
color: #fff; color: #fff;
opacity: 0.7; opacity: 0.7;
font-size: 20px; font-size: 20px;
line-height: 24px;
align-self: flex-start; ${isMobileDevice
? css`
font-size: 10px;
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
`
: ''};
` `
export const MatchDate = styled.span` export const MatchDate = styled.span`

@ -1,34 +0,0 @@
import { useMemo } from 'react'
import { format, parseISO } from 'date-fns'
import { MatchInfo } from 'requests'
import { ScDate, ScDateMonth } from './styled'
type Props = {
profile: MatchInfo,
}
export const MatchDate = ({ profile }: Props) => {
const {
date,
} = { ...profile! }
const month = useMemo(() => (
format(parseISO(date), 'MMM dd,')
), [date])
const hour = useMemo(() => (
format(parseISO(date), ' HH:mm')
), [date])
return (
<ScDate>
<ScDateMonth>
{month}
</ScDateMonth>
{hour}
</ScDate>
)
}

@ -1,10 +0,0 @@
import styled from 'styled-components'
export const ScDate = styled.div`
font-size: 10px;
color: rgba(255, 255, 255, 0.7);
`
export const ScDateMonth = styled.span`
font-weight: 600;
`

@ -1,73 +0,0 @@
import { useCallback } from 'react'
import { ProfileTypes } from 'config'
import { getName } from 'features/Name'
import { useMatchSwitchesStore } from 'features/MatchSwitches'
import { useLexicsStore } from 'features/LexicsStore'
import { usePageParams } from 'hooks/usePageParams'
import { MatchInfo, Team } from 'requests'
import {
Score,
ScoreWrapper,
StyledLink,
ScTeam,
Wrapper,
} from './styled'
type Props = {
profile: MatchInfo,
teamNameLimit?: number,
}
export const TeamsDetails = ({ profile, teamNameLimit }: Props) => {
const { sportType } = usePageParams()
const { isScoreHidden } = useMatchSwitchesStore()
const { suffix } = useLexicsStore()
const {
team1,
team2,
} = { ...profile! }
const getTeamName = useCallback((team: Team) => {
let name = getName({ nameObj: team, suffix })
if (!!teamNameLimit && name.length > teamNameLimit) name = name.substring(0, teamNameLimit).concat('...')
return name
}, [suffix, teamNameLimit])
return (
<Wrapper>
<ScTeam>
<StyledLink
id={team1.id}
profileType={ProfileTypes.TEAMS}
sportType={sportType}
>
{getTeamName(team1)}
</StyledLink>
</ScTeam>
<ScoreWrapper>
<Score>
{
isScoreHidden
? '-'
: `${team1.score} - ${team2.score}`
}
</Score>
</ScoreWrapper>
<ScTeam>
<StyledLink
id={team2.id}
profileType={ProfileTypes.TEAMS}
sportType={sportType}
>
{getTeamName(team2)}
</StyledLink>
</ScTeam>
</Wrapper>
)
}

@ -1,70 +0,0 @@
import styled, { css } from 'styled-components'
import { isMobileDevice } from 'config/userAgent'
import { ProfileLink } from 'features/ProfileLink'
import { ProfileLogo } from 'features/ProfileLogo'
export const Wrapper = styled.div`
display: flex;
font-weight: 600;
margin-bottom: 5px;
`
export const ScTeam = styled.div`
font-size: 21px;
${isMobileDevice
? css`
font-size: 16px;
`
: ''};
`
export const StyledLink = styled(ProfileLink)`
display: flex;
align-items: center;
color: white;
font-size: 14px;
&:hover {
text-decoration: underline;
}
`
export const ScoreWrapper = styled.div`
margin: 0 10px;
display: flex;
flex-direction: column;
align-items: center;
`
export const Score = styled.span`
font-size: 14px;
white-space: nowrap;
`
export const MatchStatus = styled.span`
text-align: center;
background-color: #CC0000;
border-radius: 1.3px;
font-weight: 600;
font-size: 13px;
line-height: 16px;
letter-spacing: 0.05em;
text-transform: uppercase;
padding: 2.5px 14px;
margin-top: 6px;
`
export const Logo = styled(ProfileLogo)`
width: 41px;
height: 41px;
margin: 0 9px;
${isMobileDevice
? css`
width: 30px;
height: 30px;
`
: ''};
`

@ -1,28 +0,0 @@
import { MatchInfo } from 'requests'
import { TournamentSubtitle } from 'features/TournamentSubtitle'
import { Wrapper, WrapperTop } from './styled'
import { TeamsDetails } from './components/TeamsDetails'
import { MatchDate } from './components/MatchDate'
import { useMatchPageStore } from '../../store'
type Props = {
profile: MatchInfo,
}
export const MatchProfileCardMobile = ({ profile }: Props) => {
const { profileCardShown } = useMatchPageStore()
if (!profile) return null
return (
<Wrapper isHidden={!profileCardShown}>
<WrapperTop>
<TeamsDetails profile={profile} teamNameLimit={14} />
<MatchDate profile={profile} />
</WrapperTop>
<TournamentSubtitle countryId={profile!.country_id} tournament={profile!.tournament} />
</Wrapper>
)
}

@ -1,19 +0,0 @@
import styled, { css } from 'styled-components/macro'
export const Wrapper = styled.div<{isHidden?: boolean}>`
transition: 0.3s linear;
padding: 0 5px;
color: white;
margin-bottom: 15px;
${({ isHidden }) => (isHidden ? css`
height: 0;
opacity: 0;
margin-bottom: 0;
` : '')}
`
export const WrapperTop = styled.div`
display: flex;
justify-content: space-between;
`

@ -1,15 +0,0 @@
import { useToggle } from 'hooks'
export const useMatchPage = () => {
const {
close: hideProfileCard,
isOpen: profileCardShown,
open: showProfileCard,
} = useToggle(true)
return {
hideProfileCard,
profileCardShown,
showProfileCard,
}
}

@ -1,28 +0,0 @@
import { useCallback, useState } from 'react'
import type { Events } from 'requests'
import { getMatchEvents } from 'requests'
import { usePageParams } from 'hooks/usePageParams'
import { useEventsLexics } from './useEventsLexics'
export const useEvents = () => {
const [events, setEvents] = useState<Events>([])
const { fetchLexics } = useEventsLexics()
const { profileId: matchId, sportType } = usePageParams()
const fetchMatchEvents = useCallback(() => {
getMatchEvents({
matchId,
sportType,
}).then(fetchLexics)
.then(setEvents)
}, [
fetchLexics,
matchId,
sportType,
])
return { events, fetchMatchEvents }
}

@ -1,25 +0,0 @@
import { useCallback } from 'react'
import isEmpty from 'lodash/isEmpty'
import map from 'lodash/map'
import uniq from 'lodash/uniq'
import type { Events } from 'requests'
import { useLexicsStore } from 'features/LexicsStore'
export const useEventsLexics = () => {
const { addLexicsConfig } = useLexicsStore()
const fetchLexics = useCallback((events: Events) => {
const lexics = uniq(map(events, ({ l }) => l))
if (!isEmpty(lexics)) {
addLexicsConfig(lexics)
}
return events
}, [addLexicsConfig])
return { fetchLexics }
}

@ -1,64 +0,0 @@
import { useEffect, useMemo } from 'react'
import debounce from 'lodash/debounce'
import { usePageParams } from 'hooks/usePageParams'
import { useInterval } from 'hooks/useInterval'
import { useMatchPopupStore } from 'features/MatchPopup'
import { useEvents } from './useEvents'
const MATCH_DATA_POLL_INTERVAL = 60000
const MATCH_PLAYLISTS_DELAY = 5000
export const useMatchData = (live: boolean = false) => {
const { profileId: matchId, sportType } = usePageParams()
const { fetchMatchPlaylists, matchPlaylists } = useMatchPopupStore()
const { events, fetchMatchEvents } = useEvents()
const fetchPlaylistsDebounced = useMemo(
() => debounce(fetchMatchPlaylists, MATCH_PLAYLISTS_DELAY),
[fetchMatchPlaylists],
)
useEffect(() => {
fetchMatchPlaylists({
id: matchId,
sportType,
withFullMatchDuration: !live,
})
fetchMatchEvents()
}, [
live,
matchId,
sportType,
fetchMatchPlaylists,
fetchMatchEvents,
])
const intervalCallback = () => {
fetchPlaylistsDebounced({
id: matchId,
sportType,
withFullMatchDuration: !live,
})
fetchMatchEvents()
}
const { start, stop } = useInterval({
callback: intervalCallback,
intervalDuration: MATCH_DATA_POLL_INTERVAL,
startImmediate: false,
})
useEffect(() => {
if (live) {
start()
} else {
stop()
}
}, [live, start, stop])
return { events, matchPlaylists }
}

@ -1,76 +0,0 @@
import {
useEffect,
useState,
useMemo,
} from 'react'
import type { MatchInfo } from 'requests'
import { getMatchInfo } from 'requests'
import { usePageParams } from 'hooks/usePageParams'
import { parseDate } from 'helpers/parseDate'
import type { Playlists } from '../types'
import { useMatchData } from './useMatchData'
import { useTournamentData } from './useTournamentData'
const addScoresFromPlaylists = (
profile: MatchInfo,
playlists: Playlists,
): MatchInfo => (
profile
? {
...profile,
team1: {
...profile?.team1,
score: playlists.score1,
},
team2: {
...profile?.team2,
score: playlists.score2,
},
}
: null
)
export const useMatchProfile = () => {
const [matchProfile, setMatchProfile] = useState<MatchInfo>(null)
const { profileId: matchId, sportType } = usePageParams()
useEffect(() => {
getMatchInfo(sportType, matchId).then(setMatchProfile)
}, [sportType, matchId])
useEffect(() => {
let getIntervalMatch: ReturnType<typeof setInterval>
if (matchProfile?.live && !matchProfile.youtube_link) {
getIntervalMatch = setInterval(
() => getMatchInfo(sportType, matchId).then(setMatchProfile), 1000 * 60 * 3,
)
}
return () => clearInterval(getIntervalMatch)
}, [matchProfile, sportType, matchId])
const { events, matchPlaylists } = useMatchData(matchProfile?.live)
const profile = useMemo(
() => addScoresFromPlaylists(matchProfile, matchPlaylists),
[matchProfile, matchPlaylists],
)
const { tournamentData } = useTournamentData(matchProfile?.tournament.id ?? null)
const isStarted = useMemo(() => (
profile?.date
? parseDate(profile.date) < new Date()
: true
), [profile?.date])
return {
events,
isStarted,
profile,
tournamentData,
}
}

@ -15,11 +15,10 @@ import { ProfileTypes } from 'config'
import { usePageLogger } from 'hooks/usePageLogger' import { usePageLogger } from 'hooks/usePageLogger'
import { usePageParams } from 'hooks/usePageParams' import { usePageParams } from 'hooks/usePageParams'
import { MatchPageStore } from './store'
import { MatchPageStore, useMatchPageStore } from './store'
import { SubscriptionGuard } from './components/SubscriptionGuard' import { SubscriptionGuard } from './components/SubscriptionGuard'
import { LiveMatch } from './components/LiveMatch' import { LiveMatch } from './components/LiveMatch'
import { useMatchProfile } from './hooks/useMatchProfile'
import { Wrapper } from './styled' import { Wrapper } from './styled'
import { FinishedMatch } from './components/FinishedMatch' import { FinishedMatch } from './components/FinishedMatch'
@ -28,12 +27,7 @@ const MatchPageComponent = () => {
const history = useHistory() const history = useHistory()
const { addRemoveFavorite, userFavorites } = useUserFavoritesStore() const { addRemoveFavorite, userFavorites } = useUserFavoritesStore()
const { const { isStarted, profile } = useMatchPageStore()
events,
isStarted,
profile,
tournamentData,
} = useMatchProfile()
const { const {
profileType, profileType,
@ -77,16 +71,10 @@ const MatchPageComponent = () => {
<SubscriptionGuard> <SubscriptionGuard>
<Wrapper> <Wrapper>
{playFromOTT && ( {playFromOTT && (
<LiveMatch <LiveMatch />
tournamentData={tournamentData}
/>
)} )}
{playFromScout && ( {playFromScout && (
<FinishedMatch <FinishedMatch />
events={events}
profile={profile}
tournamentData={tournamentData}
/>
)} )}
</Wrapper> </Wrapper>
</SubscriptionGuard> </SubscriptionGuard>

@ -12,6 +12,7 @@ import { usePageParams } from 'hooks/usePageParams'
import { parseDate } from 'helpers/parseDate' import { parseDate } from 'helpers/parseDate'
import { useTournamentData } from './useTournamentData'
import { useMatchData } from './useMatchData' import { useMatchData } from './useMatchData'
import type { Playlists } from '../../types' import type { Playlists } from '../../types'
@ -70,13 +71,14 @@ export const useMatchPage = () => {
() => addScoresFromPlaylists(matchProfile, matchPlaylists), () => addScoresFromPlaylists(matchProfile, matchPlaylists),
[matchProfile, matchPlaylists], [matchProfile, matchPlaylists],
) )
const isStarted = useMemo(() => ( const isStarted = useMemo(() => (
profile?.date profile?.date
? parseDate(profile.date) < new Date() ? parseDate(profile.date) < new Date()
: true : true
), [profile?.date]) ), [profile?.date])
const { tournamentData } = useTournamentData(matchProfile?.tournament.id ?? null)
return { return {
events, events,
handlePlaylistClick, handlePlaylistClick,
@ -88,5 +90,6 @@ export const useMatchPage = () => {
selectedPlaylist, selectedPlaylist,
setFullMatchPlaylistDuration, setFullMatchPlaylistDuration,
showProfileCard, showProfileCard,
tournamentData,
} }
} }

@ -3,8 +3,6 @@ import {
useCallback, useCallback,
} from 'react' } from 'react'
import isEmpty from 'lodash/isEmpty'
import type { SportTypes } from 'config/sportTypes' import type { SportTypes } from 'config/sportTypes'
import { getMatchPlaylists } from 'requests/getMatchPlaylists' import { getMatchPlaylists } from 'requests/getMatchPlaylists'
@ -34,12 +32,7 @@ export const useMatchPlaylists = () => {
} = useSelectedPlaylist() } = useSelectedPlaylist()
const setInitialSeletedPlaylist = useCallback((playlists: Playlists) => { const setInitialSeletedPlaylist = useCallback((playlists: Playlists) => {
setSelectedPlaylist((playlist) => { setSelectedPlaylist(playlists.match[0])
if (!playlist && !isEmpty(playlists.match)) {
return playlists.match[0]
}
return playlist
})
return playlists return playlists
}, [setSelectedPlaylist]) }, [setSelectedPlaylist])

@ -18,7 +18,7 @@ import { parseDate } from 'helpers/parseDate'
import { usePageParams } from 'hooks/usePageParams' import { usePageParams } from 'hooks/usePageParams'
import { TournamentData } from '../types' import { TournamentData } from '../../types'
export const useTournamentData = (tournamentId: number | null) => { export const useTournamentData = (tournamentId: number | null) => {
const { sportType } = usePageParams() const { sportType } = usePageParams()

@ -17,12 +17,10 @@ export const Wrapper = styled.div`
${isMobileDevice ${isMobileDevice
? css` ? css`
height: calc(100vh - 40px);
@media screen and (orientation: landscape) { @media screen and (orientation: landscape) {
padding-top: 20px;
flex-direction: row; flex-direction: row;
justify-content: space-between;
margin-left: 10px;
width: 100vw;
} }
` `
: ''}; : ''};

@ -1,5 +1,7 @@
import styled, { css } from 'styled-components/macro' import styled, { css } from 'styled-components/macro'
import { isMobileDevice } from 'config/userAgent'
export const Wrapper = styled.div` export const Wrapper = styled.div`
color: #FFFFFF; color: #FFFFFF;
display: flex; display: flex;
@ -10,6 +12,14 @@ export const Wrapper = styled.div`
> :not(:last-child) { > :not(:last-child) {
margin-right: 20px; margin-right: 20px;
} }
${isMobileDevice ? css`
@media screen and (orientation: landscape){
> :not(:last-child) {
margin-right: 3px;
}
}
` : ''}
` `
export const WeekDay = styled.div.attrs(() => ({ export const WeekDay = styled.div.attrs(() => ({

@ -1,14 +1,20 @@
import styled from 'styled-components/macro' import styled, { css } from 'styled-components/macro'
import { customScrollbar } from 'features/Common' import { customScrollbar } from 'features/Common'
import { isMobileDevice } from '../../../../config/userAgent'
export const MatchesWrapper = styled.div` export const MatchesWrapper = styled.div`
overflow-y: scroll; overflow-y: auto;
max-height: calc(100vh - 170px); max-height: calc(100vh - 170px);
padding-right: 5px;
> * { > * {
margin-bottom: 15px; margin-bottom: 15px;
} }
${customScrollbar} ${customScrollbar}
${isMobileDevice ? css`
overflow: hidden;
max-height: initial;
` : ''}
` `

@ -6,25 +6,16 @@ import {
import reduce from 'lodash/reduce' import reduce from 'lodash/reduce'
import { Playlists, TournamentData } from 'features/MatchPage/types' import { useMatchPageStore } from 'features/MatchPage/store'
import type { Events } from 'requests'
import { Tabs } from './config' import { Tabs } from './config'
export type Props = { export const useMatchSidePlaylists = () => {
events: Events,
playlists: Playlists,
tournamentData: TournamentData,
}
export const useMatchSidePlaylists = (props: Props) => {
const { const {
events, events,
playlists, matchPlaylists: playlists,
tournamentData, tournamentData,
} = props } = useMatchPageStore()
const [selectedTab, setSelectedTab] = useState<Tabs>(Tabs.WATCH) const [selectedTab, setSelectedTab] = useState<Tabs>(Tabs.WATCH)
const isWatchTabVisible = useMemo(() => { const isWatchTabVisible = useMemo(() => {

@ -1,19 +1,12 @@
import { useRef } from 'react' import { useRef } from 'react'
import type { Events, MatchInfo } from 'requests' import type { PlaylistOption } from 'features/MatchPage/types'
import type {
PlaylistOption,
Playlists,
TournamentData,
} from 'features/MatchPage/types'
import { Tab, TabsGroup } from 'features/Common' import { Tab, TabsGroup } from 'features/Common'
import { T9n } from 'features/T9n' import { T9n } from 'features/T9n'
import { useMatchPageStore } from 'features/MatchPage/store'
import { useEventListener } from 'hooks' import { useEventListener } from 'hooks'
import { useMatchPageStore } from 'features/MatchPage/store'
import { isIOS } from 'config/userAgent' import { isIOS } from 'config/userAgent'
import { Tabs } from './config' import { Tabs } from './config'
@ -22,7 +15,6 @@ import { TabWatch } from './components/TabWatch'
import { TabVideo } from './components/TabVideo' import { TabVideo } from './components/TabVideo'
import { useMatchSidePlaylists } from './hooks' import { useMatchSidePlaylists } from './hooks'
import { import {
BackToTopBtn,
Wrapper, Wrapper,
TabsWrapper, TabsWrapper,
Container, Container,
@ -35,39 +27,30 @@ const tabPanes = {
} }
type Props = { type Props = {
events: Events,
onSelect: (option: PlaylistOption) => void, onSelect: (option: PlaylistOption) => void,
playlists: Playlists,
profile: MatchInfo,
selectedPlaylist?: PlaylistOption, selectedPlaylist?: PlaylistOption,
tournamentData: TournamentData,
} }
export const MatchSidePlaylists = ({ export const MatchSidePlaylists = ({
events,
onSelect, onSelect,
playlists,
profile,
selectedPlaylist, selectedPlaylist,
tournamentData,
}: Props) => { }: Props) => {
const {
events,
hideProfileCard,
matchPlaylists: playlists,
profile,
showProfileCard,
tournamentData,
} = useMatchPageStore()
const { const {
isEventTabVisible, isEventTabVisible,
isVideoTabVisible, isVideoTabVisible,
isWatchTabVisible, isWatchTabVisible,
onTabClick, onTabClick,
selectedTab, selectedTab,
} = useMatchSidePlaylists({ } = useMatchSidePlaylists()
events,
playlists,
tournamentData,
})
const {
hideProfileCard,
profileCardShown,
showProfileCard,
} = useMatchPageStore()
const TabPane = tabPanes[selectedTab] const TabPane = tabPanes[selectedTab]
@ -76,9 +59,7 @@ export const MatchSidePlaylists = ({
useEventListener({ useEventListener({
callback: () => { callback: () => {
const screenLandscape = isIOS ? window.orientation : window.screen.orientation.type const screenLandscape = isIOS ? window.orientation : window.screen.orientation.type
const yOffset = isIOS const yOffset = containerRef.current?.scrollTop
? containerRef.current?.offsetTop
: containerRef.current?.scrollTop
const isScreenLandscape = isIOS const isScreenLandscape = isIOS
? (screenLandscape === 90 || screenLandscape === -90) ? (screenLandscape === 90 || screenLandscape === -90)
: (screenLandscape === 'landscape-primary' || screenLandscape === 'landscape-secondary') : (screenLandscape === 'landscape-primary' || screenLandscape === 'landscape-secondary')
@ -86,12 +67,12 @@ export const MatchSidePlaylists = ({
if (yOffset && yOffset > 10 && !isScreenLandscape) hideProfileCard() if (yOffset && yOffset > 10 && !isScreenLandscape) hideProfileCard()
if (yOffset && yOffset < 10) showProfileCard() if (yOffset && yOffset < 10) showProfileCard()
}, },
event: isIOS ? 'touchmove' : 'scroll', event: 'scroll',
target: containerRef, target: containerRef,
}) })
return ( return (
<Wrapper> <Wrapper ref={containerRef}>
<TabsWrapper> <TabsWrapper>
<TabsGroup> <TabsGroup>
{isWatchTabVisible ? ( {isWatchTabVisible ? (
@ -119,15 +100,9 @@ export const MatchSidePlaylists = ({
</Tab> </Tab>
) : null} ) : null}
</TabsGroup> </TabsGroup>
<BackToTopBtn
hidden={profileCardShown}
onClick={() => {
containerRef.current?.scrollTo(0, 0)
showProfileCard()
}}
/>
</TabsWrapper> </TabsWrapper>
<Container forVideoTab={selectedTab === Tabs.VIDEO} ref={containerRef}> <Container forVideoTab={selectedTab === Tabs.VIDEO}>
<TabPane <TabPane
tournamentData={tournamentData} tournamentData={tournamentData}
events={events} events={events}

@ -8,12 +8,10 @@ import { customScrollbar } from 'features/Common'
export const Wrapper = styled.div` export const Wrapper = styled.div`
${isMobileDevice ${isMobileDevice
? css` ? css`
@media (orientation: landscape){ overflow-y: auto;
min-width: 250px; width: 100%;
}
@media (max-width: 569px) and (orientation: landscape){ ${customScrollbar}
min-width: 200px;
}
` `
: ''}; : ''};
` `
@ -21,7 +19,6 @@ ${isMobileDevice
export const TabsWrapper = styled.div` export const TabsWrapper = styled.div`
padding-left: 14px; padding-left: 14px;
padding-right: 18px; padding-right: 18px;
position: relative;
${isMobileDevice ${isMobileDevice
? css` ? css`
@ -39,19 +36,19 @@ export const Container = styled.div<TContainer>`
margin-top: 14px; margin-top: 14px;
padding: 0 10px 0 14px; padding: 0 10px 0 14px;
max-height: calc(100vh - 170px); max-height: calc(100vh - 170px);
overflow-y: ${({ forVideoTab }) => (forVideoTab ? 'none' : 'scroll')}; overflow-y: ${({ forVideoTab }) => (forVideoTab ? 'hidden' : 'auto')};
${customScrollbar} ${customScrollbar}
@media ${devices.tablet} { @media ${devices.tablet} {
max-height: calc(100vh - 40px);
margin-top: 15px; margin-top: 15px;
} }
${isMobileDevice ${isMobileDevice
? css` ? css`
padding: 0 5px; padding: 0 5px;
overflow-y: auto; overflow-y: hidden;
max-height: initial;
@media (max-width: 750px){ @media (max-width: 750px){
width: 100%; width: 100%;
@ -138,27 +135,3 @@ export const BlockTitle = styled.span`
color: rgba(255, 255, 255, 0.5); color: rgba(255, 255, 255, 0.5);
text-transform: uppercase; text-transform: uppercase;
` `
export const BackToTopBtn = styled.div`
position: absolute;
left: 50%;
bottom: -100%;
background: rgba(0, 0, 0, 0.5);
border-radius: 0px 0 20px 20px;
width: 37px;
height: 34px;
z-index: 1;
cursor: pointer;
transform: translate(-50%, 6%);
:after {
content: '';
width: 10px;
height: 10px;
position: absolute;
border-left: 2px solid rgb(255, 255, 255);
border-top: 2px solid rgb(255, 255, 255);
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(45deg);
}
`

@ -1,6 +1,6 @@
import { useSlider } from 'features/StreamPlayer/hooks/useSlider' import { useSlider } from 'features/StreamPlayer/hooks/useSlider'
import { TimeTooltip } from 'features/StreamPlayer/components/TimeTooltip' import { TimeTooltip } from 'features/StreamPlayer/components/TimeTooltip'
import { Scrubber } from 'features/StreamPlayer/components/ProgressBar/styled' import { Scrubber, ScrubberContainer } from 'features/StreamPlayer/components/ProgressBar/styled'
import { Chapters } from 'features/StreamPlayer/components/Chapters' import { Chapters } from 'features/StreamPlayer/components/Chapters'
import type { Props } from './hooks' import type { Props } from './hooks'
@ -35,9 +35,11 @@ export const ProgressBar = (props: ProgressBarProps) => {
<ProgressBarList ref={progressBarRef}> <ProgressBarList ref={progressBarRef}>
<Chapters chapters={calculatedChapters} /> <Chapters chapters={calculatedChapters} />
{isScrubberVisible === false ? null : ( {isScrubberVisible === false ? null : (
<ScrubberContainer>
<Scrubber style={{ left: `${playedProgressInPercent}%` }}> <Scrubber style={{ left: `${playedProgressInPercent}%` }}>
<TimeTooltip time={time} /> <TimeTooltip time={time} />
</Scrubber> </Scrubber>
</ScrubberContainer>
)} )}
</ProgressBarList> </ProgressBarList>
) )

@ -1,3 +1,5 @@
export const REWIND_SECONDS = 5 import { isMobileDevice } from 'config/userAgent'
export const REWIND_SECONDS = isMobileDevice ? 10 : 5
export const HOUR_IN_MILLISECONDS = 60 * 60 * 1000 export const HOUR_IN_MILLISECONDS = 60 * 60 * 1000

@ -274,7 +274,7 @@ export const useMultiSourcePlayer = ({
video2Ref, video2Ref,
videoQualities, videoQualities,
wrapperRef, wrapperRef,
...useControlsVisibility(isFullscreen), ...useControlsVisibility(isFullscreen, playing),
...useVolume(), ...useVolume(),
} }
} }

@ -12,6 +12,7 @@ import {
import { VideoPlayer } from 'features/VideoPlayer' import { VideoPlayer } from 'features/VideoPlayer'
import { Controls } from 'features/StreamPlayer/components/Controls' import { Controls } from 'features/StreamPlayer/components/Controls'
import { Name } from 'features/Name' import { Name } from 'features/Name'
import RewindMobile from 'features/StreamPlayer/components/RewindMobile'
import { isMobileDevice } from 'config/userAgent' import { isMobileDevice } from 'config/userAgent'
@ -27,13 +28,15 @@ export const MultiSourcePlayer = (props: Props) => {
activePlayer, activePlayer,
activeSrc, activeSrc,
allPlayedProgress, allPlayedProgress,
centerControlsVisible,
chapters, chapters,
controlsVisible,
duration, duration,
hideCenterControls,
isFirstChapterPlaying, isFirstChapterPlaying,
isFullscreen, isFullscreen,
isLastChapterPlaying, isLastChapterPlaying,
loadedProgress, loadedProgress,
mainControlsVisible,
muted, muted,
nextSrc, nextSrc,
numberOfChapters, numberOfChapters,
@ -63,6 +66,7 @@ export const MultiSourcePlayer = (props: Props) => {
rewindForward, rewindForward,
seek, seek,
selectedQuality, selectedQuality,
showCenterControls,
togglePlaying, togglePlaying,
video1Ref, video1Ref,
video2Ref, video2Ref,
@ -76,7 +80,7 @@ export const MultiSourcePlayer = (props: Props) => {
<PlayerWrapper <PlayerWrapper
ref={wrapperRef} ref={wrapperRef}
playing={playing} playing={playing}
onClick={onPlayerClick} onClick={isMobileDevice ? showCenterControls : onPlayerClick}
onMouseMove={onMouseMove} onMouseMove={onMouseMove}
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
@ -123,7 +127,7 @@ export const MultiSourcePlayer = (props: Props) => {
onReady={onReady} onReady={onReady}
/> />
{isMobileDevice && isFullscreen && controlsVisible && profile && ( {isMobileDevice && isFullscreen && mainControlsVisible && profile && (
<TeamsDetailsWrapper> <TeamsDetailsWrapper>
<Name nameObj={profile.team1} /> <Name nameObj={profile.team1} />
{` ${profile.team1.score}-${profile.team2.score} `} {` ${profile.team1.score}-${profile.team2.score} `}
@ -132,22 +136,29 @@ export const MultiSourcePlayer = (props: Props) => {
)} )}
{ready && ( {ready && (
<CenterControls playing={playing}> <CenterControls controlsVisible={centerControlsVisible} playing={playing}>
<Backward size='lg' onClick={rewindBackward}>{REWIND_SECONDS}</Backward> {isMobileDevice
? <RewindMobile isBackward rewindCallback={rewindBackward} />
: <Backward size='lg' onClick={rewindBackward}>{REWIND_SECONDS}</Backward>}
<PlayStop <PlayStop
size='lg' size='lg'
marginRight={0} marginRight={0}
playing={playing} playing={playing}
onClickCapture={togglePlaying} onClickCapture={() => {
togglePlaying()
hideCenterControls()
}}
/> />
<Forward size='lg' onClick={rewindForward}>{REWIND_SECONDS}</Forward> {isMobileDevice
? <RewindMobile isForward rewindCallback={rewindForward} />
: <Forward size='lg' onClick={rewindForward}>{REWIND_SECONDS}</Forward>}
</CenterControls> </CenterControls>
)} )}
<Controls <Controls
activeChapterIndex={activeChapterIndex} activeChapterIndex={activeChapterIndex}
allPlayedProgress={allPlayedProgress} allPlayedProgress={allPlayedProgress}
chapters={chapters} multiSourceChapters={chapters}
controlsVisible={controlsVisible} controlsVisible={mainControlsVisible}
duration={duration} duration={duration}
isFirstChapterPlaying={isFirstChapterPlaying} isFirstChapterPlaying={isFirstChapterPlaying}
isFullscreen={isFullscreen} isFullscreen={isFullscreen}
@ -174,7 +185,7 @@ export const MultiSourcePlayer = (props: Props) => {
volume={volume} volume={volume}
volumeInPercent={volumeInPercent} volumeInPercent={volumeInPercent}
/> />
<ControlsGradient isVisible={controlsVisible} /> <ControlsGradient isVisible={mainControlsVisible} />
</PlayerWrapper> </PlayerWrapper>
) )
} }

@ -22,6 +22,7 @@ export const ScSportsFilter = styled.div<PropsFilter>`
width: 30%; width: 30%;
letter-spacing: 0.15rem; letter-spacing: 0.15rem;
font-size: 10px; font-size: 10px;
white-space: nowrap;
` `
: ''}; : ''};
` `

@ -179,7 +179,7 @@ export const ScModal = styled(BaseModal)`
height: auto; height: auto;
${ModalCloseButton} { ${ModalCloseButton} {
padding: 16px 14px 15px 15px; padding: 0;
} }
` `
: ''}; : ''};

@ -1,4 +1,6 @@
import styled from 'styled-components/macro' import styled, { css } from 'styled-components/macro'
import { isMobileDevice } from 'config/userAgent'
export const ChapterList = styled.div` export const ChapterList = styled.div`
width: 100%; width: 100%;
@ -23,6 +25,12 @@ export const LoadedProgress = styled.div`
background-color: rgba(255, 255, 255, 0.6); background-color: rgba(255, 255, 255, 0.6);
height: 100%; height: 100%;
max-width: 100%; max-width: 100%;
${isMobileDevice
? css`
background-color: rgba(255, 255, 255, 0.3);
`
: ''}
` `
export const PlayedProgress = styled.div` export const PlayedProgress = styled.div`
@ -31,4 +39,10 @@ export const PlayedProgress = styled.div`
background-color: #CC0000; background-color: #CC0000;
height: 100%; height: 100%;
max-width: 100%; max-width: 100%;
${isMobileDevice
? css`
background-color: rgba(255, 255, 255, 1);
`
: ''}
` `

@ -15,10 +15,9 @@ export const Controls = styled.div<FullscreenProps>`
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25)); filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25));
bottom: 0; bottom: ${({ isFullscreen }) => (isFullscreen ? '20px' : 0)};
@media (orientation: landscape){ @media (orientation: landscape){
bottom: ${({ isFullscreen }) => (isFullscreen ? '20px' : 0)};
width: 100%; width: 100%;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);

@ -7,17 +7,17 @@ import { isMobileDevice } from 'config/userAgent'
import { secondsToHms } from 'helpers/secondsToHms' import { secondsToHms } from 'helpers/secondsToHms'
import { ProgressBar as ProgressBarMultiSource } from 'features/MultiSourcePlayer/components/ProgressBar' import { ProgressBar as ProgressBarMultiSource } from 'features/MultiSourcePlayer/components/ProgressBar'
import { Chapters } from 'features/MultiSourcePlayer/types' import { Chapters as MultiSourceChapters } from 'features/MultiSourcePlayer/types'
import { Chapters as LiveChapters } from 'features/StreamPlayer/types'
import { ControlsMobile } from './Components/ControlsMobile' import { ControlsMobile } from './Components/ControlsMobile'
import { ControlsWeb } from './Components/ControlsWeb' import { ControlsWeb } from './Components/ControlsWeb'
// import { ProgressBar } from '../ProgressBar' import { ProgressBar } from '../ProgressBar'
export type ControlsProps = { export type ControlsProps = {
activeChapterIndex?: number, activeChapterIndex: number,
allPlayedProgress?: number, allPlayedProgress: number,
backToLive?: () => void, backToLive?: () => void,
chapters?: Chapters,
controlsVisible: boolean, controlsVisible: boolean,
duration: number, duration: number,
isFirstChapterPlaying?: boolean, isFirstChapterPlaying?: boolean,
@ -25,12 +25,13 @@ export type ControlsProps = {
isLastChapterPlaying?: boolean, isLastChapterPlaying?: boolean,
isLive?: boolean, isLive?: boolean,
isStorage?: boolean, isStorage?: boolean,
liveChapters?: LiveChapters,
loadedProgress: number, loadedProgress: number,
multiSourceChapters?: MultiSourceChapters,
muted: boolean, muted: boolean,
numberOfChapters?: number, numberOfChapters?: number,
onFullscreenClick: () => void, onFullscreenClick: () => void,
onProgressChange?: (progress: number, seeking: boolean) => void, onProgressChange: (progress: number, seeking: boolean) => void,
onProgressChangeLive?: (progress: number) => void,
onQualitySelect: (quality: string) => void, onQualitySelect: (quality: string) => void,
onTouchEnd: DebouncedFunc<() => void>, onTouchEnd: DebouncedFunc<() => void>,
onTouchStart: () => void, onTouchStart: () => void,
@ -58,14 +59,14 @@ export const Controls = (props: ControlsProps) => {
const { const {
activeChapterIndex = 0, activeChapterIndex = 0,
allPlayedProgress = 0, allPlayedProgress = 0,
chapters,
controlsVisible, controlsVisible,
duration, duration,
isLive, isLive,
isStorage, isStorage,
liveChapters,
loadedProgress, loadedProgress,
multiSourceChapters,
onProgressChange, onProgressChange,
// onProgressChangeLive,
onTouchEnd, onTouchEnd,
onTouchStart, onTouchStart,
playedProgress, playedProgress,
@ -86,21 +87,24 @@ export const Controls = (props: ControlsProps) => {
]) ])
const progressBarElement = useMemo(() => { const progressBarElement = useMemo(() => {
if (isLive || isStorage) { if (isLive || isStorage) {
// return ( return (
// <ProgressBar <ProgressBar
// duration={duration} duration={duration}
// isScrubberVisible={controlsVisible} isScrubberVisible={controlsVisible}
// onPlayedProgressChange={onProgressChangeLive!} onPlayedProgressChange={onProgressChange}
// playedProgress={playedProgress} playedProgress={playedProgress}
// loadedProgress={loadedProgress} loadedProgress={loadedProgress}
// /> activeChapterIndex={activeChapterIndex}
// ) allPlayedProgress={allPlayedProgress}
chapters={liveChapters!}
/>
)
} }
return ( return (
<ProgressBarMultiSource <ProgressBarMultiSource
activeChapterIndex={activeChapterIndex} activeChapterIndex={activeChapterIndex}
allPlayedProgress={allPlayedProgress} allPlayedProgress={allPlayedProgress}
chapters={chapters} chapters={multiSourceChapters}
duration={duration} duration={duration}
isScrubberVisible={controlsVisible} isScrubberVisible={controlsVisible}
onTouchEnd={onTouchEnd} onTouchEnd={onTouchEnd}
@ -113,14 +117,14 @@ export const Controls = (props: ControlsProps) => {
}, [ }, [
activeChapterIndex, activeChapterIndex,
allPlayedProgress, allPlayedProgress,
chapters, liveChapters,
multiSourceChapters,
controlsVisible, controlsVisible,
duration, duration,
isLive, isLive,
isStorage, isStorage,
loadedProgress, loadedProgress,
onProgressChange, onProgressChange,
// onProgressChangeLive,
onTouchEnd, onTouchEnd,
onTouchStart, onTouchStart,
playedProgress, playedProgress,

@ -1,6 +1,6 @@
import { useSlider } from 'features/StreamPlayer/hooks/useSlider' import { useSlider } from 'features/StreamPlayer/hooks/useSlider'
import { TimeTooltip } from 'features/StreamPlayer/components/TimeTooltip' import { TimeTooltip } from 'features/StreamPlayer/components/TimeTooltip'
import { Scrubber } from 'features/StreamPlayer/components/ProgressBar/styled' import { Scrubber, ScrubberContainer } from 'features/StreamPlayer/components/ProgressBar/styled'
import { Chapters } from '../Chapters' import { Chapters } from '../Chapters'
import type { Props } from './hooks' import type { Props } from './hooks'
@ -8,7 +8,7 @@ import { useProgressBar } from './hooks'
import { ProgressBarList } from './styled' import { ProgressBarList } from './styled'
export const ProgressBar = (props: Props) => { export const ProgressBar = (props: Props) => {
const { onPlayedProgressChange } = props const { isScrubberVisible, onPlayedProgressChange } = props
const progressBarRef = useSlider({ onChange: onPlayedProgressChange }) const progressBarRef = useSlider({ onChange: onPlayedProgressChange })
const { const {
calculatedChapters, calculatedChapters,
@ -18,9 +18,13 @@ export const ProgressBar = (props: Props) => {
return ( return (
<ProgressBarList ref={progressBarRef}> <ProgressBarList ref={progressBarRef}>
<Chapters chapters={calculatedChapters} /> <Chapters chapters={calculatedChapters} />
{isScrubberVisible === false ? null : (
<ScrubberContainer>
<Scrubber style={{ left: `${playedProgressInPercent}%` }}> <Scrubber style={{ left: `${playedProgressInPercent}%` }}>
<TimeTooltip time={time} /> <TimeTooltip time={time} />
</Scrubber> </Scrubber>
</ScrubberContainer>
)}
</ProgressBarList> </ProgressBarList>
) )
} }

@ -8,6 +8,23 @@ export const ProgressBarList = styled.div`
flex-grow: 1; flex-grow: 1;
height: 4px; height: 4px;
position: relative; position: relative;
background-color: rgba(255, 255, 255, 0.3);
cursor: pointer;
${isMobileDevice
? css`
height: 1px;
`
: ''}
`
export const ScrubberContainer = styled.div`
${isMobileDevice
? css`
position: relative;
margin: 0 7px;
`
: ''}
` `
export const Scrubber = styled.div` export const Scrubber = styled.div`
@ -15,7 +32,7 @@ export const Scrubber = styled.div`
outline: none; outline: none;
position: absolute; position: absolute;
top: 0; top: 0;
transform: translate(-50%, -43%); transform: translate(-50%, -38%);
z-index: 3; z-index: 3;
width: 18px; width: 18px;
height: 18px; height: 18px;
@ -29,10 +46,10 @@ export const Scrubber = styled.div`
${isMobileDevice ${isMobileDevice
? css` ? css`
width: 30px; width: 14px;
height: 30px; height: 14px;
background-clip: padding-box; background-color: #FFFFFF;
border: 10px solid transparent; transform: translate(-50%, -48%);
` `
: ''} : ''}
` `

@ -0,0 +1,67 @@
import {
useCallback,
useMemo,
useState,
} from 'react'
import debounce from 'lodash/debounce'
import {
Background,
RewindIcon,
Time,
} from './styled'
export type RewindProps = {
isBackward?: boolean,
isForward?: boolean,
rewindCallback: () => void,
}
const RewindMobile = ({
isBackward,
isForward,
rewindCallback,
}: RewindProps) => {
const [isActive, setIsActive] = useState(false)
const [time, setTime] = useState(0)
const resetData = useCallback(() => {
setIsActive(false)
setTime(0)
}, [])
const resetDataDebounced = useMemo(() => (
debounce(resetData, 1500)
), [resetData])
const handleDoubleClick = useCallback(() => {
setIsActive(true)
rewindCallback()
setTime(time + 10)
resetDataDebounced()
}, [resetDataDebounced, rewindCallback, time])
const TimeContent = useMemo(() => (
<Time
isBackward={isBackward}
isForward={isForward}
>
<span>{isBackward ? '-' : '+'}{time}</span>
<RewindIcon src={`/images/rewind-${isBackward ? 'left' : 'right'}.svg`} />
</Time>
), [isBackward, isForward, time])
return (
<Background
isActive={isActive}
isBackward={isBackward}
isForward={isForward}
onDoubleClick={handleDoubleClick}
>
{isActive && TimeContent}
</Background>
)
}
export default RewindMobile

@ -0,0 +1,41 @@
import styled, { css } from 'styled-components/macro'
type RewindProps = {
isActive?: boolean,
isBackward?: boolean,
isForward?: boolean,
}
export const Background = styled.div<RewindProps>`
width: 920px;
height: 920px;
border-radius: 50%;
background-color: ${({ isActive }) => (isActive ? 'rgba(0, 0, 0, 0.4)' : 'rgba(0, 0, 0, 0)')};
position: absolute;
${({ isBackward }) => isBackward && css`right: 60%;`}
${({ isForward }) => isForward && css`left: 60%;`}
transition: all 0.3s ease-in-out;
`
export const Time = styled.div<RewindProps>`
position: absolute;
color: #FFFFFF;
top: 50%;
font-size: 20px;
transform: translate(0, -50%);
display: flex;
align-items: center;
user-select: none;
${({ isBackward }) => isBackward && css`right: 2%;`}
${({ isForward }) => isForward && css`left: 2%;`}
> :first-child {
margin-right: 10px;
}
`
export const RewindIcon = styled.img`
width: 43px;
height: 28px;
`

@ -1,10 +1,12 @@
import YouTube from 'react-youtube' import YouTube from 'react-youtube'
import { useMatchPageStore } from 'features/MatchPage/store'
import { PlayerWrapper } from '../../styled' import { PlayerWrapper } from '../../styled'
import { useVideoPlayer, Props } from '../../hooks' import { useVideoPlayer, Props } from '../../hooks'
export const YoutubePlayer = (props: Props) => { export const YoutubePlayer = (props: Props) => {
const { profile } = props const { profile } = useMatchPageStore()
const { const {
onMouseMove, onMouseMove,

@ -2,6 +2,8 @@ import type { HlsConfig } from 'hls.js'
import { readToken } from 'helpers/token' import { readToken } from 'helpers/token'
import { isMobileDevice } from 'config/userAgent'
export const streamConfig: Partial<HlsConfig> = { export const streamConfig: Partial<HlsConfig> = {
liveSyncDuration: 30, liveSyncDuration: 30,
maxBufferLength: 30, maxBufferLength: 30,
@ -12,6 +14,6 @@ export const streamConfig: Partial<HlsConfig> = {
}, },
} }
export const REWIND_SECONDS = 5 export const REWIND_SECONDS = isMobileDevice ? 10 : 5
export const HOUR_IN_MILLISECONDS = 60 * 60 * 1000 export const HOUR_IN_MILLISECONDS = 60 * 60 * 1000

@ -20,8 +20,6 @@ import { useLiveMatch } from 'features/MatchPage/components/LiveMatch/hooks'
import type { Chapters } from 'features/StreamPlayer/types' import type { Chapters } from 'features/StreamPlayer/types'
import { MatchInfo } from 'requests/getMatchInfo'
import { REWIND_SECONDS } from '../config' import { REWIND_SECONDS } from '../config'
import { useHlsPlayer } from './useHlsPlayer' import { useHlsPlayer } from './useHlsPlayer'
import { useFullscreen } from './useFullscreen' import { useFullscreen } from './useFullscreen'
@ -50,11 +48,10 @@ const initialState = {
export type Props = { export type Props = {
chapters: Chapters, chapters: Chapters,
isLive?: boolean, isLive: boolean,
onDurationChange?: (duration: number) => void, onDurationChange?: (duration: number) => void,
onPlayingChange: (playing: boolean) => void, onPlayingChange: (playing: boolean) => void,
onProgressChange: (seconds: number) => void, onProgressChange: (seconds: number) => void,
profile?: MatchInfo,
resumeFrom?: number, resumeFrom?: number,
url?: string, url?: string,
} }
@ -414,7 +411,7 @@ export const useVideoPlayer = ({
url, url,
videoRef, videoRef,
wrapperRef, wrapperRef,
...useControlsVisibility(isFullscreen), ...useControlsVisibility(isFullscreen, playing),
...useVolume(), ...useVolume(),
...useVideoQuality(hls), ...useVideoQuality(hls),
} }

@ -1,40 +1,56 @@
import { import { useCallback, useMemo } from 'react'
useMemo,
useState,
useCallback,
} from 'react'
import debounce from 'lodash/debounce' import debounce from 'lodash/debounce'
export const useControlsVisibility = (isFullscreen: boolean) => { import { useToggle } from 'hooks'
const [controlsVisible, setControlsVisibility] = useState(true)
const show = useCallback(() => { export const useControlsVisibility = (isFullscreen: boolean, playing: boolean) => {
setControlsVisibility(true) const {
}, []) close: hideCenterControls,
isOpen: centerControlsVisible,
open: showCenterControls,
} = useToggle(playing)
const hide = useCallback(() => { const hideCenterDebounced = useMemo(() => (
setControlsVisibility(false) debounce(hideCenterControls, 2000)
}, []) ), [hideCenterControls])
const showCenter = useCallback(() => {
if (playing) {
showCenterControls()
hideCenterDebounced()
} else {
showCenterControls()
}
}, [hideCenterDebounced, playing, showCenterControls])
const {
close: hideMainControls,
isOpen: mainControlsVisible,
open: showMainControls,
} = useToggle(true)
const hideDebounced = useMemo( const hideDebounced = useMemo(
() => debounce(hide, 3000), () => debounce(hideMainControls, 3000),
[hide], [hideMainControls],
) )
const onMouseMove = () => { const onMouseMove = () => {
show() showMainControls()
if (isFullscreen) { if (isFullscreen) {
hideDebounced() hideDebounced()
} }
} }
return { return {
controlsVisible, centerControlsVisible,
onMouseEnter: show, hideCenterControls,
onMouseLeave: hide, mainControlsVisible,
onMouseEnter: showMainControls,
onMouseLeave: hideMainControls,
onMouseMove, onMouseMove,
onTouchEnd: hideDebounced, onTouchEnd: hideDebounced,
onTouchStart: show, onTouchStart: showMainControls,
showCenterControls: showCenter,
} }
} }

@ -1,62 +1,49 @@
import { Fragment } from 'react'
import { T9n } from 'features/T9n'
import { Loader } from 'features/Loader' import { Loader } from 'features/Loader'
import { VideoPlayer } from 'features/VideoPlayer' import { VideoPlayer } from 'features/VideoPlayer'
import { useMatchPageStore } from 'features/MatchPage/store'
import { Name } from 'features/Name'
import { WaterMark } from 'components/WaterMark' import { WaterMark } from 'components/WaterMark'
import { secondsToHms } from 'helpers' import { isMobileDevice } from 'config/userAgent'
import { HOUR_IN_MILLISECONDS, REWIND_SECONDS } from './config' import { REWIND_SECONDS } from './config'
import { VolumeBar } from './components/VolumeBar'
import { Settings } from './components/Settings'
import { ProgressBar } from './components/ProgressBar'
import { import {
PlayerWrapper, PlayerWrapper,
Controls,
ControlsRow,
ControlsGroup,
CenterControls, CenterControls,
PlayStop, PlayStop,
Fullscreen,
LoaderWrapper, LoaderWrapper,
Backward, Backward,
Forward, Forward,
PlaybackTime,
ControlsGradient, ControlsGradient,
LiveBtn, TeamsDetailsWrapper,
ChaptersText,
Next,
Prev,
// WaterMark,
} from './styled' } from './styled'
import type { Props } from './hooks' import type { Props } from './hooks'
import { useVideoPlayer } from './hooks' import { useVideoPlayer } from './hooks'
import { useAuthStore } from '../AuthStore' import { useAuthStore } from '../AuthStore'
import { Controls } from './components/Controls'
import RewindMobile from './components/RewindMobile'
/** /**
* HLS плеер, применяется на лайв и завершенных матчах * HLS плеер, применяется на лайв и завершенных матчах
*/ */
export const StreamPlayer = (props: Props) => { export const StreamPlayer = (props: Props) => {
const { isLive, profile } = props const { profile } = useMatchPageStore()
const { user } = useAuthStore() const { user } = useAuthStore()
const { const {
activeChapterIndex, activeChapterIndex,
allPlayedProgress,
backToLive, backToLive,
buffering, buffering,
centerControlsVisible,
chapters, chapters,
controlsVisible,
duration, duration,
isFirstChapterPlaying, hideCenterControls,
isFullscreen, isFullscreen,
isLastChapterPlaying,
loadedProgress, loadedProgress,
mainControlsVisible,
muted, muted,
numberOfChapters,
onDuration, onDuration,
onError, onError,
onFullscreenClick, onFullscreenClick,
@ -79,13 +66,12 @@ export const StreamPlayer = (props: Props) => {
onWaiting, onWaiting,
playedProgress, playedProgress,
playing, playing,
playNextChapter,
playPrevChapter,
ready, ready,
rewindBackward, rewindBackward,
rewindForward, rewindForward,
seek, seek,
selectedQuality, selectedQuality,
showCenterControls,
togglePlaying, togglePlaying,
url, url,
videoQualities, videoQualities,
@ -98,7 +84,7 @@ export const StreamPlayer = (props: Props) => {
<PlayerWrapper <PlayerWrapper
ref={wrapperRef} ref={wrapperRef}
playing={playing} playing={playing}
onClick={onPlayerClick} onClick={isMobileDevice ? showCenterControls : onPlayerClick}
onMouseMove={onMouseMove} onMouseMove={onMouseMove}
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
@ -135,100 +121,65 @@ export const StreamPlayer = (props: Props) => {
onError={onError} onError={onError}
crossOrigin='use-credentials' crossOrigin='use-credentials'
/> />
{isMobileDevice && isFullscreen && mainControlsVisible && profile && (
<TeamsDetailsWrapper>
<Name nameObj={profile.team1} />
{` ${profile.team1.score}-${profile.team2.score} `}
<Name nameObj={profile.team2} />
</TeamsDetailsWrapper>
)}
{ready && ( {ready && (
<CenterControls playing={playing}> <CenterControls controlsVisible={centerControlsVisible} playing={playing}>
<Backward size='lg' onClick={rewindBackward}> {isMobileDevice
{REWIND_SECONDS} ? <RewindMobile isBackward rewindCallback={rewindBackward} />
</Backward> : <Backward size='lg' onClick={rewindBackward}>{REWIND_SECONDS}</Backward>}
<PlayStop <PlayStop
size='lg' size='lg'
marginRight={0} marginRight={0}
playing={playing} playing={playing}
onClick={togglePlaying} onClickCapture={() => {
togglePlaying()
hideCenterControls()
}}
/> />
<Forward size='lg' onClick={rewindForward}> {isMobileDevice
{REWIND_SECONDS} ? <RewindMobile isForward rewindCallback={rewindForward} />
</Forward> : <Forward size='lg' onClick={rewindForward}>{REWIND_SECONDS}</Forward>}
</CenterControls> </CenterControls>
)} )}
<Controls visible={controlsVisible}> <Controls
<ControlsRow> allPlayedProgress={playedProgress}
<ProgressBar backToLive={backToLive}
activeChapterIndex={activeChapterIndex} controlsVisible={mainControlsVisible}
allPlayedProgress={allPlayedProgress}
duration={duration} duration={duration}
chapters={chapters} isFullscreen={isFullscreen}
onPlayedProgressChange={onProgressChange} isLive={profile?.live}
playedProgress={playedProgress} isStorage={profile?.storage}
loadedProgress={loadedProgress} loadedProgress={loadedProgress}
/>
</ControlsRow>
<ControlsRow>
<ControlsGroup>
<PlayStop onClickCapture={togglePlaying} playing={playing} />
{
numberOfChapters > 1 && (
<Fragment>
<Prev
disabled={isFirstChapterPlaying}
onClick={() => playPrevChapter()}
/>
<ChaptersText>
{activeChapterIndex + 1} / {numberOfChapters}
</ChaptersText>
<Next
disabled={isLastChapterPlaying}
onClick={() => playNextChapter()}
/>
</Fragment>
)
}
<VolumeBar
value={volumeInPercent}
muted={muted} muted={muted}
onChange={onVolumeChange} onFullscreenClick={onFullscreenClick}
onClick={onVolumeClick} onProgressChange={onProgressChange}
/> onQualitySelect={onQualitySelect}
{ onTouchEnd={onTouchEnd}
isLive onTouchStart={onTouchStart}
? ( onVolumeChange={onVolumeChange}
<PlaybackTime> onVolumeClick={onVolumeClick}
{secondsToHms(allPlayedProgress / 1000)} playedProgress={playedProgress}
</PlaybackTime> playing={playing}
) rewindBackward={rewindBackward}
: ( rewindForward={rewindForward}
<PlaybackTime width={duration > HOUR_IN_MILLISECONDS ? 150 : 130}>
{secondsToHms(allPlayedProgress / 1000)}
{' / '}
{secondsToHms(duration / 1000)}
</PlaybackTime>
)
}
<Backward onClick={rewindBackward}>{REWIND_SECONDS}</Backward>
<Forward onClick={rewindForward}>{REWIND_SECONDS}</Forward>
</ControlsGroup>
<ControlsGroup>
{
isLive && (
<LiveBtn onClick={backToLive}>
<T9n t='live' />
</LiveBtn>
)
}
<Settings
onSelect={onQualitySelect}
selectedQuality={selectedQuality} selectedQuality={selectedQuality}
togglePlaying={togglePlaying}
videoQualities={videoQualities} videoQualities={videoQualities}
volume={volume}
volumeInPercent={volumeInPercent}
activeChapterIndex={activeChapterIndex}
liveChapters={chapters}
/> />
<Fullscreen <ControlsGradient isVisible={mainControlsVisible} />
onClick={onFullscreenClick}
isFullscreen={isFullscreen}
/>
</ControlsGroup>
</ControlsRow>
</Controls>
<ControlsGradient />
</PlayerWrapper> </PlayerWrapper>
) )
} }

@ -170,10 +170,13 @@ export const PlayStop = styled(ButtonBase)<PlayStopProps>`
)}; )};
${isMobileDevice ${isMobileDevice
? css` ? css`
width: 20%; width: ${sizes.sm}px;
height: 60%; height: ${sizes.sm}px;
margin-right: 0; margin-right: 0;
padding: 0; padding: 0;
position: absolute;
left: 50%;
transform: translate(-50%, 0);
` `
: ''}; : ''};
` `
@ -271,6 +274,7 @@ export const PlaybackTime = styled.span<PlaybackTimeProps>`
` `
type CenterControlsProps = { type CenterControlsProps = {
controlsVisible: boolean,
playing: boolean, playing: boolean,
} }
@ -287,11 +291,14 @@ export const CenterControls = styled.div<CenterControlsProps>`
transition: opacity 0.3s ease-in-out; transition: opacity 0.3s ease-in-out;
opacity: ${({ playing }) => (playing ? 0 : 1)}; opacity: ${({ playing }) => (playing ? 0 : 1)};
pointer-events: ${({ playing }) => (playing ? 'none' : 'auto')}; pointer-events: ${({ playing }) => (playing ? 'none' : 'auto')};
${isMobileDevice
? css` ${({ controlsVisible, playing }) => isMobileDevice && css`
width: 70%; width: 100%;
` height: 100%;
: ''}; overflow: hidden;
opacity: ${controlsVisible || !playing ? 1 : 0};
pointer-events: ${controlsVisible || !playing ? 'auto' : 'none'};
`}
` `
export const LiveBtn = styled(ButtonBase)` export const LiveBtn = styled(ButtonBase)`

@ -35,6 +35,7 @@ export const CardWrapper = styled.div<{
padding: 10px; padding: 10px;
width: 100%; width: 100%;
height: 50px; height: 50px;
justify-content: space-between;
` `
: ''}; : ''};
` `
@ -61,7 +62,7 @@ export const CountMatches = styled.span<{ color?: string }>`
color: ${({ color }) => color}; color: ${({ color }) => color};
width: 20px; width: 20px;
text-align: end; text-align: end;
margin: 0 9px 0 20px; margin: 0 9px 0 10px;
` `
export const ScFirstInfo = styled.div` export const ScFirstInfo = styled.div`
@ -69,7 +70,8 @@ export const ScFirstInfo = styled.div`
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
width: 80%; margin-right: 10px;
overflow: hidden;
` `
export const ScSecondInfo = styled.div` export const ScSecondInfo = styled.div`
@ -77,7 +79,6 @@ export const ScSecondInfo = styled.div`
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: flex-end; justify-content: flex-end;
width: 100%;
` `
export const ScMatchesWrapper = styled.ul` export const ScMatchesWrapper = styled.ul`

@ -2,7 +2,7 @@ import { Fragment } from 'react'
import { SportIcon } from 'components/SportIcon/SportIcon' import { SportIcon } from 'components/SportIcon/SportIcon'
import { useName } from 'features/Name' import { Name } from 'features/Name'
import { useUserFavoritesStore } from 'features/UserFavorites/store' import { useUserFavoritesStore } from 'features/UserFavorites/store'
import { ProfileTypes, SportTypes } from 'config' import { ProfileTypes, SportTypes } from 'config'
@ -12,12 +12,13 @@ import { usePageParams } from 'hooks/usePageParams'
import { TournamentType } from 'requests' import { TournamentType } from 'requests'
import { isMatchPage } from 'helpers/isMatchPage'
import { import {
CountryFlag, CountryFlag,
FavoriteSign, FavoriteSign,
NameSignWrapper,
Wrapper, Wrapper,
TournamentName, StyledLink,
} from './styled' } from './styled'
type Props = { type Props = {
@ -33,7 +34,6 @@ export const TournamentSubtitle = ({
}: Props) => { }: Props) => {
const { isInFavorites } = useUserFavoritesStore() const { isInFavorites } = useUserFavoritesStore()
const { sportType: sportTypeFromUrl } = usePageParams() const { sportType: sportTypeFromUrl } = usePageParams()
const tournamentName = useName(tournament)
const tournamentInFavorites = isInFavorites(ProfileTypes.TOURNAMENTS, tournament.id) const tournamentInFavorites = isInFavorites(ProfileTypes.TOURNAMENTS, tournament.id)
return ( return (
@ -44,14 +44,16 @@ export const TournamentSubtitle = ({
<CountryFlag src={`https://instatscout.com/images/flags/48/${countryId}.png`} /> <CountryFlag src={`https://instatscout.com/images/flags/48/${countryId}.png`} />
</Fragment> </Fragment>
)} )}
{tournament && ( <StyledLink
<NameSignWrapper> id={tournament.id}
<TournamentName isLeftSide={isLffClient} title={tournamentName}> isLeftSide={isLffClient}
{tournamentName} isMatchPage={isMatchPage()}
</TournamentName> profileType={ProfileTypes.TOURNAMENTS}
sportType={sportType ?? sportTypeFromUrl}
>
<Name nameObj={tournament} />
</StyledLink>
{tournamentInFavorites && <FavoriteSign marginLeft={12} />} {tournamentInFavorites && <FavoriteSign marginLeft={12} />}
</NameSignWrapper>
)}
</Wrapper> </Wrapper>
) )
} }

@ -2,6 +2,8 @@ import styled, { css } from 'styled-components'
import { isMobileDevice } from 'config/userAgent' import { isMobileDevice } from 'config/userAgent'
import { ProfileLink } from 'features/ProfileLink'
export const Wrapper = styled.div` export const Wrapper = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;
@ -13,6 +15,7 @@ export const CountryFlag = styled.img`
margin-left: 0.567rem; margin-left: 0.567rem;
object-fit: contain; object-fit: contain;
object-position: bottom; object-position: bottom;
${isMobileDevice ${isMobileDevice
? css` ? css`
width: 12px; width: 12px;
@ -27,31 +30,32 @@ export const NameSignWrapper = styled.div`
max-width: 90%; max-width: 90%;
align-items: center; align-items: center;
` `
type StyledLinkProps = {
const nameStyles = css` isMatchPage?: boolean,
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
`
type TTournamentName = {
isLeftSide?: boolean, isLeftSide?: boolean,
} }
export const TournamentName = styled.span<TTournamentName>` export const StyledLink = styled(ProfileLink)<StyledLinkProps>`
color: rgba(255, 255, 255, 0.7); color: rgba(255, 255, 255, 0.7);
font-size: 0.567rem; font-size: 0.567rem;
line-height: 0.95rem; line-height: 0.95rem;
margin-left: ${({ isLeftSide }) => (isLeftSide ? '0px' : '0.567rem')}; margin-left: ${({ isLeftSide }) => (isLeftSide ? '0px' : '0.567rem')};
&:hover {
text-decoration: underline;
color: rgba(255, 255, 255, 1);
}
${({ isMatchPage }) => (isMatchPage
? css`
font-size: 14px;
${isMobileDevice ${isMobileDevice
? css` ? css`
font-size: 10px; font-size: 10px;
line-height: 100%;
max-width: 100%;
` `
: ''}; : ''};
` : null)}
${nameStyles}
` `
type FavoriteSignProps = { type FavoriteSignProps = {

Loading…
Cancel
Save