@inventure71/paddockjs 2.0.1 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +176 -1
- package/INSTALL_AND_UPDATE.md +24 -4
- package/README.md +84 -512
- package/docs/architecture.md +38 -24
- package/docs/component_inventory.md +72 -0
- package/docs/composable-layouts.md +77 -0
- package/docs/concepts.md +2 -2
- package/docs/custom_model_controller.md +6 -6
- package/docs/data-helpers.md +50 -0
- package/docs/data_contract.md +104 -27
- package/docs/getting-started.md +75 -0
- package/docs/headless-environment.md +68 -0
- package/docs/index.md +18 -4
- package/docs/learnings.md +6 -6
- package/docs/rules.md +12 -8
- package/docs/sense_contract.md +1 -1
- package/docs/system_specs.md +71 -21
- package/docs/theming.md +64 -0
- package/docs/training.md +26 -19
- package/docs/troubleshooting.md +76 -0
- package/docs/upgrading-hosts-to-3.md +42 -0
- package/examples/python/README.md +6 -0
- package/examples/python/base_policy_server.py +19 -0
- package/package.json +10 -3
- package/src/api/PaddockSimulatorController.js +150 -24
- package/src/api/runtimeTheme.js +59 -0
- package/src/app/F1SimulatorApp.js +213 -29
- package/src/app/banners/raceDataBannerController.js +71 -36
- package/src/app/banners/stewardMessageController.js +5 -4
- package/src/app/camera/cameraConstants.js +2 -0
- package/src/app/camera/cameraController.js +86 -29
- package/src/app/camera/cameraGeometry.js +12 -1
- package/src/app/domBindings.js +89 -9
- package/src/app/layoutScheduler.js +27 -0
- package/src/app/layoutSupport.js +165 -0
- package/src/app/raceOverlayClearanceSupport.js +158 -0
- package/src/app/readouts/carOverviewRenderer.js +16 -8
- package/src/app/readouts/controlStateRenderer.js +1 -1
- package/src/app/readouts/raceStatusRenderer.js +30 -21
- package/src/app/readouts/readoutFormatters.js +2 -0
- package/src/app/readouts/telemetryRenderer.js +17 -8
- package/src/app/readouts/timingTowerRenderer.js +262 -49
- package/src/app/runtime/frameLoop.js +5 -1
- package/src/app/runtime/runtimeVisibility.js +66 -5
- package/src/config/cssValues.js +16 -0
- package/src/config/defaultOptions.js +58 -38
- package/src/config/themeOptions.js +830 -0
- package/src/config/timingGapMode.js +13 -0
- package/src/config/urlOptions.js +25 -0
- package/src/data/championship.js +14 -9
- package/src/data/index.d.ts +212 -0
- package/src/data/index.js +14 -0
- package/src/environment/controllerLoop.js +17 -3
- package/src/environment/episode.js +11 -1
- package/src/environment/index.d.ts +1 -2
- package/src/environment/metrics.js +59 -16
- package/src/environment/observationVector.js +50 -33
- package/src/environment/observations.js +89 -20
- package/src/environment/options.js +1 -1
- package/src/environment/runtime.js +44 -11
- package/src/environment/scenarios.js +1 -1
- package/src/environment/sensors/carRays.js +48 -37
- package/src/environment/sensors/index.js +501 -97
- package/src/environment/sensors/indexedRayBands.js +134 -46
- package/src/environment/sensors/rayBandTrace.js +1171 -0
- package/src/environment/sensors/rayChannels.js +108 -0
- package/src/environment/sensors/rayConfig.js +71 -25
- package/src/environment/sensors/rayGeometry.js +23 -17
- package/src/environment/sensors/sensorTargets.js +67 -1
- package/src/environment/sensors/surfaceRays.js +167 -118
- package/src/environment/sensors/trackRays.js +157 -48
- package/src/index.d.ts +153 -9
- package/src/index.js +52 -3
- package/src/simulation/collisionGeometry.js +260 -96
- package/src/simulation/driver/driverController.js +1 -1
- package/src/simulation/driver/edgeRecovery.js +1 -1
- package/src/simulation/driver/racingControls.js +2 -2
- package/src/simulation/driver/rejoinControls.js +2 -2
- package/src/simulation/pit/pitFlow.js +21 -9
- package/src/simulation/pit/pitKinematics.js +2 -2
- package/src/simulation/pit/pitQueue.js +5 -3
- package/src/simulation/pit/pitRouteMovement.js +9 -4
- package/src/simulation/pit/pitRouting.js +79 -46
- package/src/simulation/pit/pitState.js +1 -0
- package/src/simulation/pit/pitTireService.js +7 -3
- package/src/simulation/race/raceFinish.js +25 -8
- package/src/simulation/race/raceOrder.js +189 -32
- package/src/simulation/race/raceProgress.js +129 -37
- package/src/simulation/race/raceSetup.js +4 -6
- package/src/simulation/race/raceStep.js +26 -14
- package/src/simulation/race/safetyCar.js +1 -1
- package/src/simulation/race/stalledDnf.js +1 -1
- package/src/simulation/raceSimulation.js +20 -17
- package/src/simulation/rules/collisionSteward.js +40 -23
- package/src/simulation/rules/rulesReview.js +77 -56
- package/src/simulation/snapshots/raceSnapshots.js +37 -0
- package/src/simulation/timing/drsTiming.js +143 -30
- package/src/simulation/timing/gapEstimation.js +25 -3
- package/src/simulation/timing/lapTelemetry.js +138 -40
- package/src/simulation/timing/raceTiming.js +5 -1
- package/src/simulation/timing/sectorPerformance.js +104 -26
- package/src/simulation/timing/timingHistory.js +229 -14
- package/src/simulation/timing/timingLines.js +65 -7
- package/src/simulation/track/pitLaneLayout.js +30 -3
- package/src/simulation/track/pitLaneState.js +218 -19
- package/src/simulation/track/spatialQueries.js +66 -33
- package/src/simulation/track/trackMath.js +91 -24
- package/src/simulation/track/trackModel.js +2 -0
- package/src/simulation/track/trackQueryIndex.js +1468 -189
- package/src/simulation/track/trackQueryPitIndex.js +138 -14
- package/src/simulation/track/trackQueryScratch.js +7 -0
- package/src/simulation/track/trackQueryStats.js +25 -0
- package/src/simulation/track/trackSnapshotJson.js +139 -0
- package/src/simulation/track/trackStatePolicy.js +249 -2
- package/src/simulation/vehicle/contactResolution.js +99 -62
- package/src/simulation/vehicle/mainTrackWheelSurface.js +73 -34
- package/src/simulation/vehicle/pitWheelSurface.js +81 -28
- package/src/simulation/vehicle/runoffResponse.js +54 -29
- package/src/simulation/vehicle/vehicleGeometry.js +245 -51
- package/src/simulation/vehicle/vehicleKinematics.js +2 -2
- package/src/simulation/vehicle/vehiclePhysics.js +45 -30
- package/src/simulation/vehicle/vehicleSnapshots.js +35 -1
- package/src/simulation/vehicle/vehicleState.js +7 -1
- package/src/simulation/vehicle/wheelSurface.js +807 -92
- package/src/simulation/warmup/runtimeWarmup.js +0 -1
- package/src/styles.css +1568 -519
- package/src/ui/bannerTemplates.js +38 -28
- package/src/ui/cameraControlsTemplate.js +16 -5
- package/src/ui/carOverviewTemplate.js +12 -5
- package/src/ui/raceCanvasTemplate.js +31 -8
- package/src/ui/raceControlsTemplate.js +27 -10
- package/src/ui/shellTemplate.js +3 -1
- package/src/ui/telemetryDrawerTemplate.js +23 -10
- package/src/ui/telemetryTemplates.js +58 -32
- package/src/ui/templateUtils.js +48 -0
- package/src/ui/timingTowerTemplate.js +29 -12
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { F1SimulatorApp } from '../app/F1SimulatorApp.js';
|
|
2
|
+
import { installLayoutSupport } from '../app/layoutSupport.js';
|
|
2
3
|
import {
|
|
3
4
|
createRaceDataPanelMarkup,
|
|
4
5
|
createTelemetrySectorBannerMarkup,
|
|
@@ -18,7 +19,10 @@ import {
|
|
|
18
19
|
createTimingTowerMarkup,
|
|
19
20
|
} from '../ui/componentTemplates.js';
|
|
20
21
|
import { applyPaddockThemeCssVariables, resolveF1SimulatorOptions } from '../config/defaultOptions.js';
|
|
22
|
+
import { formatCssUrl } from '../config/cssValues.js';
|
|
21
23
|
import { mergeRestartOptions } from '../config/restartOptions.js';
|
|
24
|
+
import { getNextTimingGapMode, normalizeTimingGapMode } from '../config/timingGapMode.js';
|
|
25
|
+
import { createThemeSync, resolveRuntimeThemeModeOptions, resolveRuntimeThemeOptions } from './runtimeTheme.js';
|
|
22
26
|
|
|
23
27
|
function assertMountTarget(root, label) {
|
|
24
28
|
if (!root || typeof root !== 'object' || !('innerHTML' in root)) {
|
|
@@ -26,10 +30,10 @@ function assertMountTarget(root, label) {
|
|
|
26
30
|
}
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
function setPackageCssVariables(root, assets, theme) {
|
|
33
|
+
function setPackageCssVariables(root, assets, theme, context = {}) {
|
|
30
34
|
root.classList?.add?.('f1-sim-component');
|
|
31
|
-
root.style?.setProperty?.('--broadcast-panel-surface',
|
|
32
|
-
applyPaddockThemeCssVariables(root, theme);
|
|
35
|
+
root.style?.setProperty?.('--broadcast-panel-surface', formatCssUrl(assets.broadcastPanel));
|
|
36
|
+
applyPaddockThemeCssVariables(root, theme, context);
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
function createCompositeRoot(getRoots, getOptions) {
|
|
@@ -38,6 +42,9 @@ function createCompositeRoot(getRoots, getOptions) {
|
|
|
38
42
|
setProperty(name, value) {
|
|
39
43
|
getRoots().forEach((root) => root.style?.setProperty?.(name, value));
|
|
40
44
|
},
|
|
45
|
+
removeProperty(name) {
|
|
46
|
+
getRoots().forEach((root) => root.style?.removeProperty?.(name));
|
|
47
|
+
},
|
|
41
48
|
},
|
|
42
49
|
querySelector(selector) {
|
|
43
50
|
for (const root of getRoots()) {
|
|
@@ -49,9 +56,15 @@ function createCompositeRoot(getRoots, getOptions) {
|
|
|
49
56
|
querySelectorAll(selector) {
|
|
50
57
|
return getRoots().flatMap((root) => [...(root.querySelectorAll?.(selector) ?? [])]);
|
|
51
58
|
},
|
|
52
|
-
|
|
59
|
+
setAttribute(name, value) {
|
|
60
|
+
getRoots().forEach((root) => root.setAttribute?.(name, value));
|
|
61
|
+
},
|
|
62
|
+
removeAttribute(name) {
|
|
63
|
+
getRoots().forEach((root) => root.removeAttribute?.(name));
|
|
64
|
+
},
|
|
65
|
+
applyCssVariables(context = {}) {
|
|
53
66
|
const options = getOptions();
|
|
54
|
-
getRoots().forEach((root) => setPackageCssVariables(root, options.assets, options.theme));
|
|
67
|
+
getRoots().forEach((root) => setPackageCssVariables(root, options.assets, options.theme, context));
|
|
55
68
|
},
|
|
56
69
|
};
|
|
57
70
|
}
|
|
@@ -60,35 +73,41 @@ export class PaddockSimulatorController {
|
|
|
60
73
|
constructor(options = {}) {
|
|
61
74
|
this.options = resolveF1SimulatorOptions(options);
|
|
62
75
|
this.roots = new Map();
|
|
76
|
+
this.mountRenderers = new Map();
|
|
77
|
+
this.layoutSupportCleanups = new Map();
|
|
63
78
|
this.app = null;
|
|
64
79
|
this.compositeRoot = createCompositeRoot(() => [...this.roots.values()], () => this.options);
|
|
65
80
|
}
|
|
66
81
|
|
|
67
|
-
mountComponent(root, key,
|
|
82
|
+
mountComponent(root, key, createMarkup) {
|
|
68
83
|
assertMountTarget(root, `mount ${key}`);
|
|
69
84
|
if (this.app) {
|
|
70
85
|
throw new Error('Mount PaddockJS components before calling start().');
|
|
71
86
|
}
|
|
72
|
-
|
|
87
|
+
const render = typeof createMarkup === 'function' ? createMarkup : () => createMarkup;
|
|
88
|
+
root.innerHTML = render();
|
|
73
89
|
setPackageCssVariables(root, this.options.assets, this.options.theme);
|
|
90
|
+
this.layoutSupportCleanups.get(key)?.();
|
|
91
|
+
this.layoutSupportCleanups.set(key, installLayoutSupport(root));
|
|
74
92
|
this.roots.set(key, root);
|
|
93
|
+
this.mountRenderers.set(key, render);
|
|
75
94
|
return root;
|
|
76
95
|
}
|
|
77
96
|
|
|
78
97
|
mountRaceControls(root) {
|
|
79
|
-
return this.mountComponent(root, 'race-controls', createRaceControlsMarkup(this.options));
|
|
98
|
+
return this.mountComponent(root, 'race-controls', () => createRaceControlsMarkup(this.options));
|
|
80
99
|
}
|
|
81
100
|
|
|
82
101
|
mountCameraControls(root) {
|
|
83
|
-
return this.mountComponent(root, 'camera-controls', createCameraControlsMarkup(this.options));
|
|
102
|
+
return this.mountComponent(root, 'camera-controls', () => createCameraControlsMarkup(this.options));
|
|
84
103
|
}
|
|
85
104
|
|
|
86
105
|
mountSafetyCarControl(root) {
|
|
87
|
-
return this.mountComponent(root, 'safety-car-control', createSafetyCarControlMarkup(this.options));
|
|
106
|
+
return this.mountComponent(root, 'safety-car-control', () => createSafetyCarControlMarkup(this.options));
|
|
88
107
|
}
|
|
89
108
|
|
|
90
109
|
mountTimingTower(root) {
|
|
91
|
-
return this.mountComponent(root, 'timing-tower', createTimingTowerMarkup(this.options));
|
|
110
|
+
return this.mountComponent(root, 'timing-tower', () => createTimingTowerMarkup(this.options));
|
|
92
111
|
}
|
|
93
112
|
|
|
94
113
|
mountRaceCanvas(root, {
|
|
@@ -96,50 +115,55 @@ export class PaddockSimulatorController {
|
|
|
96
115
|
includeTimingTower = false,
|
|
97
116
|
includeTelemetrySectorBanner = false,
|
|
98
117
|
timingTowerVerticalFit,
|
|
118
|
+
responsiveNarrowLayout,
|
|
99
119
|
} = {}) {
|
|
100
|
-
return this.mountComponent(root, 'race-canvas', createRaceCanvasMarkup({
|
|
120
|
+
return this.mountComponent(root, 'race-canvas', () => createRaceCanvasMarkup({
|
|
101
121
|
...this.options,
|
|
102
122
|
includeRaceDataPanel,
|
|
103
123
|
includeTimingTower,
|
|
104
124
|
includeTelemetrySectorBanner,
|
|
105
125
|
timingTowerVerticalFit,
|
|
126
|
+
responsiveNarrowLayout,
|
|
106
127
|
}));
|
|
107
128
|
}
|
|
108
129
|
|
|
109
130
|
mountTelemetryPanel(root, { includeOverview } = {}) {
|
|
110
|
-
return this.mountComponent(root, 'telemetry-stack', createTelemetryPanelMarkup(this.options, { includeOverview }));
|
|
131
|
+
return this.mountComponent(root, 'telemetry-stack', () => createTelemetryPanelMarkup(this.options, { includeOverview }));
|
|
111
132
|
}
|
|
112
133
|
|
|
113
134
|
mountTelemetryCore(root) {
|
|
114
|
-
return this.mountComponent(root, 'telemetry-core', createTelemetryCoreMarkup(this.options));
|
|
135
|
+
return this.mountComponent(root, 'telemetry-core', () => createTelemetryCoreMarkup(this.options));
|
|
115
136
|
}
|
|
116
137
|
|
|
117
138
|
mountTelemetrySectors(root) {
|
|
118
|
-
return this.mountComponent(root, 'telemetry-sectors', createTelemetrySectorsMarkup(this.options));
|
|
139
|
+
return this.mountComponent(root, 'telemetry-sectors', () => createTelemetrySectorsMarkup(this.options));
|
|
119
140
|
}
|
|
120
141
|
|
|
121
142
|
mountTelemetryLapTimes(root) {
|
|
122
|
-
return this.mountComponent(root, 'telemetry-lap-times', createTelemetryLapTimesMarkup(this.options));
|
|
143
|
+
return this.mountComponent(root, 'telemetry-lap-times', () => createTelemetryLapTimesMarkup(this.options));
|
|
123
144
|
}
|
|
124
145
|
|
|
125
146
|
mountTelemetrySectorTimes(root) {
|
|
126
|
-
return this.mountComponent(root, 'telemetry-sector-times', createTelemetrySectorTimesMarkup(this.options));
|
|
147
|
+
return this.mountComponent(root, 'telemetry-sector-times', () => createTelemetrySectorTimesMarkup(this.options));
|
|
127
148
|
}
|
|
128
149
|
|
|
129
150
|
mountTelemetrySectorBanner(root) {
|
|
130
|
-
return this.mountComponent(root, 'telemetry-sector-banner', createTelemetrySectorBannerMarkup(this.options));
|
|
151
|
+
return this.mountComponent(root, 'telemetry-sector-banner', () => createTelemetrySectorBannerMarkup(this.options));
|
|
131
152
|
}
|
|
132
153
|
|
|
133
154
|
mountRaceTelemetryDrawer(root, options = {}) {
|
|
134
|
-
return this.mountComponent(root, 'race-telemetry-drawer', createRaceTelemetryDrawerMarkup(this.options, options));
|
|
155
|
+
return this.mountComponent(root, 'race-telemetry-drawer', () => createRaceTelemetryDrawerMarkup(this.options, options));
|
|
135
156
|
}
|
|
136
157
|
|
|
137
158
|
mountCarDriverOverview(root) {
|
|
138
|
-
return this.mountComponent(root, 'car-driver-overview', createCarDriverOverviewMarkup(this.options));
|
|
159
|
+
return this.mountComponent(root, 'car-driver-overview', () => createCarDriverOverviewMarkup(this.options));
|
|
139
160
|
}
|
|
140
161
|
|
|
141
162
|
mountRaceDataPanel(root) {
|
|
142
|
-
return this.mountComponent(root, 'race-data-panel', createRaceDataPanelMarkup(
|
|
163
|
+
return this.mountComponent(root, 'race-data-panel', () => createRaceDataPanelMarkup({
|
|
164
|
+
...this.options,
|
|
165
|
+
standalone: true,
|
|
166
|
+
}));
|
|
143
167
|
}
|
|
144
168
|
|
|
145
169
|
querySelector(selector) {
|
|
@@ -165,17 +189,86 @@ export class PaddockSimulatorController {
|
|
|
165
189
|
destroy() {
|
|
166
190
|
this.app?.destroy();
|
|
167
191
|
this.app = null;
|
|
192
|
+
this.layoutSupportCleanups.forEach((cleanup) => cleanup());
|
|
193
|
+
this.layoutSupportCleanups.clear();
|
|
168
194
|
this.roots.forEach((root) => {
|
|
169
195
|
root.innerHTML = '';
|
|
170
196
|
});
|
|
171
197
|
this.roots.clear();
|
|
198
|
+
this.mountRenderers.clear();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
rerenderMountedComponents() {
|
|
202
|
+
this.roots.forEach((root, key) => {
|
|
203
|
+
const render = this.mountRenderers.get(key);
|
|
204
|
+
if (render) root.innerHTML = render();
|
|
205
|
+
setPackageCssVariables(root, this.options.assets, this.options.theme);
|
|
206
|
+
this.layoutSupportCleanups.get(key)?.();
|
|
207
|
+
this.layoutSupportCleanups.set(key, installLayoutSupport(root));
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
syncOptionsFromRunningApp() {
|
|
212
|
+
const timingGapMode = this.app?.getTimingGapMode?.();
|
|
213
|
+
if (!timingGapMode) return this.options;
|
|
214
|
+
this.options = {
|
|
215
|
+
...this.options,
|
|
216
|
+
ui: {
|
|
217
|
+
...this.options.ui,
|
|
218
|
+
timingGapMode: normalizeTimingGapMode(timingGapMode),
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
return this.options;
|
|
172
222
|
}
|
|
173
223
|
|
|
174
224
|
restart(nextOptions = {}) {
|
|
175
|
-
const
|
|
176
|
-
|
|
225
|
+
const currentOptions = this.app ? this.syncOptionsFromRunningApp() : this.options;
|
|
226
|
+
const nextResolvedOptions = resolveF1SimulatorOptions(mergeRestartOptions(currentOptions, nextOptions));
|
|
227
|
+
if (this.app) {
|
|
228
|
+
const previousOptions = this.options;
|
|
229
|
+
this.options = nextResolvedOptions;
|
|
230
|
+
try {
|
|
231
|
+
this.app.restart(nextResolvedOptions);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
this.options = previousOptions;
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
177
238
|
this.options = nextResolvedOptions;
|
|
178
|
-
this.
|
|
239
|
+
this.rerenderMountedComponents();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
setTheme(themeInput = {}) {
|
|
243
|
+
const currentOptions = this.app ? this.syncOptionsFromRunningApp() : this.options;
|
|
244
|
+
const nextResolvedOptions = resolveRuntimeThemeOptions(currentOptions, themeInput);
|
|
245
|
+
this.options = nextResolvedOptions;
|
|
246
|
+
if (this.app?.setTheme) {
|
|
247
|
+
this.app.setTheme(nextResolvedOptions.theme);
|
|
248
|
+
} else {
|
|
249
|
+
this.compositeRoot.applyCssVariables();
|
|
250
|
+
}
|
|
251
|
+
return nextResolvedOptions.theme;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
setThemeMode(mode) {
|
|
255
|
+
const currentOptions = this.app ? this.syncOptionsFromRunningApp() : this.options;
|
|
256
|
+
const nextResolvedOptions = resolveRuntimeThemeModeOptions(currentOptions, mode);
|
|
257
|
+
this.options = nextResolvedOptions;
|
|
258
|
+
if (this.app?.setTheme) {
|
|
259
|
+
this.app.setTheme(nextResolvedOptions.theme);
|
|
260
|
+
} else {
|
|
261
|
+
this.compositeRoot.applyCssVariables();
|
|
262
|
+
}
|
|
263
|
+
return nextResolvedOptions.theme;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
getTheme() {
|
|
267
|
+
return this.options.theme;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
syncThemeFrom(source, options = {}) {
|
|
271
|
+
return createThemeSync(this, source, options);
|
|
179
272
|
}
|
|
180
273
|
|
|
181
274
|
selectDriver(driverId) {
|
|
@@ -223,6 +316,39 @@ export class PaddockSimulatorController {
|
|
|
223
316
|
return this.app?.simulationSpeed ?? 1;
|
|
224
317
|
}
|
|
225
318
|
|
|
319
|
+
setTimingGapMode(mode) {
|
|
320
|
+
const timingGapMode = this.app
|
|
321
|
+
? this.app.setTimingGapMode(mode)
|
|
322
|
+
: normalizeTimingGapMode(mode);
|
|
323
|
+
this.options = {
|
|
324
|
+
...this.options,
|
|
325
|
+
ui: {
|
|
326
|
+
...this.options.ui,
|
|
327
|
+
timingGapMode,
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
return this.options.ui.timingGapMode;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
getTimingGapMode() {
|
|
334
|
+
return this.app?.getTimingGapMode?.() ?? normalizeTimingGapMode(this.options.ui?.timingGapMode);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
toggleTimingGapMode() {
|
|
338
|
+
if (this.app) {
|
|
339
|
+
const timingGapMode = this.app.toggleTimingGapMode();
|
|
340
|
+
this.options = {
|
|
341
|
+
...this.options,
|
|
342
|
+
ui: {
|
|
343
|
+
...this.options.ui,
|
|
344
|
+
timingGapMode,
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
return timingGapMode;
|
|
348
|
+
}
|
|
349
|
+
return this.setTimingGapMode(getNextTimingGapMode(this.options.ui?.timingGapMode));
|
|
350
|
+
}
|
|
351
|
+
|
|
226
352
|
servePenalty(penaltyId) {
|
|
227
353
|
return this.app?.servePenalty(penaltyId) ?? null;
|
|
228
354
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { resolveF1SimulatorOptions } from '../config/defaultOptions.js';
|
|
2
|
+
import { mergeThemeInputs } from '../config/themeOptions.js';
|
|
3
|
+
|
|
4
|
+
export function resolveRuntimeThemeOptions(currentOptions, themeInput = {}) {
|
|
5
|
+
return resolveF1SimulatorOptions({
|
|
6
|
+
...currentOptions,
|
|
7
|
+
theme: mergeThemeInputs(currentOptions?.theme, themeInput),
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function resolveRuntimeThemeModeOptions(currentOptions, mode) {
|
|
12
|
+
return resolveRuntimeThemeOptions(currentOptions, { mode });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createThemeSync(simulator, source, {
|
|
16
|
+
attribute = 'data-theme',
|
|
17
|
+
map,
|
|
18
|
+
} = {}) {
|
|
19
|
+
if (!source || typeof source !== 'object') {
|
|
20
|
+
throw new Error('syncThemeFrom requires a DOM element or attribute source.');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const readValue = () => {
|
|
24
|
+
const rawValue = typeof source.getAttribute === 'function'
|
|
25
|
+
? source.getAttribute(attribute)
|
|
26
|
+
: source?.dataset?.[attributeToDatasetKey(attribute)];
|
|
27
|
+
if (map && Object.hasOwn(map, rawValue)) return map[rawValue];
|
|
28
|
+
return rawValue;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const sync = () => {
|
|
32
|
+
const value = readValue();
|
|
33
|
+
if (value && typeof value === 'object') {
|
|
34
|
+
simulator.setTheme(value);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
simulator.setThemeMode(typeof value === 'string' && value !== '' ? value : 'dark');
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
sync();
|
|
41
|
+
|
|
42
|
+
const Observer = globalThis.MutationObserver;
|
|
43
|
+
if (typeof Observer !== 'function' || typeof source.getAttribute !== 'function') {
|
|
44
|
+
return () => {};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const observer = new Observer(sync);
|
|
48
|
+
observer.observe(source, {
|
|
49
|
+
attributes: true,
|
|
50
|
+
attributeFilter: [attribute],
|
|
51
|
+
});
|
|
52
|
+
return () => observer.disconnect();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function attributeToDatasetKey(attribute) {
|
|
56
|
+
return String(attribute)
|
|
57
|
+
.replace(/^data-/, '')
|
|
58
|
+
.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
59
|
+
}
|