@focus8/settings-registry 0.8.3 → 0.8.5

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.js CHANGED
@@ -12,8 +12,34 @@
12
12
  * App-specific defaults (theme, locale, enrolled mode) are parameterized
13
13
  * via `createRegistry(config)`.
14
14
  */
15
- export const ALARM_TIMEOUTS = [1, 2, 3, 5, 10];
15
+ export const ALARM_TIMEOUTS = [5, 30, 60, 120, 180, 300, 600];
16
16
  export const LOCK_SCREEN_MAX_IMAGES = 10;
17
+ /**
18
+ * Evaluate a visibility/disabled rule against current setting values.
19
+ * Returns `true` if the setting should be visible (or disabled, depending on usage).
20
+ * `undefined` rule = always true (no restriction).
21
+ */
22
+ export function evaluateVisibility(rule, getValue) {
23
+ if (!rule)
24
+ return true;
25
+ const conditions = Array.isArray(rule) ? rule : [rule];
26
+ return conditions.every((c) => {
27
+ const value = getValue(c.dependsOn);
28
+ if ('equals' in c)
29
+ return value === c.equals;
30
+ if ('notEquals' in c)
31
+ return value !== c.notEquals;
32
+ if ('isTruthy' in c)
33
+ return !!value;
34
+ return true;
35
+ });
36
+ }
37
+ /** Convenience: returns true when the setting should be disabled. */
38
+ export function evaluateDisabled(rule, getValue) {
39
+ if (!rule)
40
+ return false;
41
+ return evaluateVisibility(rule, getValue);
42
+ }
17
43
  // ---------------------------------------------------------------------------
18
44
  // Setting categories — used for UI grouping and profile organization
19
45
  // ---------------------------------------------------------------------------
@@ -96,53 +122,121 @@ export function getCategoriesForCalendarType(calendarType) {
96
122
  });
97
123
  }
98
124
  // ---------------------------------------------------------------------------
99
- // Settings groupsuser-facing groups that reorganize raw categories
125
+ // Settings sectionstop-level groupings (e.g. "Kalender", "Enhet")
100
126
  // ---------------------------------------------------------------------------
