Skip to content

Commit f4a695b

Browse files
authored
Add trackmap settings (#43)
1 parent 4177c55 commit f4a695b

File tree

12 files changed

+345
-87
lines changed

12 files changed

+345
-87
lines changed

src/app/storage/defaultDashboard.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ export const defaultDashboard: DashboardLayout = {
7070
width: 400,
7171
height: 600,
7272
},
73+
config: {
74+
enableTurnNames: false,
75+
},
7376
},
7477
{
7578
id: 'weather',

src/frontend/components/Settings/SettingsLayout.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,6 @@ export const SettingsLayout = () => {
127127
<Link to="/settings/map" className={menuItemClass('/map')}>
128128
<div className="flex flex-row gap-2 items-center">
129129
Track Map
130-
<span className="text-xs bg-yellow-600 text-yellow-100 px-2 py-0.5 rounded-full flex flex-row gap-1 items-center">
131-
Experimental
132-
</span>
133130
</div>
134131
</Link>
135132
</li>
Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
11
import { useState } from 'react';
22
import { BaseSettingsSection } from '../components/BaseSettingsSection';
3-
import { TrackMapWidgetSettings } from '../types';
43
import { useDashboard } from '@irdashies/context';
4+
import { ToggleSwitch } from '../components/ToggleSwitch';
5+
6+
const SETTING_ID = 'map';
7+
8+
interface TrackMapSettings {
9+
enabled: boolean;
10+
config: {
11+
enableTurnNames: boolean;
12+
};
13+
}
14+
15+
const defaultConfig: TrackMapSettings['config'] = {
16+
enableTurnNames: false
17+
};
518

619
export const TrackMapSettings = () => {
720
const { currentDashboard } = useDashboard();
8-
const [settings, setSettings] = useState<TrackMapWidgetSettings>({
9-
enabled: currentDashboard?.widgets.find(w => w.id === 'map')?.enabled ?? false,
10-
config: currentDashboard?.widgets.find(w => w.id === 'map')?.config ?? {},
21+
const [settings, setSettings] = useState<TrackMapSettings>({
22+
enabled: currentDashboard?.widgets.find(w => w.id === SETTING_ID)?.enabled ?? false,
23+
config: currentDashboard?.widgets.find(w => w.id === SETTING_ID)?.config as TrackMapSettings['config'] ?? defaultConfig,
1124
});
1225

1326
if (!currentDashboard) {
@@ -22,13 +35,27 @@ export const TrackMapSettings = () => {
2235
onSettingsChange={setSettings}
2336
widgetId="map"
2437
>
25-
<div className="bg-yellow-600/20 text-yellow-100 p-4 rounded-md mb-4">
26-
<p>This feature is experimental and may not work as expected.</p>
27-
</div>
28-
{/* Add specific settings controls here */}
29-
<div className="text-slate-300">
30-
Additional settings will appear here
31-
</div>
38+
{(handleConfigChange) => (
39+
<div className="space-y-4">
40+
<div className="bg-yellow-600/20 text-yellow-100 p-4 rounded-md mb-4">
41+
<p>This is still a work in progress. There are several tracks still missing, please report any issues/requests.</p>
42+
</div>
43+
<div className="flex items-center justify-between">
44+
<div>
45+
<span className="text-sm text-slate-300">Enable Turn Names</span>
46+
<p className="text-xs text-slate-400">
47+
Show turn numbers and names on the track map
48+
</p>
49+
</div>
50+
<ToggleSwitch
51+
enabled={settings.config.enableTurnNames}
52+
onToggle={(enabled) => handleConfigChange({
53+
enableTurnNames: enabled
54+
})}
55+
/>
56+
</div>
57+
</div>
58+
)}
3259
</BaseSettingsSection>
3360
);
3461
};

src/frontend/components/Settings/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ export interface WeatherWidgetSettings extends BaseWidgetSettings {
3131
}
3232

3333
export interface TrackMapWidgetSettings extends BaseWidgetSettings {
34-
// Add specific track map settings here
34+
config: {
35+
enableTurnNames: boolean;
36+
};
3537
}
3638

3739
export type InputWidgetSettings = BaseWidgetSettings<InputSettings>;

src/frontend/components/TrackMap/TrackCanvas.stories.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,24 @@ import { Meta, StoryObj } from '@storybook/react-vite';
22
import { TrackCanvas, TrackDriver } from './TrackCanvas';
33
import { useEffect, useState } from 'react';
44
import tracks from './tracks/tracks.json';
5-
import { BROKEN_TRACKS } from './tracks/broken-tracks';
5+
import { BROKEN_TRACKS } from './tracks/brokenTracks';
66

77
export default {
88
component: TrackCanvas,
9+
args: {
10+
enableTurnNames: false,
11+
debug: true,
12+
},
913
argTypes: {
1014
trackId: {
1115
control: { type: 'number' },
1216
},
17+
enableTurnNames: {
18+
control: { type: 'boolean' },
19+
},
20+
debug: {
21+
control: { type: 'boolean' },
22+
},
1323
},
1424
} as Meta;
1525

@@ -358,7 +368,7 @@ const allTrackIds = Object.keys(tracks)
358368
.sort((a, b) => a - b);
359369

360370
export const AllTracksGrid: Story = {
361-
render: () => {
371+
render: (args) => {
362372
const trackSize = 150;
363373

364374
return (
@@ -387,6 +397,8 @@ export const AllTracksGrid: Story = {
387397
<TrackCanvas
388398
trackId={trackId}
389399
drivers={sampleData}
400+
enableTurnNames={args.enableTurnNames}
401+
debug={args.debug}
390402
/>
391403
</div>
392404
</div>
@@ -398,7 +410,7 @@ export const AllTracksGrid: Story = {
398410
};
399411

400412
export const BrokenTracksGrid: Story = {
401-
render: () => {
413+
render: (args) => {
402414
const trackSize = 200;
403415

404416
return (
@@ -427,6 +439,8 @@ export const BrokenTracksGrid: Story = {
427439
<TrackCanvas
428440
trackId={brokenTrack.id}
429441
drivers={sampleData}
442+
enableTurnNames={args.enableTurnNames}
443+
debug={args.debug}
430444
/>
431445
</div>
432446
</div>

src/frontend/components/TrackMap/TrackCanvas.tsx

Lines changed: 50 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,22 @@ import { useEffect, useMemo, useRef, useState } from 'react';
22
import { Driver } from '@irdashies/types';
33
import tracks from './tracks/tracks.json';
44
import { getColor, getTailwindStyle } from '@irdashies/utils/colors';
5-
import { shouldShowTrack } from './tracks/broken-tracks';
5+
import { shouldShowTrack } from './tracks/brokenTracks';
66
import { TrackDebug } from './TrackDebug';
7+
import { useStartFinishLine } from './hooks/useStartFinishLine';
8+
import {
9+
setupCanvasContext,
10+
drawTrack,
11+
drawStartFinishLine,
12+
drawTurnNames,
13+
drawDrivers,
14+
} from './trackDrawingUtils';
715

816
export interface TrackProps {
917
trackId: number;
1018
drivers: TrackDriver[];
19+
enableTurnNames?: boolean;
20+
debug?: boolean;
1121
}
1222

1323
export interface TrackDriver {
@@ -36,13 +46,15 @@ export interface TrackDrawing {
3646
}[];
3747
}
3848

39-
// currently its a bit messy with the turns, so we disable them for now
40-
const ENABLE_TURNS = true;
41-
4249
const TRACK_DRAWING_WIDTH = 1920;
4350
const TRACK_DRAWING_HEIGHT = 1080;
4451

45-
export const TrackCanvas = ({ trackId, drivers }: TrackProps) => {
52+
export const TrackCanvas = ({
53+
trackId,
54+
drivers,
55+
enableTurnNames,
56+
debug,
57+
}: TrackProps) => {
4658
const canvasRef = useRef<HTMLCanvasElement>(null);
4759
const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 });
4860

@@ -81,6 +93,12 @@ export const TrackCanvas = ({ trackId, drivers }: TrackProps) => {
8193
return colors;
8294
}, [drivers]);
8395

96+
// Get start/finish line calculations
97+
const startFinishLine = useStartFinishLine({
98+
startFinishPoint: trackDrawing?.startFinish?.point,
99+
trackPathPoints: trackDrawing?.active?.trackPathPoints,
100+
});
101+
84102
// Position calculation based on the percentage of the track completed
85103
const calculatePositions = useMemo(() => {
86104
if (
@@ -198,73 +216,38 @@ export const TrackCanvas = ({ trackId, drivers }: TrackProps) => {
198216
const offsetX = (rect.width - TRACK_DRAWING_WIDTH * scale) / 2;
199217
const offsetY = (rect.height - TRACK_DRAWING_HEIGHT * scale) / 2;
200218

201-
// Save context state
202-
ctx.save();
203-
204-
// Apply scaling and centering
205-
ctx.translate(offsetX, offsetY);
206-
ctx.scale(scale, scale);
207-
208-
// Shadow
209-
ctx.shadowColor = 'black';
210-
ctx.shadowBlur = 2;
211-
ctx.shadowOffsetX = 1;
212-
ctx.shadowOffsetY = 1;
219+
// Setup canvas context with scaling and shadow
220+
setupCanvasContext(ctx, scale, offsetX, offsetY);
213221

214-
// Draw track
215-
if (path2DObjects.inside) {
216-
ctx.strokeStyle = 'white';
217-
ctx.lineWidth = 20;
218-
ctx.stroke(path2DObjects.inside);
219-
}
220-
221-
// Draw start/finish line
222-
if (path2DObjects.startFinish) {
223-
ctx.lineWidth = 10;
224-
ctx.strokeStyle = getColor('red');
225-
ctx.stroke(path2DObjects.startFinish);
226-
}
227-
228-
// Draw turn numbers
229-
if (ENABLE_TURNS) {
230-
trackDrawing.turns?.forEach((turn) => {
231-
if (!turn.content || !turn.x || !turn.y) return;
232-
ctx.textAlign = 'center';
233-
ctx.textBaseline = 'middle';
234-
ctx.fillStyle = 'white';
235-
ctx.font = '2rem sans-serif';
236-
ctx.fillText(turn.content, turn.x, turn.y);
237-
});
238-
}
239-
240-
// Draw drivers
241-
Object.values(calculatePositions)
242-
.sort((a, b) => Number(a.isPlayer) - Number(b.isPlayer)) // draws player last to be on top
243-
.forEach(({ driver, position }) => {
244-
const color = driverColors[driver.CarIdx];
245-
if (!color) return;
246-
247-
ctx.fillStyle = color.fill;
248-
ctx.beginPath();
249-
ctx.arc(position.x, position.y, 40, 0, 2 * Math.PI);
250-
ctx.fill();
251-
ctx.textAlign = 'center';
252-
ctx.textBaseline = 'middle';
253-
ctx.fillStyle = color.text;
254-
ctx.font = '2rem sans-serif';
255-
ctx.fillText(driver.CarNumber, position.x, position.y);
256-
});
222+
// Draw all elements
223+
drawTrack(ctx, path2DObjects);
224+
drawStartFinishLine(ctx, startFinishLine);
225+
drawTurnNames(ctx, trackDrawing.turns, enableTurnNames);
226+
drawDrivers(ctx, calculatePositions, driverColors);
257227

258228
// Restore context state
259229
ctx.restore();
260-
}, [calculatePositions, path2DObjects, trackDrawing?.turns, driverColors, canvasSize]);
230+
}, [
231+
calculatePositions,
232+
path2DObjects,
233+
trackDrawing?.turns,
234+
driverColors,
235+
canvasSize,
236+
enableTurnNames,
237+
trackDrawing?.startFinish?.point,
238+
trackDrawing?.active?.trackPathPoints,
239+
startFinishLine,
240+
]);
261241

262242
// Development/Storybook mode - show debug info and canvas
263-
if (import.meta.env?.DEV || import.meta.env?.MODE === 'storybook') {
243+
if (debug) {
264244
return (
265245
<div className="overflow-hidden w-full h-full">
266246
<TrackDebug trackId={trackId} trackDrawing={trackDrawing} />
267-
<canvas className="will-change-transform w-full h-full" ref={canvasRef}></canvas>
247+
<canvas
248+
className="will-change-transform w-full h-full"
249+
ref={canvasRef}
250+
></canvas>
268251
</div>
269252
);
270253
}
@@ -274,7 +257,10 @@ export const TrackCanvas = ({ trackId, drivers }: TrackProps) => {
274257

275258
return (
276259
<div className="overflow-hidden w-full h-full">
277-
<canvas className="will-change-transform w-full h-full" ref={canvasRef}></canvas>
260+
<canvas
261+
className="will-change-transform w-full h-full"
262+
ref={canvasRef}
263+
></canvas>
278264
</div>
279265
);
280266
};

src/frontend/components/TrackMap/TrackDebug.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { TrackDrawing } from './TrackCanvas';
2-
import { getBrokenTrackInfo } from './tracks/broken-tracks';
2+
import { getBrokenTrackInfo } from './tracks/brokenTracks';
33

44
interface TrackDebugProps {
55
trackId: number;
Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
import { useTrackId } from './hooks/useTrackId';
22
import { useDriverProgress } from './hooks/useDriverProgress';
3+
import { useTrackMapSettings } from './hooks/useTrackMapSettings';
34
import { TrackCanvas } from './TrackCanvas';
45

6+
const debug = import.meta.env.DEV || import.meta.env.MODE === 'storybook';
7+
58
export const TrackMap = () => {
69
const trackId = useTrackId();
710
const driversTrackData = useDriverProgress();
11+
const settings = useTrackMapSettings();
812

913
if (!trackId) return <></>;
1014

11-
return <TrackCanvas trackId={trackId} drivers={driversTrackData} />;
15+
return (
16+
<TrackCanvas
17+
trackId={trackId}
18+
drivers={driversTrackData}
19+
enableTurnNames={settings?.enableTurnNames ?? false}
20+
debug={debug}
21+
/>
22+
);
1223
};

0 commit comments

Comments
 (0)