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
144 changes: 137 additions & 7 deletions src/frontend/components/Settings/sections/StandingsSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,55 @@ const defaultConfig: StandingsWidgetSettings['config'] = {
lastTime: { enabled: true },
fastestTime: { enabled: true },
background: { opacity: 0 },
driverStandings: {
buffer: 3,
numNonClassDrivers: 3,
minPlayerClassDrivers: 10,
numTopDrivers: 3,
},
};

// Migration function to handle missing properties in the new config format
const migrateConfig = (savedConfig: unknown): StandingsWidgetSettings['config'] => {
const migrateConfig = (
savedConfig: unknown
): StandingsWidgetSettings['config'] => {
if (!savedConfig || typeof savedConfig !== 'object') return defaultConfig;

const config = savedConfig as Record<string, unknown>;

// Handle new format with missing properties
return {
iRatingChange: { enabled: (config.iRatingChange as { enabled?: boolean })?.enabled ?? true },
iRatingChange: {
enabled:
(config.iRatingChange as { enabled?: boolean })?.enabled ?? true,
},
badge: { enabled: (config.badge as { enabled?: boolean })?.enabled ?? true },
delta: { enabled: (config.delta as { enabled?: boolean })?.enabled ?? true },
lastTime: { enabled: (config.lastTime as { enabled?: boolean })?.enabled ?? true },
fastestTime: { enabled: (config.fastestTime as { enabled?: boolean })?.enabled ?? true },
background: { opacity: (config.background as { opacity?: number })?.opacity ?? 0 },
lastTime: {
enabled: (config.lastTime as { enabled?: boolean })?.enabled ?? true,
},
fastestTime: {
enabled: (config.fastestTime as { enabled?: boolean })?.enabled ?? true,
},
background: {
opacity: (config.background as { opacity?: number })?.opacity ?? 0,
},
driverStandings: {
buffer:
(config.driverStandings as { buffer?: number })?.buffer ??
defaultConfig.driverStandings.buffer,
numNonClassDrivers:
(config.driverStandings as { numNonClassDrivers?: number })
?.numNonClassDrivers ??
defaultConfig.driverStandings.numNonClassDrivers,
minPlayerClassDrivers:
(config.driverStandings as { minPlayerClassDrivers?: number })
?.minPlayerClassDrivers ??
defaultConfig.driverStandings.minPlayerClassDrivers,
numTopDrivers:
(config.driverStandings as { numTopDrivers?: number })?.numTopDrivers ??
defaultConfig.driverStandings.numTopDrivers,
},
};
};

Expand Down Expand Up @@ -105,16 +138,113 @@ export const StandingsSettings = () => {
}
/>
</div>
</div>
</div>
{/* Driver Standings Settings */}
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium text-slate-200">
Driver Standings
</h3>
</div>
<div className="space-y-3 pl-4">
<div className="flex items-center justify-between">
<span className="text-sm text-slate-300">
Drivers to show around player
</span>
<input
type="number"
value={settings.config.driverStandings.buffer}
onChange={(e) =>
handleConfigChange({
driverStandings: {
...settings.config.driverStandings,
buffer: parseInt(e.target.value),
},
})
}
className="w-20 bg-slate-700 text-white rounded-md px-2 py-1"
/>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-slate-300">
Drivers to show in other classes
</span>
<input
type="number"
value={settings.config.driverStandings.numNonClassDrivers}
onChange={(e) =>
handleConfigChange({
driverStandings: {
...settings.config.driverStandings,
numNonClassDrivers: parseInt(e.target.value),
},
})
}
className="w-20 bg-slate-700 text-white rounded-md px-2 py-1"
/>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-slate-300">
Minimum drivers in player&apos;s class
</span>
<input
type="number"
value={settings.config.driverStandings.minPlayerClassDrivers}
onChange={(e) =>
handleConfigChange({
driverStandings: {
...settings.config.driverStandings,
minPlayerClassDrivers: parseInt(e.target.value),
},
})
}
className="w-20 bg-slate-700 text-white rounded-md px-2 py-1"
/>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-slate-300">
Top drivers to always show in player&apos;s class
</span>
<input
type="number"
value={settings.config.driverStandings.numTopDrivers}
onChange={(e) =>
handleConfigChange({
driverStandings: {
...settings.config.driverStandings,
numTopDrivers: parseInt(e.target.value),
},
})
}
className="w-20 bg-slate-700 text-white rounded-md px-2 py-1"
/>
</div>
</div>
</div>

{/* Background Settings */}
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium text-slate-200">
Background
</h3>
</div>
<div className="space-y-3 pl-4">
<div className="flex items-center justify-between">
<span className="text-sm text-slate-300">Background Opacity</span>
<span className="text-sm text-slate-300">
Background Opacity
</span>
<div className="flex items-center gap-2">
<input
type="range"
min="0"
max="100"
value={settings.config.background.opacity}
onChange={(e) =>
handleConfigChange({ background: { opacity: parseInt(e.target.value) } })
handleConfigChange({
background: { opacity: parseInt(e.target.value) },
})
}
className="w-20 h-2 bg-slate-600 rounded-lg appearance-none cursor-pointer"
/>
Expand Down
6 changes: 6 additions & 0 deletions src/frontend/components/Settings/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ export interface StandingsWidgetSettings extends BaseWidgetSettings {
lastTime: { enabled: boolean };
fastestTime: { enabled: boolean };
background: { opacity: number };
driverStandings: {
buffer: number;
numNonClassDrivers: number;
minPlayerClassDrivers: number;
numTopDrivers: number;
};
};
}

