Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/app/storage/defaultDashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ export const defaultDashboard: DashboardLayout = {
width: 400,
height: 296,
},
config: {
buffer: 3,
},
},
{
id: 'map',
Expand Down
46 changes: 39 additions & 7 deletions src/frontend/components/Settings/sections/RelativeSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
import { useState } from 'react';
import { BaseSettingsSection } from '../components/BaseSettingsSection';
import { RelativeWidgetSettings } from '../types';
import { useDashboard } from '@irdashies/context';

const SETTING_ID = 'relative';

interface RelativeWidgetSettings {
enabled: boolean;
config: {
buffer: number;
};
}

const defaultConfig: RelativeWidgetSettings['config'] = {
buffer: 3,
};

export const RelativeSettings = () => {
const { currentDashboard } = useDashboard();
const savedSettings = currentDashboard?.widgets.find(w => w.id === SETTING_ID) as RelativeWidgetSettings | undefined;
const [settings, setSettings] = useState<RelativeWidgetSettings>({
enabled: currentDashboard?.widgets.find(w => w.id === 'relative')?.enabled ?? false,
config: currentDashboard?.widgets.find(w => w.id === 'relative')?.config ?? {},
enabled: savedSettings?.enabled ?? true,
config: savedSettings?.config ?? defaultConfig,
});

if (!currentDashboard) {
Expand All @@ -22,10 +35,29 @@ export const RelativeSettings = () => {
onSettingsChange={setSettings}
widgetId="relative"
>
{/* Add specific settings controls here */}
<div className="text-slate-300">
Additional settings will appear here
</div>
{(handleConfigChange) => (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<span className="text-sm text-slate-300">Buffer Size</span>
<p className="text-xs text-slate-400">
Number of drivers to show above and below the player
</p>
</div>
<select
value={settings.config.buffer}
onChange={(e) => handleConfigChange({ buffer: parseInt(e.target.value) })}
className="bg-slate-700 text-slate-200 px-3 py-1 rounded border border-slate-600 focus:border-blue-500 focus:outline-none"
>
{Array.from({ length: 10 }, (_, i) => (
<option key={i} value={i + 1}>
{i + 1}
</option>
))}
</select>
</div>
</div>
)}
</BaseSettingsSection>
);
};
4 changes: 3 additions & 1 deletion src/frontend/components/Settings/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ export interface StandingsWidgetSettings extends BaseWidgetSettings {
}

export interface RelativeWidgetSettings extends BaseWidgetSettings {
// Add specific relative settings here
config: {
buffer: number;
};
}

export interface WeatherWidgetSettings extends BaseWidgetSettings {
Expand Down
107 changes: 78 additions & 29 deletions src/frontend/components/Standings/Relative.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,96 @@
import { useAutoAnimate } from '@formkit/auto-animate/react';
import { DriverInfoRow } from './components/DriverInfoRow/DriverInfoRow';
import { useDriverRelatives } from './hooks/useDriverRelatives';
import { useRelativeSettings, useDriverRelatives } from './hooks';
import { DriverRatingBadge } from './components/DriverRatingBadge/DriverRatingBadge';
import { SessionBar } from './components/SessionBar/SessionBar';
import { SessionFooter } from './components/SessionFooter/SessionFooter';

export const Relative = () => {
const standings = useDriverRelatives({ buffer: 3 });
const config = useRelativeSettings();
const buffer = config?.buffer ?? 3;
const standings = useDriverRelatives({ buffer });
const [parent] = useAutoAnimate();

// Always render 2 * buffer + 1 rows (buffer above + player + buffer below)
const totalRows = 2 * buffer + 1;
const playerIndex = standings.findIndex((result) => result.isPlayer);

// If no player found, render empty table with consistent height
if (playerIndex === -1) {
const emptyRows = Array.from({ length: totalRows }, (_, index) => (
<DummyDriverRow key={`empty-${index}`} />
));

return (
<div className="w-full h-full">
<SessionBar />
<table className="w-full table-auto text-sm border-separate border-spacing-y-0.5 mb-3 mt-3">
<tbody ref={parent}>{emptyRows}</tbody>
</table>
<SessionFooter />
</div>
);
}

// Create an array of fixed size with placeholder rows
const rows = Array.from({ length: totalRows }, (_, index) => {
// Calculate the actual index in the standings array
// Center the player in the middle of the display
const centerIndex = Math.floor(totalRows / 2); // buffer
const actualIndex = index - centerIndex + playerIndex;
const result = standings[actualIndex];

if (!result) {
// If no result, render a dummy row with visibility hidden
return <DummyDriverRow key={`placeholder-${index}`} />;
}

return (
<DriverInfoRow
key={result.carIdx}
carIdx={result.carIdx}
classColor={result.carClass.color}
carNumber={result.driver?.carNum || ''}
name={result.driver?.name || ''}
isPlayer={result.isPlayer}
hasFastestTime={result.hasFastestTime}
delta={result.delta}
position={result.classPosition}
onPitRoad={result.onPitRoad}
onTrack={result.onTrack}
radioActive={result.radioActive}
isLapped={result.lappedState === 'behind'}
isLappingAhead={result.lappedState === 'ahead'}
badge={
<DriverRatingBadge
license={result.driver?.license}
rating={result.driver?.rating}
/>
}
/>
);
});

return (
<div className="w-full h-full">
<SessionBar />
<table className="w-full table-auto text-sm border-separate border-spacing-y-0.5 mb-3 mt-3">
<tbody ref={parent}>
{standings.map((result) => (
<DriverInfoRow
key={result.carIdx}
carIdx={result.carIdx}
classColor={result.carClass.color}
carNumber={result.driver?.carNum || ''}
name={result.driver?.name || ''}
isPlayer={result.isPlayer}
hasFastestTime={result.hasFastestTime}
delta={result.delta}
position={result.classPosition}
onPitRoad={result.onPitRoad}
onTrack={result.onTrack}
radioActive={result.radioActive}
isLapped={result.lappedState === 'behind'}
isLappingAhead={result.lappedState === 'ahead'}
badge={
<DriverRatingBadge
license={result.driver?.license}
rating={result.driver?.rating}
/>
}
/>
))}
</tbody>
<tbody ref={parent}>{rows}</tbody>
</table>

<SessionFooter />
</div>
);
};

// Dummy driver row component with visibility hidden to maintain consistent height
const DummyDriverRow = () => (
<DriverInfoRow
carIdx={0}
classColor={0}
carNumber="33"
name="Franz Hermann"
isPlayer={false}
hasFastestTime={false}
hidden={true}
/>
);
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ interface DriverRowInfoProps {
radioActive?: boolean;
isLapped?: boolean;
isLappingAhead?: boolean;
hidden?: boolean;
}

export const DriverInfoRow = ({
Expand All @@ -42,6 +43,7 @@ export const DriverInfoRow = ({
isLapped,
isLappingAhead,
iratingChange,
hidden,
}: DriverRowInfoProps) => {
// convert seconds to mm:ss:ms
const lastTimeString = formatTime(lastTime);
Expand All @@ -56,6 +58,7 @@ export const DriverInfoRow = ({
isPlayer ? 'text-amber-300' : '',
!isPlayer && isLapped ? 'text-blue-400' : '',
!isPlayer && isLappingAhead ? 'text-red-400' : '',
hidden ? 'invisible' : '',
].join(' ')}
>
<td
Expand All @@ -69,7 +72,7 @@ export const DriverInfoRow = ({
#{carNumber}
</td>
<td className={`px-2 py-0.5 w-full`}>
<div className="flex justify-between align-center">
<div className="flex justify-between align-center items-center">
<div className="flex">
<span
className={`animate-pulse transition-[width] duration-300 ${radioActive ? 'w-4 mr-1' : 'w-0 overflow-hidden'}`}
Expand All @@ -79,7 +82,7 @@ export const DriverInfoRow = ({
<span className="truncate">{name}</span>
</div>
{onPitRoad && (
<span className="text-white animate-pulse text-xs border-yellow-500 border-2 rounded-md px-2">
<span className="text-white animate-pulse text-xs border-yellow-500 border-2 rounded-md text-center text-nowrap px-2 m-0 leading-tight">
PIT
</span>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const DriverRatingBadge = ({

return (
<div
className={`text-center text-white text-nowrap border-solid rounded-md text-xs m-0 px-1 border-2 ${color}`}
className={`text-center text-white text-nowrap border-solid rounded-md text-xs m-0 px-1 border-2 leading-tight ${color}`}
>
{formattedLicense} {simplifiedRating}k
</div>
Expand Down
2 changes: 2 additions & 0 deletions src/frontend/components/Standings/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export * from './useCarClassStats';
export * from './useDriverIncidents';
export * from './useDriverStandings';
export * from './useDriverRelatives';
export * from './useSessionLapCount';
export * from './useStandingsSettings';
export * from './useRelativeSettings';
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { useDashboard } from '@irdashies/context';
import { RelativeWidgetSettings } from '../../Settings/types';

export const useRelativeSettings = (): RelativeWidgetSettings['config'] => {
const { currentDashboard } = useDashboard();
const widget = currentDashboard?.widgets.find(w => w.id === 'relative')?.config;
return widget as RelativeWidgetSettings['config'];
};