From 8e3238636d7038017625bedf79e3d1fbe48519d7 Mon Sep 17 00:00:00 2001 From: Ruslan Khayrullin Date: Wed, 11 Jan 2023 19:45:51 +0500 Subject: [PATCH] feat(in-140): playing episodes --- src/config/index.tsx | 1 + src/config/keyboardKeys.tsx | 3 + src/features/CircleAnimationBar/index.tsx | 43 +++- src/features/CircleAnimationBar/styled.tsx | 21 +- src/features/MatchCard/styled.tsx | 4 +- .../components/FinishedMatch/index.tsx | 10 +- .../components/LiveMatch/hooks/index.tsx | 14 +- .../MatchPage/components/LiveMatch/index.tsx | 10 +- .../MatchPage/helpers/getHalfTime.tsx | 52 ++++ src/features/MatchPage/store/hooks/index.tsx | 147 ++++++++--- .../MatchPage/store/hooks/useMatchData.tsx | 44 +--- .../MatchPage/store/hooks/usePlayersStats.tsx | 29 ++- .../MatchPage/store/hooks/useStatsTab.tsx | 125 ++++++++- .../MatchPage/store/hooks/useTeamsStats.tsx | 24 +- .../components/CircleAnimationBar/index.tsx | 4 +- .../components/PlayersTable/Cell.tsx | 24 +- .../components/PlayersTable/hooks/index.tsx | 15 +- .../PlayersTable/hooks/usePlayers.tsx | 10 +- .../PlayersTable/hooks/useTable.tsx | 65 ++++- .../components/PlayersTable/index.tsx | 238 ++++++++++-------- .../components/PlayersTable/styled.tsx | 58 +++-- .../components/PlayersTable/types.tsx | 3 - .../components/TabEvents/index.tsx | 25 +- .../components/TabStats/index.tsx | 10 +- .../components/TeamsStatsTable/Cell.tsx | 163 ++++++++++++ .../components/TeamsStatsTable/hooks.tsx | 28 +-- .../components/TeamsStatsTable/index.tsx | 83 ++---- .../components/TeamsStatsTable/styled.tsx | 26 +- .../components/TeamsStatsTable/types.tsx | 6 - src/features/MatchSidePlaylists/hooks.tsx | 10 +- src/features/MatchSidePlaylists/index.tsx | 9 +- src/features/MatchSidePlaylists/styled.tsx | 11 +- .../MultiSourcePlayer/hooks/index.tsx | 9 +- src/features/StreamPlayer/hooks/index.tsx | 18 +- src/features/StreamPlayer/index.tsx | 6 +- src/requests/getMatchInfo.tsx | 2 +- src/requests/getMatchParticipants.tsx | 5 +- src/requests/getPlayersStats.tsx | 4 +- src/requests/getStatsEvents.tsx | 57 +++++ src/requests/getTeamsStats.tsx | 4 +- src/requests/index.tsx | 1 + 41 files changed, 965 insertions(+), 456 deletions(-) create mode 100644 src/config/keyboardKeys.tsx create mode 100644 src/features/MatchPage/helpers/getHalfTime.tsx create mode 100644 src/features/MatchSidePlaylists/components/TeamsStatsTable/Cell.tsx delete mode 100644 src/features/MatchSidePlaylists/components/TeamsStatsTable/types.tsx create mode 100644 src/requests/getStatsEvents.tsx diff --git a/src/config/index.tsx b/src/config/index.tsx index 87f53711..2188fac4 100644 --- a/src/config/index.tsx +++ b/src/config/index.tsx @@ -10,3 +10,4 @@ export * from './currencies' export * from './dashes' export * from './env' export * from './userAgent' +export * from './keyboardKeys' diff --git a/src/config/keyboardKeys.tsx b/src/config/keyboardKeys.tsx new file mode 100644 index 00000000..97d3b125 --- /dev/null +++ b/src/config/keyboardKeys.tsx @@ -0,0 +1,3 @@ +export enum KEYBOARD_KEYS { + Enter = 'Enter', +} diff --git a/src/features/CircleAnimationBar/index.tsx b/src/features/CircleAnimationBar/index.tsx index 7139059d..4228993f 100644 --- a/src/features/CircleAnimationBar/index.tsx +++ b/src/features/CircleAnimationBar/index.tsx @@ -4,7 +4,7 @@ import { useEffect } from 'react' import isEmpty from 'lodash/isEmpty' import size from 'lodash/size' -import type { Events } from 'requests' +import { useMatchPageStore } from 'features/MatchPage/store' import { fullEpisodesDuration } from './helpers' import { Svg, Circle } from './styled' @@ -24,26 +24,33 @@ export const initialCircleAnimation: TCircleAnimation = { } type Props = { - circleAnimation?: TCircleAnimation, - filteredEvents: Events, - setWatchAllEpisodesTimer: (showTimer: boolean) => void, + className?: string, + size?: number, + text?: string, } export type TSetCircleAnimation = Dispatch> export const CircleAnimationBar = ({ - circleAnimation, - filteredEvents, - setWatchAllEpisodesTimer, + className, + size: svgSize = 14, + text, }: Props) => { + const { + circleAnimation, + filteredEvents, + setWatchAllEpisodesTimer, + } = useMatchPageStore() + const { plaingOrder, playedProgress, playing, ready, - } = circleAnimation! + } = circleAnimation + const timeOfAllEpisodes = fullEpisodesDuration(filteredEvents) - const remainingEvents = filteredEvents.slice(plaingOrder - 1) + const remainingEvents = filteredEvents.slice(plaingOrder && plaingOrder - 1) const fullTimeOfRemainingEpisodes = !isEmpty(remainingEvents) ? fullEpisodesDuration(remainingEvents) : 0 @@ -52,6 +59,8 @@ export const CircleAnimationBar = ({ const currentAnimationTime = Math.round(fullTimeOfRemainingEpisodes - (playedProgress / 1000)) const currentEpisodesPercent = 100 - (100 / (timeOfAllEpisodes / currentAnimationTime)) + const strokeDashOffset = svgSize * Math.PI + useEffect(() => { if (currentEpisodesPercent >= 100 && (plaingOrder === size(filteredEvents))) { setWatchAllEpisodesTimer(false) @@ -64,7 +73,10 @@ export const CircleAnimationBar = ({ ]) return ( - + + {text && ( + + {text} + + )} ) } diff --git a/src/features/CircleAnimationBar/styled.tsx b/src/features/CircleAnimationBar/styled.tsx index 230f5734..eb50140f 100644 --- a/src/features/CircleAnimationBar/styled.tsx +++ b/src/features/CircleAnimationBar/styled.tsx @@ -4,9 +4,12 @@ type TCircle = { animationPause?: boolean, currentAnimationTime: number, currentEpisodesPercent: number, + strokeDashOffset: number, } -const strokeDashOffset = 43.5 +type SvgProps = { + size: number, +} const clockAnimation = (currentEpisodesPercent?: number) => keyframes` from { @@ -17,10 +20,12 @@ const clockAnimation = (currentEpisodesPercent?: number) => keyframes` } ` -export const Svg = styled.svg` +export const Svg = styled.svg` + --size: ${({ size }) => `${size}px`}; + background-color: #5EB2FF; - width: 14px; - height: 14px; + width: var(--size); + height: var(--size); position: relative; border-radius: 50%; ` @@ -28,12 +33,12 @@ export const Svg = styled.svg` export const Circle = styled.circle` fill: transparent; stroke: white; - stroke-width: 14px; - stroke-dasharray: ${strokeDashOffset}; - stroke-dashoffset: ${strokeDashOffset}; + stroke-width: var(--size); + stroke-dasharray: ${({ strokeDashOffset }) => strokeDashOffset}; + stroke-dashoffset: ${({ strokeDashOffset }) => strokeDashOffset}; transform: rotate(-90deg); transform-origin: center; - animation-name: ${({ currentEpisodesPercent }) => ( + animation-name: ${({ currentEpisodesPercent, strokeDashOffset }) => ( clockAnimation(strokeDashOffset - (strokeDashOffset * currentEpisodesPercent / 100)) )}; animation-duration: ${({ currentAnimationTime }) => `${currentAnimationTime}s`}; diff --git a/src/features/MatchCard/styled.tsx b/src/features/MatchCard/styled.tsx index 3bab6d0d..37bad8db 100644 --- a/src/features/MatchCard/styled.tsx +++ b/src/features/MatchCard/styled.tsx @@ -108,7 +108,7 @@ export const Preview = styled.img` width: 100%; height: 100%; object-fit: cover; - opacity: 0.4; + opacity: 0.2; ` export const MatchTimeInfo = styled.div` @@ -243,7 +243,7 @@ export const Team = styled.span` justify-content: space-between; align-items: center; font-weight: 600; - font-size: 0.85rem; + font-size: 13px; line-height: 1.14rem; color: #fff; diff --git a/src/features/MatchPage/components/FinishedMatch/index.tsx b/src/features/MatchPage/components/FinishedMatch/index.tsx index 16aa7883..bd831561 100644 --- a/src/features/MatchPage/components/FinishedMatch/index.tsx +++ b/src/features/MatchPage/components/FinishedMatch/index.tsx @@ -1,9 +1,7 @@ -import { Fragment, useState } from 'react' +import { Fragment } from 'react' import isEmpty from 'lodash/isEmpty' -import type { TCircleAnimation } from 'features/CircleAnimationBar' -import { initialCircleAnimation } from 'features/CircleAnimationBar' import { MatchSidePlaylists } from 'features/MatchSidePlaylists' import { MultiSourcePlayer } from 'features/MultiSourcePlayer' @@ -16,12 +14,10 @@ import { MatchDescription } from '../MatchDescription' import { useMatchPageStore } from '../../store' export const FinishedMatch = () => { - const [circleAnimation, setCircleAnimation] = useState(initialCircleAnimation) const { access, isOpenFiltersPopup, profile, - setPlayingProgress, } = useMatchPageStore() const { chapters, @@ -53,11 +49,9 @@ export const FinishedMatch = () => { @@ -66,8 +60,6 @@ export const FinishedMatch = () => { diff --git a/src/features/MatchPage/components/LiveMatch/hooks/index.tsx b/src/features/MatchPage/components/LiveMatch/hooks/index.tsx index eab6f4b1..e41669e7 100644 --- a/src/features/MatchPage/components/LiveMatch/hooks/index.tsx +++ b/src/features/MatchPage/components/LiveMatch/hooks/index.tsx @@ -16,12 +16,9 @@ import { usePlaylistLogger } from './usePlaylistLogger' export const useLiveMatch = () => { const { handlePlaylistClick, - isPlayFilterEpisodes, - playNextEpisode, profile, selectedPlaylist, setFullMatchPlaylistDuration, - setPlayingProgress, } = useMatchPageStore() const { profileId: matchId, sportType } = usePageParams() const resume = useResumeUrlParam() @@ -46,7 +43,7 @@ export const useLiveMatch = () => { } = usePlaylistLogger() const { - onPlayerProgressChange: playerProgressChange, + onPlayerProgressChange, onPlayingChange: notifyProgressLogger, } = usePlayerProgressReporter() @@ -66,22 +63,13 @@ export const useLiveMatch = () => { } handlePlaylistClick(playlist, e) } - - const onPlayerProgressChange = (seconds: number, period = 0) => { - playerProgressChange(seconds, period) - setPlayingProgress(seconds * 1000) - } - return { chapters, - isPlayFilterEpisodes, onDurationChange, onPlayerProgressChange, onPlayingChange, onPlaylistSelect, - playNextEpisode, resume: resume ?? fromStartIfStreamPaused, - selectedPlaylist, streamUrl: `${API_ROOT}/video/stream/${sportType}/${matchId}.m3u8`, } } diff --git a/src/features/MatchPage/components/LiveMatch/index.tsx b/src/features/MatchPage/components/LiveMatch/index.tsx index 94dd47bd..7d5e39fd 100644 --- a/src/features/MatchPage/components/LiveMatch/index.tsx +++ b/src/features/MatchPage/components/LiveMatch/index.tsx @@ -1,9 +1,7 @@ -import { Fragment, useState } from 'react' +import { Fragment } from 'react' import isEmpty from 'lodash/isEmpty' -import type { TCircleAnimation } from 'features/CircleAnimationBar' -import { initialCircleAnimation } from 'features/CircleAnimationBar' import { useMatchPageStore } from 'features/MatchPage/store' import { StreamPlayer } from 'features/StreamPlayer' import { YoutubePlayer } from 'features/StreamPlayer/components/YoutubePlayer' @@ -15,8 +13,6 @@ import { useLiveMatch } from './hooks' import { MatchDescription } from '../MatchDescription' export const LiveMatch = () => { - const [circleAnimation, setCircleAnimation] = useState(initialCircleAnimation) - const { profile, selectedPlaylist, @@ -43,12 +39,10 @@ export const LiveMatch = () => { onProgressChange={onPlayerProgressChange} resumeFrom={resume} url={streamUrl} - setCircleAnimation={setCircleAnimation} /> ) : ( !isEmpty(chapters) && ( { diff --git a/src/features/MatchPage/helpers/getHalfTime.tsx b/src/features/MatchPage/helpers/getHalfTime.tsx new file mode 100644 index 00000000..5d458671 --- /dev/null +++ b/src/features/MatchPage/helpers/getHalfTime.tsx @@ -0,0 +1,52 @@ +import head from 'lodash/head' +import last from 'lodash/last' +import inRange from 'lodash/inRange' + +import type { VideoBounds } from 'requests' + +export const getHalfTime = (videoBounds: VideoBounds, currentTime: number) => { + const firstBound = head(videoBounds) + const lastBound = last(videoBounds) + + const matchSecond = (Number(firstBound?.s) || 0) + currentTime + + if (matchSecond > (Number(lastBound?.e) || 0)) { + return {} + } + + if (matchSecond < Number(videoBounds[1].s)) { + return { + period: 1, + second: 1, + } + } + + let period = 1 + let second = 1 + + for (let i = 1; i < videoBounds.length; i++) { + const { e, s } = videoBounds[i] + + if (inRange( + matchSecond, + Number(s), + Number(e) + 1, + )) { + period = i + second = matchSecond - Number(videoBounds[i].s) + break + } else if (inRange( + matchSecond, + Number(e) + 1, + Number(videoBounds[i + 1].s), + )) { + period = i + 1 + break + } + } + + return { + period, + second, + } +} diff --git a/src/features/MatchPage/store/hooks/index.tsx b/src/features/MatchPage/store/hooks/index.tsx index beb98bed..05a16c95 100644 --- a/src/features/MatchPage/store/hooks/index.tsx +++ b/src/features/MatchPage/store/hooks/index.tsx @@ -10,13 +10,15 @@ import isEmpty from 'lodash/isEmpty' import { FULL_GAME_KEY } from 'features/MatchPage/helpers/buildPlaylists' import { useAuthStore } from 'features/AuthStore' +import { Tabs } from 'features/MatchSidePlaylists/config' +import { initialCircleAnimation } from 'features/CircleAnimationBar' +import type { TCircleAnimation } from 'features/CircleAnimationBar' import type { MatchInfo } from 'requests/getMatchInfo' import { getMatchInfo } from 'requests/getMatchInfo' import { getViewMatchDuration } from 'requests/getViewMatchDuration' -import { usePageParams } from 'hooks/usePageParams' -import { useToggle } from 'hooks/useToggle' +import { usePageParams, useToggle } from 'hooks' import { parseDate } from 'helpers/parseDate' @@ -24,6 +26,31 @@ import { useTournamentData } from './useTournamentData' import { useMatchData } from './useMatchData' import { useFiltersPopup } from './useFitersPopup' import { useTabEvents } from './useTabEvents' +import { useTeamsStats } from './useTeamsStats' +import { useStatsTab } from './useStatsTab' +import { usePlayersStats } from './usePlayersStats' + +type PlayingData = { + player: { + id: number | null, + paramId: number | null, + }, + team: { + id: number | null, + paramId: number | null, + }, +} + +const initPlayingData: PlayingData = { + player: { + id: null, + paramId: null, + }, + team: { + id: null, + paramId: null, + }, +} const ACCESS_TIME = 60 @@ -31,6 +58,15 @@ export const useMatchPage = () => { const [matchProfile, setMatchProfile] = useState(null) const [watchAllEpisodesTimer, setWatchAllEpisodesTimer] = useState(false) const [access, setAccess] = useState(true) + const [playingProgress, setPlayingProgress] = useState(0) + const [playingData, setPlayingData] = useState(initPlayingData) + const [plaingOrder, setPlaingOrder] = useState(0) + const [isPlayFilterEpisodes, setIsPlayingFiltersEpisodes] = useState(false) + const [selectedTab, setSelectedTab] = useState(Tabs.WATCH) + const [circleAnimation, setCircleAnimation] = useState(initialCircleAnimation) + + const isStatsTab = selectedTab === Tabs.STATS + const { profileId: matchId, sportType } = usePageParams() const { user, userInfo } = useAuthStore() @@ -47,6 +83,7 @@ export const useMatchPage = () => { matchPlaylists, selectedPlaylist, setFullMatchPlaylistDuration, + setSelectedPlaylist, } = useMatchData(matchProfile) const profile = matchProfile @@ -63,9 +100,9 @@ export const useMatchPage = () => { filters, isAllActionsChecked, isEmptyFilters, + isFirstTeamPlayersChecked, isOpen: isOpenFiltersPopup, - resetEvents, - resetPlayers, + isSecondTeamPlayersChecked, toggle: togglePopup, toggleActiveEvents, toggleActivePlayers, @@ -131,24 +168,56 @@ export const useMatchPage = () => { return () => clearInterval(getIntervalMatch) }) + const disablePlayingEpisodes = () => { + setIsPlayingFiltersEpisodes(false) + setWatchAllEpisodesTimer(false) + setCircleAnimation(initialCircleAnimation) + } + const { - events, - handlePlaylistClick, - isEmptyPlayersStats, + circleAnimation: statsCircleAnimation, + filteredEvents: statsFilteredEvents, isPlayersStatsFetching, + isPlayFilterEpisodes: isStatsPlayFilterEpisodes, isTeamsStatsFetching, - matchPlaylists, + plaingOrder: statsPlaingOrder, + playEpisodes: playStatsEpisodes, + playNextEpisode: playStatsNextEpisode, + setCircleAnimation: setStatsCircleAnimation, + setIsPlayersStatsFetching, + setIsPlayingFiltersEpisodes: setStatsIsPlayinFiltersEpisodes, + setIsTeamsStatsFetching, + setPlaingOrder: setStatsPlaingOrder, + setWatchAllEpisodesTimer: setStatsWatchAllEpisodesTimer, + statsType, + toggleStatsType, + watchAllEpisodesTimer: statsWatchAllEpisodesTimer, + } = useStatsTab({ + disablePlayingEpisodes, + handlePlaylistClick, + selectedPlaylist, + setSelectedPlaylist, + }) + + const { teamsStats } = useTeamsStats({ + matchProfile, + playingProgress, + selectedPlaylist, + setIsTeamsStatsFetching, + statsType, + }) + + const { + isEmptyPlayersStats, playersData, playersStats, + } = usePlayersStats({ + matchProfile, + playingProgress, selectedPlaylist, - setFullMatchPlaylistDuration, - setPlayingProgress, + setIsPlayersStatsFetching, statsType, - teamsStats, - toggleStatsType, - } = useMatchData(matchProfile) - - const profile = matchProfile + }) const isStarted = useMemo(() => ( profile?.date @@ -171,9 +240,6 @@ export const useMatchPage = () => { } }, [events, filters]) - const [plaingOrder, setPlaingOrder] = useState(0) - const [isPlayFilterEpisodes, setIsPlayinFiltersEpisodes] = useState(false) - const { activeStatus, episodesToPlay, @@ -185,16 +251,12 @@ export const useMatchPage = () => { 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) + setIsPlayingFiltersEpisodes(false) return } @@ -202,11 +264,16 @@ export const useMatchPage = () => { setPlaingOrder(currentOrder + 1) } const playEpisodes = () => { + setPlayingData(initPlayingData) + setStatsWatchAllEpisodesTimer(true) + setStatsIsPlayinFiltersEpisodes(false) + setStatsCircleAnimation(initialCircleAnimation) + if (!watchAllEpisodesTimer) { setWatchAllEpisodesTimer(true) } - setIsPlayinFiltersEpisodes(true) + setIsPlayingFiltersEpisodes(true) if (matchProfile?.live) { handlePlaylistClick({ @@ -219,10 +286,6 @@ export const useMatchPage = () => { } } - const disablePlayingEpisodes = () => { - setIsPlayinFiltersEpisodes(false) - } - return { access, activeEvents, @@ -232,41 +295,53 @@ export const useMatchPage = () => { allActionsToggle, allPlayersToggle, applyFilters, + circleAnimation: isStatsTab ? statsCircleAnimation : circleAnimation, closePopup, countOfFilters, disablePlayingEpisodes, events, - filteredEvents, + filteredEvents: isStatsTab ? statsFilteredEvents : filteredEvents, handlePlaylistClick, hideProfileCard, isAllActionsChecked, isEmptyFilters, isEmptyPlayersStats, + isFirstTeamPlayersChecked, isLiveMatch, isOpenFiltersPopup, - isPlayFilterEpisodes, + isPlayFilterEpisodes: isStatsTab ? isStatsPlayFilterEpisodes : isPlayFilterEpisodes, isPlayersStatsFetching, + isSecondTeamPlayersChecked, isStarted, isTeamsStatsFetching, likeImage, likeToggle, matchPlaylists, - plaingOrder, + plaingOrder: isStatsTab ? statsPlaingOrder : plaingOrder, playEpisodes, - playNextEpisode, + playNextEpisode: isStatsTab ? playStatsNextEpisode : playNextEpisode, + playStatsEpisodes, playersData, playersStats, + playingData, + playingProgress, profile, profileCardShown, reversedGroupEvents, selectedPlaylist, + selectedTab, + setCircleAnimation: isStatsTab ? setStatsCircleAnimation : setCircleAnimation, setFullMatchPlaylistDuration, - setIsPlayinFiltersEpisodes, - setPlaingOrder, + setIsPlayingFiltersEpisodes: isStatsTab + ? setStatsIsPlayinFiltersEpisodes + : setIsPlayersStatsFetching, + setPlaingOrder: isStatsTab ? setStatsPlaingOrder : setPlaingOrder, + setPlayingData, setPlayingProgress, setReversed, + setSelectedTab, setUnreversed, - setWatchAllEpisodesTimer, + setWatchAllEpisodesTimer: isStatsTab ? setStatsWatchAllEpisodesTimer : setWatchAllEpisodesTimer, showProfileCard, statsType, teamsStats, @@ -277,6 +352,6 @@ export const useMatchPage = () => { tournamentData, uniqEvents, user, - watchAllEpisodesTimer, + watchAllEpisodesTimer: isStatsTab ? statsWatchAllEpisodesTimer : watchAllEpisodesTimer, } } diff --git a/src/features/MatchPage/store/hooks/useMatchData.tsx b/src/features/MatchPage/store/hooks/useMatchData.tsx index e98b53c4..892e274f 100644 --- a/src/features/MatchPage/store/hooks/useMatchData.tsx +++ b/src/features/MatchPage/store/hooks/useMatchData.tsx @@ -11,14 +11,12 @@ import type { MatchInfo } from 'requests/getMatchInfo' import { usePageParams } from 'hooks/usePageParams' import { useInterval } from 'hooks/useInterval' +import { FULL_GAME_KEY } from 'features/MatchPage/helpers/buildPlaylists' import { useDuration } from 'features/MultiSourcePlayer/hooks/useDuration' import { useMatchPopupStore } from 'features/MatchPopup' import { useMatchPlaylists } from './useMatchPlaylists' import { useEvents } from './useEvents' -import { useTeamsStats } from './useTeamsStats' -import { useStatsTab } from './useStatsTab' -import { usePlayersStats } from './usePlayersStats' const MATCH_DATA_POLL_INTERVAL = 60000 const MATCH_PLAYLISTS_DELAY = 5000 @@ -27,7 +25,6 @@ export const useMatchData = (profile: MatchInfo) => { const { profileId: matchId, sportType } = usePageParams() const { chapters } = useMatchPopupStore() const [matchDuration, setMatchDuration] = useState(0) - const [playingProgress, setPlayingProgress] = useState(0) const { fetchMatchPlaylists, handlePlaylistClick, @@ -39,33 +36,6 @@ export const useMatchData = (profile: MatchInfo) => { const { events, fetchMatchEvents } = useEvents() - const { - isPlayersStatsFetching, - isTeamsStatsFetching, - setIsPlayersStatsFetching, - setIsTeamsStatsFetching, - statsType, - toggleStatsType, - } = useStatsTab() - - const { teamsStats } = useTeamsStats({ - matchProfile: profile, - playingProgress, - setIsTeamsStatsFetching, - statsType, - }) - - const { - isEmptyPlayersStats, - playersData, - playersStats, - } = usePlayersStats({ - matchProfile: profile, - playingProgress, - setIsPlayersStatsFetching, - statsType, - }) - const fetchPlaylistsDebounced = useMemo( () => debounce(fetchMatchPlaylists, MATCH_PLAYLISTS_DELAY), [fetchMatchPlaylists], @@ -113,7 +83,7 @@ export const useMatchData = (profile: MatchInfo) => { }, [profile?.live, start, stop]) useEffect(() => { - selectedPlaylist?.id === 'full_game' && setMatchDuration(chaptersDuration) + selectedPlaylist?.id === FULL_GAME_KEY && setMatchDuration(chaptersDuration) // eslint-disable-next-line }, [profile, chaptersDuration]) @@ -125,17 +95,9 @@ export const useMatchData = (profile: MatchInfo) => { return { events, handlePlaylistClick, - isEmptyPlayersStats, - isPlayersStatsFetching, - isTeamsStatsFetching, matchPlaylists, - playersData, - playersStats, selectedPlaylist, setFullMatchPlaylistDuration, - setPlayingProgress, - statsType, - teamsStats, - toggleStatsType, + setSelectedPlaylist, } } diff --git a/src/features/MatchPage/store/hooks/usePlayersStats.tsx b/src/features/MatchPage/store/hooks/usePlayersStats.tsx index b5b377ad..285bc4ba 100644 --- a/src/features/MatchPage/store/hooks/usePlayersStats.tsx +++ b/src/features/MatchPage/store/hooks/usePlayersStats.tsx @@ -9,6 +9,7 @@ import throttle from 'lodash/throttle' import isEmpty from 'lodash/isEmpty' import every from 'lodash/every' import find from 'lodash/find' +import isUndefined from 'lodash/isUndefined' import type { MatchInfo, @@ -19,7 +20,10 @@ import { getPlayersStats, getMatchParticipants } from 'requests' import { useObjectState, usePageParams } from 'hooks' +import type{ PlaylistOption } from 'features/MatchPage/types' import { StatsType } from 'features/MatchSidePlaylists/components/TabStats/config' +import { FULL_GAME_KEY } from 'features/MatchPage/helpers/buildPlaylists' +import { getHalfTime } from 'features/MatchPage/helpers/getHalfTime' const REQUEST_DELAY = 3000 const STATS_POLL_INTERVAL = 30000 @@ -27,6 +31,7 @@ const STATS_POLL_INTERVAL = 30000 type UsePlayersStatsArgs = { matchProfile: MatchInfo, playingProgress: number, + selectedPlaylist?: PlaylistOption, setIsPlayersStatsFetching: Dispatch>, statsType: StatsType, } @@ -39,6 +44,7 @@ type PlayersData = { export const usePlayersStats = ({ matchProfile, playingProgress, + selectedPlaylist, setIsPlayersStatsFetching, statsType, }: UsePlayersStatsArgs) => { @@ -53,8 +59,6 @@ export const usePlayersStats = ({ const isCurrentStats = statsType === StatsType.CURRENT_STATS - const progressSec = Math.floor(playingProgress / 1000) - const isEmptyPlayersStats = (teamId: number) => ( isEmpty(playersStats[teamId]) || every(playersStats[teamId], isEmpty) @@ -62,13 +66,17 @@ export const usePlayersStats = ({ ) const fetchPlayers = useMemo(() => throttle(async (second?: number) => { - if (!matchProfile?.team1.id || !matchProfile?.team2.id) return null + if ( + !matchProfile?.team1.id + || !matchProfile?.team2.id + || !matchProfile?.video_bounds + ) return null try { return getMatchParticipants({ matchId, - second, sportType, + ...(!isUndefined(second) && getHalfTime(matchProfile.video_bounds, second)), }) } catch (e) { return Promise.reject(e) @@ -77,18 +85,19 @@ export const usePlayersStats = ({ matchId, matchProfile?.team1.id, matchProfile?.team2.id, + matchProfile?.video_bounds, sportType, ]) const fetchPlayersStats = useMemo(() => (async (team: 'team1' | 'team2', second?: number) => { - if (!sportName || !matchProfile?.[team].id) return null + if (!sportName || !matchProfile?.[team].id || !matchProfile?.video_bounds) return null try { return getPlayersStats({ matchId, - second, sportName, teamId: matchProfile[team].id, + ...(!isUndefined(second) && getHalfTime(matchProfile.video_bounds, second)), }) } catch (e) { return Promise.reject(e) @@ -102,6 +111,8 @@ export const usePlayersStats = ({ ]) const fetchData = useMemo(() => throttle(async (second?: number) => { + if (selectedPlaylist?.id !== FULL_GAME_KEY || !matchProfile?.video_bounds) return + const [res1, res2, res3] = await Promise.all([ fetchPlayers(second), fetchPlayersStats('team1', second), @@ -123,11 +134,13 @@ export const usePlayersStats = ({ setIsPlayersStatsFetching(false) }, REQUEST_DELAY), [ + selectedPlaylist?.id, fetchPlayers, fetchPlayersStats, setPlayersStats, matchProfile?.team1.id, matchProfile?.team2.id, + matchProfile?.video_bounds, setIsPlayersStatsFetching, ]) @@ -153,11 +166,11 @@ export const usePlayersStats = ({ useEffect(() => { if (isCurrentStats) { - fetchData(progressSec) + fetchData(playingProgress) } }, [ fetchData, - progressSec, + playingProgress, isCurrentStats, matchProfile?.live, ]) diff --git a/src/features/MatchPage/store/hooks/useStatsTab.tsx b/src/features/MatchPage/store/hooks/useStatsTab.tsx index 39064d06..f637198d 100644 --- a/src/features/MatchPage/store/hooks/useStatsTab.tsx +++ b/src/features/MatchPage/store/hooks/useStatsTab.tsx @@ -1,11 +1,62 @@ +import type { Dispatch, SetStateAction } from 'react' import { useState } from 'react' +import map from 'lodash/map' +import isEqual from 'lodash/isEqual' + +import { isIOS } from 'config' + +import type { + Episode, + Episodes, + Events, +} from 'requests' + +import type { EventPlaylistOption, PlaylistOption } from 'features/MatchPage/types' +import type { TCircleAnimation } from 'features/CircleAnimationBar' +import { initialCircleAnimation } from 'features/CircleAnimationBar' +import { PlaylistTypes } from 'features/MatchPage/types' import { StatsType } from 'features/MatchSidePlaylists/components/TabStats/config' -export const useStatsTab = () => { +type UseStatsTabArgs = { + disablePlayingEpisodes: () => void, + handlePlaylistClick: (playlist: PlaylistOption) => void, + selectedPlaylist?: PlaylistOption, + setSelectedPlaylist: Dispatch>, +} + +type PlayNextEpisodeArgs = { + episodesToPlay?: Array, + order?: number, +} + +const EPISODE_TIMESTAMP_OFFSET = 0.001 + +const addOffset = ({ + e, + h, + s, +}: Episode) => ({ + e: e + EPISODE_TIMESTAMP_OFFSET, + h, + s: s + EPISODE_TIMESTAMP_OFFSET, +}) + +export const useStatsTab = ({ + disablePlayingEpisodes, + handlePlaylistClick, + selectedPlaylist, + setSelectedPlaylist, +}: UseStatsTabArgs) => { const [statsType, setStatsType] = useState(StatsType.FINAL_STATS) const [isPlayersStatsFetching, setIsPlayersStatsFetching] = useState(false) const [isTeamsStatsFetching, setIsTeamsStatsFetching] = useState(false) + const [stateEpisodesToPlay, setEpisodesToPlay] = useState>([]) + const [filteredEvents, setFilteredEvents] = useState([]) + const [plaingOrder, setPlaingOrder] = useState(0) + const [isPlayFilterEpisodes, setIsPlayingFiltersEpisodes] = useState(false) + const [watchAllEpisodesTimer, setWatchAllEpisodesTimer] = useState(false) + const [circleAnimation, setCircleAnimation] = useState(initialCircleAnimation) const isFinalStatsType = statsType === StatsType.FINAL_STATS @@ -17,12 +68,84 @@ export const useStatsTab = () => { setIsPlayersStatsFetching(true) } + const getEpisodesToPlay = (episodes: Episodes) => map(episodes, (episode, i) => ({ + episodes: [ + /** При проигрывании нового эпизода с такими же e и s, как у текущего + воспроизведение начинается не с начала, чтобы пофиксить это добавляем + небольшой оффсет + */ + isEqual(episode, selectedPlaylist?.episodes[0]) + ? addOffset(episode) + : episode, + ], + id: i, + type: PlaylistTypes.EVENT, + })) as Array + + const playNextEpisode = ({ + order, + episodesToPlay = stateEpisodesToPlay, + }: PlayNextEpisodeArgs = {}) => { + const currentOrder = order === 0 ? order : plaingOrder + const isLastEpisode = currentOrder === episodesToPlay.length + + if (isLastEpisode) { + setPlaingOrder(0) + setIsPlayingFiltersEpisodes(false) + + return + } + + if (currentOrder !== 0) { + handlePlaylistClick(episodesToPlay[currentOrder]) + } + + setPlaingOrder(currentOrder + 1) + } + + const playEpisodes = (episodes: Episodes) => { + disablePlayingEpisodes() + + const episodesToPlay = getEpisodesToPlay(episodes) + + /** + * на ios видео иногда не останавлвается после завершения эпизода, + * данный хак исправляет проблему + */ + if (isIOS) { + setSelectedPlaylist({ + ...episodesToPlay[0], + episodes: [addOffset(episodesToPlay[0].episodes[0])], + }) + } + + setEpisodesToPlay(episodesToPlay) + setFilteredEvents(episodes as Events) + + setWatchAllEpisodesTimer(true) + setIsPlayingFiltersEpisodes(true) + + handlePlaylistClick(episodesToPlay[0]) + playNextEpisode({ episodesToPlay, order: 0 }) + } + return { + circleAnimation, + filteredEvents, + isPlayFilterEpisodes, isPlayersStatsFetching, isTeamsStatsFetching, + plaingOrder, + playEpisodes, + playNextEpisode, + setCircleAnimation, setIsPlayersStatsFetching, + setIsPlayingFiltersEpisodes, setIsTeamsStatsFetching, + setPlaingOrder, + setWatchAllEpisodesTimer, statsType, toggleStatsType, + watchAllEpisodesTimer, } } diff --git a/src/features/MatchPage/store/hooks/useTeamsStats.tsx b/src/features/MatchPage/store/hooks/useTeamsStats.tsx index 895d48fc..81694b3c 100644 --- a/src/features/MatchPage/store/hooks/useTeamsStats.tsx +++ b/src/features/MatchPage/store/hooks/useTeamsStats.tsx @@ -6,13 +6,17 @@ import { } from 'react' import throttle from 'lodash/throttle' +import isUndefined from 'lodash/isUndefined' import type { MatchInfo } from 'requests' import { getTeamsStats, TeamStatItem } from 'requests' import { usePageParams } from 'hooks/usePageParams' +import type { PlaylistOption } from 'features/MatchPage/types' import { StatsType } from 'features/MatchSidePlaylists/components/TabStats/config' +import { FULL_GAME_KEY } from 'features/MatchPage/helpers/buildPlaylists' +import { getHalfTime } from 'features/MatchPage/helpers/getHalfTime' const REQUEST_DELAY = 3000 const STATS_POLL_INTERVAL = 30000 @@ -20,6 +24,7 @@ const STATS_POLL_INTERVAL = 30000 type UseTeamsStatsArgs = { matchProfile: MatchInfo, playingProgress: number, + selectedPlaylist?: PlaylistOption, setIsTeamsStatsFetching: Dispatch>, statsType: StatsType, } @@ -27,6 +32,7 @@ type UseTeamsStatsArgs = { export const useTeamsStats = ({ matchProfile, playingProgress, + selectedPlaylist, setIsTeamsStatsFetching, statsType, }: UseTeamsStatsArgs) => { @@ -36,18 +42,16 @@ export const useTeamsStats = ({ const { profileId: matchId, sportName } = usePageParams() - const progressSec = Math.floor(playingProgress / 1000) - const isCurrentStats = statsType === StatsType.CURRENT_STATS const fetchTeamsStats = useMemo(() => throttle(async (second?: number) => { - if (!sportName) return + if (!sportName || selectedPlaylist?.id !== FULL_GAME_KEY || !matchProfile?.video_bounds) return try { const data = await getTeamsStats({ matchId, - second, sportName, + ...(!isUndefined(second) && getHalfTime(matchProfile.video_bounds, second)), }) setTeamsStats(data) @@ -55,7 +59,13 @@ export const useTeamsStats = ({ // eslint-disable-next-line no-empty } catch (e) {} - }, REQUEST_DELAY), [matchId, setIsTeamsStatsFetching, sportName]) + }, REQUEST_DELAY), [ + matchProfile?.video_bounds, + selectedPlaylist?.id, + matchId, + setIsTeamsStatsFetching, + sportName, + ]) useEffect(() => { let interval: NodeJS.Timeout @@ -75,9 +85,9 @@ export const useTeamsStats = ({ useEffect(() => { if (isCurrentStats) { - fetchTeamsStats(progressSec) + fetchTeamsStats(playingProgress) } - }, [fetchTeamsStats, progressSec, isCurrentStats]) + }, [fetchTeamsStats, playingProgress, isCurrentStats]) return { statsType, diff --git a/src/features/MatchSidePlaylists/components/CircleAnimationBar/index.tsx b/src/features/MatchSidePlaylists/components/CircleAnimationBar/index.tsx index 56e90dda..d6032627 100644 --- a/src/features/MatchSidePlaylists/components/CircleAnimationBar/index.tsx +++ b/src/features/MatchSidePlaylists/components/CircleAnimationBar/index.tsx @@ -4,7 +4,9 @@ import { CircleAnimationBar as CircleAnimationBarBase } from 'features/CircleAni export const CircleAnimationBar = styled(CircleAnimationBarBase)` position: absolute; - transform: translateY(-50%); + top: 50%; + left: 50%; + transform: translate(-50%, -50%); circle { stroke: #4086C6; diff --git a/src/features/MatchSidePlaylists/components/PlayersTable/Cell.tsx b/src/features/MatchSidePlaylists/components/PlayersTable/Cell.tsx index 01733beb..2122ee81 100644 --- a/src/features/MatchSidePlaylists/components/PlayersTable/Cell.tsx +++ b/src/features/MatchSidePlaylists/components/PlayersTable/Cell.tsx @@ -1,10 +1,14 @@ import type { PropsWithChildren, HTMLProps } from 'react' -import { memo } from 'react' +import { memo, useRef } from 'react' import { createPortal } from 'react-dom' -import { isMobileDevice } from 'config' +import { isMobileDevice, KEYBOARD_KEYS } from 'config' -import { useModalRoot, useTooltip } from 'hooks' +import { + useEventListener, + useModalRoot, + useTooltip, +} from 'hooks' import { Tooltip, CellContainer } from './styled' @@ -27,6 +31,8 @@ const CellFC = ({ sorted, tooltipText, }: PropsWithChildren) => { + const cellRef = useRef(null) + const { isTooltipShown, onMouseLeave, @@ -36,8 +42,20 @@ const CellFC = ({ const modalRoot = useModalRoot() + useEventListener({ + callback: (e) => { + if (e.key !== KEYBOARD_KEYS.Enter) return + + // @ts-expect-error + onClick() + }, + event: 'keydown', + target: cellRef, + }) + return ( { paramId: null, }) + const { plaingOrder, setCircleAnimation } = useMatchPageStore() + const { getPlayerName, getPlayerParams, @@ -20,6 +24,7 @@ export const usePlayersTable = ({ teamId }: PlayersTableProps) => { const { containerRef, getDisplayedValue, + handleParamClick, handleScroll, handleSortClick, isExpanded, @@ -37,11 +42,19 @@ export const usePlayersTable = ({ teamId }: PlayersTableProps) => { teamId, }) + useEffect(() => { + setCircleAnimation((state) => ({ + ...state, + plaingOrder, + })) + }, [setCircleAnimation, plaingOrder]) + return { containerRef, getDisplayedValue, getPlayerName, getPlayerParams, + handleParamClick, handleScroll, handleSortClick, isExpanded, diff --git a/src/features/MatchSidePlaylists/components/PlayersTable/hooks/usePlayers.tsx b/src/features/MatchSidePlaylists/components/PlayersTable/hooks/usePlayers.tsx index 0a7f457a..0c3de6d3 100644 --- a/src/features/MatchSidePlaylists/components/PlayersTable/hooks/usePlayers.tsx +++ b/src/features/MatchSidePlaylists/components/PlayersTable/hooks/usePlayers.tsx @@ -4,7 +4,7 @@ import orderBy from 'lodash/orderBy' import isNil from 'lodash/isNil' import trim from 'lodash/trim' -import type { Player, PlayerParam } from 'requests' +import type { Player } from 'requests' import { useToggle } from 'hooks' @@ -32,8 +32,6 @@ export const usePlayers = ({ sortCondition, teamId }: UsePlayersArgs) => { [playersStats, teamId], ) - const getDisplayedValue = ({ val }: PlayerParam) => (isNil(val) ? '-' : val) - const getPlayerName = useCallback((player: Player) => ( trim(player[`lastname_${suffix}`] || '') ), [suffix]) @@ -49,7 +47,7 @@ export const usePlayers = ({ sortCondition, teamId }: UsePlayersArgs) => { const players = playersData[matchProfile?.team1.id === teamId ? 'team1' : 'team2'] return isNil(sortCondition.paramId) - ? orderBy(players, getPlayerName) + ? orderBy(players, 'ord') : orderBy( players, [ @@ -58,12 +56,11 @@ export const usePlayers = ({ sortCondition, teamId }: UsePlayersArgs) => { return isNil(paramValue) ? -1 : paramValue }, - getPlayerName, + 'ord', ], sortCondition.dir, ) }, [ - getPlayerName, getParamValue, playersData, matchProfile?.team1.id, @@ -73,7 +70,6 @@ export const usePlayers = ({ sortCondition, teamId }: UsePlayersArgs) => { ]) return { - getDisplayedValue, getPlayerName, getPlayerParams, isExpanded, diff --git a/src/features/MatchSidePlaylists/components/PlayersTable/hooks/useTable.tsx b/src/features/MatchSidePlaylists/components/PlayersTable/hooks/useTable.tsx index d63924d2..5ad49b80 100644 --- a/src/features/MatchSidePlaylists/components/PlayersTable/hooks/useTable.tsx +++ b/src/features/MatchSidePlaylists/components/PlayersTable/hooks/useTable.tsx @@ -17,17 +17,18 @@ import isNil from 'lodash/isNil' import reduce from 'lodash/reduce' import forEach from 'lodash/forEach' import values from 'lodash/values' -import round from 'lodash/round' import map from 'lodash/map' import { isMobileDevice } from 'config' import type { PlayerParam, PlayersStats } from 'requests' +import { getStatsEvents } from 'requests' -import { useToggle } from 'hooks' +import { usePageParams, useToggle } from 'hooks' import { useMatchPageStore } from 'features/MatchPage/store' import { useLexicsConfig } from 'features/LexicsStore' +import { getHalfTime } from 'features/MatchPage/helpers/getHalfTime' import type { SortCondition } from '../types' import { @@ -36,6 +37,7 @@ import { DISPLAYED_PARAMS_COLUMNS, SCROLLBAR_WIDTH, } from '../config' +import { StatsType } from '../../TabStats/config' type UseTableArgs = { setSortCondition: Dispatch>, @@ -52,7 +54,7 @@ export const useTable = ({ const tableWrapperRef = useRef(null) const [showLeftArrow, setShowLeftArrow] = useState(false) - const [showRightArrow, setShowRightArrow] = useState(false) + const [showRightArrow, setShowRightArrow] = useState(true) const [paramColumnWidth, setParamColumnWidth] = useState(PARAM_COLUMN_WIDTH_DEFAULT) const { @@ -60,7 +62,17 @@ export const useTable = ({ isOpen: isExpanded, toggle: toggleIsExpanded, } = useToggle() - const { playersStats } = useMatchPageStore() + const { + playersStats, + playingProgress, + playStatsEpisodes, + profile, + setIsPlayingFiltersEpisodes, + setPlayingData, + setWatchAllEpisodesTimer, + statsType, + } = useMatchPageStore() + const { profileId, sportType } = usePageParams() const params = useMemo(() => ( reduce>( @@ -135,7 +147,7 @@ export const useTable = ({ tableWrapperRef.current?.scrollBy(paramColumnWidth, 0) } - const getDisplayedValue = ({ val }: PlayerParam) => (isNil(val) ? '-' : round(val, 2)) + const getDisplayedValue = ({ val }: PlayerParam) => (isNil(val) ? '-' : String(val)) const handleScroll = (e: SyntheticEvent) => { const { @@ -165,6 +177,42 @@ export const useTable = ({ }) } + const handleParamClick = async (paramId: number, playerId: number) => { + setWatchAllEpisodesTimer(false) + setIsPlayingFiltersEpisodes(false) + + setPlayingData({ + player: { + id: playerId, + paramId, + }, + team: { + id: null, + paramId: null, + }, + }) + + try { + const events = await getStatsEvents({ + matchId: profileId, + paramId, + playerId, + sportType, + teamId, + ...(statsType === StatsType.CURRENT_STATS && profile?.video_bounds && ( + getHalfTime(profile.video_bounds, playingProgress) + )), + }) + + playStatsEpisodes(events) + // eslint-disable-next-line no-empty + } catch (e) {} + } + + useLayoutEffect(() => { + setParamColumnWidth(getParamColumnWidth()) + }, [getParamColumnWidth, containerRef.current?.clientWidth]) + useLayoutEffect(() => { const { clientWidth = 0, @@ -175,11 +223,7 @@ export const useTable = ({ const scrollRight = scrollWidth - (scrollLeft + clientWidth) setShowRightArrow(scrollRight > 0) - }, [isExpanded, tableWrapperRef.current?.clientWidth, paramsCount]) - - useLayoutEffect(() => { - setParamColumnWidth(getParamColumnWidth()) - }, [getParamColumnWidth, tableWrapperRef.current?.clientWidth]) + }, [isExpanded]) useEffect(() => { if (isExpanded && paramsCount <= DISPLAYED_PARAMS_COLUMNS) { @@ -190,6 +234,7 @@ export const useTable = ({ return { containerRef, getDisplayedValue, + handleParamClick, handleScroll, handleSortClick, isExpanded, diff --git a/src/features/MatchSidePlaylists/components/PlayersTable/index.tsx b/src/features/MatchSidePlaylists/components/PlayersTable/index.tsx index e0de514f..59907ff1 100644 --- a/src/features/MatchSidePlaylists/components/PlayersTable/index.tsx +++ b/src/features/MatchSidePlaylists/components/PlayersTable/index.tsx @@ -1,7 +1,7 @@ import { Fragment } from 'react' import map from 'lodash/map' -// import includes from 'lodash/includes' +import includes from 'lodash/includes' import { PlayerParam } from 'requests' @@ -30,6 +30,7 @@ import { Arrow, ExpandButton, } from './styled' +import { CircleAnimationBar } from '../CircleAnimationBar' export const PlayersTable = (props: PlayersTableProps) => { const { @@ -37,6 +38,7 @@ export const PlayersTable = (props: PlayersTableProps) => { getDisplayedValue, getPlayerName, getPlayerParams, + handleParamClick, handleScroll, handleSortClick, isExpanded, @@ -54,13 +56,11 @@ export const PlayersTable = (props: PlayersTableProps) => { } = usePlayersTable(props) const { translate } = useLexicsStore() const { sportName } = usePageParams() - const { isPlayersStatsFetching } = useMatchPageStore() - - if (isPlayersStatsFetching) { - return ( - - ) - } + const { + isPlayersStatsFetching, + playingData, + watchAllEpisodesTimer, + } = useMatchPageStore() const firstColumnWidth = isExpanded ? FIRST_COLUMN_WIDTH_EXPANDED : FIRST_COLUMN_WIDTH_DEFAULT @@ -69,114 +69,132 @@ export const PlayersTable = (props: PlayersTableProps) => { ref={containerRef} isExpanded={isExpanded} > - - {!isExpanded && ( - - {showRightArrow && ( - - - - )} - - )} - -
- - - {showLeftArrow && ( - + : ( + + {!isExpanded && ( + + {showRightArrow && ( + - - + + )} - {showExpandButton && ( - + )} +
+
+ + - - - - )} - - {map(params, ({ - id, - lexic, - lexica_short, - }) => ( - - - - ))} - -
- - - {map(players, (player) => { - const playerName = getPlayerName(player) - const playerNum = player.num ?? player.club_shirt_num - const playerProfileUrl = `/${sportName}/players/${player.id}` - - return ( - - - {playerNum}{' '} - - {playerName} - + {showLeftArrow && ( + + + + )} + {showExpandButton && ( + + + + + )} - {map(params, ({ id }) => { - const playerParam = getPlayerParams(player.id)[id] as PlayerParam | undefined - const value = playerParam ? getDisplayedValue(playerParam) : '-' - // eslint-disable-next-line max-len - // const clickable = Boolean(playerParam?.clickable) && !includes([0, '-'], value) - const sorted = sortCondition.paramId === id + {map(params, ({ + id, + lexic, + lexica_short, + }) => ( + + + + ))} + + - return ( - - {value} + + {map(players, (player) => { + const playerName = getPlayerName(player) + const playerNum = player.num ?? player.club_shirt_num + const playerProfileUrl = `/${sportName}/players/${player.id}` + + return ( + + + {playerNum}{' '} + + {playerName} + - ) - })} - - ) - })} - -
- + {map(params, (param) => { + const playerParam = getPlayerParams(player.id)[ + param.id + ] as PlayerParam | undefined + const value = playerParam ? getDisplayedValue(playerParam) : '-' + // eslint-disable-next-line max-len + const clickable = Boolean(playerParam?.clickable) && !includes([0, '-'], value) + const sorted = sortCondition.paramId === param.id + const onClick = () => { + clickable && handleParamClick(param.id, player.id) + } + + return ( + + {watchAllEpisodesTimer + && param.id === playingData.player.paramId + && player.id === playingData.player.id + ? ( + + ) + : value} + + ) + })} + + ) + })} + + + + )} ) } diff --git a/src/features/MatchSidePlaylists/components/PlayersTable/styled.tsx b/src/features/MatchSidePlaylists/components/PlayersTable/styled.tsx index 851c2690..849b54d5 100644 --- a/src/features/MatchSidePlaylists/components/PlayersTable/styled.tsx +++ b/src/features/MatchSidePlaylists/components/PlayersTable/styled.tsx @@ -2,7 +2,7 @@ import { Link } from 'react-router-dom' import styled, { css } from 'styled-components/macro' -import { isMobileDevice } from 'config' +import { isIOS, isMobileDevice } from 'config' import { customScrollbar } from 'features/Common' import { TooltipWrapper } from 'features/Tooltip' @@ -34,8 +34,7 @@ type TableWrapperProps = { export const TableWrapper = styled.div` max-width: 100%; - max-height: calc(100vh - 203px); - border-radius: 5px; + clip-path: inset(0 0 0 0 round 5px); overflow-x: auto; scroll-behavior: smooth; background: @@ -55,6 +54,18 @@ export const TableWrapper = styled.div` right: 14px; ` : '')} + + ${isMobileDevice + ? '' + : css` + max-height: calc(100vh - 203px); + `}; + + ${isIOS + ? css` + overscroll-behavior: none; + ` + : ''}; ` export const Table = styled.table` @@ -141,6 +152,7 @@ type CellContainerProps = { export const CellContainer = styled.td.attrs(({ clickable }: CellContainerProps) => ({ ...clickable && { tabIndex: 0 }, }))` + position: relative; display: flex; justify-content: center; align-items: center; @@ -177,29 +189,12 @@ export const CellContainer = styled.td.attrs(({ clickable }: CellContainerProps) : '')} ` -export const Header = styled.thead` - position: sticky; - left: 0; - top: 0; - z-index: 2; - - ${CellContainer} { - background-color: #292929; - color: ${({ theme }) => theme.colors.white}; - cursor: pointer; - } - - ${CellContainer}:first-child { - cursor: unset; - } -` - export const Row = styled.tr` position: relative; display: flex; width: 100%; height: 45px; - border-bottom: 0.5px solid ${({ theme }) => theme.colors.secondary}; + border-bottom: 0.5px solid #5C5C5C; z-index: 1; :last-child:not(:first-child) { @@ -218,6 +213,27 @@ export const Row = styled.tr` } ` +export const Header = styled.thead` + position: sticky; + left: 0; + top: 0; + z-index: 2; + + ${Row} { + border-bottom-color: ${({ theme }) => theme.colors.secondary}; + } + + ${CellContainer} { + background-color: #292929; + color: ${({ theme }) => theme.colors.white}; + cursor: pointer; + } + + ${CellContainer}:first-child { + cursor: unset; + } +` + export const Arrow = styled(ArrowBase)` width: 10px; height: 10px; diff --git a/src/features/MatchSidePlaylists/components/PlayersTable/types.tsx b/src/features/MatchSidePlaylists/components/PlayersTable/types.tsx index 7eae9005..a5717bd4 100644 --- a/src/features/MatchSidePlaylists/components/PlayersTable/types.tsx +++ b/src/features/MatchSidePlaylists/components/PlayersTable/types.tsx @@ -1,7 +1,4 @@ -import { TCircleAnimation } from 'features/CircleAnimationBar' - export type PlayersTableProps = { - circleAnimation?: TCircleAnimation, teamId: number, } diff --git a/src/features/MatchSidePlaylists/components/TabEvents/index.tsx b/src/features/MatchSidePlaylists/components/TabEvents/index.tsx index 205e0ff2..7813e38c 100644 --- a/src/features/MatchSidePlaylists/components/TabEvents/index.tsx +++ b/src/features/MatchSidePlaylists/components/TabEvents/index.tsx @@ -8,7 +8,6 @@ import size from 'lodash/size' import { T9n } from 'features/T9n' import type { PlaylistOption } from 'features/MatchPage/types' import { useMatchPageStore } from 'features/MatchPage/store' -import type { TCircleAnimation, TSetCircleAnimation } from 'features/CircleAnimationBar' import { CircleAnimationBar } from 'features/CircleAnimationBar' import type { MatchInfo } from 'requests' @@ -30,25 +29,20 @@ import { } from './styled' type Props = { - circleAnimation?: TCircleAnimation, onSelect: (option: PlaylistOption) => void, profile: MatchInfo, selectedPlaylist?: PlaylistOption, - setCircleAnimation?: TSetCircleAnimation, } export const TabEvents = ({ - circleAnimation, onSelect, profile, selectedPlaylist, - setCircleAnimation, }: Props) => { const { activeStatus, countOfFilters, disablePlayingEpisodes, - filteredEvents, isEmptyFilters, isLiveMatch, likeImage, @@ -56,6 +50,7 @@ export const TabEvents = ({ plaingOrder, playEpisodes, reversedGroupEvents, + setCircleAnimation, setReversed, setUnreversed, setWatchAllEpisodesTimer, @@ -64,12 +59,10 @@ export const TabEvents = ({ } = useMatchPageStore() useEffect(() => { - if (setCircleAnimation) { - setCircleAnimation((state) => ({ - ...state, - plaingOrder, - })) - } + setCircleAnimation((state) => ({ + ...state, + plaingOrder, + })) }, [setCircleAnimation, plaingOrder]) if (!profile) return null @@ -101,15 +94,11 @@ export const TabEvents = ({ {size(flatten(reversedGroupEvents))} - + playEpisodes()}> {watchAllEpisodesTimer && ( - + )} )} diff --git a/src/features/MatchSidePlaylists/components/TabStats/index.tsx b/src/features/MatchSidePlaylists/components/TabStats/index.tsx index 290139a6..72c6b359 100644 --- a/src/features/MatchSidePlaylists/components/TabStats/index.tsx +++ b/src/features/MatchSidePlaylists/components/TabStats/index.tsx @@ -10,7 +10,6 @@ import { useModalRoot } from 'hooks' import { T9n } from 'features/T9n' import { useMatchPageStore } from 'features/MatchPage/store' import { Name } from 'features/Name' -import { TCircleAnimation, TSetCircleAnimation } from 'features/CircleAnimationBar' import { useLexicsStore } from 'features/LexicsStore' import { Tabs } from './config' @@ -38,12 +37,7 @@ const tabPanes = { [Tabs.TEAM2]: (props: ComponentProps) => , } -type Props = { - circleAnimation?: TCircleAnimation, - setCircleAnimation?: TSetCircleAnimation, -} - -export const TabStats = ({ circleAnimation, setCircleAnimation }: Props) => { +export const TabStats = () => { const { isFinalStatsType, isTooltipShown, @@ -157,8 +151,6 @@ export const TabStats = ({ circleAnimation, setCircleAnimation }: Props) => { {isTooltipShown && modalRoot.current && createPortal( diff --git a/src/features/MatchSidePlaylists/components/TeamsStatsTable/Cell.tsx b/src/features/MatchSidePlaylists/components/TeamsStatsTable/Cell.tsx new file mode 100644 index 00000000..04a32f37 --- /dev/null +++ b/src/features/MatchSidePlaylists/components/TeamsStatsTable/Cell.tsx @@ -0,0 +1,163 @@ +import { Fragment, useRef } from 'react' + +import isNumber from 'lodash/isNumber' + +import { KEYBOARD_KEYS } from 'config' + +import type { Param, TeamStatItem } from 'requests' +import { getStatsEvents } from 'requests' + +import { usePageParams, useEventListener } from 'hooks' + +import { getHalfTime } from 'features/MatchPage/helpers/getHalfTime' +import { useMatchPageStore } from 'features/MatchPage/store' + +import { StatsType } from '../TabStats/config' +import { CircleAnimationBar } from '../CircleAnimationBar' + +import { + CellContainer, + ParamValueContainer, + ParamValue, + Divider, +} from './styled' + +type CellProps = { + teamId: number, + teamStatItem: TeamStatItem | null, +} + +export const Cell = ({ + teamId, + teamStatItem, +}: CellProps) => { + const paramValueContainerRef = useRef(null) + + const { profileId, sportType } = usePageParams() + + const { + playingData, + playingProgress, + playStatsEpisodes, + profile, + setIsPlayingFiltersEpisodes, + setPlayingData, + setWatchAllEpisodesTimer, + statsType, + watchAllEpisodesTimer, + } = useMatchPageStore() + + const isClickable = (param: Param) => ( + Boolean(param.val) && param.clickable + ) + + const getDisplayedValue = (val: number | null) => ( + isNumber(val) ? String(val) : '-' + ) + + const onParamClick = async (param: Param) => { + if (!isClickable(param)) return + + setWatchAllEpisodesTimer(false) + setIsPlayingFiltersEpisodes(false) + + setPlayingData({ + player: { + id: null, + paramId: null, + }, + team: { + id: teamId, + paramId: param.id, + }, + }) + + try { + const events = await getStatsEvents({ + matchId: profileId, + paramId: param.id, + sportType, + teamId, + ...(statsType === StatsType.CURRENT_STATS && profile?.video_bounds && ( + getHalfTime(profile.video_bounds, playingProgress) + )), + }) + + playStatsEpisodes(events) + // eslint-disable-next-line no-empty + } catch (e) {} + } + + useEventListener({ + callback: (e) => { + if (e.key !== KEYBOARD_KEYS.Enter || !teamStatItem) return + + const paramId = Number((e.target as HTMLElement).dataset.paramId) + + const param = paramId && (teamStatItem.param1.id === paramId + ? teamStatItem.param1 + : teamStatItem.param2) + + param && onParamClick(param) + }, + event: 'keydown', + target: paramValueContainerRef, + }) + + if (!teamStatItem) return null + + return ( + + + {watchAllEpisodesTimer + && playingData.team.paramId === teamStatItem.param1.id + && playingData.team.id === teamId + ? ( + + + + ) + : ( + onParamClick(teamStatItem.param1)} + data-param-id={teamStatItem.param1.id} + > + {getDisplayedValue(teamStatItem.param1.val)} + + )} + + {teamStatItem.param2 && ( + + {watchAllEpisodesTimer + && playingData.team.paramId === teamStatItem.param2.id + && playingData.team.id === teamId + ? ( + + + + ) + : ( + + / + onParamClick(teamStatItem.param2!)} + data-param-id={teamStatItem.param2.id} + > + {getDisplayedValue(teamStatItem.param2.val)} + + + )} + + )} + + + ) +} diff --git a/src/features/MatchSidePlaylists/components/TeamsStatsTable/hooks.tsx b/src/features/MatchSidePlaylists/components/TeamsStatsTable/hooks.tsx index d4698aea..035d2eee 100644 --- a/src/features/MatchSidePlaylists/components/TeamsStatsTable/hooks.tsx +++ b/src/features/MatchSidePlaylists/components/TeamsStatsTable/hooks.tsx @@ -1,17 +1,16 @@ -import isNumber from 'lodash/isNumber' -import find from 'lodash/find' -import round from 'lodash/round' +import { useEffect } from 'react' -import type { Param } from 'requests' +import find from 'lodash/find' import { useMatchPageStore } from 'features/MatchPage/store' export const useTeamsStatsTable = () => { - const { profile, teamsStats } = useMatchPageStore() - - const getDisplayedValue = (val: any) => ( - isNumber(val) ? round(val, 2) : '-' - ) + const { + plaingOrder, + profile, + setCircleAnimation, + teamsStats, + } = useMatchPageStore() const getStatItemById = (paramId: number) => { if (!profile) return null @@ -19,13 +18,14 @@ export const useTeamsStatsTable = () => { return find(teamsStats[profile?.team2.id], ({ param1 }) => param1.id === paramId) || null } - const isClickable = (param: Param) => ( - Boolean(param.val) && param.clickable - ) + useEffect(() => { + setCircleAnimation((state) => ({ + ...state, + plaingOrder, + })) + }, [setCircleAnimation, plaingOrder]) return { - getDisplayedValue, getStatItemById, - isClickable, } } diff --git a/src/features/MatchSidePlaylists/components/TeamsStatsTable/index.tsx b/src/features/MatchSidePlaylists/components/TeamsStatsTable/index.tsx index f3a3afb2..a67c3284 100644 --- a/src/features/MatchSidePlaylists/components/TeamsStatsTable/index.tsx +++ b/src/features/MatchSidePlaylists/components/TeamsStatsTable/index.tsx @@ -1,5 +1,3 @@ -import { Fragment } from 'react' - import map from 'lodash/map' import { useMatchPageStore } from 'features/MatchPage/store' @@ -7,34 +5,27 @@ import { useLexicsStore } from 'features/LexicsStore' import { Loader } from 'features/Loader' import { defaultTheme } from 'features/Theme/config' -import { Props } from './types' import { useTeamsStatsTable } from './hooks' +import { Cell } from './Cell' import { Container, TableWrapper, Table, Header, Row, - Cell, + CellContainer, TeamShortName, - ParamValueContainer, - ParamValue, StatItemTitle, - Divider, } from './styled' -export const TeamsStatsTable = (props: Props) => { +export const TeamsStatsTable = () => { const { isTeamsStatsFetching, profile, teamsStats, } = useMatchPageStore() - const { - getDisplayedValue, - getStatItemById, - // isClickable, - } = useTeamsStatsTable() + const { getStatItemById } = useTeamsStatsTable() const { shortSuffix } = useLexicsStore() @@ -52,19 +43,19 @@ export const TeamsStatsTable = (props: Props) => {
- + - - - + + + - +
@@ -75,55 +66,19 @@ export const TeamsStatsTable = (props: Props) => { return ( - - - - {getDisplayedValue(team1StatItem.param1.val)} - - {team1StatItem.param2 && ( - - / - - {getDisplayedValue(team1StatItem.param2.val)} - - - )} - - + - + {statItemTitle} - + - - {team2StatItem && ( - - - {getDisplayedValue(team2StatItem.param1.val)} - - {team2StatItem.param2 && ( - - / - - {getDisplayedValue(team2StatItem.param2.val)} - - - )} - - )} - + ) })} diff --git a/src/features/MatchSidePlaylists/components/TeamsStatsTable/styled.tsx b/src/features/MatchSidePlaylists/components/TeamsStatsTable/styled.tsx index 9aa59d15..d1a4ba0d 100644 --- a/src/features/MatchSidePlaylists/components/TeamsStatsTable/styled.tsx +++ b/src/features/MatchSidePlaylists/components/TeamsStatsTable/styled.tsx @@ -1,5 +1,7 @@ import styled, { css } from 'styled-components/macro' +import { isMobileDevice } from 'config' + import { Name } from 'features/Name' import { customScrollbar } from 'features/Common' @@ -7,12 +9,17 @@ export const Container = styled.div`` export const TableWrapper = styled.div` width: 100%; - max-height: calc(100vh - 203px); overflow: auto; font-size: 11px; - border-radius: 5px; + clip-path: inset(0 0 0 0 round 5px); background-color: #333333; + ${isMobileDevice + ? '' + : css` + max-height: calc(100vh - 203px); + `}; + ${customScrollbar} ` @@ -29,12 +36,11 @@ export const TeamShortName = styled(Name)` letter-spacing: -0.078px; text-transform: uppercase; font-weight: 600; - opacity: 0.5; ` -export const Cell = styled.td` +export const CellContainer = styled.td` height: 45px; - border-bottom: 0.5px solid rgba(255, 255, 255, 0.5); + border-bottom: 0.5px solid #5C5C5C; background-color: #333333; :nth-child(2) { @@ -57,7 +63,7 @@ export const Cell = styled.td` export const Row = styled.tr` :last-child:not(:first-child) { - ${Cell} { + ${CellContainer} { border-bottom: none; } } @@ -68,8 +74,9 @@ export const Header = styled.thead` top: 0; z-index: 1; - ${Cell} { + ${CellContainer} { background-color: #292929; + border-bottom-color: ${({ theme }) => theme.colors.secondary}; } ` @@ -82,6 +89,11 @@ type TParamValue = { export const ParamValue = styled.span.attrs(({ clickable }: TParamValue) => ({ ...clickable && { tabIndex: 0 }, }))` + display: inline-block; + width: 15px; + height: 15px; + text-align: center; + position: relative; font-weight: 600; color: ${({ clickable, theme }) => (clickable ? '#5EB2FF' : theme.colors.white)}; diff --git a/src/features/MatchSidePlaylists/components/TeamsStatsTable/types.tsx b/src/features/MatchSidePlaylists/components/TeamsStatsTable/types.tsx deleted file mode 100644 index 7bfc8c82..00000000 --- a/src/features/MatchSidePlaylists/components/TeamsStatsTable/types.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { TCircleAnimation, TSetCircleAnimation } from 'features/CircleAnimationBar' - -export type Props = { - circleAnimation?: TCircleAnimation, - setCircleAnimation?: TSetCircleAnimation, -} diff --git a/src/features/MatchSidePlaylists/hooks.tsx b/src/features/MatchSidePlaylists/hooks.tsx index 72296124..fa1de484 100644 --- a/src/features/MatchSidePlaylists/hooks.tsx +++ b/src/features/MatchSidePlaylists/hooks.tsx @@ -1,8 +1,4 @@ -import { - useEffect, - useMemo, - useState, -} from 'react' +import { useEffect, useMemo } from 'react' import reduce from 'lodash/reduce' import isEmpty from 'lodash/isEmpty' @@ -19,10 +15,11 @@ export const useMatchSidePlaylists = () => { isEmptyPlayersStats, matchPlaylists: playlists, profile: matchProfile, + selectedTab, + setSelectedTab, teamsStats, tournamentData, } = useMatchPageStore() - const [selectedTab, setSelectedTab] = useState(Tabs.WATCH) const playListFilter = useMemo(() => reduce( playlists.match, @@ -84,6 +81,7 @@ export const useMatchSidePlaylists = () => { isPlayersTabVisible, isStatsTabVisible, isWatchTabVisible, + setSelectedTab, ]) useEffect(() => { diff --git a/src/features/MatchSidePlaylists/index.tsx b/src/features/MatchSidePlaylists/index.tsx index a7baaa77..fb857905 100644 --- a/src/features/MatchSidePlaylists/index.tsx +++ b/src/features/MatchSidePlaylists/index.tsx @@ -4,7 +4,6 @@ import { useState, } from 'react' -import type { TCircleAnimation, TSetCircleAnimation } from 'features/CircleAnimationBar' import type { PlaylistOption } from 'features/MatchPage/types' import { useMatchPageStore } from 'features/MatchPage/store' @@ -36,22 +35,19 @@ const tabPanes = { } type Props = { - circleAnimation?: TCircleAnimation, onSelect: (option: PlaylistOption) => void, selectedPlaylist?: PlaylistOption, - setCircleAnimation?: TSetCircleAnimation, } export const MatchSidePlaylists = ({ - circleAnimation, onSelect, selectedPlaylist, - setCircleAnimation, }: Props) => { const { hideProfileCard, matchPlaylists: playlists, profile, + selectedTab, showProfileCard, tournamentData, } = useMatchPageStore() @@ -64,7 +60,6 @@ export const MatchSidePlaylists = ({ isWatchTabVisible, onTabClick, playListFilter, - selectedTab, } = useMatchSidePlaylists() const TabPane = tabPanes[selectedTab] @@ -152,8 +147,6 @@ export const MatchSidePlaylists = ({ forWatchTab={selectedTab === Tabs.WATCH} > ` display: flex; justify-content: center; - gap: 20px; + gap: ${isMobileDevice ? 30 : 20}px; ${({ hasLessThanFourTabs }) => (hasLessThanFourTabs ? css` @@ -125,7 +128,7 @@ export const Container = styled.div` ${isMobileDevice ? css` padding: 0 5px; - padding-bottom: 20px; + padding-bottom: ${isIOS ? 60 : 78}px; overflow-y: hidden; max-height: initial; diff --git a/src/features/MultiSourcePlayer/hooks/index.tsx b/src/features/MultiSourcePlayer/hooks/index.tsx index 5d8f42d1..048ddf0b 100644 --- a/src/features/MultiSourcePlayer/hooks/index.tsx +++ b/src/features/MultiSourcePlayer/hooks/index.tsx @@ -7,7 +7,6 @@ import { import size from 'lodash/size' -import type { TSetCircleAnimation } from 'features/CircleAnimationBar' import { useControlsVisibility } from 'features/StreamPlayer/hooks/useControlsVisibility' import { useFullscreen } from 'features/StreamPlayer/hooks/useFullscreen' import { useVolume } from 'features/VideoPlayer/hooks/useVolume' @@ -57,22 +56,20 @@ export type Props = { chapters: Chapters, isOpenPopup?: boolean, onError?: () => void, - onPlayerProgressChange?: (ms: number) => void, onPlayingChange: (playing: boolean) => void, profile: MatchInfo, - setCircleAnimation: TSetCircleAnimation, } export const useMultiSourcePlayer = ({ chapters, onError, - onPlayerProgressChange, onPlayingChange, - setCircleAnimation, }: Props) => { const { isPlayFilterEpisodes, playNextEpisode, + setCircleAnimation, + setPlayingProgress, } = useMatchPageStore() const { profileId, sportType } = usePageParams() @@ -204,7 +201,7 @@ export const useMultiSourcePlayer = ({ timeForStatistics.current = (value + chapter.startMs) / 1000 setPlayerState({ playedProgress: value }) - onPlayerProgressChange?.(playedMs + chapter.startMs) + setPlayingProgress(Math.floor(value / 1000)) } const onEnded = () => { diff --git a/src/features/StreamPlayer/hooks/index.tsx b/src/features/StreamPlayer/hooks/index.tsx index 41b52413..684be166 100644 --- a/src/features/StreamPlayer/hooks/index.tsx +++ b/src/features/StreamPlayer/hooks/index.tsx @@ -19,12 +19,12 @@ import { useInterval, } from 'hooks' -import type { TSetCircleAnimation } from 'features/CircleAnimationBar' import type { Chapters } from 'features/StreamPlayer/types' import { useVolume } from 'features/VideoPlayer/hooks/useVolume' import { useNoNetworkPopupStore } from 'features/NoNetworkPopup' import { useLiveMatch } from 'features/MatchPage/components/LiveMatch/hooks' import { useLexicsStore } from 'features/LexicsStore' +import { useMatchPageStore } from 'features/MatchPage/store' import { VIEW_INTERVAL_MS, saveMatchStats } from 'requests' @@ -62,7 +62,6 @@ export type Props = { onPlayingChange: (playing: boolean) => void, onProgressChange: (seconds: number) => void, resumeFrom?: number, - setCircleAnimation: TSetCircleAnimation, url?: string, } @@ -73,7 +72,6 @@ export const useVideoPlayer = ({ onPlayingChange, onProgressChange: progressChangeCallback, resumeFrom, - setCircleAnimation, }: Props) => { const [{ activeChapterIndex, @@ -88,14 +86,16 @@ export const useVideoPlayer = ({ seeking, }, setPlayerState] = useObjectState({ ...initialState, chapters: chaptersProps }) + const { onPlaylistSelect } = useLiveMatch() + const { lang } = useLexicsStore() + const { profileId, sportType } = usePageParams() const { isPlayFilterEpisodes, - onPlaylistSelect, playNextEpisode, selectedPlaylist, - } = useLiveMatch() - const { lang } = useLexicsStore() - const { profileId, sportType } = usePageParams() + setCircleAnimation, + setPlayingProgress, + } = useMatchPageStore() /** время для сохранения статистики просмотра матча */ const timeForStatistics = useRef(0) @@ -187,7 +187,7 @@ export const useVideoPlayer = ({ } const onWaiting = () => { - setPlayerState({ buffering: true }) + setPlayerState({ buffering: !ready }) } const onPlaying = () => { @@ -225,6 +225,7 @@ export const useVideoPlayer = ({ setPlayerState({ playedProgress: value }) timeForStatistics.current = (value + chapter.startMs) / 1000 + setPlayingProgress(Math.floor(value / 1000)) progressChangeCallback(value / 1000) } @@ -425,7 +426,6 @@ export const useVideoPlayer = ({ }, [ready, videoRef]) useEffect(() => { - if (!setCircleAnimation) return setCircleAnimation((state) => ({ ...state, playedProgress, diff --git a/src/features/StreamPlayer/index.tsx b/src/features/StreamPlayer/index.tsx index b8ed65cd..0d550ab1 100644 --- a/src/features/StreamPlayer/index.tsx +++ b/src/features/StreamPlayer/index.tsx @@ -35,7 +35,11 @@ const tournamentsWithWatermark = { * HLS плеер, применяется на лайв и завершенных матчах */ export const StreamPlayer = (props: Props) => { - const { isOpenFiltersPopup, profile } = useMatchPageStore() + const { + access, + isOpenFiltersPopup, + profile, + } = useMatchPageStore() const { user } = useAuthStore() const { diff --git a/src/requests/getMatchInfo.tsx b/src/requests/getMatchInfo.tsx index 30e8df28..ba392a1d 100644 --- a/src/requests/getMatchInfo.tsx +++ b/src/requests/getMatchInfo.tsx @@ -25,7 +25,7 @@ export type VideoBound = { s: string, } -type VideoBounds = Array +export type VideoBounds = Array export type MatchInfo = { access?: boolean, diff --git a/src/requests/getMatchParticipants.tsx b/src/requests/getMatchParticipants.tsx index 2869470f..41848848 100644 --- a/src/requests/getMatchParticipants.tsx +++ b/src/requests/getMatchParticipants.tsx @@ -24,6 +24,7 @@ export type Player = { nickname_eng: string | null, nickname_rus: string | null, num: number | null, + ord: number, weight: number | null, } @@ -42,12 +43,14 @@ type Response = { type GetMatchParticipantsArgs = { matchId: number, + period?: number, second?: number, sportType: SportTypes, } export const getMatchParticipants = async ({ matchId, + period, second, sportType, }: GetMatchParticipantsArgs) => { @@ -57,7 +60,7 @@ export const getMatchParticipants = async ({ const response: Response = await callApi({ config, - url: `${STATS_API_URL}/ask/participants?sport_id=${sportType}&match_id=${matchId}${isUndefined(second) ? '' : `&second=${second}`}`, + url: `${STATS_API_URL}/ask/participants?sport_id=${sportType}&match_id=${matchId}${isUndefined(second) ? '' : `&second=${second}&half=${period}`}`, }) if (response.error) Promise.reject(response) diff --git a/src/requests/getPlayersStats.tsx b/src/requests/getPlayersStats.tsx index 7bae6058..af898d49 100644 --- a/src/requests/getPlayersStats.tsx +++ b/src/requests/getPlayersStats.tsx @@ -30,6 +30,7 @@ type Response = { type GetPlayersStatsArgs = { matchId: number, + period?: number, second?: number, sportName: string, teamId: number, @@ -37,6 +38,7 @@ type GetPlayersStatsArgs = { export const getPlayersStats = async ({ matchId, + period, second, sportName, teamId, @@ -47,7 +49,7 @@ export const getPlayersStats = async ({ const response: Response = await callApi({ config, - url: `${STATS_API_URL}/${sportName}/matches/${matchId}/teams/${teamId}/players/stats${isUndefined(second) ? '' : `?second=${second}`}`, + url: `${STATS_API_URL}/${sportName}/matches/${matchId}/teams/${teamId}/players/stats${isUndefined(second) ? '' : `?second=${second}&half=${period}`}`, }) if (response.error) Promise.reject(response) diff --git a/src/requests/getStatsEvents.tsx b/src/requests/getStatsEvents.tsx new file mode 100644 index 00000000..f6a40344 --- /dev/null +++ b/src/requests/getStatsEvents.tsx @@ -0,0 +1,57 @@ +import { SportTypes, STATS_API_URL } from 'config' + +import { callApi } from 'helpers' + +import { Episodes } from './getMatchPlaylists' + +type Response = { + data?: Episodes, + error?: { + code: string, + message: string, + }, +} + +type GetStatsEventsArgs = { + matchId: number, + paramId: number, + period?: number, + playerId?: number, + second?: number, + sportType: SportTypes, + teamId: number, +} + +export const getStatsEvents = async ({ + matchId, + paramId, + period, + playerId, + second, + sportType, + teamId, +}: GetStatsEventsArgs) => { + const config = { + body: { + half: period, + match_id: matchId, + match_second: second, + offset_end: 6, + offset_start: 6, + option_id: 0, + param_id: paramId, + player_id: playerId, + sport_id: sportType, + team_id: teamId, + }, + } + + const response: Response = await callApi({ + config, + url: `${STATS_API_URL}/video`, + }) + + if (response.error) Promise.reject(response) + + return Promise.resolve(response.data || []) +} diff --git a/src/requests/getTeamsStats.tsx b/src/requests/getTeamsStats.tsx index 18fdcde9..ecf7db2f 100644 --- a/src/requests/getTeamsStats.tsx +++ b/src/requests/getTeamsStats.tsx @@ -34,12 +34,14 @@ type Response = { type GetTeamsStatsArgs = { matchId: number, + period?: number, second?: number, sportName: string, } export const getTeamsStats = async ({ matchId, + period, second, sportName, }: GetTeamsStatsArgs) => { @@ -49,7 +51,7 @@ export const getTeamsStats = async ({ const response: Response = await callApi({ config, - url: `${STATS_API_URL}/${sportName}/matches/${matchId}/teams/stats?group_num=0${isUndefined(second) ? '' : `&second=${second}`}`, + url: `${STATS_API_URL}/${sportName}/matches/${matchId}/teams/stats?group_num=0${isUndefined(second) ? '' : `&second=${second}&half=${period}`}`, }) if (response.error) Promise.reject(response) diff --git a/src/requests/index.tsx b/src/requests/index.tsx index 0685ea9b..3b1a6a78 100644 --- a/src/requests/index.tsx +++ b/src/requests/index.tsx @@ -30,3 +30,4 @@ export * from './getTokenVirtualUser' export * from './getTeamsStats' export * from './getPlayersStats' export * from './getMatchParticipants' +export * from './getStatsEvents'