Expand Down
4 changes: 2 additions & 2 deletions src/frontend/components/Standings/Standings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import {

export const Standings = () => {
const [parent] = useAutoAnimate();
const standings = useDriverStandings({ buffer: 3 });
const classStats = useCarClassStats();
const settings = useStandingsSettings();
const standings = useDriverStandings(settings?.driverStandings);
const classStats = useCarClassStats();
return (
<div
className={`w-full bg-slate-800/[var(--bg-opacity)] rounded-sm p-2 text-white overflow-hidden`}
Expand Down
82 changes: 82 additions & 0 deletions src/frontend/components/Standings/createStandings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,8 @@ describe('createStandings', () => {

const filteredDrivers = sliceRelevantDrivers(results, 'GT3');

expect(filteredDrivers[0][1]).toHaveLength(10);

expect(filteredDrivers).toEqual([
[
'GT3',
Expand All @@ -326,10 +328,88 @@ describe('createStandings', () => {
{ name: '5. David' },
{ name: '6. Sebastian' },
{ name: '7. Nico' },
{ name: '8. Eve' },
{ name: '9. Frank' },
{ name: '10. Max' },
],
],
]);
});

it('should return at least 10 drivers if available', () => {
const results: [string, DummyStanding[]][] = [
[
'GT3',
[
{ name: '1. Bob' },
{ name: '2. Alice' },
{ name: '3. Charlie' },
{ name: '4. David' },
{ name: '5. Eve' },
{ name: '6. Frank' },
{ name: '7. Player', isPlayer: true },
{ name: '8. Hannah' },
{ name: '9. Irene' },
{ name: '10. Jack' },
{ name: '11. Kevin' },
],
],
];
const filteredDrivers = sliceRelevantDrivers(results, 'GT3');
expect(filteredDrivers[0][1].length).toBe(10);
});

it('should allow minPlayerClassDrivers to be configured', () => {
const results: [string, DummyStanding[]][] = [
[
'GT3',
[
{ name: '1. Bob' },
{ name: '2. Alice' },
{ name: '3. Charlie' },
{ name: '4. David' },
{ name: '5. Eve' },
{ name: '6. Frank' },
{ name: '7. Player', isPlayer: true },
{ name: '8. Hannah' },
{ name: '9. Irene' },
{ name: '10. Jack' },
{ name: '11. Kevin' },
],
],
];
const filteredDrivers = sliceRelevantDrivers(results, 'GT3', {
minPlayerClassDrivers: 5,
});
expect(filteredDrivers[0][1].length).toBe(10);
});

it('should allow numTopDrivers to be configured', () => {
const results: [string, DummyStanding[]][] = [
[
'GT3',
[
{ name: '1. Bob' },
{ name: '2. Alice' },
{ name: '3. Charlie' },
{ name: '4. David' },
{ name: '5. Eve' },
{ name: '6. Frank' },
{ name: '7. Player', isPlayer: true },
{ name: '8. Hannah' },
{ name: '9. Irene' },
{ name: '10. Jack' },
{ name: '11. Kevin' },
],
],
];
const filteredDrivers = sliceRelevantDrivers(results, 'GT3', {
numTopDrivers: 1,
});
expect(filteredDrivers[0][1].length).toBe(9);
expect(filteredDrivers[0][1][0].name).toBe('1. Bob');
expect(filteredDrivers[0][1][1].name).toBe('4. David');
});
});
});

Expand All @@ -347,6 +427,8 @@ function createStandings(
options?: {
buffer?: number;
numNonClassDrivers?: number;
minPlayerClassDrivers?: number;
numTopDrivers?: number;
}
) {
const standings = createDriverStandings(
Expand Down
Loading