feat(#ott-2698): add filters popup

keep-around/b214ac7012ef42593bee62c207888a2593bc5a38
alexnofoget 3 years ago
parent 873718947f
commit 291d44c858
  1. 3
      public/images/boldArrowGray.svg
  2. 3
      public/images/boldArrowWhite.svg
  3. 3
      public/images/filter-gray.svg
  4. 3
      public/images/filter-white.svg
  5. 6
      public/images/likeDisable.svg
  6. 11
      src/config/lexics/indexLexics.tsx
  7. 3
      src/features/MatchPage/components/FinishedMatch/index.tsx
  8. 103
      src/features/MatchPage/store/hooks/index.tsx
  9. 101
      src/features/MatchPage/store/hooks/useFitersPopup.tsx
  10. 80
      src/features/MatchPage/store/hooks/useTabEvents.tsx
  11. 1
      src/features/MatchPage/styled.tsx
  12. 11
      src/features/MatchSidePlaylists/components/EventsList/index.tsx
  13. 145
      src/features/MatchSidePlaylists/components/FiltersPopup/index.tsx
  14. 199
      src/features/MatchSidePlaylists/components/FiltersPopup/styled.tsx
  15. 175
      src/features/MatchSidePlaylists/components/TabEvents/index.tsx
  16. 156
      src/features/MatchSidePlaylists/components/TabEvents/styled.tsx
  17. 5
      src/features/MatchSidePlaylists/hooks.tsx
  18. 2
      src/features/MatchSidePlaylists/index.tsx
  19. 17
      src/features/MultiSourcePlayer/hooks/index.tsx
  20. 4
      src/features/MultiSourcePlayer/index.tsx
  21. 2
      src/features/Name/index.tsx
  22. 4
      src/features/StreamPlayer/components/YoutubePlayer/index.tsx
  23. 12
      src/features/StreamPlayer/index.tsx

@ -0,0 +1,3 @@
<svg width="11" height="9" viewBox="0 0 11 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.63397 0.5C5.01887 -0.166667 5.98113 -0.166667 6.36603 0.5L10.2631 7.25C10.648 7.91667 10.1669 8.75 9.39711 8.75L1.60289 8.75C0.833085 8.75 0.35196 7.91667 0.73686 7.25L4.63397 0.5Z" fill="white" fill-opacity="0.6"/>
</svg>

After

Width:  |  Height:  |  Size: 329 B

@ -0,0 +1,3 @@
<svg width="11" height="9" viewBox="0 0 11 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.36603 8.5C5.98113 9.16667 5.01887 9.16667 4.63397 8.5L0.73686 1.75C0.35196 1.08333 0.833086 0.249999 1.60289 0.249999L9.39712 0.25C10.1669 0.25 10.648 1.08333 10.2631 1.75L6.36603 8.5Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 314 B

@ -0,0 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.9488 0.332386C12.0452 0.565341 12.0055 0.764205 11.8296 0.928977L7.63399 5.13068V11.4545C7.63399 11.6932 7.52336 11.8608 7.30209 11.9574C7.22833 11.9858 7.15741 12 7.08933 12C6.93614 12 6.80849 11.946 6.70636 11.8381L4.5277 9.65625C4.41991 9.5483 4.36601 9.42045 4.36601 9.27273V5.13068L0.170385 0.928977C-0.00549628 0.764205 -0.0452114 0.565341 0.0512396 0.332386C0.147691 0.110795 0.315062 0 0.553353 0H11.4466C11.6849 0 11.8523 0.110795 11.9488 0.332386Z" fill="white" fill-opacity="0.6"/>
</svg>

After

Width:  |  Height:  |  Size: 609 B

@ -0,0 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.9488 0.332386C12.0452 0.565341 12.0055 0.764205 11.8296 0.928977L7.63399 5.13068V11.4545C7.63399 11.6932 7.52336 11.8608 7.30209 11.9574C7.22833 11.9858 7.15741 12 7.08933 12C6.93614 12 6.80849 11.946 6.70636 11.8381L4.5277 9.65625C4.41991 9.5483 4.36601 9.42045 4.36601 9.27273V5.13068L0.170385 0.928977C-0.00549628 0.764205 -0.0452114 0.565341 0.0512396 0.332386C0.147691 0.110795 0.315062 0 0.553353 0H11.4466C11.6849 0 11.8523 0.110795 11.9488 0.332386Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 590 B

@ -0,0 +1,6 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.6">
<path d="M6.775 0C6.22677 0 6.41113 1.18396 6.41113 1.18396C6.41113 1.18396 5.23948 4.44905 4.11876 5.31925C3.87861 5.57418 3.72821 5.85608 3.6336 6.0865C3.61177 6.14288 3.59237 6.1968 3.57538 6.24583C3.50019 6.40026 3.34008 6.63803 2.99805 6.84639L4.11876 11.771C4.11876 11.771 5.8532 11.999 7.59249 11.9622C8.28869 12.0186 9.02613 12.0235 9.61317 11.896C11.6072 11.467 11.1075 10.0625 11.1075 10.0625C12.1821 9.24618 11.5708 8.22645 11.5708 8.22645C12.5265 7.21898 11.5878 6.3733 11.5878 6.3733C11.5878 6.3733 12.1045 5.55947 11.4374 4.94421C10.6053 4.17451 8.34691 4.68682 8.34691 4.68682C8.18923 4.71379 8.02185 4.74811 7.84235 4.79223C7.84235 4.79223 7.06124 5.15992 7.84235 2.76503C8.62588 0.370142 7.32323 0 6.775 0Z" fill="white"/>
<path d="M2.98857 11.4158L2.13954 7.33935C2.09103 7.10648 1.85815 6.91528 1.62285 6.91528H0.00242578L0 11.8374H2.64653C2.88426 11.8399 3.03708 11.6487 2.98857 11.4158Z" fill="white"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@ -38,6 +38,16 @@ const matchPopupLexics = {
watching_now: 16041,
}
const filterPopup = {
all_actions: 547,
apply_filter: 15117,
episodes_selected: 19779,
filter: 8385,
filter_match_events: 15166,
watch_all: 2454,
}
const confirmPopup = {
consent: 15431,
i_agree: 15430,
@ -163,6 +173,7 @@ export const indexLexics = {
watch_from_last_pause: 13022,
watch_now: 13020,
...filterPopup,
...confirmPopup,
...highlightsPageLexic,
...preferencesPopupLexics,

@ -14,7 +14,7 @@ import { MatchDescription } from '../MatchDescription'
import { useMatchPageStore } from '../../store'
export const FinishedMatch = () => {
const { profile } = useMatchPageStore()
const { isOpenPopup, profile } = useMatchPageStore()
const {
chapters,
closeSettingsPopup,
@ -44,6 +44,7 @@ export const FinishedMatch = () => {
{!isEmpty(chapters) && (
<Fragment>
<MultiSourcePlayer
isOpenPopup={isOpenPopup}
chapters={chapters}
onPlayingChange={onPlayingChange}
profile={profile}

@ -3,6 +3,11 @@ import {
useState,
useMemo,
} from 'react'
import includes from 'lodash/includes'
import filter from 'lodash/filter'
import isEmpty from 'lodash/isEmpty'
import { useToggle } from 'hooks'
import type { MatchInfo } from 'requests/getMatchInfo'
@ -14,6 +19,8 @@ import { parseDate } from 'helpers/parseDate'
import { useTournamentData } from './useTournamentData'
import { useMatchData } from './useMatchData'
import { useFiltersPopup } from './useFitersPopup'
import { useTabEvents } from './useTabEvents'
import type { Playlists } from '../../types'
@ -45,6 +52,23 @@ export const useMatchPage = () => {
open: showProfileCard,
} = useToggle(true)
const {
activeEvents,
activeFirstTeamPlayers,
activeSecondTeamPlayers,
applyFilters,
close: closePopup,
countOfFilters,
filters,
isEmptyFilters,
isOpen: isOpenPopup,
resetEvents,
resetPlayers,
toggle: togglePopup,
toggleActiveEvents,
toggleActivePlayers,
} = useFiltersPopup()
useEffect(() => {
getMatchInfo(sportType, matchId).then(setMatchProfile)
}, [sportType, matchId])
@ -79,17 +103,96 @@ export const useMatchPage = () => {
const { tournamentData } = useTournamentData(matchProfile?.tournament.id ?? null)
const filteredEvents = useMemo(() => {
switch (true) {
case (!isEmpty(filters.events) && !isEmpty(filters.players)):
return filter(events, (event) => includes(filters.players, event.p)
&& includes(filters.events, event.l))
case (!isEmpty(filters.players)):
return filter(events, (event) => includes(filters.players, event.p))
case (!isEmpty(filters.events)):
return filter(events, (event) => includes(filters.events, event.l))
default: return events
}
}, [events, filters])
const [plaingOrder, setPlaingOrder] = useState(0)
const [isPlayFilterEpisodes, setIsPlayinFiltersEpisodes] = useState(false)
const {
activeStatus,
episodesToPlay,
isLiveMatch,
likeImage,
likeToggle,
reversedGroupEvents,
setReversed,
setUnreversed,
} = useTabEvents({ events: filteredEvents, profile })
useEffect(() => {
if (plaingOrder > episodesToPlay.length) setPlaingOrder(0)
}, [plaingOrder, episodesToPlay])
const playNextEpisode = (order?: number) => {
const isLastEpisode = plaingOrder === episodesToPlay.length
const currentOrder = order === 0 ? order : plaingOrder
if (isLastEpisode) {
setIsPlayinFiltersEpisodes(false)
return
}
handlePlaylistClick(episodesToPlay[currentOrder])
setPlaingOrder(currentOrder + 1)
}
const playEpisodes = () => {
setIsPlayinFiltersEpisodes(true)
playNextEpisode(0)
}
const disablePlayingEpisodes = () => {
setIsPlayinFiltersEpisodes(false)
}
return {
activeEvents,
activeFirstTeamPlayers,
activeSecondTeamPlayers,
activeStatus,
applyFilters,
closePopup,
countOfFilters,
disablePlayingEpisodes,
events,
filteredEvents,
handlePlaylistClick,
hideProfileCard,
isEmptyFilters,
isLiveMatch,
isOpenPopup,
isPlayFilterEpisodes,
isStarted,
likeImage,
likeToggle,
matchPlaylists,
playEpisodes,
playNextEpisode,
profile,
profileCardShown,
resetEvents,
resetPlayers,
reversedGroupEvents,
selectedPlaylist,
setFullMatchPlaylistDuration,
setIsPlayinFiltersEpisodes,
setPlaingOrder,
setReversed,
setUnreversed,
showProfileCard,
toggleActiveEvents,
toggleActivePlayers,
togglePopup,
tournamentData,
}
}

@ -0,0 +1,101 @@
import { useMemo, useState } from 'react'
import includes from 'lodash/includes'
import filter from 'lodash/filter'
import isEmpty from 'lodash/isEmpty'
import size from 'lodash/size'
type TTogglePlayers = {
id: Number,
team: String,
}
type TFilters = {
events: Array<Number>,
players: Array<Number>,
}
export const useFiltersPopup = () => {
const [isOpen, setIsOpen] = useState(false)
const [activeEvents, setActiveEvents] = useState<Array<Number>>([])
const [activeFirstTeamPlayers, setActiveFirstTeamPlayers] = useState<Array<Number>>([])
const [activeSecondTeamPlayers, setActiveSecondTeamPlayers] = useState<Array<Number>>([])
const [activeFilters, setActiveFilters] = useState<TFilters>({ events: [], players: [] })
const toggle = () => {
setIsOpen(!isOpen)
}
const close = () => {
setIsOpen(false)
}
const toggleActiveEvents = (id: number) => () => {
setActiveEvents(includes(activeEvents, id)
? [...filter(activeEvents, (item) => item !== id)]
: [...activeEvents, id])
}
const toggleActivePlayers = ({ id, team }: TTogglePlayers) => () => {
const teamState = team === 'team1'
? activeFirstTeamPlayers
: activeSecondTeamPlayers
const setterTeamState = team === 'team1'
? setActiveFirstTeamPlayers
: setActiveSecondTeamPlayers
setterTeamState(includes(teamState, id)
? [...filter(teamState, (item) => item !== id)]
: [...teamState, id])
}
const resetPlayers = (team: string) => () => {
const teamState = team === 'team1'
? activeFirstTeamPlayers
: activeSecondTeamPlayers
const setterTeamState = team === 'team1'
? setActiveFirstTeamPlayers
: setActiveSecondTeamPlayers
if (isEmpty(teamState)) return
setterTeamState([])
}
const resetEvents = () => {
if (isEmpty(activeEvents)) return
setActiveEvents([])
}
const applyFilters = () => {
setActiveFilters({
events: [...activeEvents],
players: [...activeFirstTeamPlayers, ...activeSecondTeamPlayers],
})
close()
}
const isEmptyFilters = useMemo(() => (
isEmpty(activeFilters.events) && isEmpty(activeFilters.players)), [activeFilters])
const countOfFilters = useMemo(() => (
size(activeFilters.events) + size(activeFilters.players)), [activeFilters])
return {
activeEvents,
activeFirstTeamPlayers,
activeSecondTeamPlayers,
applyFilters,
close,
countOfFilters,
filters: activeFilters,
isEmptyFilters,
isOpen,
resetEvents,
resetPlayers,
toggle,
toggleActiveEvents,
toggleActivePlayers,
}
}

@ -0,0 +1,80 @@
import {
useCallback,
useMemo,
useState,
} from 'react'
import map from 'lodash/map'
import groupBy from 'lodash/groupBy'
import reverse from 'lodash/reverse'
import filter from 'lodash/filter'
import values from 'lodash/values'
import flatten from 'lodash/flatten'
import { Events, MatchInfo } from 'requests'
import { useToggle } from 'hooks'
import { EventPlaylistOption } from 'features/MatchPage/types'
type TPropsTabEvents = {
events?: Events,
profile?: MatchInfo,
}
export const useTabEvents = ({
events,
profile,
}: TPropsTabEvents) => {
const [likesOnly, setLikesOnly] = useState(false)
const {
close: setUnreversed,
isOpen: areEventsReversed,
open: setReversed,
} = useToggle()
const likeToggle = useCallback(() => setLikesOnly((state) => !state), [])
const likeImage = likesOnly ? '/images/like-active-icon.svg' : '/images/likeDisable.svg'
const isLiveMatch = profile?.live
const reverseStatus = (areEventsReversed || isLiveMatch) && (areEventsReversed !== isLiveMatch)
const activeStatus = isLiveMatch ? areEventsReversed : !areEventsReversed
const reversedGroupEvents = useMemo(() => {
const eventsList = likesOnly ? filter(events, 'like') : events
const groupedEvents = values(
groupBy(
reverseStatus
? reverse([...eventsList!])
: eventsList,
({ h }) => h,
),
)
return reverseStatus
? reverse(groupedEvents)
: groupedEvents
}, [
events,
likesOnly,
reverseStatus,
])
const episodesToPlay = map(flatten(reversedGroupEvents), ((event) => ({
episodes: [{
e: event.e,
h: event.h,
s: event.s,
}],
id: event.n,
type: 3,
}))) as Array<EventPlaylistOption>
return {
activeStatus,
episodesToPlay,
isLiveMatch,
likeImage,
likeToggle,
reversedGroupEvents,
setReversed,
setUnreversed,
}
}

@ -32,6 +32,7 @@ export const Container = styled.div`
max-height: 896px;
display: flex;
flex-direction: column;
position: relative;
@media ${devices.tablet} {
flex-grow: 0;

@ -21,6 +21,7 @@ import {
} from '../TabEvents/styled'
type Props = {
disablePlayingEpisodes?: () => void,
events: Events,
onSelect: (option: PlaylistOption) => void,
profile: MatchInfo,
@ -28,6 +29,7 @@ type Props = {
}
export const EventsList = ({
disablePlayingEpisodes,
events,
onSelect,
profile,
@ -64,13 +66,20 @@ export const EventsList = ({
: profile?.team2
const eventWithoutClick = isLffClient && profile?.live
const eventClick = () => {
!eventWithoutClick && onSelect(eventPlaylist)
if (disablePlayingEpisodes) {
disablePlayingEpisodes()
}
}
return (
<Event key={event.n}>
<EventButton
active={isEqual(eventPlaylist, selectedPlaylist)}
event={event}
isHomeTeam={isHomeTeam}
onClick={() => !eventWithoutClick && onSelect(eventPlaylist)}
onClick={eventClick}
team={team}
/>
</Event>

@ -0,0 +1,145 @@
import uniq from 'lodash/uniq'
import map from 'lodash/map'
import isEmpty from 'lodash/isEmpty'
import includes from 'lodash/includes'
import filter from 'lodash/filter'
import { T9n } from 'features/T9n'
import { useMatchPageStore } from 'features/MatchPage/store'
import { PlayerPlaylistOption } from 'features/MatchPage/types'
import { CloseButton } from 'features/PopupComponents'
import { Name, useName } from 'features/Name'
import {
Checkbox,
CloseButtonContainer,
PartBlock,
HeaderText,
ItemBlock,
ItemsContainer,
ItemText,
MainCheckboxContainer,
PopupContainer,
PopupHeader,
Button,
ButtonConatiner,
PlayerNumber,
} from './styled'
type TLabelProps = {
player: PlayerPlaylistOption,
}
const Label = ({ player }: TLabelProps) => {
const { num } = player
return (
<ItemText>
<PlayerNumber>
{num}
</PlayerNumber>
<Name nameObj={player} />
</ItemText>
)
}
export const FiltersPopup = () => {
const {
activeEvents,
activeFirstTeamPlayers,
activeSecondTeamPlayers,
applyFilters,
closePopup,
events,
matchPlaylists,
profile,
resetEvents,
resetPlayers,
toggleActiveEvents,
toggleActivePlayers,
} = useMatchPageStore()
const currentEvents = filter(events, (event) => event.pl !== undefined
&& event.t !== undefined)
const uniqEvents = uniq(map(currentEvents, ({ l }) => l))
const team1Name = useName(profile!.team1)
const team2Name = useName(profile!.team2)
return (
<PopupContainer>
<CloseButtonContainer>
<CloseButton onClick={closePopup} />
</CloseButtonContainer>
<PopupHeader>
<HeaderText>
<T9n t='filter' />
</HeaderText>
</PopupHeader>
<PartBlock>
<MainCheckboxContainer>
<Checkbox
onChange={resetEvents}
checked={isEmpty(activeEvents)}
labelLexic='all_actions'
/>
</MainCheckboxContainer>
<ItemsContainer>
{map(uniqEvents, ((event) => (
<ItemBlock key={event}>
<Checkbox
checked={includes(activeEvents, event)}
onChange={toggleActiveEvents(event)}
labelLexic={event}
/>
</ItemBlock>
)))}
</ItemsContainer>
</PartBlock>
<PartBlock>
<MainCheckboxContainer>
<Checkbox
checked={isEmpty(activeFirstTeamPlayers)}
label={team1Name}
onChange={resetPlayers('team1')}
/>
</MainCheckboxContainer>
<ItemsContainer>
{map(matchPlaylists.players.team1, ((player) => (
<ItemBlock key={player.id}>
<Checkbox
onChange={toggleActivePlayers({ id: player.id, team: 'team1' })}
checked={includes(activeFirstTeamPlayers, player.id)}
label={(<Label player={player} />)}
/>
</ItemBlock>
)))}
</ItemsContainer>
</PartBlock>
<PartBlock>
<MainCheckboxContainer>
<Checkbox
checked={isEmpty(activeSecondTeamPlayers)}
label={team2Name}
onChange={resetPlayers('team2')}
/>
</MainCheckboxContainer>
<ItemsContainer>
{map(matchPlaylists.players.team2, ((player) => (
<ItemBlock key={player.id}>
<Checkbox
onChange={toggleActivePlayers({ id: player.id, team: 'team2' })}
checked={includes(activeSecondTeamPlayers, player.id)}
label={(<Label player={player} />)}
/>
</ItemBlock>
)))}
</ItemsContainer>
</PartBlock>
<ButtonConatiner>
<Button onClick={applyFilters}>
<T9n t='apply_filter' />
</Button>
</ButtonConatiner>
</PopupContainer>
)
}

@ -0,0 +1,199 @@
import styled, { css } from 'styled-components/macro'
import { Checkbox as BaseCheckbox } from 'features/Common/Checkbox'
import { Label } from 'features/Common/Checkbox/styled'
import { CheckboxSvg } from 'features/Common/Checkbox/Icon'
import { customScrollbar } from 'features/Common'
import { NameStyled } from 'features/Name'
import { isMobileDevice } from 'config/userAgent'
import { BaseButton } from 'features/PopupComponents'
export const PopupContainer = styled.div`
background: #333333;
box-shadow: 0px 2px 40px rgba(0, 0, 0, 0.6);
border-radius: 2px;
display: flex;
flex-direction: column;
position: absolute;
overflow-y: auto;
z-index: 1000;
height: 100%;
right: 0;
top: 0;
${customScrollbar}
${isMobileDevice
? css`
position: fixed;
height: calc(100vh - 40px);
width: 100%;
padding-bottom: 70px;
top: 40px;`
: css`
width: 651px;`}
`
export const PopupHeader = styled.div`
flex: 0 0 auto;
${isMobileDevice
? css`
height: auto;
padding: 12px 0px 0px 0px;
margin-bottom: 14px;`
: css`
height: 50px;
padding: 17px 22px 0px 22px;`}
`
export const CloseButtonContainer = styled.div`
position: absolute;
top: 14px;
right: 12px;
${BaseButton} {
top: -4px;
right:0 ;
}
`
export const HeaderText = styled.div`
font-family: 'Montserrat';
font-style: normal;
font-weight: 700;
text-transform: uppercase;
color: #FFFFFF;
${isMobileDevice
? css`
font-size: 12px;
line-height: 15px;
text-align: center;`
: css`
font-size: 18px;
line-height: 22px;
margin-bottom: 21px;`}
`
export const PartBlock = styled.div`
flex: 1 0 auto;
border-bottom: 1px solid #505050;
padding: 17px 22px 17px 22px;
`
export const ItemsContainer = styled.ul`
display: flex;
flex-direction: row;
flex-wrap: wrap;
`
export const MainCheckboxContainer = styled.div`
margin-bottom: 15px;
display: flex;
`
export const ItemBlock = styled.div`
flex: 0 0 33.33%;
margin-bottom: 4px;
${isMobileDevice
? css`
flex: 0 0 50%;`
: css`
flex: 0 0 33.33%;`}
`
export const Checkbox = styled(BaseCheckbox)`
height: 28px;
display: block;
${Label} {
height: 100%;
font-style: normal;
font-weight: 400;
${isMobileDevice
? css`
font-size: 12px;
line-height: 18px;`
: css`
font-size: 14px;
line-height: 16px;`}
${({ checked }) => (checked
? css``
: css`
color: rgba(255, 255, 255, 0.6);`
)}
}
${CheckboxSvg} {
margin-right: 8px;
width: 20px;
height: 20px;
}
`
export const ItemText = styled.div`
display: flex;
height: 100%;
align-items: center;
${NameStyled} {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 130px;
}
${isMobileDevice
? css`
width: 110px;`
: css`
width: 130px;`}
`
export const TeamBlock = styled.div``
export const ButtonConatiner = styled.div`
display: flex;
align-items: center;
justify-content: center;
flex: 0 0 66px;
${isMobileDevice
? css`
position: fixed;
bottom: 14px;
left: 50%;
transform: translate(-50%, 0);`
: css``}
`
export const Button = styled.button`
height: 41px;
background: #294FC4;
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3);
border-radius: 5px;
border: none;
cursor: pointer;
font-size: 15px;
line-height: 50px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
color: #ffff;
${isMobileDevice
? css`
width: 301px;`
: css`
width: 167px;`}
`
export const PlayerNumber = styled.span`
margin-right: 8px;
font-size: 11px;
line-height: 16px;
font-weight: 600;
`

@ -1,21 +1,14 @@
import {
Fragment,
useCallback,
useMemo,
useState,
} from 'react'
import { Fragment } from 'react'
import groupBy from 'lodash/groupBy'
import isEmpty from 'lodash/isEmpty'
import map from 'lodash/map'
import reverse from 'lodash/reverse'
import filter from 'lodash/filter'
import values from 'lodash/values'
import flatten from 'lodash/flatten'
import size from 'lodash/size'
import { T9n } from 'features/T9n'
import type { PlaylistOption } from 'features/MatchPage/types'
import { useToggle } from 'hooks'
import type { Events, MatchInfo } from 'requests'
import { useMatchPageStore } from 'features/MatchPage/store'
import type { MatchInfo } from 'requests'
import { EventsList } from '../EventsList'
import {
@ -26,109 +19,105 @@ import {
Tabs,
Tab,
LikeToggle,
Filters,
ButtonsBlock,
HoverTooltip,
EpisodesCount,
SelectedEpisodes,
WatchButton,
} from './styled'
type Props = {
events: Events,
onSelect: (option: PlaylistOption) => void,
profile: MatchInfo,
selectedPlaylist?: PlaylistOption,
}
export const TabEvents = ({
events,
onSelect,
profile,
selectedPlaylist,
}: Props) => {
const {
close: setUnreversed,
isOpen: areEventsReversed,
open: setReversed,
} = useToggle()
const [likesOnly, setLikesOnly] = useState(false)
const likeToggle = useCallback(() => setLikesOnly((state) => !state), [])
const likeImage = likesOnly ? '/images/like-active-icon.svg' : '/images/like-icon.svg'
const isLiveMatch = profile?.live
const reverseStatus = (areEventsReversed || isLiveMatch) && (areEventsReversed !== isLiveMatch)
const activeStatus = isLiveMatch ? areEventsReversed : !areEventsReversed
const reversedGroupEvents = useMemo(() => {
const eventsList = likesOnly ? filter(events, 'like') : events
const groupedEvents = values(
groupBy(
reverseStatus
? reverse([...eventsList])
: eventsList,
({ h }) => h,
),
)
return reverseStatus
? reverse(groupedEvents)
: groupedEvents
}, [
events,
likesOnly,
reverseStatus,
])
activeStatus,
countOfFilters,
disablePlayingEpisodes,
isEmptyFilters,
isLiveMatch,
likeImage,
likeToggle,
playEpisodes,
reversedGroupEvents,
setReversed,
setUnreversed,
togglePopup,
} = useMatchPageStore()
if (!profile) return null
return (
<Wrapper>
{isEmpty(events)
? (
<BlockTitle>
<T9n t='no_data' />
</BlockTitle>
)
: (
<Fragment>
<Tabs>
<Tab
active={activeStatus}
onClick={isLiveMatch ? setReversed : setUnreversed}
>
<T9n t='from_start_match' />
</Tab>
<Tab
active={!activeStatus}
onClick={isLiveMatch ? setUnreversed : setReversed}
>
<T9n t='from_end_match' />
</Tab>
</Tabs>
<LikeToggle src={likeImage} onClick={likeToggle} />
<Fragment>
<ButtonsBlock>
<Tabs>
<T9n t={activeStatus ? 'from_start_match' : 'from_end_match'} />
<Tab
active={activeStatus}
onClick={isLiveMatch ? setReversed : setUnreversed}
/>
<Tab
active={!activeStatus}
onClick={isLiveMatch ? setUnreversed : setReversed}
/>
</Tabs>
<LikeToggle src={likeImage} onClick={likeToggle} />
<Filters active={!isEmptyFilters} onClick={togglePopup}>
<HoverTooltip>
<T9n t='filter_match_events' />
</HoverTooltip>
</Filters>
</ButtonsBlock>
{countOfFilters !== 0 && size(flatten(reversedGroupEvents)) !== 0 && (
<SelectedEpisodes>
<EpisodesCount>
{size(flatten(reversedGroupEvents))} <T9n t='episodes_selected' />
</EpisodesCount>
<WatchButton onClick={playEpisodes}>
<T9n t='watch_all' />
</WatchButton>
</SelectedEpisodes>
)}
{isEmpty(reversedGroupEvents)
? (
<BlockTitle>
<T9n t='no_data' />
</BlockTitle>
) : (
<HalfList>
{
map(reversedGroupEvents, (halfEvents, idx) => {
const firstEvent = halfEvents[0]
const isFirstBlock = idx === 0
{map(reversedGroupEvents, (halfEvents, idx) => {
const firstEvent = halfEvents[0]
const isFirstBlock = idx === 0
return (
<HalfEvents key={firstEvent.h}>
{
!isFirstBlock && (
<BlockTitle>
<T9n t='half_time' />
</BlockTitle>
)
}
<EventsList
events={halfEvents}
onSelect={onSelect}
profile={profile}
selectedPlaylist={selectedPlaylist}
/>
</HalfEvents>
)
})
}
return (
<HalfEvents key={firstEvent.h}>
{!isFirstBlock && (
<BlockTitle>
<T9n t='half_time' />
</BlockTitle>
)}
<EventsList
disablePlayingEpisodes={disablePlayingEpisodes}
events={halfEvents}
onSelect={onSelect}
profile={profile}
selectedPlaylist={selectedPlaylist}
/>
</HalfEvents>
)
})}
</HalfList>
</Fragment>
)}
)}
</Fragment>
</Wrapper>
)
}

@ -4,7 +4,8 @@ import { isMobileDevice } from 'config/userAgent'
import { ProfileLogo } from 'features/ProfileLogo'
import { Tabs as TabsBase, Tab as TabBase } from '../PlayersPlaylists/styled'
import { Tabs as TabsBase } from '../PlayersPlaylists/styled'
import {
BlockTitle as BlockTitleBase,
Button as ButtonBase,
@ -52,7 +53,7 @@ type AvatarProps = {
isPlayer: boolean,
}
export const Avatar = styled(ProfileLogo)<AvatarProps>`
export const Avatar = styled(ProfileLogo) <AvatarProps>`
position: absolute;
width: 35px;
height: 43px;
@ -138,40 +139,56 @@ export const EventTime = styled(Title)`
export const Tabs = styled(TabsBase)`
margin-top: 0px;
margin-bottom: 18px;
font-style: normal;
font-weight: 400;
font-size: 12px;
letter-spacing: -0.32px;
color: #ffff;
flex: 1 1 auto;
${isMobileDevice ? 'padding-left: 33px;' : 'padding-left: 20px;'}
`
export const Tab = styled(TabBase)`
text-transform: none;
type TTab = {
active?: boolean,
}
:first-child {
margin-right: 15px;
export const Tab = styled.span<TTab>`
text-transform: none;
border: none;
cursor: pointer;
width: 13px;
height: 13px;
:nth-child(2) {
margin-left: 3px;
}
${isMobileDevice
${({ active }) => (active
? css`
font-size: 14px;
@media (orientation: portrait) {
width: 120px;
}
background: url(/images/boldArrowWhite.svg) no-repeat;
background-size: 13px 13px;
:nth-child(3) {
transform: rotate(180deg);
}
`
: css`
background: url(/images/boldArrowGray.svg) no-repeat;
background-size: 13px 13px;
:nth-child(2) {
transform: rotate(180deg);
}
`
: ''}
)}
`
export const LikeToggle = styled.img`
position: absolute;
right: 0;
top: 9px;
width: 15px;
height: 15px;
width: 12px;
height: 12px;
cursor: pointer;
${isMobileDevice
? css`
right: 20px;
`
: ''};
`
export const BlockTitle = styled(BlockTitleBase)`
@ -185,7 +202,7 @@ type ButtonProps = {
like?: boolean,
}
export const Button = styled(ButtonBase)<ButtonProps>`
export const Button = styled(ButtonBase) <ButtonProps>`
${({ like }) => (
like ? css`
background: url(/images/event-like-bg.png);
@ -212,3 +229,90 @@ export const Button = styled(ButtonBase)<ButtonProps>`
: ''
)}
`
export const ButtonsBlock = styled.div`
position: relative;
display: flex;
width: 100%;
height: 20px;
margin-bottom: 7px;
`
export const HoverTooltip = styled.span`
display: flex;
background: #FFFFFF;
border-radius: 6px;
left: 8px;
top: 30px;
transform: translate(-100%, -50%);
font-weight: 600;
font-size: 15px;
text-align: center;
flex-direction: column;
position: absolute;
justify-content: center;
align-items: center;
min-width: 160px;
height: 21px;
text-decoration: none;
color: #000000;
opacity: 0;
transition: all 0.1s ease;
pointer-events: none;
z-index: 1000;
`
type TFilters = {
active?: boolean,
}
export const Filters = styled.span<TFilters>`
cursor: pointer;
width: 12px;
height: 12px;
position: relative;
margin-left: 15px;
${({ active }) => (active
? css`
background: url('/images/filter-white.svg') no-repeat;
`
: css`
background: url('/images/filter-gray.svg')no-repeat;
`)}
:hover ${HoverTooltip} {
opacity: 1;
z-index: 1;
}
`
export const SelectedEpisodes = styled.div`
display: flex;
gap: 18px;
`
export const EpisodesCount = styled.div`
font-weight: 600;
font-size: 12px;
line-height: 20px;
text-align: right;
letter-spacing: -0.32px;
color: rgba(255, 255, 255, 0.5);
`
export const WatchButton = styled.span`
font-weight: 600;
font-size: 12px;
height: 16px;
padding-left: 12px;
letter-spacing: -0.32px;
color: #ffff;
cursor: pointer;
background: url('/images/player-play.svg') left 2px no-repeat;
background-size: 9px 11px;
:hover {
box-shadow: 0 1px #ffff;
}
`

@ -12,6 +12,7 @@ import { Tabs } from './config'
export const useMatchSidePlaylists = () => {
const {
closePopup,
events,
matchPlaylists: playlists,
tournamentData,
@ -52,6 +53,10 @@ export const useMatchSidePlaylists = () => {
}
}, [isEventTabVisible, isVideoTabVisible, isWatchTabVisible])
useEffect(() => {
if (selectedTab !== Tabs.EVENTS) closePopup()
}, [selectedTab, closePopup])
return {
isEventTabVisible,
isVideoTabVisible,

@ -36,7 +36,6 @@ export const MatchSidePlaylists = ({
selectedPlaylist,
}: Props) => {
const {
events,
hideProfileCard,
matchPlaylists: playlists,
profile,
@ -105,7 +104,6 @@ export const MatchSidePlaylists = ({
<Container forVideoTab={selectedTab === Tabs.VIDEO}>
<TabPane
tournamentData={tournamentData}
events={events}
onSelect={onSelect}
playlists={playlists}
profile={profile}

@ -11,6 +11,7 @@ import { useControlsVisibility } from 'features/StreamPlayer/hooks/useControlsVi
import { useFullscreen } from 'features/StreamPlayer/hooks/useFullscreen'
import { useVolume } from 'features/VideoPlayer/hooks/useVolume'
import { useNoNetworkPopupStore } from 'features/NoNetworkPopup'
import { useMatchPageStore } from 'features/MatchPage/store'
import { useEventListener, useObjectState } from 'hooks'
@ -43,6 +44,7 @@ export type PlayerState = typeof initialState
export type Props = {
chapters: Chapters,
isOpenPopup?: boolean,
onError?: () => void,
onPlayingChange: (playing: boolean) => void,
profile: MatchInfo,
@ -53,6 +55,11 @@ export const useMultiSourcePlayer = ({
onError,
onPlayingChange,
}: Props) => {
const {
isPlayFilterEpisodes,
playNextEpisode,
} = useMatchPageStore()
const numberOfChapters = size(chapters)
const [
{
@ -180,7 +187,6 @@ export const useMultiSourcePlayer = ({
const onEnded = () => {
playNextChapter()
}
const onPause = () => {
setPlayerState({ playing: false })
}
@ -199,14 +205,21 @@ export const useMultiSourcePlayer = ({
useEffect(() => {
const { duration: chapterDuration } = getActiveChapter()
if (playedProgress >= chapterDuration && !seeking) {
if (playedProgress >= chapterDuration && !seeking && isPlayFilterEpisodes) {
setPlayerState({ playedProgress: 0 })
playNextEpisode()
}
if (playedProgress >= chapterDuration && !seeking && !isPlayFilterEpisodes) {
playNextChapter()
}
}, [
isPlayFilterEpisodes,
playNextEpisode,
getActiveChapter,
playedProgress,
seeking,
playNextChapter,
setPlayerState,
])
useEventListener({

@ -13,6 +13,7 @@ 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 { FiltersPopup } from 'features/MatchSidePlaylists/components/FiltersPopup'
import { isMobileDevice } from 'config/userAgent'
@ -22,7 +23,7 @@ import { Players } from './types'
import { REWIND_SECONDS } from './config'
export const MultiSourcePlayer = (props: Props) => {
const { profile } = props
const { isOpenPopup, profile } = props
const {
activeChapterIndex,
activePlayer,
@ -94,6 +95,7 @@ export const MultiSourcePlayer = (props: Props) => {
</LoaderWrapper>
)
}
{isOpenPopup && <FiltersPopup />}
<VideoPlayer
src={firstPlayerActive ? activeSrc : nextSrc}
playing={firstPlayerActive ? playing : false}

@ -14,7 +14,7 @@ type Props = {
prefix?: string,
}
const NameStyled = styled.span``
export const NameStyled = styled.span``
type Args = {
nameObj: ObjectWithName,

@ -1,12 +1,13 @@
import YouTube from 'react-youtube'
import { useMatchPageStore } from 'features/MatchPage/store'
import { FiltersPopup } from 'features/MatchSidePlaylists/components/FiltersPopup'
import { PlayerWrapper } from '../../styled'
import { useVideoPlayer, Props } from '../../hooks'
export const YoutubePlayer = (props: Props) => {
const { profile } = useMatchPageStore()
const { isOpenPopup, profile } = useMatchPageStore()
const {
onMouseMove,
@ -33,6 +34,7 @@ export const YoutubePlayer = (props: Props) => {
onTouchStart={onTouchStart}
onTouchEnd={onTouchEnd}
>
{isOpenPopup && <FiltersPopup />}
<YouTube
videoId={key}
opts={{

@ -2,6 +2,7 @@ import { Loader } from 'features/Loader'
import { VideoPlayer } from 'features/VideoPlayer'
import { useMatchPageStore } from 'features/MatchPage/store'
import { Name } from 'features/Name'
import { FiltersPopup } from 'features/MatchSidePlaylists/components/FiltersPopup'
import { WaterMark } from 'components/WaterMark'
@ -29,7 +30,7 @@ import RewindMobile from './components/RewindMobile'
* HLS плеер, применяется на лайв и завершенных матчах
*/
export const StreamPlayer = (props: Props) => {
const { profile } = useMatchPageStore()
const { isOpenPopup, profile } = useMatchPageStore()
const { user } = useAuthStore()
const {
@ -91,14 +92,15 @@ export const StreamPlayer = (props: Props) => {
onTouchStart={onTouchStart}
onTouchEnd={onTouchEnd}
>
{isOpenPopup && <FiltersPopup />}
<LoaderWrapper buffering={buffering}>
<Loader color='#515151' />
</LoaderWrapper>
{(profile?.tournament.id === 1136)
&& playing
&& (
<WaterMark value={user?.profile?.sub} />
)}
&& playing
&& (
<WaterMark value={user?.profile?.sub} />
)}
<VideoPlayer
width='100%'
height='100%'

Loading…
Cancel
Save