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 @@ -70,6 +70,9 @@ export const defaultDashboard: DashboardLayout = {
width: 400,
height: 600,
},
config: {
enableTurnNames: false,
},
},
{
id: 'weather',
Expand Down
3 changes: 0 additions & 3 deletions src/frontend/components/Settings/SettingsLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,6 @@ export const SettingsLayout = () => {
<Link to="/settings/map" className={menuItemClass('/map')}>
<div className="flex flex-row gap-2 items-center">
Track Map
<span className="text-xs bg-yellow-600 text-yellow-100 px-2 py-0.5 rounded-full flex flex-row gap-1 items-center">
Experimental
</span>
</div>
</Link>
</li>
Expand Down
49 changes: 38 additions & 11 deletions src/frontend/components/Settings/sections/TrackMapSettings.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 { TrackMapWidgetSettings } from '../types';
import { useDashboard } from '@irdashies/context';
import { ToggleSwitch } from '../components/ToggleSwitch';

const SETTING_ID = 'map';

interface TrackMapSettings {
enabled: boolean;
config: {
enableTurnNames: boolean;
};
}

const defaultConfig: TrackMapSettings['config'] = {
enableTurnNames: false
};

export const TrackMapSettings = () => {
const { currentDashboard } = useDashboard();
const [settings, setSettings] = useState<TrackMapWidgetSettings>({
enabled: currentDashboard?.widgets.find(w => w.id === 'map')?.enabled ?? false,
config: currentDashboard?.widgets.find(w => w.id === 'map')?.config ?? {},
const [settings, setSettings] = useState<TrackMapSettings>({
enabled: currentDashboard?.widgets.find(w => w.id === SETTING_ID)?.enabled ?? false,
config: currentDashboard?.widgets.find(w => w.id === SETTING_ID)?.config as TrackMapSettings['config'] ?? defaultConfig,
});

if (!currentDashboard) {
Expand All @@ -22,13 +35,27 @@ export const TrackMapSettings = () => {
onSettingsChange={setSettings}
widgetId="map"
>
<div className="bg-yellow-600/20 text-yellow-100 p-4 rounded-md mb-4">
<p>This feature is experimental and may not work as expected.</p>
</div>
{/* Add specific settings controls here */}
<div className="text-slate-300">
Additional settings will appear here
</div>
{(handleConfigChange) => (
<div className="space-y-4">
<div className="bg-yellow-600/20 text-yellow-100 p-4 rounded-md mb-4">
<p>This is still a work in progress. There are several tracks still missing, please report any issues/requests.</p>
</div>
<div className="flex items-center justify-between">
<div>
<span className="text-sm text-slate-300">Enable Turn Names</span>
<p className="text-xs text-slate-400">
Show turn numbers and names on the track map
</p>
</div>
<ToggleSwitch
enabled={settings.config.enableTurnNames}
onToggle={(enabled) => handleConfigChange({
enableTurnNames: enabled
})}
/>
</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 @@ -31,7 +31,9 @@ export interface WeatherWidgetSettings extends BaseWidgetSettings {
}

export interface TrackMapWidgetSettings extends BaseWidgetSettings {
// Add specific track map settings here
config: {
enableTurnNames: boolean;
};
}

export type InputWidgetSettings = BaseWidgetSettings<InputSettings>;
Expand Down
20 changes: 17 additions & 3 deletions src/frontend/components/TrackMap/TrackCanvas.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,24 @@ import { Meta, StoryObj } from '@storybook/react-vite';
import { TrackCanvas, TrackDriver } from './TrackCanvas';
import { useEffect, useState } from 'react';
import tracks from './tracks/tracks.json';
import { BROKEN_TRACKS } from './tracks/broken-tracks';
import { BROKEN_TRACKS } from './tracks/brokenTracks';

export default {
component: TrackCanvas,
args: {
enableTurnNames: false,
debug: true,
},
argTypes: {
trackId: {
control: { type: 'number' },
},
enableTurnNames: {
control: { type: 'boolean' },
},
debug: {
control: { type: 'boolean' },
},
},
} as Meta;

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

export const AllTracksGrid: Story = {
render: () => {
render: (args) => {
const trackSize = 150;

return (
Expand Down Expand Up @@ -387,6 +397,8 @@ export const AllTracksGrid: Story = {
<TrackCanvas
trackId={trackId}
drivers={sampleData}
enableTurnNames={args.enableTurnNames}
debug={args.debug}
/>
</div>
</div>
Expand All @@ -398,7 +410,7 @@ export const AllTracksGrid: Story = {
};

export const BrokenTracksGrid: Story = {
render: () => {
render: (args) => {
const trackSize = 200;

return (
Expand Down Expand Up @@ -427,6 +439,8 @@ export const BrokenTracksGrid: Story = {
<TrackCanvas
trackId={brokenTrack.id}
drivers={sampleData}
enableTurnNames={args.enableTurnNames}
debug={args.debug}
/>
</div>
</div>
Expand Down
114 changes: 50 additions & 64 deletions src/frontend/components/TrackMap/TrackCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,22 @@ import { useEffect, useMemo, useRef, useState } from 'react';
import { Driver } from '@irdashies/types';
import tracks from './tracks/tracks.json';
import { getColor, getTailwindStyle } from '@irdashies/utils/colors';
import { shouldShowTrack } from './tracks/broken-tracks';
import { shouldShowTrack } from './tracks/brokenTracks';
import { TrackDebug } from './TrackDebug';
import { useStartFinishLine } from './hooks/useStartFinishLine';
import {
setupCanvasContext,
drawTrack,
drawStartFinishLine,
drawTurnNames,
drawDrivers,
} from './trackDrawingUtils';

export interface TrackProps {
trackId: number;
drivers: TrackDriver[];
enableTurnNames?: boolean;
debug?: boolean;
}

export interface TrackDriver {
Expand Down Expand Up @@ -36,13 +46,15 @@ export interface TrackDrawing {
}[];
}

// currently its a bit messy with the turns, so we disable them for now
const ENABLE_TURNS = true;

const TRACK_DRAWING_WIDTH = 1920;
const TRACK_DRAWING_HEIGHT = 1080;

export const TrackCanvas = ({ trackId, drivers }: TrackProps) => {
export const TrackCanvas = ({
trackId,
drivers,
enableTurnNames,
debug,
}: TrackProps) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 });

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

// Get start/finish line calculations
const startFinishLine = useStartFinishLine({
startFinishPoint: trackDrawing?.startFinish?.point,
trackPathPoints: trackDrawing?.active?.trackPathPoints,
});

// Position calculation based on the percentage of the track completed
const calculatePositions = useMemo(() => {
if (
Expand Down Expand Up @@ -198,73 +216,38 @@ export const TrackCanvas = ({ trackId, drivers }: TrackProps) => {
const offsetX = (rect.width - TRACK_DRAWING_WIDTH * scale) / 2;
const offsetY = (rect.height - TRACK_DRAWING_HEIGHT * scale) / 2;

// Save context state
ctx.save();

// Apply scaling and centering
ctx.translate(offsetX, offsetY);
ctx.scale(scale, scale);

// Shadow
ctx.shadowColor = 'black';
ctx.shadowBlur = 2;
ctx.shadowOffsetX = 1;
ctx.shadowOffsetY = 1;
// Setup canvas context with scaling and shadow
setupCanvasContext(ctx, scale, offsetX, offsetY);

// Draw track
if (path2DObjects.inside) {
ctx.strokeStyle = 'white';
ctx.lineWidth = 20;
ctx.stroke(path2DObjects.inside);
}

// Draw start/finish line
if (path2DObjects.startFinish) {
ctx.lineWidth = 10;
ctx.strokeStyle = getColor('red');
ctx.stroke(path2DObjects.startFinish);
}

// Draw turn numbers
if (ENABLE_TURNS) {
trackDrawing.turns?.forEach((turn) => {
if (!turn.content || !turn.x || !turn.y) return;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = 'white';
ctx.font = '2rem sans-serif';
ctx.fillText(turn.content, turn.x, turn.y);
});
}

// Draw drivers
Object.values(calculatePositions)
.sort((a, b) => Number(a.isPlayer) - Number(b.isPlayer)) // draws player last to be on top
.forEach(({ driver, position }) => {
const color = driverColors[driver.CarIdx];
if (!color) return;

ctx.fillStyle = color.fill;
ctx.beginPath();
ctx.arc(position.x, position.y, 40, 0, 2 * Math.PI);
ctx.fill();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = color.text;
ctx.font = '2rem sans-serif';
ctx.fillText(driver.CarNumber, position.x, position.y);
});
// Draw all elements
drawTrack(ctx, path2DObjects);
drawStartFinishLine(ctx, startFinishLine);
drawTurnNames(ctx, trackDrawing.turns, enableTurnNames);
drawDrivers(ctx, calculatePositions, driverColors);

// Restore context state
ctx.restore();
}, [calculatePositions, path2DObjects, trackDrawing?.turns, driverColors, canvasSize]);
}, [
calculatePositions,
path2DObjects,
trackDrawing?.turns,
driverColors,
canvasSize,
enableTurnNames,
trackDrawing?.startFinish?.point,
trackDrawing?.active?.trackPathPoints,
startFinishLine,
]);

// Development/Storybook mode - show debug info and canvas
if (import.meta.env?.DEV || import.meta.env?.MODE === 'storybook') {
if (debug) {
return (
<div className="overflow-hidden w-full h-full">
<TrackDebug trackId={trackId} trackDrawing={trackDrawing} />
<canvas className="will-change-transform w-full h-full" ref={canvasRef}></canvas>
<canvas
className="will-change-transform w-full h-full"
ref={canvasRef}
></canvas>
</div>
);
}
Expand All @@ -274,7 +257,10 @@ export const TrackCanvas = ({ trackId, drivers }: TrackProps) => {

return (
<div className="overflow-hidden w-full h-full">
<canvas className="will-change-transform w-full h-full" ref={canvasRef}></canvas>
<canvas
className="will-change-transform w-full h-full"
ref={canvasRef}
></canvas>
</div>
);
};
2 changes: 1 addition & 1 deletion src/frontend/components/TrackMap/TrackDebug.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TrackDrawing } from './TrackCanvas';
import { getBrokenTrackInfo } from './tracks/broken-tracks';
import { getBrokenTrackInfo } from './tracks/brokenTracks';

interface TrackDebugProps {
trackId: number;
Expand Down
13 changes: 12 additions & 1 deletion src/frontend/components/TrackMap/TrackMap.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import { useTrackId } from './hooks/useTrackId';
import { useDriverProgress } from './hooks/useDriverProgress';
import { useTrackMapSettings } from './hooks/useTrackMapSettings';
import { TrackCanvas } from './TrackCanvas';

const debug = import.meta.env.DEV || import.meta.env.MODE === 'storybook';

export const TrackMap = () => {
const trackId = useTrackId();
const driversTrackData = useDriverProgress();
const settings = useTrackMapSettings();

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

return <TrackCanvas trackId={trackId} drivers={driversTrackData} />;
return (
<TrackCanvas
trackId={trackId}
drivers={driversTrackData}
enableTurnNames={settings?.enableTurnNames ?? false}
debug={debug}
/>
);
};
Loading