Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
40 changes: 33 additions & 7 deletions src/frontend/components/Settings/sections/RelativeSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { useState } from 'react';
import { BaseSettingsSection } from '../components/BaseSettingsSection';
import { RelativeWidgetSettings } from '../types';
import { useDashboard } from '@irdashies/context';
import { RelativeWidgetSettings } from '../types';

const SETTING_ID = 'relative';

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 +29,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'];
};