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. 62
      src/features/MatchPage/components/MatchDescription/index.tsx
  10. 62
      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. 14
      src/features/MatchSidePlaylists/components/TabVideo/styled.tsx
  29. 17
      src/features/MatchSidePlaylists/hooks.tsx
  30. 59
      src/features/MatchSidePlaylists/index.tsx
  31. 45
      src/features/MatchSidePlaylists/styled.tsx
  32. 10
      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. 14
      src/features/StreamPlayer/components/ProgressBar/index.tsx
  42. 29
      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. 175
      src/features/StreamPlayer/index.tsx
  50. 27
      src/features/StreamPlayer/styled.tsx
  51. 7
      src/features/TournamentList/components/TournamentMobile/styled.tsx
  52. 26
      src/features/TournamentSubtitle/index.tsx
  53. 36
      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;
width: 9px;
height: 9px;
margin-left: 10px;
`
: ''};
@ -96,17 +97,6 @@ export const DateButton = styled(BaseButton) <DateButtonProps>`
? 'opacity: 1;'
: ''
)}
${isMobileDevice
? css`
/* right: 14px;
width: 16px;
height: 16px; */
@media screen and (orientation: landscape){
/* right: 20px; */
}
`
: ''};
`
export const WeekDaysWrapper = styled.div`

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

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

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

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

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

