Skip to content

Commit d560020

Browse files
committed
add settings for input widget
1 parent 11df947 commit d560020

26 files changed

+517
-171
lines changed

.storybook/telemetryDecorator.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import { Decorator } from '@storybook/react';
2-
import { SessionProvider, TelemetryProvider } from '@irdashies/context';
2+
import { DashboardProvider, SessionProvider, TelemetryProvider } from '@irdashies/context';
33
import { generateMockDataFromPath } from '../src/app/bridge/iracingSdk/mock-data/generateMockData';
4+
import { mockDashboardBridge } from '../src/frontend/components/Settings/__mocks__/mockBridge';
45

56
// eslint-disable-next-line react/display-name
67
export const TelemetryDecorator: (path?: string) => Decorator = (path) => (Story) => (
78
<>
89
<SessionProvider bridge={generateMockDataFromPath(path)} />
910
<TelemetryProvider bridge={generateMockDataFromPath(path)} />
10-
<Story />
11+
<DashboardProvider bridge={mockDashboardBridge}>
12+
<Story />
13+
</DashboardProvider>
1114
</>
1215
);

src/app/storage/dashboards.spec.ts

Lines changed: 36 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -7,41 +7,46 @@ import {
77
updateDashboardWidget,
88
} from './dashboards';
99
import { defaultDashboard } from './defaultDashboard';
10-
import * as storage from './storage';
10+
11+
const mockReadData = vi.hoisted(() => vi.fn());
12+
const mockWriteData = vi.hoisted(() => vi.fn());
1113

1214
vi.mock('./storage', () => ({
13-
readData: vi.fn(),
14-
writeData: vi.fn(),
15+
readData: mockReadData,
16+
writeData: mockWriteData,
1517
}));
1618

