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
2 changes: 2 additions & 0 deletions src/app/overlayManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ export class OverlayManager {
const browserWindow = new BrowserWindow({
title: `iRacing Dashies - Settings`,
frame: true,
width: 800,
height: 700,
autoHideMenuBar: true,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
Expand Down
40 changes: 33 additions & 7 deletions src/app/storage/dashboards.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
updateDashboardWidget,
} from './dashboards';
import { defaultDashboard } from './defaultDashboard';
import { DashboardLayout } from '@irdashies/types';

const mockReadData = vi.hoisted(() => vi.fn());
const mockWriteData = vi.hoisted(() => vi.fn());
Expand Down Expand Up @@ -84,7 +85,7 @@ describe('dashboards', () => {

describe('saveDashboard', () => {
it('should save a new dashboard', () => {
const newDashboard = { widgets: [] };
const newDashboard: DashboardLayout = { widgets: [], generalSettings: { fontSize: 'sm' }};
mockReadData.mockReturnValue(null);

saveDashboard('newDashboard', newDashboard);
Expand All @@ -96,7 +97,7 @@ describe('dashboards', () => {

it('should update an existing dashboard', () => {
const existingDashboards = { default: defaultDashboard };
const updatedDashboard = { widgets: [] };
const updatedDashboard: DashboardLayout = { widgets: [], generalSettings: { fontSize: 'lg' }};
mockReadData.mockReturnValue(existingDashboards);

saveDashboard('default', updatedDashboard);
Expand Down Expand Up @@ -133,15 +134,15 @@ describe('dashboards', () => {
enabled: false,
layout: { x: 100, y: 100, width: 600, height: 120 },
};
const existingDashboard = { widgets: [existingWidget] };
const existingDashboard: DashboardLayout = { widgets: [existingWidget], generalSettings: { fontSize: 'sm' } };
mockReadData.mockReturnValue({
default: existingDashboard,
});

updateDashboardWidget(updatedWidget);

expect(mockWriteData).toHaveBeenCalledWith('dashboards', {
default: { widgets: [updatedWidget] },
default: { widgets: [updatedWidget], generalSettings: { fontSize: 'sm' } },
});
});

Expand All @@ -156,15 +157,15 @@ describe('dashboards', () => {
enabled: true,
layout: { x: 100, y: 100, width: 600, height: 120 },
};
const existingDashboard = { widgets: [existingWidget] };
const existingDashboard: DashboardLayout = { widgets: [existingWidget], generalSettings: { fontSize: 'sm' } };
mockReadData.mockReturnValue({
custom: existingDashboard,
});

updateDashboardWidget(updatedWidget, 'custom');

expect(mockWriteData).toHaveBeenCalledWith('dashboards', {
custom: { widgets: [updatedWidget] },
custom: { widgets: [updatedWidget], generalSettings: { fontSize: 'sm' } },
});
});

Expand All @@ -174,7 +175,7 @@ describe('dashboards', () => {
enabled: true,
layout: { x: 100, y: 100, width: 600, height: 120 },
};
const existingDashboard = { widgets: [] };
const existingDashboard: DashboardLayout = { widgets: [], generalSettings: { fontSize: 'sm' } };
mockReadData.mockReturnValue({
default: existingDashboard,
});
Expand Down Expand Up @@ -209,6 +210,7 @@ describe('dashboards', () => {

it('should add missing widgets to the default dashboard if some widgets are missing', () => {
const incompleteDashboard = {
generalSettings: { fontSize: 'sm' },
widgets: defaultDashboard.widgets.slice(0, 1),
};
mockReadData.mockReturnValue({
Expand All @@ -235,4 +237,28 @@ describe('dashboards', () => {
expect(mockWriteData).not.toHaveBeenCalled();
});
});

describe('generalSettings', () => {
it('should add general settings from the default dashboard if none exist', () => {
const dashboard: DashboardLayout = { widgets: [] };
mockReadData.mockReturnValue({
default: dashboard,
});

const updatedDashboard = getOrCreateDefaultDashboard();

expect(updatedDashboard.generalSettings).toEqual({ fontSize: 'sm' });
});

it('should preserve general settings from the existing dashboard', () => {
const dashboard: DashboardLayout = { widgets: [], generalSettings: { fontSize: 'lg' } };
mockReadData.mockReturnValue({
default: dashboard,
});

const updatedDashboard = getOrCreateDefaultDashboard();

expect(updatedDashboard.generalSettings).toEqual({ fontSize: 'lg' });
});
});
});
38 changes: 30 additions & 8 deletions src/app/storage/dashboards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ const DASHBOARDS_KEY = 'dashboards';

const isDashboardChanged = (oldDashboard: DashboardLayout | undefined, newDashboard: DashboardLayout): boolean => {
if (!oldDashboard) return true;


// Compare generalSettings
if (JSON.stringify(oldDashboard.generalSettings) !== JSON.stringify(newDashboard.generalSettings)) {
return true;
}

// Compare widgets length
if (oldDashboard.widgets.length !== newDashboard.widgets.length) return true;

// Compare each widget
return oldDashboard.widgets.some((oldWidget, index) => {
const newWidget = newDashboard.widgets[index];
Expand All @@ -29,10 +34,16 @@ export const getOrCreateDefaultDashboard = () => {

// add missing widgets and save
const updatedDashboard = {
...dashboard,
generalSettings: {
...defaultDashboard.generalSettings,
...dashboard.generalSettings,
},
widgets: [...dashboard.widgets, ...missingWidgets].map((widget) => {
// add missing default widget config
if (!widget.config) {
return { ...widget, config: defaultDashboard.widgets.find((w) => w.id === widget.id)?.config };
const defaultWidget = defaultDashboard.widgets.find((w) => w.id === widget.id);
if (!widget.config && defaultWidget?.config) {
return { ...widget, config: defaultWidget.config };
}
return widget;
}),
Expand Down Expand Up @@ -85,11 +96,22 @@ export const saveDashboard = (
) => {
const dashboards = listDashboards();
const existingDashboard = dashboards[id];


// Merge the existing dashboard with the new value to preserve structure
const mergedDashboard: DashboardLayout = {
...existingDashboard,
...value,
widgets: value.widgets || existingDashboard?.widgets || [],
generalSettings: {
...existingDashboard?.generalSettings,
...value.generalSettings,
}
};

// Only save and emit if there are actual changes
if (isDashboardChanged(existingDashboard, value)) {
dashboards[id] = value;
if (isDashboardChanged(existingDashboard, mergedDashboard)) {
dashboards[id] = mergedDashboard;
writeData(DASHBOARDS_KEY, dashboards);
emitDashboardUpdated(value);
emitDashboardUpdated(mergedDashboard);
}
};
3 changes: 3 additions & 0 deletions src/app/storage/defaultDashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,7 @@ export const defaultDashboard: DashboardLayout = {
},
}
],
generalSettings: {
fontSize: 'sm',
},
};
6 changes: 5 additions & 1 deletion src/frontend/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Weather } from './components/Weather';
import { TrackMap } from './components/TrackMap/TrackMap';
import { FasterCarsFromBehind } from './components/FasterCarsFromBehind/FasterCarsFromBehind';
import { EditMode } from './components/EditMode/EditMode';
import { ThemeManager } from './components/ThemeManager/ThemeManager';

// TODO: type this better, right now the config comes from settings
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand All @@ -32,6 +33,7 @@ const WIDGET_MAP: Record<string, (config: any) => React.JSX.Element> = {
const AppRoutes = () => {
const { currentDashboard } = useDashboard();
const { running } = useRunningState();

return (
<Routes>
{currentDashboard?.widgets.map((widget) => {
Expand Down Expand Up @@ -60,7 +62,9 @@ const App = () => (
<TelemetryProvider bridge={window.irsdkBridge} />
<HashRouter>
<EditMode>
<AppRoutes />
<ThemeManager>
<AppRoutes />
</ThemeManager>
</EditMode>
</HashRouter>
</RunningStateProvider>
Expand Down
18 changes: 15 additions & 3 deletions src/frontend/components/Settings/SettingsLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { GearIcon, LockIcon, LockOpenIcon, PresentationChartIcon } from '@phosphor-icons/react';
import { Link, Route, Routes, useLocation } from 'react-router-dom';
import { Link, Route, Routes, useLocation, Navigate } from 'react-router-dom';
import { StandingsSettings } from './sections/StandingsSettings';
import { RelativeSettings } from './sections/RelativeSettings';
import { WeatherSettings } from './sections/WeatherSettings';
Expand All @@ -8,12 +8,13 @@ import { AdvancedSettings } from './sections/AdvancedSettings';
import { InputSettings } from './sections/InputSettings';
import { AboutSettings } from './sections/AboutSettings';
import { FasterCarsFromBehindSettings } from './sections/FasterCarsFromBehindSettings';
import { GeneralSettings } from './sections/GeneralSettings';
import { useDashboard } from '@irdashies/context';
import { useState } from 'react';

export const SettingsLayout = () => {
const location = useLocation();
const { bridge, editMode, isDemoMode, toggleDemoMode } = useDashboard();
const { bridge, editMode, isDemoMode, toggleDemoMode, currentDashboard } = useDashboard();
const [isLocked, setIsLocked] = useState(!editMode);

const isActive = (path: string) => {
Expand All @@ -30,6 +31,10 @@ export const SettingsLayout = () => {
setIsLocked(locked);
};

if (!currentDashboard) {
return <>Loading...</>;
}

return (
<div className="flex flex-col gap-4 bg-slate-700 p-4 rounded-md w-full h-full">
<div className="flex flex-row gap-4 items-center justify-between">
Expand Down Expand Up @@ -76,6 +81,11 @@ export const SettingsLayout = () => {
{/* Left Column - Widget Menu */}
<div className="w-1/3 bg-slate-800 p-4 rounded-md flex flex-col overflow-y-auto">
<ul className="flex flex-col gap-2 flex-1">
<li>
<Link to="/settings/general" className={menuItemClass('/general')}>
General
</Link>
</li>
<li>
<Link to="/settings/input" className={menuItemClass('/input')}>
Input Traces
Expand Down Expand Up @@ -114,7 +124,7 @@ export const SettingsLayout = () => {
</Link>
</li>
<li>
<Link to="/settings/map" className={menuItemClass('/track-map')}>
<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">
Expand Down Expand Up @@ -144,6 +154,8 @@ export const SettingsLayout = () => {
{/* Right Column - Widget Settings */}
<div className="w-2/3 bg-slate-800 p-4 rounded-md flex flex-col overflow-hidden">
<Routes>
<Route path="/" element={<Navigate to="/settings/general" replace />} />
<Route path="general" element={<GeneralSettings />} />
<Route path="standings" element={<StandingsSettings />} />
<Route path="relative" element={<RelativeSettings />} />
<Route path="weather" element={<WeatherSettings />} />
Expand Down
98 changes: 98 additions & 0 deletions src/frontend/components/Settings/sections/GeneralSettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { useState } from 'react';
import { useDashboard } from '@irdashies/context';
import { GeneralSettingsType } from '@irdashies/types';

const FONT_SIZE_PRESETS = {
xs: 'Extra Small',
sm: 'Small',
lg: 'Large',
xl: 'Extra Large',
};

export const GeneralSettings = () => {
const { currentDashboard, onDashboardUpdated } = useDashboard();
const [settings, setSettings] = useState<GeneralSettingsType>({
fontSize: currentDashboard?.generalSettings?.fontSize ?? 'sm',
});

if (!currentDashboard || !onDashboardUpdated) {
return <>Loading...</>;
}

const updateDashboard = (newSettings: GeneralSettingsType) => {
const updatedDashboard = {
...currentDashboard,
generalSettings: newSettings,
};
onDashboardUpdated(updatedDashboard);
};

const handleFontSizeChange = (newSize: 'xs' | 'sm' | 'lg' | 'xl') => {
const newSettings = { ...settings, fontSize: newSize };
setSettings(newSettings);
updateDashboard(newSettings);
};

return (
<div className="flex flex-col h-full space-y-6">
<div>
<h2 className="text-xl mb-4">General Settings</h2>
<p className="text-slate-400 mb-4">Configure general application settings and preferences.</p>
</div>

{/* Font Size Settings */}
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium text-slate-200">Font Size</h3>
<div className="flex items-center gap-2">
<span className="text-sm text-slate-300">{FONT_SIZE_PRESETS[settings.fontSize ?? 'sm']}</span>
</div>
</div>

{/* Font Size Presets */}
<div className="flex gap-2 mt-4">
<button
onClick={() => handleFontSizeChange('xs')}
className={`px-3 py-1 rounded text-sm ${
settings.fontSize === 'xs'
? 'bg-blue-500 text-white'
: 'bg-slate-700 text-slate-300 hover:bg-slate-600'
}`}
>
{FONT_SIZE_PRESETS.xs}
</button>
<button
onClick={() => handleFontSizeChange('sm')}
className={`px-3 py-1 rounded text-sm ${
settings.fontSize === 'sm'
? 'bg-blue-500 text-white'
: 'bg-slate-700 text-slate-300 hover:bg-slate-600'
}`}
>
{FONT_SIZE_PRESETS.sm}
</button>
<button
onClick={() => handleFontSizeChange('lg')}
className={`px-3 py-1 rounded text-sm ${
settings.fontSize === 'lg'
? 'bg-blue-500 text-white'
: 'bg-slate-700 text-slate-300 hover:bg-slate-600'
}`}
>
{FONT_SIZE_PRESETS.lg}
</button>
<button
onClick={() => handleFontSizeChange('xl')}
className={`px-3 py-1 rounded text-sm ${
settings.fontSize === 'xl'
? 'bg-blue-500 text-white'
: 'bg-slate-700 text-slate-300 hover:bg-slate-600'
}`}
>
{FONT_SIZE_PRESETS.xl}
</button>
</div>
</div>
</div>
);
};
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 w-16 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 ${color}`}
>
{formattedLicense} {simplifiedRating}k
</div>
Expand Down
Loading