@ -1,43 +1,54 @@
import { useCallback } from 'react'
import { format } from 'date-fns'
import includes from 'lodash/includes'
import type { MatchInfo } from 'requests/getMatchInfo'
import type { Team } from 'requests/getMatchInfo'
import { Name } from 'features/Name'
import { SportIcon } from 'components/SportIcon/SportIcon'
import { getName, Name } from 'features/Name'
import { useAuthStore } from 'features/AuthStore'
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 { ProfileTypes } from 'config'
import { isMobileDevice } from 'config/userAgent'
import { usePageParams } from 'hooks/usePageParams'
import {
CountryFlag,
Description,
DescriptionInnerBlock,
MatchDate,
StyledLink,
Score,
Title,
Tournament,
Views,
Time,
} from './styled'
type Props = {
profile: MatchInfo,
}
export const MatchDescription = ({
profile,
}: Props) => {
export const MatchDescription = () => {
const { sportType } = usePageParams()
const { user } = useAuthStore()
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 />
@ -50,12 +61,12 @@ export const MatchDescription = ({
} = profile
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),
isChangedTimeFormat ? 'h:mm aaa' : 'HH:mm')
isChangedTimeFormat ? 'h:mm a' : 'HH:mm')
return (
<Description>
<Description isHidden={!profileCardShown}>
<DescriptionInnerBlock>
<Title>
<StyledLink
@ -63,7 +74,7 @@ export const MatchDescription = ({
profileType={ProfileTypes.TEAMS}
sportType={sportType}
>
<Name nameObj={team1} />
{getTeamName(team1)}
</StyledLink>
<Score>
{
@ -77,22 +88,13 @@ export const MatchDescription = ({
profileType={ProfileTypes.TEAMS}
sportType={sportType}
>
<Name nameObj={team2} />
{getTeamName(team2)}
</StyledLink>
</Title>
<Tournament>
<SportIcon sport={sportType} />
<CountryFlag
src={`https://instatscout.com/images/flags/48/${country_id}.png`}
/>
<StyledLink
id={tournament.id}
profileType={ProfileTypes.TOURNAMENTS}
sportType={sportType}
>
<Name nameObj={tournament} />
</StyledLink>
</Tournament>
<TournamentSubtitle
countryId={country_id}
tournament={tournament}
/>
</DescriptionInnerBlock>
<Views>
<Time>{changedTimeFormat}</Time>

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

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

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

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

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

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

@ -1,5 +1,7 @@
import styled, { css } from 'styled-components/macro'
import { isMobileDevice } from 'config/userAgent'
export const Wrapper = styled.div`
color: #FFFFFF;
display: flex;
@ -10,6 +12,14 @@ export const Wrapper = styled.div`
> :not(:last-child) {
margin-right: 20px;
}
${isMobileDevice ? css`
@media screen and (orientation: landscape){
> :not(:last-child) {
margin-right: 3px;
}
}
` : ''}
`
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 { isMobileDevice } from '../../../../config/userAgent'
export const MatchesWrapper = styled.div`
overflow-y: scroll;
overflow-y: auto;
max-height: calc(100vh - 170px);
padding-right: 5px;
> * {
margin-bottom: 15px;
}
${customScrollbar}
${isMobileDevice ? css`
overflow: hidden;
max-height: initial;
` : ''}
`

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

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

@ -6,14 +6,12 @@ import { isMobileDevice } from 'config/userAgent'
import { customScrollbar } from 'features/Common'
export const Wrapper = styled.div`
${isMobileDevice
${isMobileDevice
? css`
@media (orientation: landscape){
min-width: 250px;
}
@media (max-width: 569px) and (orientation: landscape){
min-width: 200px;
}
overflow-y: auto;
width: 100%;
${customScrollbar}
`
: ''};
`
@ -21,7 +19,6 @@ ${isMobileDevice
export const TabsWrapper = styled.div`
padding-left: 14px;
padding-right: 18px;
position: relative;
${isMobileDevice
? css`
@ -39,20 +36,20 @@ export const Container = styled.div<TContainer>`
margin-top: 14px;
padding: 0 10px 0 14px;
max-height: calc(100vh - 170px);
overflow-y: ${({ forVideoTab }) => (forVideoTab ? 'none' : 'scroll')};
overflow-y: ${({ forVideoTab }) => (forVideoTab ? 'hidden' : 'auto')};
${customScrollbar}
@media ${devices.tablet} {
max-height: calc(100vh - 40px);
margin-top: 15px;
}
${isMobileDevice
? css`
padding: 0 5px;
overflow-y: auto;
overflow-y: hidden;
max-height: initial;
@media (max-width: 750px){
width: 100%;
}
@ -138,27 +135,3 @@ export const BlockTitle = styled.span`
color: rgba(255, 255, 255, 0.5);
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 { 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 type { Props } from './hooks'
@ -35,9 +35,11 @@ export const ProgressBar = (props: ProgressBarProps) => {
<ProgressBarList ref={progressBarRef}>
<Chapters chapters={calculatedChapters} />
{isScrubberVisible === false ? null : (
<Scrubber style={{ left: `${playedProgressInPercent}%` }}>
<TimeTooltip time={time} />
</Scrubber>
<ScrubberContainer>
<Scrubber style={{ left: `${playedProgressInPercent}%` }}>
<TimeTooltip time={time} />
</Scrubber>
</ScrubberContainer>
)}
</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

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

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

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

@ -179,7 +179,7 @@ export const ScModal = styled(BaseModal)`
height: auto;
${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`
width: 100%;
@ -23,6 +25,12 @@ export const LoadedProgress = styled.div`
background-color: rgba(255, 255, 255, 0.6);
height: 100%;
max-width: 100%;
${isMobileDevice
? css`
background-color: rgba(255, 255, 255, 0.3);
`
: ''}
`
export const PlayedProgress = styled.div`
@ -31,4 +39,10 @@ export const PlayedProgress = styled.div`
background-color: #CC0000;
height: 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;
align-items: center;
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25));
bottom: 0;
bottom: ${({ isFullscreen }) => (isFullscreen ? '20px' : 0)};
@media (orientation: landscape){
bottom: ${({ isFullscreen }) => (isFullscreen ? '20px' : 0)};
width: 100%;
left: 50%;
transform: translateX(-50%);

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

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

@ -8,6 +8,23 @@ export const ProgressBarList = styled.div`
flex-grow: 1;
height: 4px;
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`
@ -15,24 +32,24 @@ export const Scrubber = styled.div`
outline: none;
position: absolute;
top: 0;
transform: translate(-50%, -43%);
transform: translate(-50%, -38%);
z-index: 3;
width: 18px;
height: 18px;
background-color: #CC0000;
border-radius: 50%;
cursor: pointer;
:hover ${Wrapper} {
visibility: visible;
}
${isMobileDevice
? css`
width: 30px;
height: 30px;
background-clip: padding-box;
border: 10px solid transparent;
width: 14px;
height: 14px;
background-color: #FFFFFF;
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 { useMatchPageStore } from 'features/MatchPage/store'
import { PlayerWrapper } from '../../styled'
import { useVideoPlayer, Props } from '../../hooks'
export const YoutubePlayer = (props: Props) => {
const { profile } = props
const { profile } = useMatchPageStore()
const {
onMouseMove,

@ -2,6 +2,8 @@ import type { HlsConfig } from 'hls.js'
import { readToken } from 'helpers/token'
import { isMobileDevice } from 'config/userAgent'
export const streamConfig: Partial<HlsConfig> = {
liveSyncDuration: 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

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

@ -1,40 +1,56 @@
import {
useMemo,
useState,
useCallback,
} from 'react'
import { useCallback, useMemo } from 'react'
import debounce from 'lodash/debounce'
export const useControlsVisibility = (isFullscreen: boolean) => {
const [controlsVisible, setControlsVisibility] = useState(true)
import { useToggle } from 'hooks'
const show = useCallback(() => {
setControlsVisibility(true)
}, [])
export const useControlsVisibility = (isFullscreen: boolean, playing: boolean) => {
const {
close: hideCenterControls,
isOpen: centerControlsVisible,
open: showCenterControls,
} = useToggle(playing)
const hide = useCallback(() => {
setControlsVisibility(false)
}, [])
const hideCenterDebounced = useMemo(() => (
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(
() => debounce(hide, 3000),
[hide],
() => debounce(hideMainControls, 3000),
[hideMainControls],
)
const onMouseMove = () => {
show()
showMainControls()
if (isFullscreen) {
hideDebounced()
}
}
return {
controlsVisible,
onMouseEnter: show,
onMouseLeave: hide,
centerControlsVisible,
hideCenterControls,
mainControlsVisible,
onMouseEnter: showMainControls,
onMouseLeave: hideMainControls,
onMouseMove,
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 { VideoPlayer } from 'features/VideoPlayer'
import { useMatchPageStore } from 'features/MatchPage/store'
import { Name } from 'features/Name'
import { WaterMark } from 'components/WaterMark'
import { secondsToHms } from 'helpers'
import { isMobileDevice } from 'config/userAgent'
import { HOUR_IN_MILLISECONDS, REWIND_SECONDS } from './config'
import { VolumeBar } from './components/VolumeBar'
import { Settings } from './components/Settings'
import { ProgressBar } from './components/ProgressBar'
import { REWIND_SECONDS } from './config'
import {
PlayerWrapper,
Controls,
ControlsRow,
ControlsGroup,
CenterControls,
PlayStop,
Fullscreen,
LoaderWrapper,
Backward,
Forward,
PlaybackTime,
ControlsGradient,
LiveBtn,
ChaptersText,
Next,
Prev,
// WaterMark,
TeamsDetailsWrapper,
} from './styled'
import type { Props } from './hooks'
import { useVideoPlayer } from './hooks'
import { useAuthStore } from '../AuthStore'
import { Controls } from './components/Controls'
import RewindMobile from './components/RewindMobile'
/**
* HLS плеер, применяется на лайв и завершенных матчах
*/
export const StreamPlayer = (props: Props) => {
const { isLive, profile } = props
const { profile } = useMatchPageStore()
const { user } = useAuthStore()
const {
activeChapterIndex,
allPlayedProgress,
backToLive,
buffering,
centerControlsVisible,
chapters,
controlsVisible,
duration,
isFirstChapterPlaying,
hideCenterControls,
isFullscreen,
isLastChapterPlaying,
loadedProgress,
mainControlsVisible,
muted,
numberOfChapters,
onDuration,
onError,
onFullscreenClick,
@ -79,13 +66,12 @@ export const StreamPlayer = (props: Props) => {
onWaiting,
playedProgress,
playing,
playNextChapter,
playPrevChapter,
ready,
rewindBackward,
rewindForward,
seek,
selectedQuality,
showCenterControls,
togglePlaying,
url,
videoQualities,
@ -98,7 +84,7 @@ export const StreamPlayer = (props: Props) => {
<PlayerWrapper
ref={wrapperRef}
playing={playing}
onClick={onPlayerClick}
onClick={isMobileDevice ? showCenterControls : onPlayerClick}
onMouseMove={onMouseMove}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
@ -135,100 +121,65 @@ export const StreamPlayer = (props: Props) => {
onError={onError}
crossOrigin='use-credentials'
/>
{isMobileDevice && isFullscreen && mainControlsVisible && profile && (
<TeamsDetailsWrapper>
<Name nameObj={profile.team1} />
{` ${profile.team1.score}-${profile.team2.score} `}
<Name nameObj={profile.team2} />
</TeamsDetailsWrapper>
)}
{ready && (
<CenterControls playing={playing}>
<Backward size='lg' onClick={rewindBackward}>
{REWIND_SECONDS}
</Backward>
<CenterControls controlsVisible={centerControlsVisible} playing={playing}>
{isMobileDevice
? <RewindMobile isBackward rewindCallback={rewindBackward} />
: <Backward size='lg' onClick={rewindBackward}>{REWIND_SECONDS}</Backward>}
<PlayStop
size='lg'
marginRight={0}
playing={playing}
onClick={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>
)}
<Controls visible={controlsVisible}>
<ControlsRow>
<ProgressBar
activeChapterIndex={activeChapterIndex}
allPlayedProgress={allPlayedProgress}
duration={duration}
chapters={chapters}
onPlayedProgressChange={onProgressChange}
playedProgress={playedProgress}
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}
onChange={onVolumeChange}
onClick={onVolumeClick}
/>
{
isLive
? (
<PlaybackTime>
{secondsToHms(allPlayedProgress / 1000)}
</PlaybackTime>
)
: (
<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}
videoQualities={videoQualities}
/>
<Fullscreen
onClick={onFullscreenClick}
isFullscreen={isFullscreen}
/>
</ControlsGroup>
</ControlsRow>
</Controls>
<ControlsGradient />
<Controls
allPlayedProgress={playedProgress}
backToLive={backToLive}
controlsVisible={mainControlsVisible}
duration={duration}
isFullscreen={isFullscreen}
isLive={profile?.live}
isStorage={profile?.storage}
loadedProgress={loadedProgress}
muted={muted}
onFullscreenClick={onFullscreenClick}
onProgressChange={onProgressChange}
onQualitySelect={onQualitySelect}
onTouchEnd={onTouchEnd}
onTouchStart={onTouchStart}
onVolumeChange={onVolumeChange}
onVolumeClick={onVolumeClick}
playedProgress={playedProgress}
playing={playing}
rewindBackward={rewindBackward}
rewindForward={rewindForward}
selectedQuality={selectedQuality}
togglePlaying={togglePlaying}
videoQualities={videoQualities}
volume={volume}
volumeInPercent={volumeInPercent}
activeChapterIndex={activeChapterIndex}
liveChapters={chapters}
/>
<ControlsGradient isVisible={mainControlsVisible} />
</PlayerWrapper>
)
}

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

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

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

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

Loading…
Cancel
Save