1719
describe('dashboards', () => {
1820
beforeEach(() => {
19-
vi.clearAllMocks();
21+
mockReadData.mockReset();
22+
mockWriteData.mockReset();
23+
// Default mock implementation to return null (no dashboards)
24+
mockReadData.mockReturnValue(null);
2025
});
2126

2227
describe('createDefaultDashboardIfNotExists', () => {
2328
it('should create default dashboard if none exists', () => {
2429
getOrCreateDefaultDashboard();
2530

26-
expect(storage.writeData).toHaveBeenCalledWith('dashboards', {
31+
expect(mockWriteData).toHaveBeenCalledWith('dashboards', {
2732
default: defaultDashboard,
2833
});
2934
});
3035

3136
it('should not create default dashboard if one already exists', () => {
32-
vi.spyOn(storage, 'readData').mockReturnValueOnce({
37+
mockReadData.mockReturnValue({
3338
default: defaultDashboard,
3439
});
3540

3641
getOrCreateDefaultDashboard();
3742

38-
expect(storage.writeData).not.toHaveBeenCalled();
43+
expect(mockWriteData).not.toHaveBeenCalled();
3944
});
4045
});
4146

4247
describe('listDashboards', () => {
4348
it('should return an empty object if no dashboards exist', () => {
44-
vi.spyOn(storage, 'readData').mockReturnValueOnce(null);
49+
mockReadData.mockReturnValue(null);
4550

4651
const dashboards = listDashboards();
4752

@@ -50,7 +55,7 @@ describe('dashboards', () => {
5055

5156
it('should return existing dashboards', () => {
5257
const dashboardsData = { default: defaultDashboard };
53-
vi.spyOn(storage, 'readData').mockReturnValueOnce(dashboardsData);
58+
mockReadData.mockReturnValue(dashboardsData);
5459

5560
const dashboards = listDashboards();
5661

@@ -59,8 +64,8 @@ describe('dashboards', () => {
5964
});
6065

6166
describe('getDashboard', () => {
62-
it('should return undefined if no dashboards exist', () => {
63-
vi.spyOn(storage, 'readData').mockReturnValueOnce(null);
67+
it('should return null if no dashboards exist', () => {
68+
mockReadData.mockReturnValue(null);
6469

6570
const dashboard = getDashboard('default');
6671

@@ -69,7 +74,7 @@ describe('dashboards', () => {
6974

7075
it('should return the requested dashboard if it exists', () => {
7176
const dashboardsData = { default: defaultDashboard };
72-
vi.spyOn(storage, 'readData').mockReturnValueOnce(dashboardsData);
77+
mockReadData.mockReturnValue(dashboardsData);
7378

7479
const dashboard = getDashboard('default');
7580

@@ -80,73 +85,37 @@ describe('dashboards', () => {
8085
describe('saveDashboard', () => {
8186
it('should save a new dashboard', () => {
8287
const newDashboard = { widgets: [] };
83-
vi.spyOn(storage, 'readData').mockReturnValueOnce(null);
88+
mockReadData.mockReturnValue(null);
8489

8590
saveDashboard('newDashboard', newDashboard);
8691

87-
expect(storage.writeData).toHaveBeenCalledWith('dashboards', {
92+
expect(mockWriteData).toHaveBeenCalledWith('dashboards', {
8893
newDashboard,
8994
});
9095
});
9196

9297
it('should update an existing dashboard', () => {
9398
const existingDashboards = { default: defaultDashboard };
9499
const updatedDashboard = { widgets: [] };
95-
vi.spyOn(storage, 'readData').mockReturnValueOnce(existingDashboards);
100+
mockReadData.mockReturnValue(existingDashboards);
96101

97102
saveDashboard('default', updatedDashboard);
98103

99-
expect(storage.writeData).toHaveBeenCalledWith('dashboards', {
104+
expect(mockWriteData).toHaveBeenCalledWith('dashboards', {
100105
default: updatedDashboard,
101106
});
102107
});
103108
});
104109

105-
describe('updateDashboardWidget', () => {
106-
it('should update a widget in the default dashboard', () => {
107-
const updatedWidget = {
108-
id: 'input',
109-
enabled: true,
110-
layout: { x: 100, y: 100, width: 600, height: 120 },
111-
};
112-
const updatedDashboard = { widgets: [updatedWidget] };
113-
vi.spyOn(storage, 'readData').mockReturnValueOnce({
114-
default: defaultDashboard,
115-
});
116-
117-
saveDashboard('default', updatedDashboard);
118-
119-
expect(storage.writeData).toHaveBeenCalledWith('dashboards', {
120-
default: updatedDashboard,
121-
});
122-
});
123-
124-
it('should update a widget in a specific dashboard', () => {
125-
const updatedWidget = {
126-
id: 'input',
127-
enabled: true,
128-
layout: { x: 100, y: 100, width: 600, height: 120 },
129-
};
130-
const updatedDashboard = { widgets: [updatedWidget] };
131-
vi.spyOn(storage, 'readData').mockReturnValueOnce({
132-
custom: defaultDashboard,
133-
});
134-
135-
saveDashboard('custom', updatedDashboard);
136-
137-
expect(storage.writeData).toHaveBeenCalledWith('dashboards', {
138-
custom: updatedDashboard,
139-
});
140-
});
141-
});
142110
describe('updateDashboardWidget', () => {
143111
it('should throw an error if the default dashboard does not exist', () => {
112+
mockReadData.mockReturnValue(null);
113+
144114
const updatedWidget = {
145115
id: 'input',
146116
enabled: true,
147117
layout: { x: 100, y: 100, width: 600, height: 120 },
148118
};
149-
vi.spyOn(storage, 'readData').mockReturnValueOnce(null);
150119

151120
expect(() => updateDashboardWidget(updatedWidget)).toThrow(
152121
'Default dashboard not found'
@@ -165,13 +134,13 @@ describe('dashboards', () => {
165134
layout: { x: 100, y: 100, width: 600, height: 120 },
166135
};
167136
const existingDashboard = { widgets: [existingWidget] };
168-
vi.spyOn(storage, 'readData').mockReturnValueOnce({
137+
mockReadData.mockReturnValue({
169138
default: existingDashboard,
170139
});
171140

172141
updateDashboardWidget(updatedWidget);
173142

174-
expect(storage.writeData).toHaveBeenCalledWith('dashboards', {
143+
expect(mockWriteData).toHaveBeenCalledWith('dashboards', {
175144
default: { widgets: [updatedWidget] },
176145
});
177146
});
@@ -188,13 +157,13 @@ describe('dashboards', () => {
188157
layout: { x: 100, y: 100, width: 600, height: 120 },
189158
};
190159
const existingDashboard = { widgets: [existingWidget] };
191-
vi.spyOn(storage, 'readData').mockReturnValueOnce({
160+
mockReadData.mockReturnValue({
192161
custom: existingDashboard,
193162
});
194163

195164
updateDashboardWidget(updatedWidget, 'custom');
196165

197-
expect(storage.writeData).toHaveBeenCalledWith('dashboards', {
166+
expect(mockWriteData).toHaveBeenCalledWith('dashboards', {
198167
custom: { widgets: [updatedWidget] },
199168
});
200169
});
@@ -206,21 +175,19 @@ describe('dashboards', () => {
206175
layout: { x: 100, y: 100, width: 600, height: 120 },
207176
};
208177
const existingDashboard = { widgets: [] };
209-
vi.spyOn(storage, 'readData').mockReturnValueOnce({
178+
mockReadData.mockReturnValue({
210179
default: existingDashboard,
211180
});
212181

213182
updateDashboardWidget(updatedWidget);
214183

215-
expect(storage.writeData).toHaveBeenCalledWith('dashboards', {
216-
default: { widgets: [] },
217-
});
184+
expect(mockWriteData).not.toHaveBeenCalledWith();
218185
});
219186
});
220187

221188
describe('getOrCreateDefaultDashboard', () => {
222189
it('should return the default dashboard if it exists', () => {
223-
vi.spyOn(storage, 'readData').mockReturnValueOnce({
190+
mockReadData.mockReturnValue({
224191
default: defaultDashboard,
225192
});
226193

@@ -230,12 +197,12 @@ describe('dashboards', () => {
230197
});
231198

232199
it('should create and return the default dashboard if it does not exist', () => {
233-
vi.spyOn(storage, 'readData').mockReturnValueOnce(null);
200+
mockReadData.mockReturnValue(null);
234201

235202
const dashboard = getOrCreateDefaultDashboard();
236203

237204
expect(dashboard).toEqual(defaultDashboard);
238-
expect(storage.writeData).toHaveBeenCalledWith('dashboards', {
205+
expect(mockWriteData).toHaveBeenCalledWith('dashboards', {
239206
default: defaultDashboard,
240207
});
241208
});
@@ -244,28 +211,28 @@ describe('dashboards', () => {
244211
const incompleteDashboard = {
245212
widgets: defaultDashboard.widgets.slice(0, 1),
246213
};
247-
vi.spyOn(storage, 'readData').mockReturnValueOnce({
214+
mockReadData.mockReturnValue({
248215
default: incompleteDashboard,
249216
});
250217

251218
const dashboard = getOrCreateDefaultDashboard();
252219

253220
expect(dashboard.widgets).toEqual(defaultDashboard.widgets);
254-
expect(storage.writeData).toHaveBeenCalledWith('dashboards', {
221+
expect(mockWriteData).toHaveBeenCalledWith('dashboards', {
255222
default: defaultDashboard,
256223
});
257224
});
258225

259226
it('should not modify the default dashboard if all widgets are present', () => {
260227
const completeDashboard = { ...defaultDashboard };
261-
vi.spyOn(storage, 'readData').mockReturnValueOnce({
228+
mockReadData.mockReturnValue({
262229
default: completeDashboard,
263230
});
264231

265232
const dashboard = getOrCreateDefaultDashboard();
266233

267234
expect(dashboard).toEqual(completeDashboard);
268-
expect(storage.writeData).not.toHaveBeenCalled();
235+
expect(mockWriteData).not.toHaveBeenCalled();
269236
});
270237
});
271238
});

src/app/storage/dashboards.ts

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@ import { readData, writeData } from './storage';
55

66
const DASHBOARDS_KEY = 'dashboards';
77

8+
const isDashboardChanged = (oldDashboard: DashboardLayout | undefined, newDashboard: DashboardLayout): boolean => {
9+
if (!oldDashboard) return true;
10+
11+
// Compare widgets length
12+
if (oldDashboard.widgets.length !== newDashboard.widgets.length) return true;
13+
14+
// Compare each widget
15+
return oldDashboard.widgets.some((oldWidget, index) => {
16+
const newWidget = newDashboard.widgets[index];
17+
return JSON.stringify(oldWidget) !== JSON.stringify(newWidget);
18+
});
19+
};
20+
821
export const getOrCreateDefaultDashboard = () => {
922
const dashboard = getDashboard('default');
1023
if (dashboard) {
@@ -14,14 +27,17 @@ export const getOrCreateDefaultDashboard = () => {
1427
!dashboard.widgets.find((widget) => widget.id === defaultWidget.id)
1528
);
1629

17-
if (!missingWidgets.length) {
18-
return dashboard;
19-
}
20-
2130
// add missing widgets and save
2231
const updatedDashboard = {
23-
widgets: [...dashboard.widgets, ...missingWidgets],
32+
widgets: [...dashboard.widgets, ...missingWidgets].map((widget) => {
33+
// add missing default widget config
34+
if (!widget.config) {
35+
return { ...widget, config: defaultDashboard.widgets.find((w) => w.id === widget.id)?.config };
36+
}
37+
return widget;
38+
}),
2439
};
40+
2541
saveDashboard('default', updatedDashboard);
2642
return updatedDashboard;
2743
}
@@ -68,7 +84,12 @@ export const saveDashboard = (
6884
value: DashboardLayout
6985
) => {
7086
const dashboards = listDashboards();
71-
dashboards[id] = value;
72-
writeData(DASHBOARDS_KEY, dashboards);
73-
emitDashboardUpdated(value);
87+
const existingDashboard = dashboards[id];
88+
89+
// Only save and emit if there are actual changes
90+
if (isDashboardChanged(existingDashboard, value)) {
91+
dashboards[id] = value;
92+
writeData(DASHBOARDS_KEY, dashboards);
93+
emitDashboardUpdated(value);
94+
}
7495
};

src/app/storage/defaultDashboard.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,23 @@ export const defaultDashboard: DashboardLayout = {
2121
width: 600,
2222
height: 120,
2323
},
24+
config: {
25+
trace: {
26+
enabled: true,
27+
includeThrottle: true,
28+
includeBrake: true,
29+
},
30+
bar: {
31+
enabled: true,
32+
includeClutch: true,
33+
includeGear: true,
34+
includeThrottle: true,
35+
},
36+
gear: {
37+
enabled: true,
38+
unit: 'auto',
39+
},
40+
},
2441
},
2542
{
2643
id: 'relative',
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { InputContainer } from './InputContainer/InputContainer';
2+
import { useInputSettings } from './hooks/useInputSettings';
23
import { useInputs } from './hooks/useInputs';
34

45
export const Input = () => {
56
const inputs = useInputs();
7+
const settings = useInputSettings();
68

7-
return <InputContainer {...inputs} />;
9+
return <InputContainer {...inputs} settings={settings} />;
810
};

0 commit comments

Comments
 (0)