101
- /**
102
- * Settings group IDs matching the mobile app's settings menu structure.
103
- * These provide user-friendly groupings that may pull settings from multiple
104
- * raw registry categories.
105
- */
106
- export const SETTINGS_GROUP_IDS = [
127
+ export const SETTINGS_SECTION_IDS = ['calendar', 'unit'];
128
+ export const SETTINGS_SECTIONS = [
129
+ { id: 'calendar', labelKey: 'Calendar.LabelCalendar' },
130
+ { id: 'unit', labelKey: 'Settings.Unit' },
131
+ ];
132
+ // ---------------------------------------------------------------------------
133
+ // Settings menu items — ordered list of all items in the settings menu
134
+ // ---------------------------------------------------------------------------
135
+ export const SETTINGS_MENU_ITEM_IDS = [
107
136
  // Calendar section
108
137
  'calendarView',
138
+ 'calendars',
139
+ 'eventCategories',
140
+ 'notification',
109
141
  'timer',
110
- 'activities',
111
- 'dateWeather',
112
- 'touch',
142
+ 'guests',
143
+ 'eventForm',
144
+ 'chronologicalEventForm',
145
+ 'weather',
146
+ 'gallery',
147
+ 'audioClips',
148
+ 'gestures',
113
149
  'calendarType',
114
- 'chronological',
150
+ 'sharing',
115
151
  // Unit section
116
- 'appearance',
117
- 'soundAlerts',
152
+ 'screen',
153
+ 'volume',
118
154
  'lockScreen',
119
155
  'language',
120
- 'device',
156
+ 'wifi',
157
+ 'devices',
158
+ 'profiles',
159
+ 'tts',
160
+ 'updates',
161
+ 'about',
162
+ 'help',
163
+ 'dev',
164
+ // Account (rendered separately)
165
+ 'account',
166
+ 'activityLog',
121
167
  ];
122
168
  /**
123
- * Group definitions matching the mobile app's settings structure.
124
- *
125
- * i18n keys use the format `Settings.GroupLabel.<Id>` and must exist
126
- * in every consumer's i18n catalog.
169
+ * Ordered list of all settings menu items. The array order defines the
170
+ * display order within each section. Items without registry setting keys
171
+ * are navigation-only (e.g. gallery, guests).
127
172
  */
128
- export const SETTINGS_GROUPS = [
129
- // ── Calendar section ──────────────────────────────────────────────────
173
+ export const SETTINGS_MENU_ITEMS = [
174
+ // -- Calendar section --
130
175
  {
131
176
  id: 'calendarView',
132
- labelKey: 'Settings.CalendarView',
177
+ labelKey: 'Settings.Calendar',
133
178
  icon: 'Eye',
179
+ section: 'calendar',
180
+ screens: [
181
+ 'calendarDayView',
182
+ 'calendarWeekView',
183
+ 'chronologicalHeader',
184
+ 'chronologicalFooter',
185
+ 'chronologicalMenu',
186
+ 'dayColors',
187
+ 'dateTime',
188
+ ],
134
189
  keys: [
135
190
  'calendarView.showCalendarNames',
136
191
  'calendarView.splitView',
137
192
  'calendarView.dayViewZoom',
138
193
  'calendarView.weekViewZoom',
139
194
  'calendarView.calendarColumns',
195
+ 'appearance.enableDayColors',
196
+ 'chronological.dayView.displayMode',
197
+ 'chronological.header.',
198
+ 'chronological.footer.',
199
+ 'chronological.menu.',
200
+ 'chronological.quickSettings.',
201
+ 'chronological.timeOfDay.',
202
+ ],
203
+ },
204
+ {
205
+ id: 'calendars',
206
+ labelKey: 'Common.Calendars',
207
+ icon: 'Calendar',
208
+ section: 'calendar',
209
+ screens: ['calendars'],
210
+ },
211
+ {
212
+ id: 'eventCategories',
213
+ labelKey: 'EventCategory.SettingsTitle',
214
+ icon: 'Tag',
215
+ section: 'calendar',
216
+ screens: ['eventCategories'],
217
+ calendarType: 'chronological',
218
+ },
219
+ {
220
+ id: 'notification',
221
+ labelKey: 'Settings.Notification',
222
+ icon: 'Bell',
223
+ section: 'calendar',
224
+ screens: ['notification'],
225
+ keys: [
226
+ 'sound.reminderAlarmSound',
227
+ 'sound.startAlarmSound',
228
+ 'sound.endAlarmSound',
229
+ 'sound.reminderAlarmTimeout',
230
+ 'sound.reminderVolume',
140
231
  ],
141
232
  },
142
233
  {
143
234
  id: 'timer',
144
235
  labelKey: 'Settings.TimerTitle',
145
236
  icon: 'Timer',
237
+ section: 'calendar',
238
+ screens: ['timerFeatures', 'timer'],
239
+ appMode: 'ENROLLED',
146
240
  keys: [
147
241
  'chronological.timer.',
148
242
  'timer.',
@@ -150,18 +244,39 @@ export const SETTINGS_GROUPS = [
150
244
  'sound.timerAlarmTimeout',
151
245
  'sound.timerVolume',
152
246
  ],
153
- appMode: 'ENROLLED',
154
247
  },
155
248
  {
156
- id: 'activities',
157
- labelKey: 'Settings.Activities',
158
- icon: 'CalendarPlus',
159
- keys: ['eventForm.', 'chronologicalEventForm.'],
249
+ id: 'guests',
250
+ labelKey: 'Settings.GuestUsers',
251
+ icon: 'Users',
252
+ section: 'calendar',
253
+ screens: ['guests'],
254
+ },
255
+ {
256
+ id: 'eventForm',
257
+ labelKey: 'SettingsEventForm.Title',
258
+ icon: 'ClipboardList',
259
+ section: 'calendar',
260
+ screens: ['eventForm'],
261
+ calendarType: 'time-based',
262
+ keys: ['eventForm.'],
263
+ },
264
+ {
265
+ id: 'chronologicalEventForm',
266
+ labelKey: 'SettingsChronologicalEventForm.Title',
267
+ icon: 'Plus',
268
+ section: 'calendar',
269
+ screens: ['chronologicalEventForm'],
270
+ calendarType: 'chronological',
271
+ keys: ['chronologicalEventForm.'],
160
272
  },
161
273
  {
162
- id: 'dateWeather',
163
- labelKey: 'Settings.DateWeather',
274
+ id: 'weather',
275
+ labelKey: 'Settings.WeatherTitle',
164
276
  icon: 'CloudSun',
277
+ section: 'calendar',
278
+ screens: ['weather'],
279
+ calendarType: 'time-based',
165
280
  keys: [
166
281
  'calendarView.showWeatherOnTimeline',
167
282
  'calendarView.weatherLocation',
@@ -169,76 +284,167 @@ export const SETTINGS_GROUPS = [
169
284
  ],
170
285
  },
171
286
  {
172
- id: 'touch',
287
+ id: 'gallery',
288
+ labelKey: 'Gallery.Title',
289
+ icon: 'Images',
290
+ section: 'calendar',
291
+ screens: ['gallery'],
292
+ noScrollView: true,
293
+ },
294
+ {
295
+ id: 'audioClips',
296
+ labelKey: 'AudioClips.Title',
297
+ icon: 'AudioLines',
298
+ section: 'calendar',
299
+ screens: ['audioClips'],
300
+ noScrollView: true,
301
+ },
302
+ {
303
+ id: 'gestures',
173
304
  labelKey: 'Settings.FunctionsTitle',
174
305
  icon: 'Hand',
306
+ section: 'calendar',
307
+ screens: ['gestures'],
175
308
  keys: ['touch.'],
176
309
  },
177
310
  {
178
311
  id: 'calendarType',
179
- labelKey: 'Settings.CalendarType',
180
- icon: 'ArrowLeftRight',
312
+ labelKey: 'Settings.Mode',
313
+ icon: 'SlidersHorizontal',
314
+ section: 'calendar',
315
+ screens: ['calendarType'],
181
316
  keys: ['calendarView.type'],
182
317
  },
183
318
  {
184
- id: 'chronological',
185
- labelKey: 'Settings.Chronological',
186
- icon: 'List',
187
- keys: ['chronological.'],
188
- calendarType: 'chronological',
319
+ id: 'sharing',
320
+ labelKey: 'Settings.Sharing',
321
+ icon: 'Share2',
322
+ section: 'calendar',
323
+ screens: ['icalSubscriptions'],
189
324
  },
190
- // ── Unit section ──────────────────────────────────────────────────────
325
+ // -- Unit section --
191
326
  {
192
- id: 'appearance',
193
- labelKey: 'Settings.Appearance',
194
- icon: 'Palette',
195
- keys: [
196
- 'appearance.theme',
197
- 'appearance.clockType',
198
- 'appearance.enableDayColors',
199
- ],
327
+ id: 'screen',
328
+ labelKey: 'Settings.Screen',
329
+ icon: 'LaptopMinimal',
330
+ section: 'unit',
331
+ screens: ['displayDensity', 'darkMode'],
332
+ keys: ['appearance.theme', 'appearance.clockType'],
200
333
  },
201
334
  {
202
- id: 'soundAlerts',
203
- labelKey: 'Settings.SoundAlerts',
335
+ id: 'volume',
336
+ labelKey: 'Common.Volume',
204
337
  icon: 'Volume2',
338
+ section: 'unit',
339
+ screens: ['volume'],
340
+ appMode: 'ENROLLED',
205
341
  keys: [
206
342
  'sound.reminderVolume',
343
+ 'sound.timerVolume',
207
344
  'sound.mediaVolume',
208
- 'sound.reminderAlarmSound',
209
- 'sound.startAlarmSound',
210
- 'sound.endAlarmSound',
211
- 'sound.reminderAlarmTimeout',
212
- 'sound.allowCustomReminderSounds',
213
- 'notification.',
214
- 'sound.ttsEnabled',
215
- 'sound.ttsRate',
216
- 'sound.alarmSound',
217
345
  ],
218
346
  },
219
347
  {
220
348
  id: 'lockScreen',
221
349
  labelKey: 'Settings.LockScreen',
222
350
  icon: 'Lock',
223
- keys: ['lockScreen.'],
351
+ section: 'unit',
352
+ screens: ['inactivity', 'lockScreen'],
224
353
  appMode: 'ENROLLED',
354
+ keys: ['lockScreen.'],
225
355
  },
226
356
  {
227
357
  id: 'language',
228
358
  labelKey: 'Settings.Language',
229
359
  icon: 'Globe',
360
+ section: 'unit',
361
+ screens: ['language'],
230
362
  keys: ['language.'],
231
363
  },
232
364
  {
233
- id: 'device',
365
+ id: 'wifi',
366
+ labelKey: 'Settings.WifiTitle',
367
+ icon: 'Wifi',
368
+ section: 'unit',
369
+ screens: ['wifi'],
370
+ appMode: 'ENROLLED',
371
+ },
372
+ {
373
+ id: 'devices',
234
374
  labelKey: 'SettingsDevices.Title',
235
375
  icon: 'Smartphone',
376
+ section: 'unit',
377
+ screens: ['devices'],
236
378
  keys: [
237
379
  'device.',
238
380
  'calendarView.autoReturnToTodayEnabled',
239
381
  'calendarView.autoReturnToTodayTimeoutSeconds',
240
382
  ],
241
383
  },
384
+ {
385
+ id: 'tts',
386
+ labelKey: 'Settings.TtsTitle',
387
+ icon: 'Speech',
388
+ section: 'unit',
389
+ screens: ['tts'],
390
+ appMode: 'ENROLLED',
391
+ keys: [
392
+ 'sound.ttsEnabled',
393
+ 'sound.ttsRate',
394
+ ],
395
+ },
396
+ {
397
+ id: 'updates',
398
+ labelKey: 'Settings.UpdatesTitle',
399
+ icon: 'Download',
400
+ section: 'unit',
401
+ screens: ['updates'],
402
+ },
403
+ {
404
+ id: 'about',
405
+ labelKey: 'Settings.About',
406
+ icon: 'Info',
407
+ section: 'unit',
408
+ screens: ['restoreDefaults', 'recycleAppData', 'license', 'version'],
409
+ },
410
+ {
411
+ id: 'help',
412
+ labelKey: 'Settings.HelpTitle',
413
+ icon: 'CircleQuestionMark',
414
+ section: 'unit',
415
+ screens: ['help'],
416
+ },
417
+ {
418
+ id: 'dev',
419
+ labelKey: 'Settings.Dev',
420
+ icon: 'FolderCode',
421
+ section: 'unit',
422
+ screens: [
423
+ 'devDeviceInfo',
424
+ 'devComponents',
425
+ 'devStores',
426
+ 'devActions',
427
+ 'devAuth',
428
+ 'devNavigation',
429
+ 'devNetwork',
430
+ 'devAndroidSettings',
431
+ ],
432
+ },
433
+ // -- Account (rendered separately) --
434
+ {
435
+ id: 'account',
436
+ labelKey: 'Settings.Profile',
437
+ icon: 'User',
438
+ section: 'account',
439
+ screens: ['account', 'mfa'],
440
+ },
441
+ {
442
+ id: 'activityLog',
443
+ labelKey: 'SettingsActivityLog.Title',
444
+ icon: 'ScrollText',
445
+ section: 'account',
446
+ screens: ['activityLog'],
447
+ },
242
448
  ];
243
449
  /**
244
450
  * Settings excluded from remote editing (web portal / profile management).
@@ -271,6 +477,10 @@ export const SETTINGS_LABELS = {
271
477
  labelKey: 'Settings.EnableDayColors',
272
478
  descriptionKey: 'Settings.EnableDayColorsDescription',
273
479
  },
480
+ // ── Chronological day view display mode ──────────────────────────────────
481
+ 'chronological.dayView.displayMode': {
482
+ labelKey: 'Settings.DayViewDisplayMode',
483
+ },
274
484
  // ── Calendar type ───────────────────────────────────────────────────────
275
485
  'calendarView.type': {
276
486
  labelKey: 'Settings.CalendarType',
@@ -341,6 +551,7 @@ export const SETTINGS_LABELS = {
341
551
  },
342
552
  'sound.ttsRate': { labelKey: 'Settings.TtsTitle' },
343
553
  // ── Timer ───────────────────────────────────────────────────────────────
554
+ 'timer.face': { labelKey: 'Settings.TimerFace' },
344
555
  'timer.showTimeRemaining': { labelKey: 'Settings.TimerShowTimeRemaining' },
345
556
  'timer.showEndTime': { labelKey: 'Settings.TimerShowEndTime' },
346
557
  'timer.showRestartButton': { labelKey: 'Settings.TimerShowRestartButton' },
@@ -421,6 +632,9 @@ export const SETTINGS_LABELS = {
421
632
  'chronological.footer.showNewEventButton': {
422
633
  labelKey: 'ChronologicalFeatures.NewEventButton',
423
634
  },
635
+ 'chronological.footer.showNowButton': {
636
+ labelKey: 'ChronologicalFeatures.NowButton',
637
+ },
424
638
  'chronological.footer.showSettingsButton': {
425
639
  labelKey: 'ChronologicalFeatures.SettingsButton',
426
640
  },
@@ -469,15 +683,15 @@ export const SETTINGS_LABELS = {
469
683
  'eventForm.checklist': { labelKey: 'EventChecklist.DefaultName' },
470
684
  'eventForm.images': { labelKey: 'EventImageGallery.SectionTitle' },
471
685
  'eventForm.audioClips': { labelKey: 'EventAudioGallery.SectionTitle' },
472
- 'eventForm.notificationReceivers': { labelKey: 'EventForm.NotificationReceivers' },
686
+ 'eventForm.notificationReceivers': {
687
+ labelKey: 'EventForm.NotificationReceivers',
688
+ },
473
689
  'eventForm.visibility': { labelKey: 'EventVisibility.Title' },
690
+ // ── Chronological event form ────────────────────────────────────────────
691
+ 'chronologicalEventForm.field.category': {
692
+ labelKey: 'ChronologicalEventForm.CategoryField',
693
+ },
474
694
  };
475
- /**
476
- * Check whether a setting key matches a group's key matchers.
477
- */
478
- function matchesGroup(key, group) {
479
- return group.keys.some((matcher) => key === matcher || (matcher.endsWith('.') && key.startsWith(matcher)));
480
- }
481
695
  /** Return the index of the first matching key pattern in the group, for sorting. */
482
696
  function groupKeyIndex(key, keys) {
483
697
  for (let i = 0; i < keys.length; i++) {
@@ -504,68 +718,88 @@ function registryKeyOrder() {
504
718
  return _registryKeyOrder;
505
719
  }
506
720
  /**
507
- * Group parsed setting entries into user-facing groups matching the mobile
508
- * app's settings menu structure.
509
- *
510
- * Takes a flat list of parsed settings (from `parseSettingsSnapshot`) and
511
- * buckets them into the standard groups, filtering by calendar type and
512
- * excluded keys.
721
+ * Get the ordered list of setting keys for a given menu item.
722
+ * Returns keys matching the app's render order, filtered by EXCLUDED_DEVICE_SETTINGS and HIDDEN.
513
723
  */
514
- export function groupSettingsForDevice(allSettings, calendarType, appMode) {
724
+ export function getKeysForMenuItem(menuItemId, reg = defaultRegistry) {
725
+ const menuItem = SETTINGS_MENU_ITEMS.find((m) => m.id === menuItemId);
726
+ if (!menuItem?.keys || menuItem.keys.length === 0)
727
+ return [];
728
+ const matched = reg.keys.filter((key) => {
729
+ if (EXCLUDED_DEVICE_SETTINGS.has(key))
730
+ return false;
731
+ const entry = reg.entries[key];
732
+ if (entry?.uiType === 'HIDDEN')
733
+ return false;
734
+ return menuItem.keys.some((k) => k.endsWith('.') ? key.startsWith(k) : key === k);
735
+ });
736
+ const order = registryKeyOrder();
737
+ matched.sort((a, b) => {
738
+ const aIdx = groupKeyIndex(a, menuItem.keys);
739
+ const bIdx = groupKeyIndex(b, menuItem.keys);
740
+ if (aIdx !== bIdx)
741
+ return aIdx - bIdx;
742
+ return (order.get(a) ?? Infinity) - (order.get(b) ?? Infinity);
743
+ });
744
+ return matched;
745
+ }
746
+ /**
747
+ * Group parsed setting entries using the app's menu structure (SETTINGS_MENU_ITEMS).
748
+ * Only includes menu items that have `keys` defined (editable settings).
749
+ * This ensures the web editor has the exact same menu items, order, sections,
750
+ * labels, and icons as the mobile app.
751
+ */
752
+ export function groupSettingsForWeb(allSettings, calendarType, appMode) {
515
753
  // Filter excluded and hidden settings
516
754
  const settings = allSettings.filter((s) => !EXCLUDED_DEVICE_SETTINGS.has(s.key) && s.uiType !== 'HIDDEN');
517
- // Calendar type filtering for event form keys
518
755
  const isChronological = calendarType === 'chronological';
519
756
  const filteredSettings = settings.filter((s) => {
520
- if (isChronological && s.key.startsWith('eventForm.')) {
757
+ if (isChronological && s.key.startsWith('eventForm.'))
521
758
  return false;
522
- }
523
- if (!isChronological && s.key.startsWith('chronologicalEventForm.')) {
759
+ if (!isChronological && s.key.startsWith('chronologicalEventForm.'))
524
760
  return false;
525
- }
526
- // Per-setting calendar type restriction
527
- if (s.calendarType && s.calendarType !== calendarType) {
761
+ if (s.calendarType && s.calendarType !== calendarType)
528
762
  return false;
529
- }
530
- // Per-setting app mode restriction
531
- if (s.appMode && s.appMode !== appMode) {
763
+ if (s.appMode && s.appMode !== appMode)
532
764
  return false;
533
- }
534
765
  return true;
535
766
  });
536
767
  const claimed = new Set();
537
768
  const result = [];
538
- for (const group of SETTINGS_GROUPS) {
539
- // Skip groups not relevant for current calendar type
540
- if (group.calendarType && group.calendarType !== calendarType) {
769
+ for (const menuItem of SETTINGS_MENU_ITEMS) {
770
+ // Skip items without keys (navigation-only like gallery, guests, etc.)
771
+ if (!menuItem.keys || menuItem.keys.length === 0)
541
772
  continue;
542
- }
543
- // Skip groups not relevant for current app mode
544
- if (group.appMode && group.appMode !== appMode) {
773
+ // Skip items not relevant for current calendar type
774
+ if (menuItem.calendarType && menuItem.calendarType !== calendarType)
545
775
  continue;
546
- }
547
- const matched = filteredSettings.filter((s) => !claimed.has(s.key) && matchesGroup(s.key, group));
548
- if (matched.length === 0) {
776
+ // Skip items not relevant for current app mode
777
+ if (menuItem.appMode && menuItem.appMode !== appMode)
549
778
  continue;
550
- }
551
- // Sort matched settings by the order defined in group.keys,
552
- // with registry declaration order as tiebreaker for prefix matches
779
+ const matched = filteredSettings.filter((s) => {
780
+ if (claimed.has(s.key))
781
+ return false;
782
+ return menuItem.keys.some((k) => k.endsWith('.') ? s.key.startsWith(k) : s.key === k);
783
+ });
784
+ if (matched.length === 0)
785
+ continue;
786
+ // Sort by key order within the menu item's keys array
553
787
  const order = registryKeyOrder();
554
788
  matched.sort((a, b) => {
555
- const aIdx = groupKeyIndex(a.key, group.keys);
556
- const bIdx = groupKeyIndex(b.key, group.keys);
557
- if (aIdx !== bIdx) {
789
+ const aIdx = groupKeyIndex(a.key, menuItem.keys);
790
+ const bIdx = groupKeyIndex(b.key, menuItem.keys);
791
+ if (aIdx !== bIdx)
558
792
  return aIdx - bIdx;
559
- }
560
793
  return (order.get(a.key) ?? Infinity) - (order.get(b.key) ?? Infinity);
561
794
  });
562
795
  for (const s of matched) {
563
796
  claimed.add(s.key);
564
797
  }
565
798
  result.push({
566
- id: group.id,
567
- labelKey: group.labelKey,
568
- icon: group.icon,
799
+ id: menuItem.id,
800
+ labelKey: menuItem.labelKey,
801
+ icon: menuItem.icon,
802
+ section: menuItem.section,
569
803
  settings: matched,
570
804
  });
571
805
  }
@@ -581,23 +815,25 @@ export const DEFAULT_REGISTRY_CONFIG = {
581
815
  // Shared option arrays (reused across multiple settings)
582
816
  // ---------------------------------------------------------------------------
583
817
  const ALARM_SOUND_OPTIONS = [
584
- { value: 'none', labelKey: 'Settings.Option.AlarmSound.None' },
585
- { value: 'alarm1', labelKey: 'Settings.Option.AlarmSound.Alarm1' },
586
- { value: 'alarm2', labelKey: 'Settings.Option.AlarmSound.Alarm2' },
587
- { value: 'alarm3', labelKey: 'Settings.Option.AlarmSound.Alarm3' },
588
- { value: 'alarm4', labelKey: 'Settings.Option.AlarmSound.Alarm4' },
589
- { value: 'alarm5', labelKey: 'Settings.Option.AlarmSound.Alarm5' },
590
- { value: 'alarm6', labelKey: 'Settings.Option.AlarmSound.Alarm6' },
591
- { value: 'alarm7', labelKey: 'Settings.Option.AlarmSound.Alarm7' },
592
- { value: 'alarm8', labelKey: 'Settings.Option.AlarmSound.Alarm8' },
593
- { value: 'alarm9', labelKey: 'Settings.Option.AlarmSound.Alarm9' },
818
+ { value: 'none', labelKey: 'AlarmAsset.none' },
819
+ { value: 'alarm1', labelKey: 'AlarmAsset.alarm1' },
820
+ { value: 'alarm2', labelKey: 'AlarmAsset.alarm2' },
821
+ { value: 'alarm3', labelKey: 'AlarmAsset.alarm3' },
822
+ { value: 'alarm4', labelKey: 'AlarmAsset.alarm4' },
823
+ { value: 'alarm5', labelKey: 'AlarmAsset.alarm5' },
824
+ { value: 'alarm6', labelKey: 'AlarmAsset.alarm6' },
825
+ { value: 'alarm7', labelKey: 'AlarmAsset.alarm7' },
826
+ { value: 'alarm8', labelKey: 'AlarmAsset.alarm8' },
827
+ { value: 'alarm9', labelKey: 'AlarmAsset.alarm9' },
594
828
  ];
595
829
  const ALARM_TIMEOUT_OPTIONS = [
596
- { value: 1, labelKey: 'Settings.Option.AlarmTimeout.1min' },
597
- { value: 2, labelKey: 'Settings.Option.AlarmTimeout.2min' },
598
- { value: 3, labelKey: 'Settings.Option.AlarmTimeout.3min' },
599
- { value: 5, labelKey: 'Settings.Option.AlarmTimeout.5min' },
600
- { value: 10, labelKey: 'Settings.Option.AlarmTimeout.10min' },
830
+ { value: 5, labelKey: 'Duration.CountSeconds', labelValues: { count: 5 } },
831
+ { value: 30, labelKey: 'Duration.CountSeconds', labelValues: { count: 30 } },
832
+ { value: 60, labelKey: 'Duration.CountMinutes', labelValues: { count: 1 } },
833
+ { value: 120, labelKey: 'Duration.CountMinutes', labelValues: { count: 2 } },
834
+ { value: 180, labelKey: 'Duration.CountMinutes', labelValues: { count: 3 } },
835
+ { value: 300, labelKey: 'Duration.CountMinutes', labelValues: { count: 5 } },
836
+ { value: 600, labelKey: 'Duration.CountMinutes', labelValues: { count: 10 } },
601
837
  ];
602
838
  const DAY_VIEW_ZOOM_OPTIONS = [
603
839
  { value: 15, labelKey: 'Settings.Option.DayViewZoom.15min' },
@@ -628,6 +864,12 @@ function def(category, type, defaultValue, uiType, extra) {
628
864
  if (extra?.calendarType) {
629
865
  result.calendarType = extra.calendarType;
630
866
  }
867
+ if (extra?.visibleWhen) {
868
+ result.visibleWhen = extra.visibleWhen;
869
+ }
870
+ if (extra?.disabledWhen) {
871
+ result.disabledWhen = extra.disabledWhen;
872
+ }
631
873
  return result;
632
874
  }
633
875
  // ---------------------------------------------------------------------------
@@ -639,11 +881,16 @@ function buildEntries(config) {
639
881
  // Appearance
640
882
  // ═══════════════════════════════════════════════════════════════════════
641
883
  'appearance.theme': def('appearance', 'string', config.defaultTheme, 'CUSTOM_THEME_PICKER', {
642
- options: [
643
- { value: 'light', labelKey: 'Settings.Option.Theme.Light' },
644
- { value: 'dark', labelKey: 'Settings.Option.Theme.Dark' },
645
- { value: 'system', labelKey: 'Settings.Option.Theme.System' },
646
- ],
884
+ options: config.isEnrolled
885
+ ? [
886
+ { value: 'light', labelKey: 'Settings.Option.Theme.Light' },
887
+ { value: 'dark', labelKey: 'Settings.Option.Theme.Dark' },
888
+ ]
889
+ : [
890
+ { value: 'light', labelKey: 'Settings.Option.Theme.Light' },
891
+ { value: 'dark', labelKey: 'Settings.Option.Theme.Dark' },
892
+ { value: 'system', labelKey: 'Settings.Option.Theme.System' },
893
+ ],
647
894
  }),
648
895
  'appearance.clockType': def('appearance', 'string', 'digital', 'CUSTOM_CLOCK_TYPE', {
649
896
  options: [
@@ -700,10 +947,13 @@ function buildEntries(config) {
700
947
  'calendarView.showCalendarNames': def('calendarView', 'boolean', true, 'TOGGLE'),
701
948
  'calendarView.calendarColumns': def('calendarView', 'json', [], 'CUSTOM_SPLIT_VIEW_CONFIG'),
702
949
  'calendarView.autoReturnToTodayEnabled': def('calendarView', 'boolean', config.isEnrolled, 'TOGGLE'),
703
- 'calendarView.autoReturnToTodayTimeoutSeconds': def('calendarView', 'number', 300, 'SLIDER', { sliderConfig: { min: 30, max: 600, step: 30 } }),
950
+ 'calendarView.autoReturnToTodayTimeoutSeconds': def('calendarView', 'number', 300, 'SLIDER', {
951
+ sliderConfig: { min: 30, max: 600, step: 30 },
952
+ visibleWhen: { dependsOn: 'calendarView.autoReturnToTodayEnabled', isTruthy: true },
953
+ }),
704
954
  'calendarView.showWeatherOnEvents': def('calendarView', 'boolean', false, 'TOGGLE'),
705
955
  'calendarView.showWeatherOnTimeline': def('calendarView', 'boolean', false, 'TOGGLE'),
706
- 'calendarView.weatherLocation': def('calendarView', 'json', null, 'CUSTOM_WEATHER_LOCATION'),
956
+ 'calendarView.weatherLocation': def('calendarView', 'json', null, 'CUSTOM_WEATHER_LOCATION', { visibleWhen: { dependsOn: 'calendarView.showWeatherOnTimeline', isTruthy: true } }),
707
957
  // ═══════════════════════════════════════════════════════════════════════
708
958
  // Event form field visibility (time-based)
709
959
  // ═══════════════════════════════════════════════════════════════════════
@@ -721,26 +971,57 @@ function buildEntries(config) {
721
971
  // ═══════════════════════════════════════════════════════════════════════
722
972
  // Sound & alerts
723
973
  // ═══════════════════════════════════════════════════════════════════════
724
- 'sound.timerVolume': def('sound', 'number', 0.5, 'VOLUME_SLIDER'),
725
- 'sound.reminderVolume': def('sound', 'number', 0.5, 'VOLUME_SLIDER', { appMode: 'ENROLLED' }),
726
- 'sound.mediaVolume': def('sound', 'number', 0.5, 'VOLUME_SLIDER', { appMode: 'ENROLLED' }),
727
- 'sound.alarmSound': def('sound', 'string', 'alarm1', 'SELECT', { options: ALARM_SOUND_OPTIONS }),
974
+ 'sound.timerVolume': def('sound', 'number', 0.5, 'VOLUME_SLIDER', {
975
+ visibleWhen: { dependsOn: 'sound.timerAlarmSound', notEquals: 'none' },
976
+ }),
977
+ 'sound.reminderVolume': def('sound', 'number', 0.5, 'VOLUME_SLIDER', {
978
+ appMode: 'ENROLLED',
979
+ visibleWhen: { dependsOn: 'sound.reminderAlarmSound', notEquals: 'none' },
980
+ }),
981
+ 'sound.mediaVolume': def('sound', 'number', 0.5, 'VOLUME_SLIDER', {
982
+ appMode: 'ENROLLED',
983
+ }),
984
+ 'sound.alarmSound': def('sound', 'string', 'alarm1', 'SELECT', {
985
+ options: ALARM_SOUND_OPTIONS,
986
+ }),
728
987
  'sound.reminderAlarmSound': def('sound', 'string', 'alarm1', 'SELECT', { options: ALARM_SOUND_OPTIONS }),
729
- 'sound.startAlarmSound': def('sound', 'string', 'none', 'SELECT', { options: ALARM_SOUND_OPTIONS, appMode: 'ENROLLED' }),
730
- 'sound.endAlarmSound': def('sound', 'string', 'none', 'SELECT', { options: ALARM_SOUND_OPTIONS, appMode: 'ENROLLED' }),
988
+ 'sound.startAlarmSound': def('sound', 'string', 'alarm1', 'SELECT', {
989
+ options: ALARM_SOUND_OPTIONS,
990
+ visibleWhen: { dependsOn: 'calendarView.type', equals: 'chronological' },
991
+ }),
992
+ 'sound.endAlarmSound': def('sound', 'string', 'alarm1', 'SELECT', {
993
+ options: ALARM_SOUND_OPTIONS,
994
+ visibleWhen: { dependsOn: 'calendarView.type', equals: 'chronological' },
995
+ }),
731
996
  'sound.timerAlarmSound': def('sound', 'string', 'alarm1', 'SELECT', { options: ALARM_SOUND_OPTIONS }),
732
- 'sound.timerAlarmTimeout': def('sound', 'number', 3, 'SELECT', { options: ALARM_TIMEOUT_OPTIONS }),
733
- 'sound.reminderAlarmTimeout': def('sound', 'number', 3, 'SELECT', { options: ALARM_TIMEOUT_OPTIONS }),
734
- 'sound.allowCustomReminderSounds': def('sound', 'boolean', false, 'TOGGLE'),
997
+ 'sound.timerAlarmTimeout': def('sound', 'number', 180, 'SELECT', {
998
+ options: ALARM_TIMEOUT_OPTIONS,
999
+ visibleWhen: { dependsOn: 'sound.timerAlarmSound', notEquals: 'none' },
1000
+ }),
1001
+ 'sound.reminderAlarmTimeout': def('sound', 'number', 180, 'SELECT', {
1002
+ options: ALARM_TIMEOUT_OPTIONS,
1003
+ visibleWhen: { dependsOn: 'sound.reminderAlarmSound', notEquals: 'none' },
1004
+ }),
1005
+ 'sound.allowCustomReminderSounds': def('sound', 'boolean', false, 'HIDDEN'),
735
1006
  'sound.ttsEnabled': def('sound', 'boolean', config.isEnrolled, 'TOGGLE', { appMode: 'ENROLLED' }),
736
- 'sound.ttsRate': def('sound', 'number', 1.0, 'SLIDER', { sliderConfig: { min: 0.5, max: 2, step: 0.1 }, appMode: 'ENROLLED' }),
1007
+ 'sound.ttsRate': def('sound', 'number', 1.0, 'SLIDER', {
1008
+ sliderConfig: { min: 0.5, max: 2, step: 0.1 },
1009
+ appMode: 'ENROLLED',
1010
+ visibleWhen: { dependsOn: 'sound.ttsEnabled', isTruthy: true },
1011
+ }),
737
1012
  // ═══════════════════════════════════════════════════════════════════════
738
1013
  // Timer
739
1014
  // ═══════════════════════════════════════════════════════════════════════
1015
+ 'timer.face': def('timer', 'string', 'ring', 'SELECT', {
1016
+ options: [
1017
+ { value: 'ring', labelKey: 'Timer.FaceRing' },
1018
+ { value: 'bars', labelKey: 'Timer.FaceBars' },
1019
+ ],
1020
+ }),
740
1021
  'timer.showTimeRemaining': def('timer', 'boolean', true, 'TOGGLE'),
741
- 'timer.showEndTime': def('timer', 'boolean', true, 'TOGGLE', { calendarType: 'time-based' }),
1022
+ 'timer.showEndTime': def('timer', 'boolean', true, 'TOGGLE'),
742
1023
  'timer.showRestartButton': def('timer', 'boolean', true, 'TOGGLE'),
743
- 'timer.showPauseButton': def('timer', 'boolean', true, 'TOGGLE', { calendarType: 'time-based' }),
1024
+ 'timer.showPauseButton': def('timer', 'boolean', true, 'TOGGLE'),
744
1025
  // ═══════════════════════════════════════════════════════════════════════
745
1026
  // Lock screen
746
1027
  // ═══════════════════════════════════════════════════════════════════════
@@ -754,6 +1035,7 @@ function buildEntries(config) {
754
1035
  { value: 30, labelKey: 'Settings.Option.InactivityTimeout.30min' },
755
1036
  { value: 45, labelKey: 'Settings.Option.InactivityTimeout.45min' },
756
1037
  ],
1038
+ visibleWhen: { dependsOn: 'lockScreen.inactivityLockEnabled', isTruthy: true },
757
1039
  }),
758
1040
  'lockScreen.pin': def('lockScreen', 'string', '', 'PIN_INPUT'),
759
1041
  'lockScreen.clockDisplay': def('lockScreen', 'string', 'digital', 'SELECT', {
@@ -769,7 +1051,7 @@ function buildEntries(config) {
769
1051
  },
770
1052
  ],
771
1053
  }),
772
- 'lockScreen.showHourNumbers': def('lockScreen', 'boolean', true, 'TOGGLE'),
1054
+ 'lockScreen.showHourNumbers': def('lockScreen', 'boolean', true, 'TOGGLE', { visibleWhen: { dependsOn: 'lockScreen.clockDisplay', equals: 'analog' } }),
773
1055
  'lockScreen.showDate': def('lockScreen', 'boolean', true, 'TOGGLE'),
774
1056
  'lockScreen.imageMode': def('lockScreen', 'string', 'none', 'SELECT', {
775
1057
  options: [
@@ -784,7 +1066,7 @@ function buildEntries(config) {
784
1066
  },
785
1067
  ],
786
1068
  }),
787
- 'lockScreen.backgroundImage': def('lockScreen', 'json', null, 'CUSTOM_IMAGE'),
1069
+ 'lockScreen.backgroundImage': def('lockScreen', 'json', null, 'CUSTOM_IMAGE', { visibleWhen: { dependsOn: 'lockScreen.imageMode', equals: 'background' } }),
788
1070
  'lockScreen.photoFrameIntervalSeconds': def('lockScreen', 'number', 60, 'SELECT', {
789
1071
  options: [
790
1072
  { value: 30, labelKey: 'Settings.Option.PhotoFrameInterval.30sec' },
@@ -792,13 +1074,16 @@ function buildEntries(config) {
792
1074
  { value: 120, labelKey: 'Settings.Option.PhotoFrameInterval.2min' },
793
1075
  { value: 300, labelKey: 'Settings.Option.PhotoFrameInterval.5min' },
794
1076
  ],
1077
+ visibleWhen: { dependsOn: 'lockScreen.imageMode', equals: 'photoFrame' },
795
1078
  }),
796
- 'lockScreen.photoFrameImages': def('lockScreen', 'json', [], 'CUSTOM_IMAGE_ARRAY'),
1079
+ 'lockScreen.photoFrameImages': def('lockScreen', 'json', [], 'CUSTOM_IMAGE_ARRAY', { visibleWhen: { dependsOn: 'lockScreen.imageMode', equals: 'photoFrame' } }),
797
1080
  // ═══════════════════════════════════════════════════════════════════════
798
1081
  // Touch / gestures
799
1082
  // ═══════════════════════════════════════════════════════════════════════
800
- 'touch.enableTapToCreate': def('touch', 'boolean', false, 'TOGGLE'),
801
- 'touch.enableDragDrop': def('touch', 'boolean', false, 'TOGGLE'),
1083
+ 'touch.enableTapToCreate': def('touch', 'boolean', false, 'TOGGLE', { disabledWhen: { dependsOn: 'sound.ttsEnabled', isTruthy: true } }),
1084
+ 'touch.enableDragDrop': def('touch', 'boolean', false, 'TOGGLE', {
1085
+ disabledWhen: { dependsOn: 'sound.ttsEnabled', isTruthy: true },
1086
+ }),
802
1087
  // ═══════════════════════════════════════════════════════════════════════
803
1088
  // Device (not synced unless noted)
804
1089
  // ═══════════════════════════════════════════════════════════════════════
@@ -861,6 +1146,21 @@ function buildEntries(config) {
861
1146
  'chronological.quickSettings.showMediaVolume': def('chronological', 'boolean', true, 'TOGGLE'),
862
1147
  'chronological.quickSettings.showBrightness': def('chronological', 'boolean', true, 'TOGGLE'),
863
1148
  'chronological.quickSettings.showLockScreen': def('chronological', 'boolean', true, 'TOGGLE'),
1149
+ 'chronological.quickSettings.showDayViewMode': def('chronological', 'boolean', true, 'TOGGLE'),
1150
+ // Day view display mode
1151
+ 'chronological.dayView.displayMode': def('chronological', 'string', 'list', 'SELECT', {
1152
+ options: [
1153
+ {
1154
+ value: 'list',
1155
+ labelKey: 'Settings.Option.DayViewDisplayMode.List',
1156
+ },
1157
+ {
1158
+ value: 'timeline',
1159
+ labelKey: 'Settings.Option.DayViewDisplayMode.Timeline',
1160
+ },
1161
+ ],
1162
+ calendarType: 'chronological',
1163
+ }),
864
1164
  // Time-of-day periods
865
1165
  'chronological.timeOfDay.morningStart': def('chronological', 'number', 6, 'SLIDER', { sliderConfig: TIME_OF_DAY_SLIDER }),
866
1166
  'chronological.timeOfDay.forenoonStart': def('chronological', 'number', 9, 'SLIDER', { sliderConfig: TIME_OF_DAY_SLIDER }),
@@ -871,6 +1171,7 @@ function buildEntries(config) {
871
1171
  // Chronological event form
872
1172
  // ═══════════════════════════════════════════════════════════════════════
873
1173
  // Fixed fields
1174
+ 'chronologicalEventForm.fixedField.category': def('chronologicalEventForm', 'boolean', true, 'TOGGLE'),
874
1175
  'chronologicalEventForm.fixedField.allDay': def('chronologicalEventForm', 'boolean', true, 'TOGGLE'),
875
1176
  'chronologicalEventForm.fixedField.endTime': def('chronologicalEventForm', 'boolean', true, 'TOGGLE'),
876
1177
  'chronologicalEventForm.fixedField.alarm': def('chronologicalEventForm', 'boolean', true, 'TOGGLE'),
@@ -883,6 +1184,7 @@ function buildEntries(config) {
883
1184
  'chronologicalEventForm.field.extraImages': def('chronologicalEventForm', 'boolean', true, 'TOGGLE'),
884
1185
  'chronologicalEventForm.field.reminders': def('chronologicalEventForm', 'boolean', true, 'TOGGLE'),
885
1186
  'chronologicalEventForm.field.audioClips': def('chronologicalEventForm', 'boolean', true, 'TOGGLE'),
1187
+ 'chronologicalEventForm.field.category': def('chronologicalEventForm', 'boolean', true, 'TOGGLE'),
886
1188
  // Suggest end time
887
1189
  'chronologicalEventForm.suggestEndTime.enabled': def('chronologicalEventForm', 'boolean', false, 'TOGGLE'),
888
1190
  'chronologicalEventForm.suggestEndTime.value': def('chronologicalEventForm', 'number', 30, 'SLIDER', { sliderConfig: { min: 5, max: 480, step: 5 } }),
@@ -906,6 +1208,9 @@ function buildEntries(config) {
906
1208
  { value: 'Custom', labelKey: 'Settings.Option.Visibility.Custom' },
907
1209
  ],
908
1210
  }),
1211
+ // Default alarm toggles
1212
+ 'chronologicalEventForm.defaultAlarm.atStart': def('chronologicalEventForm', 'boolean', true, 'TOGGLE'),
1213
+ 'chronologicalEventForm.defaultAlarm.atEnd': def('chronologicalEventForm', 'boolean', false, 'TOGGLE'),
909
1214
  // Reminder presets
910
1215
  'chronologicalEventForm.reminderPresets.timed': def('chronologicalEventForm', 'json', [5, 15, 30, 60, 120, 1440], 'CUSTOM_REMINDER_PRESETS'),
911
1216
  'chronologicalEventForm.reminderPresets.allDay': def('chronologicalEventForm', 'json', [
@@ -1081,9 +1386,7 @@ export function parseSettingsSnapshot(json, calendarType, registry = defaultRegi
1081
1386
  groups.set(category, []);
1082
1387
  }
1083
1388
  const labelDef = SETTINGS_LABELS[key];
1084
- const entryDef = key in registry.entries
1085
- ? registry.entries[key]
1086
- : undefined;
1389
+ const entryDef = key in registry.entries ? registry.entries[key] : undefined;
1087
1390
  groups.get(category).push({
1088
1391
  key,
1089
1392
  name,
@@ -1092,10 +1395,14 @@ export function parseSettingsSnapshot(json, calendarType, registry = defaultRegi
1092
1395
  descriptionKey: labelDef?.descriptionKey,
1093
1396
  value,
1094
1397
  uiType: entryDef?.uiType ?? 'TEXT_INPUT',
1095
- ...(entryDef?.options && { options: entryDef.options }),
1398
+ ...(entryDef?.options && {
1399
+ options: entryDef.options,
1400
+ }),
1096
1401
  ...(entryDef?.sliderConfig && { sliderConfig: entryDef.sliderConfig }),
1097
1402
  ...(entryDef?.appMode && { appMode: entryDef.appMode }),
1098
1403
  ...(entryDef?.calendarType && { calendarType: entryDef.calendarType }),
1404
+ ...(entryDef?.visibleWhen && { visibleWhen: entryDef.visibleWhen }),
1405
+ ...(entryDef?.disabledWhen && { disabledWhen: entryDef.disabledWhen }),
1099
1406
  });
1100
1407
  }
1101
1408
  // Determine which categories to show