@focus8/settings-registry 0.1.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/dist/index.d.ts +318 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +293 -0
- package/dist/index.js.map +1 -0
- package/package.json +24 -0
- package/src/index.ts +715 -0
- package/tsconfig.json +22 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings Registry — single source of truth for ALL FocusPlanner app settings.
|
|
3
|
+
*
|
|
4
|
+
* This shared package defines every setting key, its category, data type,
|
|
5
|
+
* default value, and whether it should be synced to the server.
|
|
6
|
+
*
|
|
7
|
+
* Used by:
|
|
8
|
+
* - The mobile app (local SQLite settings table, React Query hooks)
|
|
9
|
+
* - The API (validate & serialize settings profiles)
|
|
10
|
+
* - The web portal (render settings viewer/editor)
|
|
11
|
+
*
|
|
12
|
+
* App-specific defaults (theme, locale, enrolled mode) are parameterized
|
|
13
|
+
* via `createRegistry(config)`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Setting types shared across mobile, API, and web
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
export type ThemeSetting = 'light' | 'dark' | 'system';
|
|
21
|
+
export type AlarmSound =
|
|
22
|
+
| 'none'
|
|
23
|
+
| 'alarm1'
|
|
24
|
+
| 'alarm2'
|
|
25
|
+
| 'alarm3'
|
|
26
|
+
| 'alarm4'
|
|
27
|
+
| 'alarm5'
|
|
28
|
+
| 'alarm6'
|
|
29
|
+
| 'alarm7'
|
|
30
|
+
| 'alarm8'
|
|
31
|
+
| 'alarm9';
|
|
32
|
+
export type LocaleCode = 'en' | 'nb';
|
|
33
|
+
|
|
34
|
+
export type CalendarType = 'chronological' | 'time-based';
|
|
35
|
+
export type ClockType = 'digital' | 'analog';
|
|
36
|
+
export type TimePickerMode = 'dials' | 'keypad';
|
|
37
|
+
export type AlarmTimeout = 1 | 2 | 3 | 5 | 10;
|
|
38
|
+
export const ALARM_TIMEOUTS: AlarmTimeout[] = [1, 2, 3, 5, 10];
|
|
39
|
+
export type CalendarViewMode =
|
|
40
|
+
| 'day'
|
|
41
|
+
| '3-days'
|
|
42
|
+
| '5-days'
|
|
43
|
+
| '7-days'
|
|
44
|
+
| 'week'
|
|
45
|
+
| 'month'
|
|
46
|
+
| 'overview';
|
|
47
|
+
export type CalendarDayViewCellZoom = 15 | 30 | 60;
|
|
48
|
+
export type InactivityTimeoutMinutes = 1 | 5 | 10 | 15 | 30 | 45;
|
|
49
|
+
export type LockScreenClockDisplay = 'none' | 'digital' | 'analog';
|
|
50
|
+
export type LockScreenImageMode = 'none' | 'background' | 'photoFrame';
|
|
51
|
+
export type PhotoFrameIntervalSeconds = 30 | 60 | 120 | 300;
|
|
52
|
+
export type EventVisibility = 'Public' | 'Private' | 'Custom';
|
|
53
|
+
export type SuggestEndTimeUnit = 'minutes' | 'hours';
|
|
54
|
+
|
|
55
|
+
export type WeatherLocation = {
|
|
56
|
+
address: string;
|
|
57
|
+
name?: string;
|
|
58
|
+
latitude: number;
|
|
59
|
+
longitude: number;
|
|
60
|
+
placeId?: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type LockScreenImage = {
|
|
64
|
+
uri: string;
|
|
65
|
+
filePath: string;
|
|
66
|
+
fileName?: string;
|
|
67
|
+
fileSize?: number;
|
|
68
|
+
width?: number;
|
|
69
|
+
height?: number;
|
|
70
|
+
order: number;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const LOCK_SCREEN_MAX_IMAGES = 10;
|
|
74
|
+
|
|
75
|
+
export type AllDayPreset = {
|
|
76
|
+
daysBefore: number;
|
|
77
|
+
time: string;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Setting categories — used for UI grouping and profile organization
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
export const SETTINGS_CATEGORIES = [
|
|
85
|
+
'appearance',
|
|
86
|
+
'calendarView',
|
|
87
|
+
'calendars',
|
|
88
|
+
'sound',
|
|
89
|
+
'timer',
|
|
90
|
+
'media',
|
|
91
|
+
'lockScreen',
|
|
92
|
+
'touch',
|
|
93
|
+
'device',
|
|
94
|
+
'language',
|
|
95
|
+
'notification',
|
|
96
|
+
'chronological',
|
|
97
|
+
'eventForm',
|
|
98
|
+
'chronologicalEventForm',
|
|
99
|
+
] as const;
|
|
100
|
+
|
|
101
|
+
export type SettingsCategory = (typeof SETTINGS_CATEGORIES)[number];
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Data type discriminator — stored alongside each setting in the DB
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
export type SettingsDataType = 'string' | 'number' | 'boolean' | 'json';
|
|
108
|
+
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// Single registry entry definition
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
export type SettingDef<T = unknown> = {
|
|
114
|
+
category: SettingsCategory;
|
|
115
|
+
type: SettingsDataType;
|
|
116
|
+
default: T;
|
|
117
|
+
/** Whether this setting should be synced to the server. Default true. */
|
|
118
|
+
sync: boolean;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Configuration for app-specific defaults
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
export type RegistryConfig = {
|
|
126
|
+
/** Whether the device is in enrolled/kiosk mode */
|
|
127
|
+
isEnrolled: boolean;
|
|
128
|
+
/** Default theme code */
|
|
129
|
+
defaultTheme: ThemeSetting;
|
|
130
|
+
/** Default locale code */
|
|
131
|
+
defaultLocale: LocaleCode;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/** Default config for non-enrolled mode */
|
|
135
|
+
export const DEFAULT_REGISTRY_CONFIG: RegistryConfig = {
|
|
136
|
+
isEnrolled: false,
|
|
137
|
+
defaultTheme: 'system',
|
|
138
|
+
defaultLocale: 'nb',
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// Helper
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
function def<T>(
|
|
146
|
+
category: SettingsCategory,
|
|
147
|
+
type: SettingsDataType,
|
|
148
|
+
defaultValue: T,
|
|
149
|
+
sync = true,
|
|
150
|
+
): SettingDef<T> {
|
|
151
|
+
return { category, type, default: defaultValue, sync };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// Entry builder (internal — used by SettingsRegistry class and factory)
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
function buildEntries(config: RegistryConfig) {
|
|
159
|
+
return {
|
|
160
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
161
|
+
// Appearance
|
|
162
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
163
|
+
'appearance.theme': def<ThemeSetting>(
|
|
164
|
+
'appearance',
|
|
165
|
+
'string',
|
|
166
|
+
config.defaultTheme,
|
|
167
|
+
),
|
|
168
|
+
'appearance.clockType': def<ClockType>('appearance', 'string', 'digital'),
|
|
169
|
+
'appearance.enableDayColors': def<boolean>('appearance', 'boolean', false),
|
|
170
|
+
|
|
171
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
172
|
+
// Calendar view
|
|
173
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
174
|
+
'calendarView.type': def<CalendarType>(
|
|
175
|
+
'calendarView',
|
|
176
|
+
'string',
|
|
177
|
+
'time-based',
|
|
178
|
+
),
|
|
179
|
+
'calendarView.view': def<CalendarViewMode>('calendarView', 'string', 'day'),
|
|
180
|
+
'calendarView.dayViewZoom': def<CalendarDayViewCellZoom>(
|
|
181
|
+
'calendarView',
|
|
182
|
+
'number',
|
|
183
|
+
60,
|
|
184
|
+
),
|
|
185
|
+
'calendarView.weekViewZoom': def<CalendarDayViewCellZoom>(
|
|
186
|
+
'calendarView',
|
|
187
|
+
'number',
|
|
188
|
+
60,
|
|
189
|
+
),
|
|
190
|
+
'calendarView.splitView': def<boolean>('calendarView', 'boolean', false),
|
|
191
|
+
'calendarView.showCalendarNames': def<boolean>(
|
|
192
|
+
'calendarView',
|
|
193
|
+
'boolean',
|
|
194
|
+
true,
|
|
195
|
+
),
|
|
196
|
+
'calendarView.calendarColumns': def<unknown[]>('calendarView', 'json', []),
|
|
197
|
+
'calendarView.autoReturnToTodayEnabled': def<boolean>(
|
|
198
|
+
'calendarView',
|
|
199
|
+
'boolean',
|
|
200
|
+
config.isEnrolled,
|
|
201
|
+
),
|
|
202
|
+
'calendarView.autoReturnToTodayTimeoutSeconds': def<number>(
|
|
203
|
+
'calendarView',
|
|
204
|
+
'number',
|
|
205
|
+
300,
|
|
206
|
+
),
|
|
207
|
+
'calendarView.showWeatherOnEvents': def<boolean>(
|
|
208
|
+
'calendarView',
|
|
209
|
+
'boolean',
|
|
210
|
+
false,
|
|
211
|
+
),
|
|
212
|
+
'calendarView.showWeatherOnTimeline': def<boolean>(
|
|
213
|
+
'calendarView',
|
|
214
|
+
'boolean',
|
|
215
|
+
false,
|
|
216
|
+
),
|
|
217
|
+
'calendarView.weatherLocation': def<WeatherLocation | null>(
|
|
218
|
+
'calendarView',
|
|
219
|
+
'json',
|
|
220
|
+
null,
|
|
221
|
+
),
|
|
222
|
+
|
|
223
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
224
|
+
// Event form field visibility (time-based)
|
|
225
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
226
|
+
'eventForm.recurrence': def<boolean>('eventForm', 'boolean', true),
|
|
227
|
+
'eventForm.reminders': def<boolean>('eventForm', 'boolean', true),
|
|
228
|
+
'eventForm.emailReminders': def<boolean>('eventForm', 'boolean', false),
|
|
229
|
+
'eventForm.location': def<boolean>('eventForm', 'boolean', true),
|
|
230
|
+
'eventForm.travelTime': def<boolean>('eventForm', 'boolean', false),
|
|
231
|
+
'eventForm.description': def<boolean>('eventForm', 'boolean', true),
|
|
232
|
+
'eventForm.checklist': def<boolean>('eventForm', 'boolean', true),
|
|
233
|
+
'eventForm.images': def<boolean>('eventForm', 'boolean', false),
|
|
234
|
+
'eventForm.audioClips': def<boolean>('eventForm', 'boolean', false),
|
|
235
|
+
'eventForm.notificationReceivers': def<boolean>(
|
|
236
|
+
'eventForm',
|
|
237
|
+
'boolean',
|
|
238
|
+
true,
|
|
239
|
+
),
|
|
240
|
+
'eventForm.visibility': def<boolean>('eventForm', 'boolean', false),
|
|
241
|
+
|
|
242
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
243
|
+
// Sound & alerts
|
|
244
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
245
|
+
'sound.timerVolume': def<number>('sound', 'number', 0.5),
|
|
246
|
+
'sound.reminderVolume': def<number>('sound', 'number', 0.5),
|
|
247
|
+
'sound.mediaVolume': def<number>('sound', 'number', 0.5),
|
|
248
|
+
'sound.alarmSound': def<AlarmSound>('sound', 'string', 'alarm1'),
|
|
249
|
+
'sound.reminderAlarmSound': def<AlarmSound>('sound', 'string', 'alarm1'),
|
|
250
|
+
'sound.timerAlarmSound': def<AlarmSound>('sound', 'string', 'alarm1'),
|
|
251
|
+
'sound.timerAlarmTimeout': def<AlarmTimeout>('sound', 'number', 3),
|
|
252
|
+
'sound.reminderAlarmTimeout': def<AlarmTimeout>('sound', 'number', 3),
|
|
253
|
+
'sound.allowCustomReminderSounds': def<boolean>('sound', 'boolean', false),
|
|
254
|
+
'sound.ttsEnabled': def<boolean>('sound', 'boolean', config.isEnrolled),
|
|
255
|
+
'sound.ttsRate': def<number>('sound', 'number', 1.0),
|
|
256
|
+
|
|
257
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
258
|
+
// Timer
|
|
259
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
260
|
+
'timer.showTimeRemaining': def<boolean>('timer', 'boolean', true),
|
|
261
|
+
'timer.showEndTime': def<boolean>('timer', 'boolean', true),
|
|
262
|
+
'timer.showRestartButton': def<boolean>('timer', 'boolean', true),
|
|
263
|
+
'timer.showPauseButton': def<boolean>('timer', 'boolean', true),
|
|
264
|
+
|
|
265
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
266
|
+
// Lock screen
|
|
267
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
268
|
+
'lockScreen.pin': def<string>('lockScreen', 'string', ''),
|
|
269
|
+
'lockScreen.inactivityLockEnabled': def<boolean>(
|
|
270
|
+
'lockScreen',
|
|
271
|
+
'boolean',
|
|
272
|
+
false,
|
|
273
|
+
),
|
|
274
|
+
'lockScreen.inactivityTimeoutMinutes': def<InactivityTimeoutMinutes>(
|
|
275
|
+
'lockScreen',
|
|
276
|
+
'number',
|
|
277
|
+
5,
|
|
278
|
+
),
|
|
279
|
+
'lockScreen.clockDisplay': def<LockScreenClockDisplay>(
|
|
280
|
+
'lockScreen',
|
|
281
|
+
'string',
|
|
282
|
+
'digital',
|
|
283
|
+
),
|
|
284
|
+
'lockScreen.showDate': def<boolean>('lockScreen', 'boolean', true),
|
|
285
|
+
'lockScreen.showHourNumbers': def<boolean>('lockScreen', 'boolean', true),
|
|
286
|
+
'lockScreen.imageMode': def<LockScreenImageMode>(
|
|
287
|
+
'lockScreen',
|
|
288
|
+
'string',
|
|
289
|
+
'none',
|
|
290
|
+
),
|
|
291
|
+
'lockScreen.backgroundImage': def<string | null>(
|
|
292
|
+
'lockScreen',
|
|
293
|
+
'json',
|
|
294
|
+
null,
|
|
295
|
+
),
|
|
296
|
+
'lockScreen.photoFrameImages': def<LockScreenImage[]>(
|
|
297
|
+
'lockScreen',
|
|
298
|
+
'json',
|
|
299
|
+
[],
|
|
300
|
+
),
|
|
301
|
+
'lockScreen.photoFrameIntervalSeconds': def<PhotoFrameIntervalSeconds>(
|
|
302
|
+
'lockScreen',
|
|
303
|
+
'number',
|
|
304
|
+
60,
|
|
305
|
+
),
|
|
306
|
+
|
|
307
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
308
|
+
// Touch / gestures
|
|
309
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
310
|
+
'touch.enableTapToCreate': def<boolean>('touch', 'boolean', false),
|
|
311
|
+
'touch.enableDragDrop': def<boolean>('touch', 'boolean', false),
|
|
312
|
+
|
|
313
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
314
|
+
// Device (not synced unless noted)
|
|
315
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
316
|
+
'device.id': def<string>('device', 'string', '', false),
|
|
317
|
+
'device.timePickerMode': def<TimePickerMode>('device', 'string', 'dials'),
|
|
318
|
+
'device.devMenuEnabled': def<boolean>('device', 'boolean', false, false),
|
|
319
|
+
'device.authWarningDismissTtlDays': def<number>(
|
|
320
|
+
'device',
|
|
321
|
+
'number',
|
|
322
|
+
3,
|
|
323
|
+
false,
|
|
324
|
+
),
|
|
325
|
+
|
|
326
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
327
|
+
// Language
|
|
328
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
329
|
+
'language.locale': def<LocaleCode>(
|
|
330
|
+
'language',
|
|
331
|
+
'string',
|
|
332
|
+
config.defaultLocale,
|
|
333
|
+
),
|
|
334
|
+
|
|
335
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
336
|
+
// Notifications
|
|
337
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
338
|
+
'notification.enabled': def<boolean>('notification', 'boolean', false),
|
|
339
|
+
'notification.notifyAllCalendars': def<boolean>(
|
|
340
|
+
'notification',
|
|
341
|
+
'boolean',
|
|
342
|
+
true,
|
|
343
|
+
),
|
|
344
|
+
'notification.enabledCalendarIds': def<string[]>(
|
|
345
|
+
'notification',
|
|
346
|
+
'json',
|
|
347
|
+
[],
|
|
348
|
+
),
|
|
349
|
+
'notification.hasBeenPrompted': def<boolean>(
|
|
350
|
+
'notification',
|
|
351
|
+
'boolean',
|
|
352
|
+
false,
|
|
353
|
+
false,
|
|
354
|
+
),
|
|
355
|
+
|
|
356
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
357
|
+
// Chronological features (header, footer, menu, quick settings, timer)
|
|
358
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
359
|
+
// Header
|
|
360
|
+
'chronological.header.showNavigationArrows': def<boolean>(
|
|
361
|
+
'chronological',
|
|
362
|
+
'boolean',
|
|
363
|
+
true,
|
|
364
|
+
),
|
|
365
|
+
'chronological.header.showClock': def<boolean>(
|
|
366
|
+
'chronological',
|
|
367
|
+
'boolean',
|
|
368
|
+
true,
|
|
369
|
+
),
|
|
370
|
+
'chronological.header.showCurrentYearInDate': def<boolean>(
|
|
371
|
+
'chronological',
|
|
372
|
+
'boolean',
|
|
373
|
+
false,
|
|
374
|
+
),
|
|
375
|
+
'chronological.header.showTimeOfDay': def<boolean>(
|
|
376
|
+
'chronological',
|
|
377
|
+
'boolean',
|
|
378
|
+
false,
|
|
379
|
+
),
|
|
380
|
+
// Footer
|
|
381
|
+
'chronological.footer.showMenuButton': def<boolean>(
|
|
382
|
+
'chronological',
|
|
383
|
+
'boolean',
|
|
384
|
+
true,
|
|
385
|
+
),
|
|
386
|
+
'chronological.footer.showViewSwitcherDay': def<boolean>(
|
|
387
|
+
'chronological',
|
|
388
|
+
'boolean',
|
|
389
|
+
true,
|
|
390
|
+
),
|
|
391
|
+
'chronological.footer.showViewSwitcherWeek': def<boolean>(
|
|
392
|
+
'chronological',
|
|
393
|
+
'boolean',
|
|
394
|
+
true,
|
|
395
|
+
),
|
|
396
|
+
'chronological.footer.showViewSwitcherMonth': def<boolean>(
|
|
397
|
+
'chronological',
|
|
398
|
+
'boolean',
|
|
399
|
+
true,
|
|
400
|
+
),
|
|
401
|
+
'chronological.footer.showTimerButton': def<boolean>(
|
|
402
|
+
'chronological',
|
|
403
|
+
'boolean',
|
|
404
|
+
true,
|
|
405
|
+
),
|
|
406
|
+
'chronological.footer.showNewEventButton': def<boolean>(
|
|
407
|
+
'chronological',
|
|
408
|
+
'boolean',
|
|
409
|
+
true,
|
|
410
|
+
),
|
|
411
|
+
'chronological.footer.showSettingsButton': def<boolean>(
|
|
412
|
+
'chronological',
|
|
413
|
+
'boolean',
|
|
414
|
+
true,
|
|
415
|
+
),
|
|
416
|
+
// Timer features
|
|
417
|
+
'chronological.timer.showNewCountdown': def<boolean>(
|
|
418
|
+
'chronological',
|
|
419
|
+
'boolean',
|
|
420
|
+
true,
|
|
421
|
+
),
|
|
422
|
+
'chronological.timer.showFromTemplate': def<boolean>(
|
|
423
|
+
'chronological',
|
|
424
|
+
'boolean',
|
|
425
|
+
true,
|
|
426
|
+
),
|
|
427
|
+
'chronological.timer.showEditTemplate': def<boolean>(
|
|
428
|
+
'chronological',
|
|
429
|
+
'boolean',
|
|
430
|
+
true,
|
|
431
|
+
),
|
|
432
|
+
'chronological.timer.showDeleteTemplate': def<boolean>(
|
|
433
|
+
'chronological',
|
|
434
|
+
'boolean',
|
|
435
|
+
true,
|
|
436
|
+
),
|
|
437
|
+
'chronological.timer.showAddTemplate': def<boolean>(
|
|
438
|
+
'chronological',
|
|
439
|
+
'boolean',
|
|
440
|
+
true,
|
|
441
|
+
),
|
|
442
|
+
// Menu
|
|
443
|
+
'chronological.menu.showSettingsButton': def<boolean>(
|
|
444
|
+
'chronological',
|
|
445
|
+
'boolean',
|
|
446
|
+
true,
|
|
447
|
+
),
|
|
448
|
+
// Quick settings
|
|
449
|
+
'chronological.quickSettings.showTimerVolume': def<boolean>(
|
|
450
|
+
'chronological',
|
|
451
|
+
'boolean',
|
|
452
|
+
true,
|
|
453
|
+
),
|
|
454
|
+
'chronological.quickSettings.showReminderVolume': def<boolean>(
|
|
455
|
+
'chronological',
|
|
456
|
+
'boolean',
|
|
457
|
+
true,
|
|
458
|
+
),
|
|
459
|
+
'chronological.quickSettings.showMediaVolume': def<boolean>(
|
|
460
|
+
'chronological',
|
|
461
|
+
'boolean',
|
|
462
|
+
true,
|
|
463
|
+
),
|
|
464
|
+
'chronological.quickSettings.showBrightness': def<boolean>(
|
|
465
|
+
'chronological',
|
|
466
|
+
'boolean',
|
|
467
|
+
true,
|
|
468
|
+
),
|
|
469
|
+
'chronological.quickSettings.showLockScreen': def<boolean>(
|
|
470
|
+
'chronological',
|
|
471
|
+
'boolean',
|
|
472
|
+
true,
|
|
473
|
+
),
|
|
474
|
+
// Time-of-day periods
|
|
475
|
+
'chronological.timeOfDay.morningStart': def<number>(
|
|
476
|
+
'chronological',
|
|
477
|
+
'number',
|
|
478
|
+
6,
|
|
479
|
+
),
|
|
480
|
+
'chronological.timeOfDay.forenoonStart': def<number>(
|
|
481
|
+
'chronological',
|
|
482
|
+
'number',
|
|
483
|
+
9,
|
|
484
|
+
),
|
|
485
|
+
'chronological.timeOfDay.afternoonStart': def<number>(
|
|
486
|
+
'chronological',
|
|
487
|
+
'number',
|
|
488
|
+
12,
|
|
489
|
+
),
|
|
490
|
+
'chronological.timeOfDay.eveningStart': def<number>(
|
|
491
|
+
'chronological',
|
|
492
|
+
'number',
|
|
493
|
+
18,
|
|
494
|
+
),
|
|
495
|
+
'chronological.timeOfDay.nightStart': def<number>(
|
|
496
|
+
'chronological',
|
|
497
|
+
'number',
|
|
498
|
+
0,
|
|
499
|
+
),
|
|
500
|
+
|
|
501
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
502
|
+
// Chronological event form
|
|
503
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
504
|
+
// Toggleable fields
|
|
505
|
+
'chronologicalEventForm.field.description': def<boolean>(
|
|
506
|
+
'chronologicalEventForm',
|
|
507
|
+
'boolean',
|
|
508
|
+
true,
|
|
509
|
+
),
|
|
510
|
+
'chronologicalEventForm.field.recurrence': def<boolean>(
|
|
511
|
+
'chronologicalEventForm',
|
|
512
|
+
'boolean',
|
|
513
|
+
true,
|
|
514
|
+
),
|
|
515
|
+
'chronologicalEventForm.field.acknowledge': def<boolean>(
|
|
516
|
+
'chronologicalEventForm',
|
|
517
|
+
'boolean',
|
|
518
|
+
true,
|
|
519
|
+
),
|
|
520
|
+
'chronologicalEventForm.field.checklist': def<boolean>(
|
|
521
|
+
'chronologicalEventForm',
|
|
522
|
+
'boolean',
|
|
523
|
+
true,
|
|
524
|
+
),
|
|
525
|
+
'chronologicalEventForm.field.extraImages': def<boolean>(
|
|
526
|
+
'chronologicalEventForm',
|
|
527
|
+
'boolean',
|
|
528
|
+
true,
|
|
529
|
+
),
|
|
530
|
+
'chronologicalEventForm.field.reminders': def<boolean>(
|
|
531
|
+
'chronologicalEventForm',
|
|
532
|
+
'boolean',
|
|
533
|
+
true,
|
|
534
|
+
),
|
|
535
|
+
'chronologicalEventForm.field.audioClips': def<boolean>(
|
|
536
|
+
'chronologicalEventForm',
|
|
537
|
+
'boolean',
|
|
538
|
+
true,
|
|
539
|
+
),
|
|
540
|
+
// Fixed fields
|
|
541
|
+
'chronologicalEventForm.fixedField.allDay': def<boolean>(
|
|
542
|
+
'chronologicalEventForm',
|
|
543
|
+
'boolean',
|
|
544
|
+
true,
|
|
545
|
+
),
|
|
546
|
+
'chronologicalEventForm.fixedField.endTime': def<boolean>(
|
|
547
|
+
'chronologicalEventForm',
|
|
548
|
+
'boolean',
|
|
549
|
+
true,
|
|
550
|
+
),
|
|
551
|
+
'chronologicalEventForm.fixedField.visibility': def<boolean>(
|
|
552
|
+
'chronologicalEventForm',
|
|
553
|
+
'boolean',
|
|
554
|
+
true,
|
|
555
|
+
),
|
|
556
|
+
// Suggest end time
|
|
557
|
+
'chronologicalEventForm.suggestEndTime.enabled': def<boolean>(
|
|
558
|
+
'chronologicalEventForm',
|
|
559
|
+
'boolean',
|
|
560
|
+
false,
|
|
561
|
+
),
|
|
562
|
+
'chronologicalEventForm.suggestEndTime.value': def<number>(
|
|
563
|
+
'chronologicalEventForm',
|
|
564
|
+
'number',
|
|
565
|
+
30,
|
|
566
|
+
),
|
|
567
|
+
'chronologicalEventForm.suggestEndTime.unit': def<SuggestEndTimeUnit>(
|
|
568
|
+
'chronologicalEventForm',
|
|
569
|
+
'string',
|
|
570
|
+
'minutes',
|
|
571
|
+
),
|
|
572
|
+
// Default visibility
|
|
573
|
+
'chronologicalEventForm.defaultVisibility': def<EventVisibility>(
|
|
574
|
+
'chronologicalEventForm',
|
|
575
|
+
'string',
|
|
576
|
+
'Private',
|
|
577
|
+
),
|
|
578
|
+
// Reminder presets
|
|
579
|
+
'chronologicalEventForm.reminderPresets.timed': def<number[]>(
|
|
580
|
+
'chronologicalEventForm',
|
|
581
|
+
'json',
|
|
582
|
+
[5, 15, 30, 60, 120, 1440],
|
|
583
|
+
),
|
|
584
|
+
'chronologicalEventForm.reminderPresets.allDay': def<AllDayPreset[]>(
|
|
585
|
+
'chronologicalEventForm',
|
|
586
|
+
'json',
|
|
587
|
+
[
|
|
588
|
+
{ daysBefore: 0, time: '09:00' },
|
|
589
|
+
{ daysBefore: 1, time: '18:00' },
|
|
590
|
+
{ daysBefore: 2, time: '09:00' },
|
|
591
|
+
],
|
|
592
|
+
),
|
|
593
|
+
} as const satisfies Record<string, SettingDef>;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// ---------------------------------------------------------------------------
|
|
597
|
+
// Derived types (based on entry shape — all configs produce the same keys)
|
|
598
|
+
// ---------------------------------------------------------------------------
|
|
599
|
+
|
|
600
|
+
type SettingsEntries = ReturnType<typeof buildEntries>;
|
|
601
|
+
|
|
602
|
+
/** Union type of every valid setting key */
|
|
603
|
+
export type SettingKey = keyof SettingsEntries;
|
|
604
|
+
|
|
605
|
+
/** Type-safe value type for a given setting key */
|
|
606
|
+
export type SettingValue<K extends SettingKey> = SettingsEntries[K]['default'];
|
|
607
|
+
|
|
608
|
+
/** Complete settings map — all keys resolved to their value types */
|
|
609
|
+
export type SettingsMap = {
|
|
610
|
+
[K in SettingKey]: SettingValue<K>;
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
// ---------------------------------------------------------------------------
|
|
614
|
+
// Registry class
|
|
615
|
+
// ---------------------------------------------------------------------------
|
|
616
|
+
|
|
617
|
+
export class SettingsRegistry {
|
|
618
|
+
/** The raw setting definitions (category, type, default, sync) */
|
|
619
|
+
readonly entries: SettingsEntries;
|
|
620
|
+
|
|
621
|
+
/** All setting keys as an array */
|
|
622
|
+
readonly keys: SettingKey[];
|
|
623
|
+
|
|
624
|
+
constructor(config: Partial<RegistryConfig> = {}) {
|
|
625
|
+
const full: RegistryConfig = { ...DEFAULT_REGISTRY_CONFIG, ...config };
|
|
626
|
+
this.entries = buildEntries(full);
|
|
627
|
+
this.keys = Object.keys(this.entries) as SettingKey[];
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/** Get the default value for a setting */
|
|
631
|
+
getDefault<K extends SettingKey>(key: K): SettingValue<K> {
|
|
632
|
+
return this.entries[key].default as SettingValue<K>;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/** Get a complete map of all default values */
|
|
636
|
+
getAllDefaults(): SettingsMap {
|
|
637
|
+
const defaults = {} as Record<string, unknown>;
|
|
638
|
+
for (const key of this.keys) {
|
|
639
|
+
defaults[key] = this.entries[key].default;
|
|
640
|
+
}
|
|
641
|
+
return defaults as SettingsMap;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/** Get the data type for a setting key */
|
|
645
|
+
getDataType(key: SettingKey): SettingsDataType {
|
|
646
|
+
return this.entries[key].type;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/** Check whether a setting should be synced */
|
|
650
|
+
isSynced(key: SettingKey): boolean {
|
|
651
|
+
return this.entries[key].sync;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/** Get all setting keys for a category */
|
|
655
|
+
getByCategory(category: SettingsCategory): SettingKey[] {
|
|
656
|
+
return this.keys.filter((key) => this.entries[key].category === category);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/** Serialize a setting value to a string for DB storage */
|
|
660
|
+
serialize(key: SettingKey, value: unknown): string {
|
|
661
|
+
const dataType = this.getDataType(key);
|
|
662
|
+
switch (dataType) {
|
|
663
|
+
case 'string':
|
|
664
|
+
return String(value ?? '');
|
|
665
|
+
case 'number':
|
|
666
|
+
return String(value ?? 0);
|
|
667
|
+
case 'boolean':
|
|
668
|
+
return value ? 'true' : 'false';
|
|
669
|
+
case 'json':
|
|
670
|
+
return JSON.stringify(value);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/** Deserialize a DB string back to a typed setting value */
|
|
675
|
+
deserialize<K extends SettingKey>(
|
|
676
|
+
key: K,
|
|
677
|
+
raw: string | null,
|
|
678
|
+
): SettingValue<K> {
|
|
679
|
+
if (raw === null || raw === undefined) {
|
|
680
|
+
return this.getDefault(key);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const dataType = this.getDataType(key);
|
|
684
|
+
switch (dataType) {
|
|
685
|
+
case 'string':
|
|
686
|
+
return raw as SettingValue<K>;
|
|
687
|
+
case 'number':
|
|
688
|
+
return Number(raw) as SettingValue<K>;
|
|
689
|
+
case 'boolean':
|
|
690
|
+
return (raw === 'true') as SettingValue<K>;
|
|
691
|
+
case 'json':
|
|
692
|
+
try {
|
|
693
|
+
return JSON.parse(raw) as SettingValue<K>;
|
|
694
|
+
} catch {
|
|
695
|
+
return this.getDefault(key);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// ---------------------------------------------------------------------------
|
|
702
|
+
// Default registry instance (non-enrolled, system theme, nb locale)
|
|
703
|
+
// ---------------------------------------------------------------------------
|
|
704
|
+
|
|
705
|
+
export const defaultRegistry = new SettingsRegistry();
|
|
706
|
+
|
|
707
|
+
// ---------------------------------------------------------------------------
|
|
708
|
+
// Factory function — returns raw entry map for local type derivation.
|
|
709
|
+
// Consumers that need precise generic inference (e.g. useQuery<K>) can use
|
|
710
|
+
// this to anchor types on a local const variable.
|
|
711
|
+
// ---------------------------------------------------------------------------
|
|
712
|
+
|
|
713
|
+
export function createSettingsRegistry(config: Partial<RegistryConfig> = {}) {
|
|
714
|
+
return buildEntries({ ...DEFAULT_REGISTRY_CONFIG, ...config });
|
|
715
|
+
}
|