Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
18 changes: 16 additions & 2 deletions src/frontend/components/TrackMap/TrackCanvas.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,20 @@ import { BROKEN_TRACKS } from './tracks/broken-tracks';

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
112 changes: 49 additions & 63 deletions src/frontend/components/TrackMap/TrackCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,20 @@ import tracks from './tracks/tracks.json';
import { getColor, getTailwindStyle } from '@irdashies/utils/colors';
import { shouldShowTrack } from './tracks/broken-tracks';
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>
);
};
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