diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..91bd42ad --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "tabWidth": 4, + "singleQuote": true, + "trailingComma": "all", + "singleAttributePerLine": true +} diff --git a/app/.eslintrc.js b/app/.eslintrc.js index be0657c7..0152eb9d 100644 --- a/app/.eslintrc.js +++ b/app/.eslintrc.js @@ -27,7 +27,10 @@ module.exports = { 'react/jsx-indent': ['error', 4], 'react/jsx-indent-props': ['error', 4], // project structure - 'react/jsx-filename-extension': ['error', { extensions: ['.js', '.jsx', '.ts', '.tsx'] }], + 'react/jsx-filename-extension': [ + 'error', + { extensions: ['.js', '.jsx', '.ts', '.tsx'] }, + ], 'import/extensions': [ 'error', 'ignorePackages', @@ -43,11 +46,31 @@ module.exports = { 'no-use-before-define': 'off', 'no-unused-vars': 'off', 'react/require-default-props': 'off', + 'operator-linebreak': 'off', + 'object-curly-newline': ['error', { consistent: true }], + 'react/no-unknown-property': 'off', + 'react/jsx-one-expression-per-line': 'off', + 'implicit-arrow-linebreak': 0, + 'react/jsx-curly-newline': 'off', + 'react/jsx-wrap-multilines': [ + 'error', + { + declaration: 'parens', + assignment: 'parens', + return: 'parens', + arrow: 'parens', + }, + ], + // 'function-paren-newline': ['error', 'multiline'], + 'function-paren-newline': ['off', 'multiline'], // due to DB IDs we have to disable this 'no-underscore-dangle': 'off', - camelcase: ['error', { - allow: ['redirect_uri', 'client_id', 'response_type'], - }], + camelcase: [ + 'error', + { + allow: ['redirect_uri', 'client_id', 'response_type'], + }, + ], // preferences '@typescript-eslint/no-use-before-define': 'off', 'no-plusplus': 'off', diff --git a/app/components/common/CleanButton.tsx b/app/components/common/CleanButton.tsx index 77faaeb8..fed45469 100644 --- a/app/components/common/CleanButton.tsx +++ b/app/components/common/CleanButton.tsx @@ -8,7 +8,7 @@ import { ButtonType } from 'antd/lib/button'; interface CleanButtonProps { children?: React.ReactNode; style?: CSSProperties; - onClick?: React.MouseEventHandler, + onClick?: React.MouseEventHandler; size?: SizeType; backColor?: string; textColor?: string; @@ -22,14 +22,14 @@ interface CleanButtonProps { } const getButtonPadding = (size: SizeType) => { - switch (size) { - case 'small': + if (size === 'small') { return '0rem 1rem'; - case 'middle': + } + if (size === 'middle') { return '0rem 1.5rem'; - default: - return '0rem 2rem'; } + // Default to large size padding + return '0rem 2rem'; }; const ButtonComponent = ({ @@ -51,11 +51,14 @@ const ButtonComponent = ({ const parsedBackColor = backColor && parse(backColor); - const darkenedBackColor = backColor && parsedBackColor - && `hsl(${parsedBackColor.hsl[0]}, ${parsedBackColor.hsl[1]}%, ${parsedBackColor.hsl[2] * 0.8}%)`; + const darkenedBackColor = useMemo(() => { + if (!darkenOnHover || !parsedBackColor) return backColor; + return `hsl(${parsedBackColor.hsl[0]}, ${parsedBackColor.hsl[1]}%, ${ + parsedBackColor.hsl[2] * 0.8 + }%)`; + }, [darkenOnHover, parsedBackColor, backColor]); - const cssTextColor = textColor - || 'rgba(255, 255, 255, 0.95)'; + const cssTextColor = textColor || 'rgba(255, 255, 255, 0.95)'; const buttonStyle = { default: { @@ -117,33 +120,45 @@ const CleanButton = ({ darkenOnHover, className, }: CleanButtonProps): JSX.Element => { - const buttonComponent: React.ReactNode = useMemo(() => ( - - {children} - - ), [children, onClick, style, backColor, textColor, type, url, size, - openInNewTab, hoverAnimation, disabled, darkenOnHover, className]); + const buttonComponent: React.ReactNode = useMemo( + () => ( + + {children} + + ), + [ + children, + onClick, + style, + backColor, + textColor, + type, + url, + size, + openInNewTab, + hoverAnimation, + disabled, + darkenOnHover, + className, + ], + ); // If there is no URL, return button directly if (!url) { - return ( - <> - {buttonComponent} - - ); + return <>{buttonComponent}; } // Handle URL return types depending on whether it should opened in a new tab @@ -151,14 +166,16 @@ const CleanButton = ({ <> {openInNewTab ? ( - + {buttonComponent} ) : ( - - {buttonComponent} - + {buttonComponent} )} ); diff --git a/app/components/common/DiscordButton.tsx b/app/components/common/DiscordButton.tsx index 2447b9ab..0433c1e5 100644 --- a/app/components/common/DiscordButton.tsx +++ b/app/components/common/DiscordButton.tsx @@ -2,9 +2,7 @@ import React from 'react'; import Link from 'next/link'; import Icon from '@ant-design/icons'; -import { - Button, -} from 'antd'; +import { Button } from 'antd'; import DiscordSVG from '../../assets/discord.svg'; @@ -19,9 +17,17 @@ const DISCORD_URL = 'https://discord.gg/RPbZHvxNRG'; const DiscordButton = (): JSX.Element => ( - + - ); export default DiscordButton; diff --git a/app/components/common/Footer.tsx b/app/components/common/Footer.tsx index c09f8394..c06d70f7 100644 --- a/app/components/common/Footer.tsx +++ b/app/components/common/Footer.tsx @@ -1,9 +1,7 @@ import React from 'react'; const Footer = () => ( -
+
{ const router = useRouter(); @@ -37,16 +40,11 @@ const PageHeaderBar = ({ />
- - {title} - + {title} {subtitle && ( - - {subtitle} - + {subtitle} )}
-
diff --git a/app/components/common/PlayerLink.tsx b/app/components/common/PlayerLink.tsx index b0ab39ec..31d79b88 100644 --- a/app/components/common/PlayerLink.tsx +++ b/app/components/common/PlayerLink.tsx @@ -7,11 +7,9 @@ interface PlayerLinkProps { webId: string; name: string; className?: string; - style?: React.CSSProperties + style?: React.CSSProperties; } -const PlayerLink = ({ - webId, name, className, style, -}: PlayerLinkProps) => ( +const PlayerLink = ({ webId, name, className, style }: PlayerLinkProps) => ( ( - )} +
+ {!visible && ( + + )} onToggleCheckbox(chartType, e.target.checked)} + onChange={onToggleCheckbox} > {ChartTypes[chartType].name} diff --git a/app/components/maps/LoadedReplays.tsx b/app/components/maps/LoadedReplays.tsx index 1d9d2ec9..0eb374d6 100644 --- a/app/components/maps/LoadedReplays.tsx +++ b/app/components/maps/LoadedReplays.tsx @@ -1,18 +1,15 @@ /* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable jsx-a11y/click-events-have-key-events */ -import { - CaretRightOutlined, EyeOutlined, -} from '@ant-design/icons'; -import { - Drawer, Row, Col, Radio, RadioChangeEvent, List, Divider, -} from 'antd'; -import React, { - useContext, useMemo, useState, -} from 'react'; +import { CaretRightOutlined, EyeOutlined } from '@ant-design/icons'; +import { Drawer, Row, Col, Radio, RadioChangeEvent, List, Divider } from 'antd'; +import React, { useContext, useMemo, useState } from 'react'; import * as ReactColor from 'react-color'; import * as THREE from 'three'; import { ReplayData } from '../../lib/api/requests/replays'; -import { CameraMode, SettingsContext } from '../../lib/contexts/SettingsContext'; +import { + CameraMode, + SettingsContext, +} from '../../lib/contexts/SettingsContext'; import GlobalTimeLineInfos from '../../lib/singletons/timeLineInfos'; import { addPlural, getRaceTimeStr } from '../../lib/utils/time'; import SideDrawerExpandButton from '../common/SideDrawerExpandButton'; @@ -29,7 +26,10 @@ interface LoadedReplaysProps { } const LoadedReplay = ({ - replay, followed, followedReplayChanged, hoveredReplayChanged, + replay, + followed, + followedReplayChanged, + hoveredReplayChanged, }: LoadedReplayProps): JSX.Element => { const [color, setColor] = useState(`#${replay.color.getHexString()}`); const [showColorPicker, setShowColorPicker] = useState(false); @@ -71,14 +71,15 @@ const LoadedReplay = ({ >
@@ -96,19 +97,21 @@ const LoadedReplay = ({ onPointerEnter={() => hoveredReplayChanged(replay)} onPointerLeave={() => hoveredReplayChanged(undefined)} > -
{showColorPicker ? ( -
{ +const LoadedReplays = ({ replays }: LoadedReplaysProps): JSX.Element => { const [visible, setVisible] = useState(true); const [followed, setFollowed] = useState(); const [hovered, setHovered] = useState(); @@ -181,7 +182,10 @@ const LoadedReplays = ({ timeLineGlobal.followedReplay = followed; timeLineGlobal.hoveredReplay = hovered; - const sortedReplays = useMemo(() => replays.sort((a, b) => a.endRaceTime - b.endRaceTime), [replays]); + const sortedReplays = useMemo( + () => replays.sort((a, b) => a.endRaceTime - b.endRaceTime), + [replays], + ); return (
@@ -189,11 +193,13 @@ const LoadedReplays = ({ - {`${replays.length} Replay${addPlural(replays.length)}`} + {`${replays.length} Replay${addPlural( + replays.length, + )}`} - )} + } /> )} { timeLineGlobal.cameraMode = e.target.value; }} + onChange={(e: RadioChangeEvent) => { + timeLineGlobal.cameraMode = e.target.value; + }} className="flex gap-4" > diff --git a/app/components/maps/MapHeader.tsx b/app/components/maps/MapHeader.tsx index 5f2b5aec..b330fa8e 100644 --- a/app/components/maps/MapHeader.tsx +++ b/app/components/maps/MapHeader.tsx @@ -9,17 +9,25 @@ interface Props { mapInfo?: MapInfo; title: string; backUrl?: string; - children?: React.ReactNode + children?: React.ReactNode; } const MapHeader = ({ - mapInfo, title, backUrl, children, + mapInfo, + title, + backUrl, + children, }: Props): JSX.Element => { - const hasExchangeId = mapInfo?.exchangeid !== undefined && mapInfo.exchangeid !== 0; + const hasExchangeId = + mapInfo?.exchangeid !== undefined && mapInfo.exchangeid !== 0; const hasMapUid = mapInfo?.mapUid !== undefined && mapInfo.mapUid !== ''; - const tmioURL = mapInfo?.mapUid ? `https://trackmania.io/#/leaderboard/${mapInfo.mapUid}` as const : undefined; - const tmxURL = mapInfo?.exchangeid ? `https://trackmania.exchange/maps/${mapInfo.exchangeid}` as const : undefined; + const tmioURL = mapInfo?.mapUid + ? (`https://trackmania.io/#/leaderboard/${mapInfo.mapUid}` as const) + : undefined; + const tmxURL = mapInfo?.exchangeid + ? (`https://trackmania.exchange/maps/${mapInfo.exchangeid}` as const) + : undefined; return ( {children} - ); }; diff --git a/app/components/maps/SectorTimeTableButton.tsx b/app/components/maps/SectorTimeTableButton.tsx index 91c3ffc3..803809d9 100644 --- a/app/components/maps/SectorTimeTableButton.tsx +++ b/app/components/maps/SectorTimeTableButton.tsx @@ -10,12 +10,12 @@ const SectorTimeTableButton = ({ onClick }: Props) => ( Sector Time Table - )} + } onClick={onClick} />
diff --git a/app/components/maps/SectorTimeTableModal.tsx b/app/components/maps/SectorTimeTableModal.tsx index 706c9a7b..23a50f66 100644 --- a/app/components/maps/SectorTimeTableModal.tsx +++ b/app/components/maps/SectorTimeTableModal.tsx @@ -1,7 +1,5 @@ import React, { useCallback, useMemo } from 'react'; -import { - Button, Empty, Modal, Table, Tooltip, -} from 'antd'; +import { Button, Empty, Modal, Table, Tooltip } from 'antd'; import { ColumnsType } from 'antd/lib/table'; import { ClockCircleOutlined, QuestionOutlined } from '@ant-design/icons'; import { ReplayInfo } from '../../lib/api/requests/replays'; @@ -23,7 +21,9 @@ const openInfoModal = () => { title: 'CP/Sector Time Table Information', content: (
- {'To view sector time deltas, you need to select at least 1 replay that includes sector times. '} + { + 'To view sector time deltas, you need to select at least 1 replay that includes sector times. ' + } {'Replays with sector times are indicated by the '} {' icon in the replay list.'} @@ -54,16 +54,27 @@ interface Props { } const SectorTimeTableModal = ({ - visible, setVisible, selectedReplays, allReplays, + visible, + setVisible, + selectedReplays, + allReplays, }: Props): JSX.Element => { const filteredReplays = useMemo( - () => filterReplaysWithValidSectorTimes(selectedReplays, allReplays) - .sort((a, b) => a.endRaceTime - b.endRaceTime), + () => + filterReplaysWithValidSectorTimes(selectedReplays, allReplays).sort( + (a, b) => a.endRaceTime - b.endRaceTime, + ), [allReplays, selectedReplays], ); const allIndividualSectorTimes = useMemo( - () => filteredReplays.map((replay) => calcIndividualSectorTimes(replay.sectorTimes!, replay.endRaceTime)), + () => + filteredReplays.map((replay) => + calcIndividualSectorTimes( + replay.sectorTimes!, + replay.endRaceTime, + ), + ), [filteredReplays], ); const fastestSectorIndices = useMemo( @@ -71,6 +82,15 @@ const SectorTimeTableModal = ({ [allIndividualSectorTimes], ); + const getBackgroundColor = ( + isTheoreticalBest?: boolean, + ): string | undefined => { + if (isTheoreticalBest) { + return THEORETICAL_BEST_BACKGROUND_COLOR; + } + return undefined; + }; + interface Entry { date?: number; playerName: string; @@ -80,6 +100,7 @@ const SectorTimeTableModal = ({ replayIndex?: number; isTheoreticalBest?: boolean; } + const columns: ColumnsType = useMemo(() => { let cols: ColumnsType = [ { @@ -88,11 +109,12 @@ const SectorTimeTableModal = ({ key: 'date', fixed: 'left', width: 200, - render: (_, entry) => ( - entry.date - ? timeDifference(new Date().getTime(), entry.date) - : '-' - ), + render: (_, entry) => { + if (!entry.date) { + return '-'; + } + return timeDifference(new Date().getTime(), entry.date); + }, }, { title: 'Player', @@ -107,11 +129,7 @@ const SectorTimeTableModal = ({ key: 'time', fixed: 'left', width: 125, - render: (_, entry) => ( - - {getRaceTimeStr(entry.time)} - - ), + render: (_, entry) => {getRaceTimeStr(entry.time)}, }, { title: 'Gap', @@ -122,70 +140,116 @@ const SectorTimeTableModal = ({ render: (_, entry) => ( {/* Do not display Gap time for fastest replay */} - {entry.replayIndex === 0 - ? '-' - : `${entry.gap > 0 ? '+' : ''}${getRaceTimeStr(entry.gap)}`} + {entry.replayIndex === 0 && '-'} + {entry.replayIndex !== 0 && + `${entry.gap > 0 ? '+' : ''}${getRaceTimeStr( + entry.gap, + )}`} ), }, { title: 'Sector Times', - children: (allIndividualSectorTimes[0] || []).map((_, sectorIndex) => ({ - title: () => { - const sectorStart = sectorIndex === 0 - ? 'Start' - : `CP ${sectorIndex}`; - const sectorEnd = sectorIndex === allIndividualSectorTimes[0].length - 1 - ? 'Finish' - : `CP ${sectorIndex + 1}`; + children: (allIndividualSectorTimes[0] || []).map( + (_, sectorIndex) => ({ + title: () => { + const getSectorStart = ( + _sectorIndex: number, + ): string => { + if (sectorIndex === 0) { + return 'Start'; + } + return `CP ${sectorIndex}`; + }; + + const getSectorEnd = ( + _sectorIndex: number, + ): string => { + if ( + sectorIndex === + allIndividualSectorTimes[0].length - 1 + ) { + return 'Finish'; + } + return `CP ${sectorIndex + 1}`; + }; + + const sectorStart = getSectorStart(sectorIndex); + const sectorEnd = getSectorEnd(sectorIndex); + + const title = `${sectorStart} âžž ${sectorEnd}`; - const title = `${sectorStart} âžž ${sectorEnd}`; + return ( + +
+ {`S${sectorIndex + 1}`} +
+
+ ); + }, + dataIndex: `sectorTimes[${sectorIndex}]`, + key: `sectorTimes[${sectorIndex}]`, + width: 75, + render: (_1, entry) => { + // Get different times + const sectorTime = entry.sectorTimes[sectorIndex]; + const referenceTime = + allIndividualSectorTimes[0][sectorIndex]; + const timeDiff = sectorTime - referenceTime; - return ( - -
- {`S${sectorIndex + 1}`} -
-
- ); - }, - dataIndex: `sectorTimes[${sectorIndex}]`, - key: `sectorTimes[${sectorIndex}]`, - width: 75, - render: (_1, entry) => { - // Get different times - const sectorTime = entry.sectorTimes[sectorIndex]; - const referenceTime = allIndividualSectorTimes[0][sectorIndex]; - const timeDiff = sectorTime - referenceTime; + let color = 'white'; + if ( + fastestSectorIndices[sectorIndex] === + entry.replayIndex || + entry.isTheoreticalBest + ) { + // fastest sector: purple + color = PURPLE_SECTOR_COLOR; + } else if ( + entry.replayIndex && + entry.replayIndex > 0 + ) { + // delta positive/negative: red/green + if (timeDiff < 0) { + color = GREEN_SECTOR_COLOR; + } else if (timeDiff > 0) { + // timeDiff is positive, so sector time is slower than reference + color = RED_SECTOR_COLOR; + } + } - let color = 'white'; - if (fastestSectorIndices[sectorIndex] === entry.replayIndex || entry.isTheoreticalBest) { - // fastest sector: purple - color = PURPLE_SECTOR_COLOR; - } else if (entry.replayIndex && entry.replayIndex > 0) { - // delta positive/negative: red/green - color = timeDiff > 0 ? RED_SECTOR_COLOR : GREEN_SECTOR_COLOR; - } + const getTimeStr = (time: number): string => { + if (entry.replayIndex === 0) { + return getRaceTimeStr(time); + } + return `${ + timeDiff <= 0 ? '-' : '+' + }${getRaceTimeStr(Math.abs(timeDiff))}`; + }; - // Generate string to display in cell - const timeStr = entry.replayIndex === 0 - ? getRaceTimeStr(sectorTime) - : `${timeDiff <= 0 ? '-' : '+'}${getRaceTimeStr(Math.abs(timeDiff))}`; + // Generate string to display in cell + const timeStr = getTimeStr(sectorTime); - // Generate hover tooltip title - const tooltipTitle = getRaceTimeStr(sectorTime); + // Generate hover tooltip title + const tooltipTitle = getRaceTimeStr(sectorTime); - return ( - -
- - {timeStr} - -
-
- ); - }, - })), + return ( + +
+ {timeStr} +
+
+ ); + }, + }), + ), }, ]; @@ -194,7 +258,9 @@ const SectorTimeTableModal = ({ ...col, onCell: (entry) => ({ style: { - backgroundColor: entry.isTheoreticalBest ? THEORETICAL_BEST_BACKGROUND_COLOR : undefined, + backgroundColor: getBackgroundColor( + entry.isTheoreticalBest, + ), }, }), })); @@ -208,10 +274,12 @@ const SectorTimeTableModal = ({ } // Retrieve best sector times from all replays - const bestSectorTimes = allIndividualSectorTimes[0].map((_, sectorIndex) => { - const replayIndex = fastestSectorIndices[sectorIndex]; - return allIndividualSectorTimes[replayIndex][sectorIndex]; - }); + const bestSectorTimes = allIndividualSectorTimes[0].map( + (_, sectorIndex) => { + const replayIndex = fastestSectorIndices[sectorIndex]; + return allIndividualSectorTimes[replayIndex][sectorIndex]; + }, + ); // Calculate theoretical best time, from best sectors const bestTime = bestSectorTimes.reduce((a, b) => a + b, 0); @@ -249,11 +317,15 @@ const SectorTimeTableModal = ({ } return data; - }, [allIndividualSectorTimes, filteredReplays, generateTheoreticalBestEntry]); + }, [ + allIndividualSectorTimes, + filteredReplays, + generateTheoreticalBestEntry, + ]); return ( CP/Sector Time Table
- )} + } centered visible={visible} footer={null} @@ -292,7 +364,9 @@ const SectorTimeTableModal = ({ dataSource={dataSource} onRow={(entry) => ({ style: { - backgroundColor: entry.isTheoreticalBest ? THEORETICAL_BEST_BACKGROUND_COLOR : undefined, + backgroundColor: getBackgroundColor( + entry.isTheoreticalBest, + ), }, })} pagination={false} diff --git a/app/components/maps/SidebarReplays.tsx b/app/components/maps/SidebarReplays.tsx index f89cc380..df47c89e 100644 --- a/app/components/maps/SidebarReplays.tsx +++ b/app/components/maps/SidebarReplays.tsx @@ -1,8 +1,16 @@ -import React, { - useState, useEffect, useContext, useMemo, -} from 'react'; +import React, { useState, useEffect, useContext, useMemo } from 'react'; import { - Button, Drawer, Dropdown, Menu, message, Popconfirm, Progress, Space, Spin, Table, Tooltip, + Button, + Drawer, + Dropdown, + Menu, + message, + Popconfirm, + Progress, + Space, + Spin, + Table, + Tooltip, } from 'antd'; import { DeleteOutlined, @@ -23,7 +31,10 @@ import PlayerLink from '../common/PlayerLink'; import { ReplayInfo } from '../../lib/api/requests/replays'; import CleanButton from '../common/CleanButton'; import useWindowDimensions from '../../lib/hooks/useWindowDimensions'; -import { DownloadState, ReplayDownloadState } from '../../lib/replays/replayDownloadState'; +import { + DownloadState, + ReplayDownloadState, +} from '../../lib/replays/replayDownloadState'; import { calcFastestSectorIndices, calcIndividualSectorTimes, @@ -68,7 +79,9 @@ const SidebarReplays = ({ const defaultPageSize = 14; - const showFinishedColumn = replays.some((replay: ReplayInfo) => !replay.raceFinished); + const showFinishedColumn = replays.some( + (replay: ReplayInfo) => !replay.raceFinished, + ); const [visible, setVisible] = useState(true); const [visibleReplays, setVisibleReplays] = useState([]); @@ -81,20 +94,22 @@ const SidebarReplays = ({ [replays], ); - const userHasReplay = useMemo( - () => { - if (!user) { - return false; - } - return replays.some( - (replay) => replay.webId === user.accountId && replay.raceFinished, - ); - }, - [replays, user], - ); + const userHasReplay = useMemo(() => { + if (!user) { + return false; + } + return replays.some( + (replay) => replay.webId === user.accountId && replay.raceFinished, + ); + }, [replays, user]); const singleReplayHasSectorTimes = useMemo( - () => replays.some((replay) => !!replay.sectorTimes && replay.sectorTimes.length === validSectorsLength), + () => + replays.some( + (replay) => + !!replay.sectorTimes && + replay.sectorTimes.length === validSectorsLength, + ), [replays, validSectorsLength], ); @@ -115,7 +130,9 @@ const SidebarReplays = ({ setVisible(!visible); }; - const getUniqueFilters = (replayFieldCallback: (replay: ReplayInfo) => string) => { + const getUniqueFilters = ( + replayFieldCallback: (replay: ReplayInfo) => string, + ) => { const uniques = Array.from(new Set(replays.map(replayFieldCallback))); return uniques.sort().map((val) => ({ text: val, value: val })); }; @@ -133,26 +150,35 @@ const SidebarReplays = ({ const onLoadReplaysWithFastestSectorTimes = () => { // Filter finished replays containing sector times, sort by time - const filteredReplays = filterReplaysWithValidSectorTimes(replays, replays) - .sort((a, b) => a.endRaceTime - b.endRaceTime); + const filteredReplays = filterReplaysWithValidSectorTimes( + replays, + replays, + ).sort((a, b) => a.endRaceTime - b.endRaceTime); if (filteredReplays.length === 0) { - message.error('Did not find any finished replays with recorded sector times.'); + message.error( + 'Did not find any finished replays with recorded sector times.', + ); return; } // Calculate individual sector time deltas - const allIndividualSectorTimes = filteredReplays - .map((replay) => calcIndividualSectorTimes(replay.sectorTimes!, replay.endRaceTime)); + const allIndividualSectorTimes = filteredReplays.map((replay) => + calcIndividualSectorTimes(replay.sectorTimes!, replay.endRaceTime), + ); // Calculate the replay indices of all fastest sector times - const fastestSectorIndices = calcFastestSectorIndices(allIndividualSectorTimes); + const fastestSectorIndices = calcFastestSectorIndices( + allIndividualSectorTimes, + ); // Get a unique set of replay indices const replayIndices = Array.from(new Set(fastestSectorIndices)); // Load all fastest replays - onLoadMultipleReplays(replayIndices.map((index) => filteredReplays[index])); + onLoadMultipleReplays( + replayIndices.map((index) => filteredReplays[index]), + ); }; const onLoadFastestTime = () => { @@ -210,7 +236,10 @@ const SidebarReplays = ({ filters: nameFilters, onFilter: (value, record) => record.playerName === value, render: (_, replay) => ( - + ), filterSearch: true, filterIcon: () => ( @@ -290,7 +319,7 @@ const SidebarReplays = ({ return (
- {(!loadingState) && ( + {!loadingState && ( onLoadReplay(replay)} className="w-full" @@ -300,7 +329,11 @@ const SidebarReplays = ({ )} {loadingState?.state === DownloadState.DOWNLOADING && (
- +
)} {loadingState?.state === DownloadState.LOADED && ( @@ -313,7 +346,10 @@ const SidebarReplays = ({
)} {loadingState?.state === DownloadState.ERROR && ( - + onLoadReplay(replay)} @@ -329,11 +365,11 @@ const SidebarReplays = ({ - )} + } cancelText="No" okText="Yes" okButtonProps={{ danger: true }} @@ -342,7 +378,14 @@ const SidebarReplays = ({
{ + onChange={( + pagination, + filters, + sorter, + currentPageData, + ) => { onReplayTableChange(pagination, currentPageData); }} dataSource={addReplayInfo(replays)} diff --git a/app/components/maps/SidebarSettings.tsx b/app/components/maps/SidebarSettings.tsx index 97fc5c4b..4e2a9bcc 100644 --- a/app/components/maps/SidebarSettings.tsx +++ b/app/components/maps/SidebarSettings.tsx @@ -1,7 +1,5 @@ import React, { useContext, useState } from 'react'; -import { - Checkbox, Drawer, Select, Col, Slider, InputNumber, -} from 'antd'; +import { Checkbox, Drawer, Select, Col, Slider, InputNumber } from 'antd'; import { CheckboxChangeEvent } from 'antd/lib/checkbox'; import { SettingOutlined } from '@ant-design/icons'; import Text from 'antd/lib/typography/Text'; @@ -15,17 +13,23 @@ const SidebarSettings = (): JSX.Element => { const [visible, setVisible] = useState(false); const windowDimensions = useWindowDimensions(); const { - lineType, changeLineType, - showGearChanges, setShowGearChanges, - showFPS, setShowFPS, - showInputOverlay, setShowInputOverlay, - replayLineOpacity, setReplayLineOpacity, - replayCarOpacity, setReplayCarOpacity, - showFullTrail, setShowFullTrail, - showTrailToStart, setShowTrailToStart, - } = useContext( - SettingsContext, - ); + lineType, + changeLineType, + showGearChanges, + setShowGearChanges, + showFPS, + setShowFPS, + showInputOverlay, + setShowInputOverlay, + replayLineOpacity, + setReplayLineOpacity, + replayCarOpacity, + setReplayCarOpacity, + showFullTrail, + setShowFullTrail, + showTrailToStart, + setShowTrailToStart, + } = useContext(SettingsContext); const timeLineGlobal = GlobalTimeLineInfos.getInstance(); @@ -49,12 +53,12 @@ const SidebarSettings = (): JSX.Element => { Settings - )} + } /> { value={lineType.name} onChange={onChangeLineType} > - {Object.keys(LineTypes).map((lineTypeKey) => { - const { name } = LineTypes[lineTypeKey]; - return ( - - {name} - - ); - })} + {Object.keys(LineTypes).map( + (lineTypeKey) => { + const { name } = + LineTypes[lineTypeKey]; + return ( + + {name} + + ); + }, + )} @@ -95,8 +105,10 @@ const SidebarSettings = (): JSX.Element => { className="w-full m-0" min={0} max={1} - onChange={(e: number) => setReplayLineOpacity(e)} - value={typeof replayLineOpacity === 'number' ? replayLineOpacity : 0} + onChange={(e: number) => + setReplayLineOpacity(e) + } + value={replayLineOpacity} step={0.1} dots /> @@ -109,8 +121,8 @@ const SidebarSettings = (): JSX.Element => { className="w-full m-0" min={0} max={1} - onChange={(e: number) => setReplayCarOpacity(e)} - value={typeof replayCarOpacity === 'number' ? replayCarOpacity : 0} + onChange={setReplayCarOpacity} + value={replayCarOpacity} step={0.1} dots /> @@ -126,7 +138,8 @@ const SidebarSettings = (): JSX.Element => { checked={showFullTrail} onChange={(e) => { setShowFullTrail(e.target.checked); - timeLineGlobal.showFullTrail = e.target.checked; + timeLineGlobal.showFullTrail = + e.target.checked; }} > Show full trail @@ -139,7 +152,8 @@ const SidebarSettings = (): JSX.Element => { checked={showTrailToStart} onChange={(e) => { setShowTrailToStart(e.target.checked); - timeLineGlobal.showTrailToStart = e.target.checked; + timeLineGlobal.showTrailToStart = + e.target.checked; }} > Show trail to start @@ -147,14 +161,20 @@ const SidebarSettings = (): JSX.Element => {
- Trail length: + + Trail length: +
{
setShowGearChanges(e.target.checked)} + onChange={(e: CheckboxChangeEvent) => + setShowGearChanges(e.target.checked) + } checked={showGearChanges} > Show Gear Changes @@ -182,7 +204,9 @@ const SidebarSettings = (): JSX.Element => { setShowInputOverlay(e.target.checked)} + onChange={(e: CheckboxChangeEvent) => + setShowInputOverlay(e.target.checked) + } checked={showInputOverlay} > Show Input Overlay @@ -195,7 +219,9 @@ const SidebarSettings = (): JSX.Element => { setShowFPS(e.target.checked)} + onChange={(e: CheckboxChangeEvent) => + setShowFPS(e.target.checked) + } checked={showFPS} > Show FPS diff --git a/app/components/users/UserHeader.tsx b/app/components/users/UserHeader.tsx index e17bf60c..57060bf7 100644 --- a/app/components/users/UserHeader.tsx +++ b/app/components/users/UserHeader.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import Link from 'next/link'; import { useRouter } from 'next/router'; import { Button, PageHeader } from 'antd'; @@ -6,34 +6,56 @@ import { Button, PageHeader } from 'antd'; import { UserInfo } from '../../lib/api/requests/users'; import UserDisplay from '../common/UserDisplay'; -interface Props { +interface UserProfilTMIOProps { + tmioURL: string; +} + +const UserProfilTMIO = (props: UserProfilTMIOProps) => { + const { tmioURL } = props; + return ( + + + + + + ); +}; + +interface UserHeaderProps { userInfo?: UserInfo; } -const UserHeader = ({ userInfo }: Props): JSX.Element => { +const UserHeader = ({ userInfo }: UserHeaderProps): JSX.Element => { const router = useRouter(); - const headerTitle = userInfo ? `User profile of ${userInfo.playerName}` : 'Profile not found'; - const tmioURL = userInfo ? `https://trackmania.io/#/player/${userInfo.webId}` : ''; + + const headerTitle = useMemo(() => { + if (!userInfo) { + return 'Profile not found'; + } + return `User profile of ${userInfo.playerName}`; + }, [userInfo]); + + const tmioURL = useMemo(() => { + if (!userInfo) { + return ''; + } + return `https://trackmania.io/#/player/${userInfo.webId}`; + }, [userInfo]); return ( router.push('/')} title={headerTitle} - subTitle={( -
- {userInfo - && ( - - - - - - )} - -
- )} + subTitle={tmioURL && } extra={} /> ); diff --git a/app/components/users/UserReplays.tsx b/app/components/users/UserReplays.tsx index 8176fb28..83d26ae2 100644 --- a/app/components/users/UserReplays.tsx +++ b/app/components/users/UserReplays.tsx @@ -1,13 +1,6 @@ import React, { useMemo } from 'react'; import Link from 'next/link'; -import { - Card, - Col, - Row, - Spin, - Statistic, - Table, -} from 'antd'; +import { Card, Col, Row, Spin, Statistic, Table } from 'antd'; import { ColumnsType } from 'antd/lib/table/interface'; import { useUserReplays } from '../../lib/api/reactQuery/hooks/query/replays'; import { ReplayInfo } from '../../lib/api/requests/replays'; @@ -27,10 +20,8 @@ interface Props { } const UserReplays = ({ userInfo }: Props): JSX.Element => { - const { - data: userReplaysResult, - isLoading: isLoadingUserReplays, - } = useUserReplays(userInfo.webId); + const { data: userReplaysResult, isLoading: isLoadingUserReplays } = + useUserReplays(userInfo.webId); const userReplays = useMemo( () => userReplaysResult?.replays || [], @@ -50,7 +41,10 @@ const UserReplays = ({ userInfo }: Props): JSX.Element => { }, [userReplays]); const calculateTotalTime = (replays: ReplayInfo[]): string => { - const totalRecordedTime = replays.reduce((a, b) => a + b.endRaceTime, 0); + const totalRecordedTime = replays.reduce( + (a, b) => a + b.endRaceTime, + 0, + ); const totalRecordedTimeStr = msToTime(totalRecordedTime); return totalRecordedTimeStr; }; @@ -60,8 +54,12 @@ const UserReplays = ({ userInfo }: Props): JSX.Element => { [userReplays], ); - const getUniqueFilters = (replayFieldCallback: (replay: ReplayInfo) => string) => { - const uniques = Array.from(new Set(userReplays.map(replayFieldCallback))); + const getUniqueFilters = ( + replayFieldCallback: (replay: ReplayInfo) => string, + ) => { + const uniques = Array.from( + new Set(userReplays.map(replayFieldCallback)), + ); return uniques.sort().map((val) => ({ text: val, value: val })); }; @@ -81,7 +79,12 @@ const UserReplays = ({ userInfo }: Props): JSX.Element => { return ( ); @@ -135,16 +138,20 @@ const UserReplays = ({ userInfo }: Props): JSX.Element => { >
- + - + - +
( - -); +const FrameRate = (): JSX.Element => ; export default FrameRate; diff --git a/app/components/viewer/Grid.tsx b/app/components/viewer/Grid.tsx index 36d893aa..50bd5aad 100644 --- a/app/components/viewer/Grid.tsx +++ b/app/components/viewer/Grid.tsx @@ -7,7 +7,11 @@ import { BLOCK_SIZE } from '../../lib/constants/block'; const GRID_COLOR = new THREE.Color(0, 0, 0); const DEFAULT_GRID_SIZE = 48 * 32; const DEFAULT_GRID_DIVISIONS = DEFAULT_GRID_SIZE / 32; -export const DEFAULT_GRID_POS = new THREE.Vector3(DEFAULT_GRID_SIZE / 2, 0, DEFAULT_GRID_SIZE / 2); +export const DEFAULT_GRID_POS = new THREE.Vector3( + DEFAULT_GRID_SIZE / 2, + 0, + DEFAULT_GRID_SIZE / 2, +); interface GridProps { replaysData: ReplayData[]; @@ -34,7 +38,11 @@ export const Grid = ({ replaysData, blockPadding }: GridProps): JSX.Element => { maxPos = maxPos.divide(BLOCK_SIZE).ceil().multiply(BLOCK_SIZE); // Set grid pos to middle of min and max, then round to a block pos - gridPos = new Vector3((minPos.x + maxPos.x) / 2, minPos.y - 8, (minPos.z + maxPos.z) / 2); + gridPos = new Vector3( + (minPos.x + maxPos.x) / 2, + minPos.y - 8, + (minPos.z + maxPos.z) / 2, + ); gridPos = gridPos .divide(BLOCK_SIZE) .round() @@ -56,9 +64,20 @@ export const Grid = ({ replaysData, blockPadding }: GridProps): JSX.Element => { args={[gridSize, gridDivisions, GRID_COLOR, GRID_COLOR]} position={gridPos} /> - - - + + + ); diff --git a/app/components/viewer/InputOverlay.tsx b/app/components/viewer/InputOverlay.tsx index e2c2512d..4039543c 100644 --- a/app/components/viewer/InputOverlay.tsx +++ b/app/components/viewer/InputOverlay.tsx @@ -4,38 +4,41 @@ import * as THREE from 'three'; import { ReplayDataPoint } from '../../lib/replays/replayData'; interface InputOverlayProps { - sampleRef: React.MutableRefObject, - camera: any + sampleRef: React.MutableRefObject; + camera: any; } interface InputOverlayItemProps { - sampleRef: React.MutableRefObject, + sampleRef: React.MutableRefObject; } -interface SteerDirectionOverlayProps -{ - sampleRef: React.MutableRefObject, - dir: 'right' | 'left', +interface SteerDirectionOverlayProps { + sampleRef: React.MutableRefObject; + dir: 'right' | 'left'; getOffsetFunc: (inputSteer: number) => number; } const getOffsetXLeft = (inputSteer: number) => { if (inputSteer < 0) { - return (10 * inputSteer - 2); + return 10 * inputSteer - 2; } - return (-2); + return -2; }; const getOffsetXRight = (inputSteer: number) => { if (inputSteer > 0) { - return ((10 * Math.abs(inputSteer) + 2)); + return 10 * Math.abs(inputSteer) + 2; } - return (2); + return 2; }; -const SteerDirectionOverlay = ({ sampleRef, dir, getOffsetFunc }: SteerDirectionOverlayProps) => { +const SteerDirectionOverlay = ({ + sampleRef, + dir, + getOffsetFunc, +}: SteerDirectionOverlayProps) => { const arrowMeshRef = useRef(); - const dirToVecArray: { [dir: string]: THREE.Vector3[]; } = { + const dirToVecArray: { [dir: string]: THREE.Vector3[] } = { left: [ new THREE.Vector3(-2, 10, 0), new THREE.Vector3(-2, -10, 0), @@ -50,11 +53,12 @@ const SteerDirectionOverlay = ({ sampleRef, dir, getOffsetFunc }: SteerDirection const arrowMeshVecs: THREE.Vector3[] = dirToVecArray[dir]; const f32array = useMemo( - () => Float32Array.from( - new Array(arrowMeshVecs.length) - .fill(0) - .flatMap((item, index) => arrowMeshVecs[index].toArray()), - ), + () => + Float32Array.from( + new Array(arrowMeshVecs.length) + .fill(0) + .flatMap((item, index) => arrowMeshVecs[index].toArray()), + ), [arrowMeshVecs], ); @@ -62,14 +66,13 @@ const SteerDirectionOverlay = ({ sampleRef, dir, getOffsetFunc }: SteerDirection if (sampleRef.current && arrowMeshRef.current) { arrowMeshVecs[2].setX(getOffsetFunc(sampleRef.current.inputSteer)); arrowMeshRef.current.geometry.setFromPoints(arrowMeshVecs); - arrowMeshRef.current.geometry.attributes.position.needsUpdate = true; + arrowMeshRef.current.geometry.attributes.position.needsUpdate = + true; } }); return ( <> - + { const brakeMeshRef = useRef(); const brakeInputFloats: Float32Array[] = [ - new Float32Array([ - -1.8, -0.1, 0, - 1.8, -0.1, 0, - 1.8, -10, 0, - ]), - new Float32Array([ - -1.8, -0.1, 0, - -1.8, -10, 0, - 1.8, -10, 0, - ]), - new Float32Array([ - -1.8, -10, 0, - 1.8, -10, 0, - 0, -12, 0, - ]), + new Float32Array([-1.8, -0.1, 0, 1.8, -0.1, 0, 1.8, -10, 0]), + new Float32Array([-1.8, -0.1, 0, -1.8, -10, 0, 1.8, -10, 0]), + new Float32Array([-1.8, -10, 0, 1.8, -10, 0, 0, -12, 0]), ]; useFrame(() => { @@ -141,32 +132,28 @@ const InputBrakeOverlay = ({ sampleRef }: InputOverlayItemProps) => { }); return ( - - { - brakeInputFloats.map((vertices) => ( - - - - - + {brakeInputFloats.map((vertices) => ( + + + - - )) - } + + + + ))} ); }; @@ -174,21 +161,9 @@ const InputBrakeOverlay = ({ sampleRef }: InputOverlayItemProps) => { const InputGasOverlay = ({ sampleRef }: InputOverlayItemProps) => { const gasMeshRef = useRef(); const accelInputFloats: Float32Array[] = [ - new Float32Array([ - -1.8, 0.1, 0, - 1.8, 0.1, 0, - 1.8, 10, 0, - ]), - new Float32Array([ - -1.8, 0.1, 0, - -1.8, 10, 0, - 1.8, 10, 0, - ]), - new Float32Array([ - -1.8, 10, 0, - 1.8, 10, 0, - 0, 12, 0, - ]), + new Float32Array([-1.8, 0.1, 0, 1.8, 0.1, 0, 1.8, 10, 0]), + new Float32Array([-1.8, 0.1, 0, -1.8, 10, 0, 1.8, 10, 0]), + new Float32Array([-1.8, 10, 0, 1.8, 10, 0, 0, 12, 0]), ]; useFrame(() => { @@ -201,32 +176,28 @@ const InputGasOverlay = ({ sampleRef }: InputOverlayItemProps) => { }); return ( - - { - accelInputFloats.map((vertices) => ( - - - - - + {accelInputFloats.map((vertices) => ( + + + - - )) - } + + + + ))} ); }; @@ -248,8 +219,16 @@ const InputOverlay = ({ sampleRef, camera }: InputOverlayProps) => { position={[0, 2, 0]} scale={0.1} > - - + + diff --git a/app/components/viewer/ReplayCars.tsx b/app/components/viewer/ReplayCars.tsx index 6aab61db..412f9328 100644 --- a/app/components/viewer/ReplayCars.tsx +++ b/app/components/viewer/ReplayCars.tsx @@ -1,19 +1,13 @@ -import { - useFrame, useThree, Camera, -} from '@react-three/fiber'; +import { useFrame, useThree, Camera } from '@react-three/fiber'; import * as THREE from 'three'; -import React, { - useRef, useState, -} from 'react'; +import React, { useRef, useState } from 'react'; import { useFBX } from '@react-three/drei'; import { ReplayData } from '../../lib/api/requests/replays'; import { ReplayDataPoint } from '../../lib/replays/replayData'; import vecToQuat from '../../lib/utils/math'; import { CameraMode } from '../../lib/contexts/SettingsContext'; import InputOverlay from './InputOverlay'; -import { - getSampleNearTime, interpolateSamples, -} from '../../lib/utils/replay'; +import { getSampleNearTime, interpolateSamples } from '../../lib/utils/replay'; import GlobalTimeLineInfos from '../../lib/singletons/timeLineInfos'; const BACK_WHEEL_Y = 35.232017517089844; @@ -29,7 +23,12 @@ interface ReplayCarProps { } const ReplayCar = ({ - replay, camera, orbitControlsRef, showInputOverlay, fbx, replayCarOpacity, + replay, + camera, + orbitControlsRef, + showInputOverlay, + fbx, + replayCarOpacity, }: ReplayCarProps) => { const mesh = useRef(); const stadiumCarMesh = useRef(); @@ -44,7 +43,8 @@ const ReplayCar = ({ // Get own material from loaded car model const carMesh: THREE.Mesh = fbx.children[0] as THREE.Mesh; - const material: THREE.MeshPhongMaterial = carMesh.material as THREE.MeshPhongMaterial; + const material: THREE.MeshPhongMaterial = + carMesh.material as THREE.MeshPhongMaterial; const matClone = material.clone(); matClone.opacity = replayCarOpacity; matClone.color = new THREE.Color( @@ -64,21 +64,40 @@ const ReplayCar = ({ useFrame((state, delta) => { timeLineGlobal.tickTime = delta * 1000; - if (mesh.current - && camPosRef.current) { - const followed = timeLineGlobal.followedReplay != null && timeLineGlobal.followedReplay._id === replay._id; - const hovered = timeLineGlobal.hoveredReplay != null && timeLineGlobal.hoveredReplay._id === replay._id; + if (mesh.current && camPosRef.current) { + const followed = + timeLineGlobal.followedReplay != null && + timeLineGlobal.followedReplay._id === replay._id; + const hovered = + timeLineGlobal.hoveredReplay != null && + timeLineGlobal.hoveredReplay._id === replay._id; // Get closest sample to TimeLine.currentRaceTime - const curSample = getSampleNearTime(replay, timeLineGlobal.currentRaceTime); + const curSample = getSampleNearTime( + replay, + timeLineGlobal.currentRaceTime, + ); currentSampleRef.current = curSample; - prevSampleRef.current = replay.samples[replay.samples.indexOf(curSample) - 1]; - - if (timeLineGlobal.currentRaceTime < replay.endRaceTime) { - interpolateSamples(prevSampleRef.current, curSample, smoothSample, timeLineGlobal.currentRaceTime); - } else { - interpolateSamples(prevSampleRef.current, curSample, smoothSample, curSample.currentRaceTime); + prevSampleRef.current = + replay.samples[replay.samples.indexOf(curSample) - 1]; + + if (prevSampleRef.current) { + if (timeLineGlobal.currentRaceTime < replay.endRaceTime) { + interpolateSamples( + prevSampleRef.current, + curSample, + smoothSample, + timeLineGlobal.currentRaceTime, + ); + } else { + interpolateSamples( + prevSampleRef.current, + curSample, + smoothSample, + curSample.currentRaceTime, + ); + } } // Get car rotation @@ -88,7 +107,11 @@ const ReplayCar = ({ ); // Move & rotate 3D car from current sample rot & pos - mesh.current.position.set(smoothSample.position.x, smoothSample.position.y, smoothSample.position.z); + mesh.current.position.set( + smoothSample.position.x, + smoothSample.position.y, + smoothSample.position.z, + ); if (stadiumCarMesh.current) { stadiumCarMesh.current.rotation.setFromQuaternion(carRotation); @@ -103,35 +126,51 @@ const ReplayCar = ({ frontRigthWheel.rotation.y = smoothSample.wheelAngle; // FR // Set wheel suspensions - rearRightWheel.position.setY(BACK_WHEEL_Y - (smoothSample.rRDamperLen * 100)); // RR - frontLeftWheel.position.setY(FRONT_WHEEL_Y - (smoothSample.fLDamperLen * 100)); // FL - rearLeftWheel.position.setY(BACK_WHEEL_Y - (smoothSample.rLDamperLen * 100)); // RL - frontRigthWheel.position.setY(FRONT_WHEEL_Y - (smoothSample.fRDamperLen * 100)); // FR + rearRightWheel.position.setY( + BACK_WHEEL_Y - smoothSample.rRDamperLen * 100, + ); // RR + frontLeftWheel.position.setY( + FRONT_WHEEL_Y - smoothSample.fLDamperLen * 100, + ); // FL + rearLeftWheel.position.setY( + BACK_WHEEL_Y - smoothSample.rLDamperLen * 100, + ); // RL + frontRigthWheel.position.setY( + FRONT_WHEEL_Y - smoothSample.fRDamperLen * 100, + ); // FR } // Camera target replay if selected if (followed) { if (orbitControlsRef && orbitControlsRef.current) { - orbitControlsRef.current.target.lerp(smoothSample.position, 0.2); + orbitControlsRef.current.target.lerp( + smoothSample.position, + 0.2, + ); if (timeLineGlobal.cameraMode === CameraMode.Follow) { // move camPosMesh to Follow position - camPosRef.current.rotation.setFromQuaternion(carRotation); + camPosRef.current.rotation.setFromQuaternion( + carRotation, + ); // move toward where the car is heading const velocitySpeed = smoothSample.velocity.length(); // Set camera position behind the car const backwardMax = 6; - const backward = (backwardMax - (velocitySpeed / backwardMax)); + const backward = + backwardMax - velocitySpeed / backwardMax; camPosRef.current.position.set( - (-smoothSample.velocity.x / 4), - (-smoothSample.velocity.y / 4), - (-smoothSample.velocity.z / 4), + -smoothSample.velocity.x / 4, + -smoothSample.velocity.y / 4, + -smoothSample.velocity.z / 4, ); // Do not force camera behind the car above a certain speed - camPosRef.current.translateZ(backward < 0 ? 0 : -backward); + camPosRef.current.translateZ( + backward < 0 ? 0 : -backward, + ); camPosRef.current.translateY(3); // move camera to camPosMesh world position const camWorldPos: THREE.Vector3 = new THREE.Vector3(); @@ -144,9 +183,15 @@ const ReplayCar = ({ // Scale car up if hovered in LoadedReplays if (stadiumCarMesh.current) { if (hovered) { - stadiumCarMesh.current.scale.lerp(new THREE.Vector3(0.02, 0.02, 0.02), 0.2); + stadiumCarMesh.current.scale.lerp( + new THREE.Vector3(0.02, 0.02, 0.02), + 0.2, + ); } else { - stadiumCarMesh.current.scale.lerp(new THREE.Vector3(0.01, 0.01, 0.01), 0.2); + stadiumCarMesh.current.scale.lerp( + new THREE.Vector3(0.01, 0.01, 0.01), + 0.2, + ); } } } @@ -184,15 +229,24 @@ const ReplayCar = ({ /> )} - {showInputOverlay - && } - - - + {showInputOverlay && ( + + )} + + + - ); diff --git a/app/components/viewer/ReplayChartHoverLocation.tsx b/app/components/viewer/ReplayChartHoverLocation.tsx index 694d4c0b..60aa06ab 100644 --- a/app/components/viewer/ReplayChartHoverLocation.tsx +++ b/app/components/viewer/ReplayChartHoverLocation.tsx @@ -20,9 +20,16 @@ const ReplayChartHoverLocation = ({ useFrame(() => { if (sphereRef.current) { if (globalChartsData.hoveredRaceTime !== undefined) { - const curSample = getSampleNearTime(replay, globalChartsData.hoveredRaceTime); + const curSample = getSampleNearTime( + replay, + globalChartsData.hoveredRaceTime, + ); - sphereRef.current.position.set(curSample.position.x, curSample.position.y, curSample.position.z); + sphereRef.current.position.set( + curSample.position.x, + curSample.position.y, + curSample.position.z, + ); sphereRef.current.scale.set(0.5, 0.5, 0.5); } else { sphereRef.current.scale.lerp(new THREE.Vector3(0, 0, 0), 0.1); @@ -30,13 +37,18 @@ const ReplayChartHoverLocation = ({ } }); - return globalChartsData.hoveredRaceTime === undefined - ? ( - - - - ) - : null; + return globalChartsData.hoveredRaceTime === undefined ? ( + + + + ) : null; }; export default ReplayChartHoverLocation; diff --git a/app/components/viewer/ReplayDnf.tsx b/app/components/viewer/ReplayDnf.tsx index 707fc0f8..d0176bf0 100644 --- a/app/components/viewer/ReplayDnf.tsx +++ b/app/components/viewer/ReplayDnf.tsx @@ -5,14 +5,18 @@ import { DoubleSide } from 'three'; import { ReplayData } from '../../lib/api/requests/replays'; interface ReplayDnfProps { - replay: ReplayData; + replay: ReplayData; } const ReplayDnf = ({ replay }: ReplayDnfProps): JSX.Element => { const mesh = useRef(); const dnfTextColor = new THREE.Color(1, 1, 1); return ( <> - + { opacity={0.5} /> - + { anchorX="center" anchorY="middle" > - + DNF diff --git a/app/components/viewer/ReplayGears.tsx b/app/components/viewer/ReplayGears.tsx index b589ae85..f61ac479 100644 --- a/app/components/viewer/ReplayGears.tsx +++ b/app/components/viewer/ReplayGears.tsx @@ -14,8 +14,15 @@ const GearIndicator = ({ gearChange }: GearIndicatorProps) => { const color = getColorFromMap(gearChange.engineCurGear, COLOR_MAP_GEARS); return ( - - + + ); }; @@ -32,7 +39,6 @@ const GearText = ({ gearChange }: GearTextProps) => { gearChange.sample.position, new THREE.Vector3(0, 5, 0), )} - args={[0, 0]} > { anchorX="center" anchorY="middle" > - + {gearChange.engineCurGear} @@ -82,7 +92,8 @@ const ReplayGears = ({ replay }: ReplayGearsProps): JSX.Element => { changes.push({ sample: curSample, engineCurGear: curSample.engineCurGear, - gearUp: curSample.engineCurGear > prevSample.engineCurGear, + gearUp: + curSample.engineCurGear > prevSample.engineCurGear, }); } } diff --git a/app/components/viewer/ReplayLines.tsx b/app/components/viewer/ReplayLines.tsx index d6a248fb..e1f862c3 100644 --- a/app/components/viewer/ReplayLines.tsx +++ b/app/components/viewer/ReplayLines.tsx @@ -1,6 +1,4 @@ -import React, { - useCallback, useEffect, useMemo, useRef, -} from 'react'; +import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import * as THREE from 'three'; import { ReplayData } from '../../lib/api/requests/replays'; import useUpdateReplayLineTrail from '../../lib/hooks/viewer/replayLines/useUpdateReplayLineTrail'; @@ -28,7 +26,10 @@ export interface LineType { export const LineTypes: { [name: string]: LineType } = { default: { name: 'Default', colorsCallback: defaultReplayColors }, speed: { name: 'Speed', colorsCallback: speedReplayColors }, - acceleration: { name: 'Acceleration', colorsCallback: accelerationReplayColors }, + acceleration: { + name: 'Acceleration', + colorsCallback: accelerationReplayColors, + }, gear: { name: 'Gear', colorsCallback: gearReplayColors }, rpm: { name: 'RPM', colorsCallback: rpmReplayColors }, inputs: { name: 'Inputs', colorsCallback: inputReplayColors }, @@ -40,10 +41,15 @@ interface ReplayLineProps { replayLineOpacity: number; } const ReplayLine = ({ - replay, lineType, replayLineOpacity, + replay, + lineType, + replayLineOpacity, }: ReplayLineProps) => { const bufferGeom = useRef(); - const points = useMemo(() => replay.samples.map((sample) => sample.position), [replay.samples]); + const points = useMemo( + () => replay.samples.map((sample) => sample.position), + [replay.samples], + ); const colorBuffer = useMemo(() => { const colors = lineType.colorsCallback(replay); @@ -55,7 +61,7 @@ const ReplayLine = ({ }, [replay, replay.color, lineType]); const onUpdate = useCallback( - (self) => { + (self: THREE.BufferGeometry) => { self.setFromPoints(points); self.setAttribute('color', colorBuffer); }, @@ -63,7 +69,11 @@ const ReplayLine = ({ ); // Update the replay line trail based on trail settings - const { manualLineTrailUpdate } = useUpdateReplayLineTrail(bufferGeom, replay, TRAIL_FADE_SEGMENT_TIME); + const { manualLineTrailUpdate } = useUpdateReplayLineTrail( + bufferGeom, + replay, + TRAIL_FADE_SEGMENT_TIME, + ); useEffect(() => { if (!bufferGeom.current) return; @@ -73,7 +83,10 @@ const ReplayLine = ({ return ( - + {showGearChanges && ( - - )} - {(replay.dnfPos.x !== 0 && replay.dnfPos.y !== 0 && replay.dnfPos.z !== 0) && ( - + )} + {/* Removed because it is no longer possible to upload an unfinished run */} + {/* {replay.dnfPos.x !== 0 && + replay.dnfPos.y !== 0 && + replay.dnfPos.z !== 0 && ( + + )} */} {/* Removed until fully implemented: */} {/* diff --git a/app/components/viewer/ReplaySectorHighlights.tsx b/app/components/viewer/ReplaySectorHighlights.tsx index 56a715ce..676274c5 100644 --- a/app/components/viewer/ReplaySectorHighlights.tsx +++ b/app/components/viewer/ReplaySectorHighlights.tsx @@ -13,25 +13,41 @@ interface SectorIndicatorProps { color?: THREE.Color; } const SectorIndicator = ({ position, color }: SectorIndicatorProps) => ( - - + + ); interface ReplaySectorIndicatorProps { replay: ReplayData; } -const ReplaySectorHighlights = ({ replay }: ReplaySectorIndicatorProps): JSX.Element => { +const ReplaySectorHighlights = ({ + replay, +}: ReplaySectorIndicatorProps): JSX.Element => { const interpolatedPositions = useMemo(() => { const positions = replay.sectorTimes?.map((sectorTime) => { const sample = getSampleNearTime(replay, sectorTime); - const prevSample = replay.samples[replay.samples.indexOf(sample) - 1]; + const prevSample = + replay.samples[replay.samples.indexOf(sample) - 1]; - const factor: number = (sectorTime - prevSample.currentRaceTime) - / (sample.currentRaceTime - prevSample.currentRaceTime); + const factor: number = + (sectorTime - prevSample.currentRaceTime) / + (sample.currentRaceTime - prevSample.currentRaceTime); const interpolatedPosition = new THREE.Vector3(); - setInterpolatedVector(interpolatedPosition, prevSample.position, sample.position, factor); + setInterpolatedVector( + interpolatedPosition, + prevSample.position, + sample.position, + factor, + ); return interpolatedPosition; }); @@ -49,7 +65,8 @@ const ReplaySectorHighlights = ({ replay }: ReplaySectorIndicatorProps): JSX.Ele const prevIndicatorPositions = useMemo(() => { const positions = replay.sectorTimes?.map((sectorTime) => { const sample = getSampleNearTime(replay, sectorTime); - const prevSample = replay.samples[replay.samples.indexOf(sample) - 1]; + const prevSample = + replay.samples[replay.samples.indexOf(sample) - 1]; return prevSample.position; }); return positions; @@ -58,16 +75,27 @@ const ReplaySectorHighlights = ({ replay }: ReplaySectorIndicatorProps): JSX.Ele return ( <> {sectorIndicatorPositions?.map((pos, i) => ( - // eslint-disable-next-line react/no-array-index-key - + ))} {interpolatedPositions?.map((pos, i) => ( - // eslint-disable-next-line react/no-array-index-key - + ))} {prevIndicatorPositions?.map((pos, i) => ( - // eslint-disable-next-line react/no-array-index-key - + ))} ); diff --git a/app/components/viewer/SceneDirectionalLight.tsx b/app/components/viewer/SceneDirectionalLight.tsx index 566fdfc1..c126c185 100644 --- a/app/components/viewer/SceneDirectionalLight.tsx +++ b/app/components/viewer/SceneDirectionalLight.tsx @@ -1,15 +1,16 @@ import React, { useCallback, useEffect, useRef } from 'react'; -import { - Color, DirectionalLight, Group, Vector3, -} from 'three'; +import { Color, DirectionalLight, Group, Vector3 } from 'three'; import { Sphere } from '@react-three/drei'; import { ReplayData } from '../../lib/api/requests/replays'; interface SceneDirectionalLightProps { replays: ReplayData[]; - showDebugLocation?: boolean + showDebugLocation?: boolean; } -const SceneDirectionalLight = ({ replays, showDebugLocation }: SceneDirectionalLightProps) => { +const SceneDirectionalLight = ({ + replays, + showDebugLocation, +}: SceneDirectionalLightProps) => { const ref = useRef(); const targetRef = useRef(); @@ -79,7 +80,10 @@ const SceneDirectionalLight = ({ replays, showDebugLocation }: SceneDirectionalL {/* Debug Location */} {showDebugLocation && ( - + )} @@ -87,8 +91,14 @@ const SceneDirectionalLight = ({ replays, showDebugLocation }: SceneDirectionalL {/* Debug Target Location */} {showDebugLocation && ( - - + + )} diff --git a/app/components/viewer/Viewer3D.tsx b/app/components/viewer/Viewer3D.tsx index b8019ad5..c486a519 100644 --- a/app/components/viewer/Viewer3D.tsx +++ b/app/components/viewer/Viewer3D.tsx @@ -1,5 +1,9 @@ import React, { - Suspense, useContext, useEffect, useRef, useState, + Suspense, + useContext, + useEffect, + useRef, + useState, } from 'react'; import * as THREE from 'three'; import { Canvas } from '@react-three/fiber'; @@ -37,11 +41,15 @@ const Viewer3D = ({ replaysData }: Props): JSX.Element => { if (!orbitControlsRef.current) return; if (prevReplaysData.current) { - if (prevReplaysData.current.length === 0 && replaysData.length > 0) { + if ( + prevReplaysData.current.length === 0 && + replaysData.length > 0 + ) { // If previous replay data was empty and current one isn't, meaning first replay selected, // set target to first sample of the first replay if (replaysData[0].samples.length > 0) { - orbitControlsRef.current.target = replaysData[0].samples[0].position.clone(); + orbitControlsRef.current.target = + replaysData[0].samples[0].position.clone(); } } } @@ -51,7 +59,10 @@ const Viewer3D = ({ replaysData }: Props): JSX.Element => { }, [replaysData, orbitControlsRef]); return ( -
+
{ shadows > - + @@ -73,7 +89,10 @@ const Viewer3D = ({ replaysData }: Props): JSX.Element => { target={DEFAULT_GRID_POS} /> - + { {showFPS && } - +
); }; diff --git a/app/components/viewer/timeline/TimeLine.tsx b/app/components/viewer/timeline/TimeLine.tsx index 1c02c37e..3f34f2f4 100644 --- a/app/components/viewer/timeline/TimeLine.tsx +++ b/app/components/viewer/timeline/TimeLine.tsx @@ -1,8 +1,6 @@ import { useState } from 'react'; import { Slider } from 'antd'; -import { - CaretRightOutlined, PauseOutlined, -} from '@ant-design/icons'; +import { CaretRightOutlined, PauseOutlined } from '@ant-design/icons'; import { ReplayData } from '../../../lib/api/requests/replays'; import { getRaceTimeStr } from '../../../lib/utils/time'; import GlobalTimeLineInfos from '../../../lib/singletons/timeLineInfos'; @@ -33,7 +31,12 @@ const TimeLineView = ({ replaysData }: TimeLineViewProps) => { timeLineGlobal.currentRaceTime = timeLineTime; if (timeLineGlobal.followedReplay !== null) { - if (!replaysData.some((replay: ReplayData) => replay._id === timeLineGlobal.followedReplay?._id)) { + if ( + !replaysData.some( + (replay: ReplayData) => + replay._id === timeLineGlobal.followedReplay?._id, + ) + ) { timeLineGlobal.followedReplay = undefined; } } @@ -50,8 +53,12 @@ const TimeLineView = ({ replaysData }: TimeLineViewProps) => { } replaysData.forEach((replay) => { - if (replay.samples[replay.samples.length - 1].currentRaceTime > timeLineGlobal.maxRaceTime) { - timeLineGlobal.maxRaceTime = replay.samples[replay.samples.length - 1].currentRaceTime; + if ( + replay.samples[replay.samples.length - 1].currentRaceTime > + timeLineGlobal.maxRaceTime + ) { + timeLineGlobal.maxRaceTime = + replay.samples[replay.samples.length - 1].currentRaceTime; } }); @@ -83,7 +90,8 @@ const TimeLineView = ({ replaysData }: TimeLineViewProps) => { const intervalCallback = () => { const raceTimeIncrement = timeLineGlobal.tickTime * speed; - const nextRaceTime = timeLineGlobal.currentRaceTime + raceTimeIncrement; + const nextRaceTime = + timeLineGlobal.currentRaceTime + raceTimeIncrement; if (nextRaceTime > timeLineGlobal.maxRaceTime || nextRaceTime < 0) { // Loop time back to 0 if time is past the max // and ensure time stays at least 0 @@ -93,7 +101,10 @@ const TimeLineView = ({ replaysData }: TimeLineViewProps) => { } }; - playInterval = setTimeout(() => startSteadyLoop(intervalCallback), timeLineGlobal.tickTime); + playInterval = setTimeout( + () => startSteadyLoop(intervalCallback), + timeLineGlobal.tickTime, + ); }; const onTogglePlay = (shouldPlay: boolean = !playing) => { @@ -116,7 +127,12 @@ const TimeLineView = ({ replaysData }: TimeLineViewProps) => { } }; - const timeFormat = (v: number | undefined) => (v !== undefined ? `${getRaceTimeStr(v)}` : ''); + const timeFormat = (v: number | undefined) => { + if (v === undefined) { + return ''; + } + return getRaceTimeStr(v); + }; return (
{ }} >
-
- +
+
-
+
{timeFormat(timeLineTime)}
@@ -147,19 +158,14 @@ const TimeLineView = ({ replaysData }: TimeLineViewProps) => { role="button" tabIndex={0} > - {playing - ? - : } + {playing ? : }
{`Speed: ${timelineSpeed.toFixed(2)}x`}
-
- {MIN_SPEED} - x -
+
{MIN_SPEED}x
{ tooltipVisible={false} />
-
- {MAX_SPEED} - x -
+
{MAX_SPEED}x
@@ -191,11 +194,12 @@ interface TimeLineProps { replaysData: ReplayData[]; } -const TimeLine = ({ - replaysData, -}: TimeLineProps): JSX.Element => ( +const TimeLine = ({ replaysData }: TimeLineProps): JSX.Element => ( <> - + ); diff --git a/app/components/viewer/timeline/TimelineSlider.tsx b/app/components/viewer/timeline/TimelineSlider.tsx index ae8cb5dc..6a4615b6 100644 --- a/app/components/viewer/timeline/TimelineSlider.tsx +++ b/app/components/viewer/timeline/TimelineSlider.tsx @@ -1,124 +1,27 @@ import React, { useCallback, useEffect, useRef } from 'react'; +import { Slider, SliderSingleProps } from 'antd'; import GlobalTimeLineInfos from '../../../lib/singletons/timeLineInfos'; +import { getRaceTimeStr } from '../../../lib/utils/time'; interface SliderProps { onChange: (value: number) => void; - yDragMargin?: number; } -const TimelineSlider = ({ - onChange, yDragMargin, -}: SliderProps) => { - const ref = useRef(null); - +const TimelineSlider = ({ onChange }: SliderProps) => { const timeLineGlobal = GlobalTimeLineInfos.getInstance(); - let mouseDown = false; - let isDraggingSlider = false; - - const updateValueUsingMousePos = (mouseX: number) => { - if (ref.current) { - const { x, width } = ref.current.getBoundingClientRect(); - - const fraction = Math.min(1, Math.max(0, (mouseX - x) / width)); - const newValue = fraction * timeLineGlobal.maxRaceTime; - - onChange(newValue); - timeLineGlobal.currentRaceTime = Math.round(newValue); - } - }; - - const handleMouseDown = useCallback((e: MouseEvent) => { - mouseDown = true; - - if (ref.current) { - const { clientX, clientY } = e; - const { - x, y, width, height, - } = ref.current.getBoundingClientRect(); - - const dragMargin = yDragMargin || 0; - - if (clientX >= x && clientX <= x + width - && clientY >= y - dragMargin && clientY <= y + height + dragMargin) { - isDraggingSlider = true; - updateValueUsingMousePos(clientX); - } - } - }, []); - const handleMouseMove = useCallback((e: MouseEvent) => { - if (mouseDown && isDraggingSlider && ref.current) { - updateValueUsingMousePos(e.clientX); - } - }, []); - const handleMouseUp = useCallback((e: MouseEvent) => { - if (isDraggingSlider) { - updateValueUsingMousePos(e.clientX); - } - mouseDown = false; - isDraggingSlider = false; - }, []); - - useEffect(() => { - document.addEventListener('mousedown', handleMouseDown); - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('mouseup', handleMouseUp); - - return () => { - document.removeEventListener('mousedown', handleMouseDown); - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('mouseup', handleMouseUp); - }; - }, []); - - const mapValueToPercent = (inputValue: number) => { - const percent = inputValue / timeLineGlobal.maxRaceTime; - return percent; - }; + const formatter: NonNullable['formatter'] = ( + value, + ) => getRaceTimeStr(value || 0); return ( -
- {/* Blue background */} -
- - {/* White line */} -
- - {/* Arrow indicator */} -
-
-
-
+ ); }; diff --git a/app/custom.d.ts b/app/custom.d.ts index 091d25e2..60bd434c 100644 --- a/app/custom.d.ts +++ b/app/custom.d.ts @@ -1,4 +1,4 @@ declare module '*.svg' { - const content: any; - export default content; + const content: any; + export default content; } diff --git a/app/lib/api/reactQuery/hooks/mutations/replays.ts b/app/lib/api/reactQuery/hooks/mutations/replays.ts index 5a72e9c3..900fd956 100644 --- a/app/lib/api/reactQuery/hooks/mutations/replays.ts +++ b/app/lib/api/reactQuery/hooks/mutations/replays.ts @@ -3,12 +3,12 @@ import QUERY_KEYS from '../../queryKeys'; import API from '../../../apiWrapper'; import { AllReplaysResult } from '../../../requests/replays'; -const useDeleteReplay = (queryClient: QueryClient) => useMutation( - API.replays.deleteReplay, - { +const useDeleteReplay = (queryClient: QueryClient) => + useMutation(API.replays.deleteReplay, { onSuccess: (_, replay) => { // Optimistic update of query data - queryClient.setQueryData(QUERY_KEYS.mapReplays(replay.mapUId), + queryClient.setQueryData( + QUERY_KEYS.mapReplays(replay.mapUId), (oldData?: AllReplaysResult) => { // Should never happen, if a replay is deleted, old data should exist if (!oldData) { @@ -17,16 +17,18 @@ const useDeleteReplay = (queryClient: QueryClient) => useMutation( // Remove deleted replay return { - replays: oldData.replays.filter((r) => r._id !== replay._id), + replays: oldData.replays.filter( + (r) => r._id !== replay._id, + ), totalResults: oldData.totalResults - 1, }; - }); + }, + ); // Invalidate queries to force refetch from server queryClient.invalidateQueries(QUERY_KEYS.mapReplays(replay.mapUId)); queryClient.invalidateQueries(QUERY_KEYS.userReplays()); }, - }, -); + }); export default useDeleteReplay; diff --git a/app/lib/api/reactQuery/hooks/query/maps.ts b/app/lib/api/reactQuery/hooks/query/maps.ts index 8ccb5b8e..5b7b9c79 100644 --- a/app/lib/api/reactQuery/hooks/query/maps.ts +++ b/app/lib/api/reactQuery/hooks/query/maps.ts @@ -4,18 +4,20 @@ import QUERY_KEYS from '../../queryKeys'; import API from '../../../apiWrapper'; import { TIME_IN_MS } from '../../../../utils/time'; -export const useAllMaps = (searchString: string = '') => useQuery( - QUERY_KEYS.allMaps(searchString), - () => API.maps.getAllMaps(searchString), -); +export const useAllMaps = (searchString: string = '') => + useQuery(QUERY_KEYS.allMaps(searchString), () => + API.maps.getAllMaps(searchString), + ); -export const useMapInfo = (mapUId?: string) => useQuery( - QUERY_KEYS.mapInfo(mapUId), - // default to empty string to satisfy type, this will not be fetched as query is disabled with mapUId is undefined: - () => API.maps.getMapInfo(mapUId || ''), - { - ...queryClient.getDefaultOptions(), - enabled: mapUId !== undefined, - staleTime: TIME_IN_MS.HOUR, // Long stale time, map info should not change often - }, -); +export const useMapInfo = (mapUId?: string) => + useQuery( + QUERY_KEYS.mapInfo(mapUId), + // eslint-disable-next-line max-len + // default to empty string to satisfy type, this will not be fetched as query is disabled with mapUId is undefined: + () => API.maps.getMapInfo(mapUId || ''), + { + ...queryClient.getDefaultOptions(), + enabled: mapUId !== undefined, + staleTime: TIME_IN_MS.HOUR, // Long stale time, map info should not change often + }, + ); diff --git a/app/lib/api/reactQuery/hooks/query/replays.ts b/app/lib/api/reactQuery/hooks/query/replays.ts index a61795ea..1cf87892 100644 --- a/app/lib/api/reactQuery/hooks/query/replays.ts +++ b/app/lib/api/reactQuery/hooks/query/replays.ts @@ -3,20 +3,22 @@ import queryClient from '../../queryClient'; import QUERY_KEYS from '../../queryKeys'; import API from '../../../apiWrapper'; -export const useMapReplays = (mapUId?: string) => useQuery( - QUERY_KEYS.mapReplays(mapUId), - () => API.replays.fetchReplays({ mapUId }), - { - ...queryClient.getDefaultOptions(), - enabled: mapUId !== undefined, - }, -); +export const useMapReplays = (mapUId?: string) => + useQuery( + QUERY_KEYS.mapReplays(mapUId), + () => API.replays.fetchReplays({ mapUId }), + { + ...queryClient.getDefaultOptions(), + enabled: mapUId !== undefined, + }, + ); -export const useUserReplays = (webId?: string) => useQuery( - QUERY_KEYS.userReplays(webId), - () => API.users.getUserReplays(webId || ''), - { - ...queryClient.getDefaultOptions(), - enabled: webId !== undefined, - }, -); +export const useUserReplays = (webId?: string) => + useQuery( + QUERY_KEYS.userReplays(webId), + () => API.users.getUserReplays(webId || ''), + { + ...queryClient.getDefaultOptions(), + enabled: webId !== undefined, + }, + ); diff --git a/app/lib/api/reactQuery/hooks/query/users.ts b/app/lib/api/reactQuery/hooks/query/users.ts index 3e25b8d7..005dad68 100644 --- a/app/lib/api/reactQuery/hooks/query/users.ts +++ b/app/lib/api/reactQuery/hooks/query/users.ts @@ -4,14 +4,15 @@ import QUERY_KEYS from '../../queryKeys'; import API from '../../../apiWrapper'; import { TIME_IN_MS } from '../../../../utils/time'; -const useUserInfo = (webId?: string) => useQuery( - QUERY_KEYS.userInfo(webId), - () => API.users.getUserInfo(webId || ''), - { - ...queryClient.getDefaultOptions(), - enabled: webId !== undefined, - staleTime: TIME_IN_MS.HOUR, // Long stale time, user info should never really change - }, -); +const useUserInfo = (webId?: string) => + useQuery( + QUERY_KEYS.userInfo(webId), + () => API.users.getUserInfo(webId || ''), + { + ...queryClient.getDefaultOptions(), + enabled: webId !== undefined, + staleTime: TIME_IN_MS.HOUR, // Long stale time, user info should never really change + }, + ); export default useUserInfo; diff --git a/app/lib/api/reactQuery/queryKeys.ts b/app/lib/api/reactQuery/queryKeys.ts index 592d2b29..36097235 100644 --- a/app/lib/api/reactQuery/queryKeys.ts +++ b/app/lib/api/reactQuery/queryKeys.ts @@ -1,19 +1,34 @@ const QUERY_KEYS = { - allMaps: (searchString: string = '') => ( - searchString !== '' ? ['allMaps', searchString] as const : ['allMaps'] - ), - mapReplays: (mapUId?: string) => ( - mapUId && mapUId !== '' ? ['mapReplays', mapUId] as const : ['mapReplays'] - ), - mapInfo: (mapUId?: string) => ( - mapUId && mapUId !== '' ? ['mapInfo', mapUId] as const : ['mapInfo'] - ), - userReplays: (userId?: string) => ( - userId && userId !== '' ? ['userReplays', userId] as const : ['userReplays'] - ), - userInfo: (webId?: string) => ( - webId && webId !== '' ? ['userInfo', webId] as const : ['userInfo'] - ), + allMaps: (searchString: string = '') => { + if (searchString !== '') { + return ['allMaps', searchString] as const; + } + return ['allMaps']; + }, + mapReplays: (mapUId?: string) => { + if (mapUId && mapUId !== '') { + return ['mapReplays', mapUId] as const; + } + return ['mapReplays']; + }, + mapInfo: (mapUId?: string) => { + if (mapUId && mapUId !== '') { + return ['mapInfo', mapUId] as const; + } + return ['mapInfo']; + }, + userReplays: (userId?: string) => { + if (userId && userId !== '') { + return ['userReplays', userId] as const; + } + return ['userReplays']; + }, + userInfo: (webId?: string) => { + if (webId && webId !== '') { + return ['userInfo', webId] as const; + } + return ['userInfo']; + }, }; export default QUERY_KEYS; diff --git a/app/lib/api/requests/auth.ts b/app/lib/api/requests/auth.ts index 6c0a169f..ecf2e565 100644 --- a/app/lib/api/requests/auth.ts +++ b/app/lib/api/requests/auth.ts @@ -6,7 +6,8 @@ interface AuthorizationResponse { accountId: string; } export const authorizeWithAccessCode = async ( - accessCode: string, clientCode?: string, + accessCode: string, + clientCode?: string, ): Promise => { const params: any = { code: accessCode, @@ -18,7 +19,9 @@ export const authorizeWithAccessCode = async ( params.clientCode = clientCode; } - const { data } = await apiInstance.post('/authorize', params, { withCredentials: true }); + const { data } = await apiInstance.post('/authorize', params, { + withCredentials: true, + }); return data; }; @@ -27,11 +30,14 @@ export interface AuthUserInfo { displayName: string; accountId: string; } -export const fetchLoggedInUser = async (): Promise => { - const hasSessionCookie = document.cookie - .split(';') - .filter((cookie) => cookie.trim().startsWith('sessionId=')) - .length > 0; +export const fetchLoggedInUser = async (): Promise< + AuthUserInfo | undefined +> => { + const hasSessionCookie = + document.cookie + .split(';') + .filter((cookie) => cookie.trim().startsWith('sessionId=')).length > + 0; if (!hasSessionCookie) { return undefined; diff --git a/app/lib/api/requests/maps.ts b/app/lib/api/requests/maps.ts index cef02d6e..fe075c30 100644 --- a/app/lib/api/requests/maps.ts +++ b/app/lib/api/requests/maps.ts @@ -21,7 +21,9 @@ export type MapWithStats = { count: number; lastUpdate: number; }; -export const getAllMaps = async (searchString: string): Promise => { +export const getAllMaps = async ( + searchString: string, +): Promise => { const { data } = await apiInstance.get('/maps', { params: { mapName: searchString, diff --git a/app/lib/api/requests/replays.ts b/app/lib/api/requests/replays.ts index 5be2cf7c..1b264817 100644 --- a/app/lib/api/requests/replays.ts +++ b/app/lib/api/requests/replays.ts @@ -1,6 +1,9 @@ import apiInstance from '../apiInstance'; import { readDataView, DataViewResult } from '../../replays/replayData'; -import { DownloadState, ReplayDownloadState } from '../../replays/replayDownloadState'; +import { + DownloadState, + ReplayDownloadState, +} from '../../replays/replayDownloadState'; interface FilterParams { mapName?: string; @@ -44,7 +47,9 @@ export type AllReplaysResult = { totalResults: number; }; -export const fetchReplays = async (filters: FilterParams = DEFAULT_FILTERS): Promise => { +export const fetchReplays = async ( + filters: FilterParams = DEFAULT_FILTERS, +): Promise => { const { data } = await apiInstance.get('/replays', { params: { ...DEFAULT_FILTERS, ...filters }, }); @@ -58,7 +63,10 @@ export const fetchReplays = async (filters: FilterParams = DEFAULT_FILTERS): Pro export interface ReplayData extends ReplayInfo, DataViewResult {} export const fetchReplayData = async ( replay: ReplayInfo, - downloadProgress?: (replay: ReplayInfo, progressEvent: ProgressEvent) => void, + downloadProgress?: ( + replay: ReplayInfo, + progressEvent: ProgressEvent, + ) => void, ): Promise => { const fetchedReplay: ReplayDownloadState = { _id: replay._id, @@ -76,12 +84,17 @@ export const fetchReplayData = async ( }); const dataView = new DataView(res.data); - const { - samples, minPos, maxPos, dnfPos, color, intervalMedian, - } = await readDataView(dataView); + const { samples, minPos, maxPos, dnfPos, color, intervalMedian } = + await readDataView(dataView); fetchedReplay.replay = { - ...replay, samples, minPos, maxPos, dnfPos, color, intervalMedian, + ...replay, + samples, + minPos, + maxPos, + dnfPos, + color, + intervalMedian, }; fetchedReplay.progress = 1; fetchedReplay.state = DownloadState.LOADED; diff --git a/app/lib/api/requests/users.ts b/app/lib/api/requests/users.ts index 956405b5..4f76b530 100644 --- a/app/lib/api/requests/users.ts +++ b/app/lib/api/requests/users.ts @@ -12,7 +12,9 @@ export const getUserInfo = async (webId: string): Promise => { return data; }; -export const getUserReplays = async (webId: string): Promise => { +export const getUserReplays = async ( + webId: string, +): Promise => { const { data } = await apiInstance.get(`/users/${webId}/replays`); return { replays: data.files, diff --git a/app/lib/charts/chartData.ts b/app/lib/charts/chartData.ts index e4b101f6..71bd8f2a 100644 --- a/app/lib/charts/chartData.ts +++ b/app/lib/charts/chartData.ts @@ -35,9 +35,14 @@ export const metricChartData = ( allRaceTimes.forEach((raceTime: number) => { for (let i = 0; i < replay.samples.length; i++) { lastSample = replay.samples[i]; - if (lastSample.currentRaceTime === raceTime - || lastSample.currentRaceTime > raceTime) { - chartData.data.push([raceTime, chartDataInfo.dataCallback(lastSample)]); + if ( + lastSample.currentRaceTime === raceTime || + lastSample.currentRaceTime > raceTime + ) { + chartData.data.push([ + raceTime, + chartDataInfo.dataCallback(lastSample), + ]); break; } } diff --git a/app/lib/charts/chartOptions.ts b/app/lib/charts/chartOptions.ts index 30ce470f..67aa29d4 100644 --- a/app/lib/charts/chartOptions.ts +++ b/app/lib/charts/chartOptions.ts @@ -1,4 +1,7 @@ -import Highcharts, { AxisOptions, AxisSetExtremesEventObject } from 'highcharts/highstock'; +import Highcharts, { + AxisOptions, + AxisSetExtremesEventObject, +} from 'highcharts/highstock'; import { RangeUpdateInfos } from '../../components/maps/ChartsDrawer'; import { ReplayData } from '../api/requests/replays'; import GlobalChartsDataSingleton from '../singletons/globalChartData'; @@ -16,11 +19,13 @@ export const globalChartOptions = ( // Higchart tooltip needs more space when > 5 replays are loaded if (options.chart && replaysData.length > 5) { - options.chart.height = (options.chart.height as number) + (replaysData.length - 5) * 34; + options.chart.height = + (options.chart.height as number) + (replaysData.length - 5) * 34; } // give more space when > 1 tooltip per replay if (options.chart && chartType.chartData.length > 1) { - options.chart.height = (options.chart.height as number) + replaysData.length * 34; + options.chart.height = + (options.chart.height as number) + replaysData.length * 34; } if (options.title) { @@ -49,7 +54,10 @@ export const globalChartOptions = ( events: { mouseOver: (event: any) => { event.preventDefault(); - if (!event.target.isNull && typeof event.target.x === 'number') { + if ( + !event.target.isNull && + typeof event.target.x === 'number' + ) { globalChartsData.hoveredRaceTime = event.target.x; } }, @@ -97,11 +105,17 @@ export const chartOptionsTemplate = (): Highcharts.Options => { tooltip: { shared: true, formatter() { + if (!this.points) { + return [`${getRaceTimeStr(this.x)}
`]; + } + + const getTooltipContent = ( + point: Highcharts.TooltipFormatterContextObject, + ): string => + `â–‰ ${point.series.name}: ${point.y.toFixed(3)}
`; + return [`${getRaceTimeStr(this.x)}
`].concat( - this.points - ? this.points.map((point) => ` - â–‰ ${point.series.name}: ${point.y.toFixed(3)} -
`) : [], + this.points.map(getTooltipContent), ); }, }, @@ -116,7 +130,7 @@ export const chartOptionsTemplate = (): Highcharts.Options => { }, scrollbar: scrollBarOptions, }; - return (options); + return options; }; export const defaultChartOptions = (): any => { @@ -136,56 +150,62 @@ export const inputSteerChartOptions = (): any => { export const rpmAndGearChartOptions = (): any => { const options = chartOptionsTemplate(); - options.yAxis = [{ - ...options.yAxis, - title: { - text: 'RPM', - }, - labels: { - format: '{value} RPM', - }, - lineWidth: 2, - height: '50%', - }, { - ...options.yAxis, - title: { - text: 'Gear', + options.yAxis = [ + { + ...options.yAxis, + title: { + text: 'RPM', + }, + labels: { + format: '{value} RPM', + }, + lineWidth: 2, + height: '50%', }, - labels: { - format: 'Gear {value}', + { + ...options.yAxis, + title: { + text: 'Gear', + }, + labels: { + format: 'Gear {value}', + }, + lineWidth: 2, + offset: 0, + top: '50%', + height: '50%', }, - lineWidth: 2, - offset: 0, - top: '50%', - height: '50%', - }]; + ]; return options; }; export const accelAndBrakeChartOptions = (): any => { const options = chartOptionsTemplate(); - options.yAxis = [{ - ...options.yAxis, - title: { - text: 'Gaz', - }, - labels: { - format: 'Gaz {value}', - }, - lineWidth: 2, - height: '50%', - }, { - ...options.yAxis, - title: { - text: 'Brake', + options.yAxis = [ + { + ...options.yAxis, + title: { + text: 'Gaz', + }, + labels: { + format: 'Gaz {value}', + }, + lineWidth: 2, + height: '50%', }, - labels: { - format: 'Brake {value}', + { + ...options.yAxis, + title: { + text: 'Brake', + }, + labels: { + format: 'Brake {value}', + }, + lineWidth: 2, + offset: 0, + top: '50%', + height: '50%', }, - lineWidth: 2, - offset: 0, - top: '50%', - height: '50%', - }]; + ]; return options; }; diff --git a/app/lib/charts/chartTypes.ts b/app/lib/charts/chartTypes.ts index 761e98c6..f50c7fe0 100644 --- a/app/lib/charts/chartTypes.ts +++ b/app/lib/charts/chartTypes.ts @@ -33,7 +33,8 @@ export const ChartTypes: { [name: string]: ChartType } = { chartData: [ { name: 'acceleration', - dataCallback: (replayData: ReplayDataPoint) => replayData.acceleration, + dataCallback: (replayData: ReplayDataPoint) => + replayData.acceleration, }, ], chartOptionsCallback: defaultChartOptions, @@ -43,7 +44,8 @@ export const ChartTypes: { [name: string]: ChartType } = { chartData: [ { name: 'inputSteer', - dataCallback: (replayData: ReplayDataPoint) => replayData.inputSteer, + dataCallback: (replayData: ReplayDataPoint) => + replayData.inputSteer, }, ], chartOptionsCallback: inputSteerChartOptions, @@ -53,7 +55,8 @@ export const ChartTypes: { [name: string]: ChartType } = { chartData: [ { name: 'engineRpm', - dataCallback: (replayData: ReplayDataPoint) => replayData.engineRpm, + dataCallback: (replayData: ReplayDataPoint) => + replayData.engineRpm, }, ], chartOptionsCallback: defaultChartOptions, @@ -63,7 +66,8 @@ export const ChartTypes: { [name: string]: ChartType } = { chartData: [ { name: 'engineCurGear', - dataCallback: (replayData: ReplayDataPoint) => replayData.engineCurGear, + dataCallback: (replayData: ReplayDataPoint) => + replayData.engineCurGear, }, ], chartOptionsCallback: defaultChartOptions, @@ -73,11 +77,13 @@ export const ChartTypes: { [name: string]: ChartType } = { chartData: [ { name: 'engineCurGear', - dataCallback: (replayData: ReplayDataPoint) => replayData.engineCurGear, + dataCallback: (replayData: ReplayDataPoint) => + replayData.engineCurGear, }, { name: 'engineRpm', - dataCallback: (replayData: ReplayDataPoint) => replayData.engineRpm, + dataCallback: (replayData: ReplayDataPoint) => + replayData.engineRpm, }, ], chartOptionsCallback: rpmAndGearChartOptions, @@ -87,11 +93,13 @@ export const ChartTypes: { [name: string]: ChartType } = { chartData: [ { name: 'inputIsBraking', - dataCallback: (replayData: ReplayDataPoint) => replayData.inputIsBraking, + dataCallback: (replayData: ReplayDataPoint) => + replayData.inputIsBraking, }, { name: 'inputGasPedal', - dataCallback: (replayData: ReplayDataPoint) => replayData.inputGasPedal, + dataCallback: (replayData: ReplayDataPoint) => + replayData.inputGasPedal, }, ], chartOptionsCallback: accelAndBrakeChartOptions, diff --git a/app/lib/contexts/AuthContext.tsx b/app/lib/contexts/AuthContext.tsx index 2fd2af58..818d8156 100644 --- a/app/lib/contexts/AuthContext.tsx +++ b/app/lib/contexts/AuthContext.tsx @@ -1,44 +1,42 @@ import { useRouter } from 'next/router'; -import React, { - createContext, useCallback, useEffect, useState, -} from 'react'; +import React, { createContext, useCallback, useEffect, useState } from 'react'; import API from '../api/apiWrapper'; import { AuthUserInfo } from '../api/requests/auth'; import { generateAuthUrl } from '../utils/auth'; import openAuthWindow from '../utils/authPopup'; export interface AuthContextProps { - user?: AuthUserInfo, - setUser: (user?: AuthUserInfo) => void, - loginUser: (code: string, state?: string) => Promise, - logoutUser: () => Promise - startAuthFlow: () => void + user?: AuthUserInfo; + setUser: (user?: AuthUserInfo) => void; + loginUser: (code: string, state?: string) => Promise; + logoutUser: () => Promise; + startAuthFlow: () => void; } export const AuthContext = createContext({ user: undefined, - setUser: (user?: AuthUserInfo) => { }, - loginUser: async (code: string, state?: string) => { }, - logoutUser: async () => { }, - startAuthFlow: () => { }, + setUser: (user?: AuthUserInfo) => {}, + loginUser: async (code: string, state?: string) => {}, + logoutUser: async () => {}, + startAuthFlow: () => {}, }); export const AuthProvider = ({ children }: any): JSX.Element => { const [user, setUser] = useState(); const { asPath } = useRouter(); - useEffect(() => { - updateLoggedInUser(); - }, [asPath]); - - const updateLoggedInUser = async () => { + const updateLoggedInUser = useCallback(async () => { const me = await API.auth.fetchLoggedInUser(); if (me === undefined) { setUser(undefined); } else if (me?.accountId !== user?.accountId) { setUser(me); } - }; + }, [user?.accountId]); + + useEffect(() => { + updateLoggedInUser(); + }, [asPath, updateLoggedInUser]); const startAuthFlow = () => { // Generate and store random string as state @@ -71,14 +69,21 @@ export const AuthProvider = ({ children }: any): JSX.Element => { if (code === undefined || code === null || typeof code !== 'string') { return; } - if (state === undefined || state === null || typeof state !== 'string') { + if ( + state === undefined || + state === null || + typeof state !== 'string' + ) { return; } const storedState = localStorage.getItem('state'); localStorage.removeItem('state'); if (storedState !== state) { - console.log(`Stored state (${storedState}) did not match incoming state (${state})`); + // eslint-disable-next-line no-console + console.log( + `Stored state (${storedState}) did not match incoming state (${state})`, + ); return; } @@ -88,9 +93,13 @@ export const AuthProvider = ({ children }: any): JSX.Element => { // helper function to make login callable from outside the Context const loginUser = async (code: string, state?: string) => { try { - const userInfo = await API.auth.authorizeWithAccessCode(code, state); + const userInfo = await API.auth.authorizeWithAccessCode( + code, + state, + ); setUser(userInfo); } catch (e) { + // eslint-disable-next-line no-console console.log(e); } }; diff --git a/app/lib/contexts/SettingsContext.tsx b/app/lib/contexts/SettingsContext.tsx index 9e80d438..20bda6d2 100644 --- a/app/lib/contexts/SettingsContext.tsx +++ b/app/lib/contexts/SettingsContext.tsx @@ -35,23 +35,23 @@ export interface SettingsContextProps { export const SettingsContext = createContext({ lineType: LineTypes.default, - changeLineType: () => { }, + changeLineType: () => {}, showGearChanges: false, - setShowGearChanges: () => { }, + setShowGearChanges: () => {}, showFPS: false, - setShowFPS: () => { }, + setShowFPS: () => {}, showInputOverlay: true, - setShowInputOverlay: () => { }, + setShowInputOverlay: () => {}, replayLineOpacity: 0.5, - setReplayLineOpacity: () => { }, + setReplayLineOpacity: () => {}, replayCarOpacity: 0.5, - setReplayCarOpacity: () => { }, + setReplayCarOpacity: () => {}, numColorChange: 0, - setNumColorChange: () => { }, + setNumColorChange: () => {}, showFullTrail: timeLineInfos.showFullTrail, - setShowFullTrail: () => { }, + setShowFullTrail: () => {}, showTrailToStart: timeLineInfos.showTrailToStart, - setShowTrailToStart: () => { }, + setShowTrailToStart: () => {}, }); export const SettingsProvider = ({ children }: any): JSX.Element => { @@ -62,8 +62,12 @@ export const SettingsProvider = ({ children }: any): JSX.Element => { const [replayLineOpacity, setReplayLineOpacity] = useState(0.5); const [replayCarOpacity, setReplayCarOpacity] = useState(0.5); const [numColorChange, setNumColorChange] = useState(0); - const [showFullTrail, setShowFullTrail] = useState(timeLineInfos.showFullTrail); - const [showTrailToStart, setShowTrailToStart] = useState(timeLineInfos.showTrailToStart); + const [showFullTrail, setShowFullTrail] = useState( + timeLineInfos.showFullTrail, + ); + const [showTrailToStart, setShowTrailToStart] = useState( + timeLineInfos.showTrailToStart, + ); const changeLineType = (type: LineType) => { setLineType(type); diff --git a/app/lib/hooks/useWindowDimensions.ts b/app/lib/hooks/useWindowDimensions.ts index 7341d92c..ac969a2c 100644 --- a/app/lib/hooks/useWindowDimensions.ts +++ b/app/lib/hooks/useWindowDimensions.ts @@ -14,7 +14,9 @@ const getWindowDimensions = () => { }; const useWindowDimensions = () => { - const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions()); + const [windowDimensions, setWindowDimensions] = useState( + getWindowDimensions(), + ); useEffect(() => { function handleResize() { diff --git a/app/lib/hooks/viewer/replayLines/useUpdateReplayLineTrail.ts b/app/lib/hooks/viewer/replayLines/useUpdateReplayLineTrail.ts index 158f838e..4ad787ad 100644 --- a/app/lib/hooks/viewer/replayLines/useUpdateReplayLineTrail.ts +++ b/app/lib/hooks/viewer/replayLines/useUpdateReplayLineTrail.ts @@ -24,13 +24,22 @@ const useUpdateReplayLineTrail = ( if (!bufferRef.current) return; const { - showFullTrail, showTrailToStart, revealTrailTime, currentRaceTime, + showFullTrail, + showTrailToStart, + revealTrailTime, + currentRaceTime, } = timeLineGlobal; - const curSampleIndex = getSampleIndexNearTime(replay, timeLineGlobal.currentRaceTime); + const curSampleIndex = getSampleIndexNearTime( + replay, + timeLineGlobal.currentRaceTime, + ); if (showFullTrail || showTrailToStart) { - if (!previousLineUpdate.current || previousLineUpdate.current.segmentUpdate) { + if ( + !previousLineUpdate.current || + previousLineUpdate.current.segmentUpdate + ) { // Reset alpha of full line if it's the first update or if the previous update was a segment update for (let i = 0; i < replay.samples.length; i++) { bufferRef.current.attributes.color.setW(i, 1); @@ -61,24 +70,42 @@ const useUpdateReplayLineTrail = ( ); // Clamp trail indices - const startTrailIndexClamped = Math.max(0, Math.min(startTrailIndex, replay.samples.length)); - const endFadeIndexClamped = Math.max(0, Math.min(endFadeIndex, curSampleIndex, replay.samples.length)); + const startTrailIndexClamped = Math.max( + 0, + Math.min(startTrailIndex, replay.samples.length), + ); + const endFadeIndexClamped = Math.max( + 0, + Math.min(endFadeIndex, curSampleIndex, replay.samples.length), + ); const prev = previousLineUpdate.current; // Update starts at trail index or previous start index (or 0 if no previous update has occured) - const updateStart = Math.min(startTrailIndexClamped, prev?.startIndex || 0); + const updateStart = Math.min( + startTrailIndexClamped, + prev?.startIndex || 0, + ); // Update starts at sample index or previous end index (or end of line if no previous update has occured) - const updateEnd = Math.max(curSampleIndex, prev?.endIndex || replay.samples.length); + const updateEnd = Math.max( + curSampleIndex, + prev?.endIndex || replay.samples.length, + ); // Update line alpha if needed (previous was not a segment update or update range changed) - if (!prev?.segmentUpdate || updateStart !== prev?.startIndex || updateEnd !== prev?.endIndex) { + if ( + !prev?.segmentUpdate || + updateStart !== prev?.startIndex || + updateEnd !== prev?.endIndex + ) { for (let i = updateStart; i < updateEnd; i++) { if (i < startTrailIndexClamped) { // Index before minimum index, hide line: set alpha to 0 bufferRef.current.attributes.color.setW(i, 0); } else if (i < endFadeIndexClamped) { // Index after trail start, before fade end, fade alpha between 0 and 1 - const alpha = (i - startTrailIndexClamped) / (endFadeIndexClamped - startTrailIndexClamped); + const alpha = + (i - startTrailIndexClamped) / + (endFadeIndexClamped - startTrailIndexClamped); bufferRef.current.attributes.color.setW(i, alpha); } else { // Index after fade end, show line: set alpha to 1 @@ -90,7 +117,10 @@ const useUpdateReplayLineTrail = ( // Set line draw range const samplesToDraw = curSampleIndex - startTrailIndexClamped; - bufferRef.current.setDrawRange(startTrailIndexClamped, samplesToDraw); + bufferRef.current.setDrawRange( + startTrailIndexClamped, + samplesToDraw, + ); // Set start and end to trail start and end previousLineUpdate.current = { diff --git a/app/lib/popups/performanceContinueConfirmation.tsx b/app/lib/popups/performanceContinueConfirmation.tsx index dd49a672..753dca46 100644 --- a/app/lib/popups/performanceContinueConfirmation.tsx +++ b/app/lib/popups/performanceContinueConfirmation.tsx @@ -4,13 +4,16 @@ import { ExclamationCircleOutlined } from '@ant-design/icons'; import { Checkbox, Modal } from 'antd'; import dayjs from 'dayjs'; -const STOP_SHOWING_PERFORMANCE_CONFIRMATION_KEY = 'stopShowingPerformanceContinueConfirmation'; +const STOP_SHOWING_PERFORMANCE_CONFIRMATION_KEY = + 'stopShowingPerformanceContinueConfirmation'; const showPerformanceConfirmationModal = ( onModalOk: () => void, onModalCancel: () => void, ) => { - const stopShowingConfirmationModal = localStorage.getItem(STOP_SHOWING_PERFORMANCE_CONFIRMATION_KEY) !== null; + const stopShowingConfirmationModal = + localStorage.getItem(STOP_SHOWING_PERFORMANCE_CONFIRMATION_KEY) !== + null; if (stopShowingConfirmationModal) { onModalOk(); return; @@ -22,16 +25,27 @@ const showPerformanceConfirmationModal = ( title: 'Potential major performance issues!', content: (
-

Based on your detected hardware, your device might struggle with the 3D viewer's performance requirements.

+

+ Based on your detected hardware, your device might struggle + with the 3D viewer's performance requirements. +


-

One of the reasons could be that you do not have hardware acceleration enabled.

-

Please try enabling hardware acceleration in your browser settings and try again.

+

+ One of the reasons could be that you do not have hardware + acceleration enabled. +

+

+ Please try enabling hardware acceleration in your browser + settings and try again. +


If you really want to continue anyway, click "Continue"


{ dontShowAgain = e.target.checked; }} + onChange={(e) => { + dontShowAgain = e.target.checked; + }} > Don't show again @@ -45,7 +59,10 @@ const showPerformanceConfirmationModal = ( onOk: () => { onModalOk(); if (dontShowAgain) { - localStorage.setItem(STOP_SHOWING_PERFORMANCE_CONFIRMATION_KEY, dayjs().unix().toString()); + localStorage.setItem( + STOP_SHOWING_PERFORMANCE_CONFIRMATION_KEY, + dayjs().unix().toString(), + ); } }, onCancel: () => { diff --git a/app/lib/popups/performanceMobileWarning.ts b/app/lib/popups/performanceMobileWarning.ts index 9a306a99..e07e03c5 100644 --- a/app/lib/popups/performanceMobileWarning.ts +++ b/app/lib/popups/performanceMobileWarning.ts @@ -4,13 +4,15 @@ import dayjs from 'dayjs'; const MOBILE_VIEWER_WARNING_SHOWN_KEY = 'mobileViewerWarningShown'; const showMobilePerformanceWarning = () => { - const shownMobileWarning = localStorage.getItem(MOBILE_VIEWER_WARNING_SHOWN_KEY) !== null; + const shownMobileWarning = + localStorage.getItem(MOBILE_VIEWER_WARNING_SHOWN_KEY) !== null; if (shownMobileWarning) return; Modal.warning({ - title: 'You\'re on mobile!', - // eslint-disable-next-line max-len - content: 'The 3D viewer is not designed for mobile use - if you want the best experience, visit the 3D viewer on a desktop.', + title: "You're on mobile!", + content: + // eslint-disable-next-line max-len + 'The 3D viewer is not designed for mobile use - if you want the best experience, visit the 3D viewer on a desktop.', centered: true, okText: 'Dismiss', okType: 'ghost', @@ -20,7 +22,10 @@ const showMobilePerformanceWarning = () => { }); // Set date of showing warning to today - localStorage.setItem(MOBILE_VIEWER_WARNING_SHOWN_KEY, dayjs().unix().toString()); + localStorage.setItem( + MOBILE_VIEWER_WARNING_SHOWN_KEY, + dayjs().unix().toString(), + ); }; export default showMobilePerformanceWarning; diff --git a/app/lib/popups/performanceWarning.tsx b/app/lib/popups/performanceWarning.tsx index eeb9d368..cbe4584e 100644 --- a/app/lib/popups/performanceWarning.tsx +++ b/app/lib/popups/performanceWarning.tsx @@ -7,7 +7,8 @@ const STOP_SHOWING_PERFORMANCE_WARNING_KEY = 'stopShowingPerformanceWarning'; const showPerformanceWarning = () => { // Don't show the warning if the user has already dismissed it - const stopShowingPerformanceWarning = localStorage.getItem(STOP_SHOWING_PERFORMANCE_WARNING_KEY) !== null; + const stopShowingPerformanceWarning = + localStorage.getItem(STOP_SHOWING_PERFORMANCE_WARNING_KEY) !== null; if (stopShowingPerformanceWarning) return; // Assign key to notification so we can close it later @@ -17,13 +18,17 @@ const showPerformanceWarning = () => { notification.warning({ key, message: 'Potential performance issues', - description: 'Based on your detected hardware, you may get lower framerates in the 3D viewer. ' - + 'If you experience issues, try using a different device.', + description: + 'Based on your detected hardware, you may get lower framerates in the 3D viewer. ' + + 'If you experience issues, try using a different device.', btn: (