@genesislcap/pbc-notify-ui 15.8.3 → 15.9.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.
Files changed (102) hide show
  1. package/README.md +39 -0
  2. package/custom-elements-manifest.config.js +5 -0
  3. package/dist/custom-elements.json +1771 -321
  4. package/dist/dts/components/components.d.ts.map +1 -1
  5. package/dist/dts/components/foundation-inbox/inbox-base/inbox-base.d.ts +2 -0
  6. package/dist/dts/components/foundation-inbox/inbox-base/inbox-base.d.ts.map +1 -1
  7. package/dist/dts/components/foundation-inbox/inbox.styles.d.ts.map +1 -1
  8. package/dist/dts/components/foundation-inbox/inbox.template.d.ts.map +1 -1
  9. package/dist/dts/components/foundation-inbox/inbox.types.d.ts +4 -1
  10. package/dist/dts/components/foundation-inbox/inbox.types.d.ts.map +1 -1
  11. package/dist/dts/components/foundation-notification-dashboard/alert-sound-attachment.d.ts +86 -0
  12. package/dist/dts/components/foundation-notification-dashboard/alert-sound-attachment.d.ts.map +1 -0
  13. package/dist/dts/components/foundation-notification-dashboard/components/rules/rule-dialog/rule-dialog.d.ts +39 -1
  14. package/dist/dts/components/foundation-notification-dashboard/components/rules/rule-dialog/rule-dialog.d.ts.map +1 -1
  15. package/dist/dts/components/foundation-notification-dashboard/components/rules/rule-dialog/rule-dialog.template.d.ts.map +1 -1
  16. package/dist/dts/components/foundation-notification-dashboard/components/rules/rule-dialog/rule-dialog.types.d.ts +8 -0
  17. package/dist/dts/components/foundation-notification-dashboard/components/rules/rule-dialog/rule-dialog.types.d.ts.map +1 -1
  18. package/dist/dts/components/foundation-notification-dashboard/components/rules/rule-management.d.ts.map +1 -1
  19. package/dist/dts/components/foundation-notification-dashboard/components/templates/template-dialog/template-dialog.d.ts +41 -1
  20. package/dist/dts/components/foundation-notification-dashboard/components/templates/template-dialog/template-dialog.d.ts.map +1 -1
  21. package/dist/dts/components/foundation-notification-dashboard/components/templates/template-dialog/template-dialog.template.d.ts.map +1 -1
  22. package/dist/dts/components/foundation-notification-dashboard/components/templates/template-dialog/template-dialog.types.d.ts +2 -1
  23. package/dist/dts/components/foundation-notification-dashboard/components/templates/template-dialog/template-dialog.types.d.ts.map +1 -1
  24. package/dist/dts/components/foundation-notification-dashboard/components/templates/template-management.d.ts.map +1 -1
  25. package/dist/dts/components/foundation-notification-dashboard/styles/dynamic-rule.styles.d.ts.map +1 -1
  26. package/dist/dts/components/foundation-sounds/foundation-sounds-panel.d.ts +73 -0
  27. package/dist/dts/components/foundation-sounds/foundation-sounds-panel.d.ts.map +1 -0
  28. package/dist/dts/components/foundation-sounds/foundation-sounds-panel.styles.d.ts +2 -0
  29. package/dist/dts/components/foundation-sounds/foundation-sounds-panel.styles.d.ts.map +1 -0
  30. package/dist/dts/components/foundation-sounds/foundation-sounds-panel.template.d.ts +3 -0
  31. package/dist/dts/components/foundation-sounds/foundation-sounds-panel.template.d.ts.map +1 -0
  32. package/dist/dts/components/foundation-sounds/foundation-sounds.d.ts +34 -0
  33. package/dist/dts/components/foundation-sounds/foundation-sounds.d.ts.map +1 -0
  34. package/dist/dts/components/foundation-sounds/foundation-sounds.styles.d.ts +3 -0
  35. package/dist/dts/components/foundation-sounds/foundation-sounds.styles.d.ts.map +1 -0
  36. package/dist/dts/index.d.ts +4 -0
  37. package/dist/dts/index.d.ts.map +1 -1
  38. package/dist/dts/react.d.ts +31 -11
  39. package/dist/dts/services/inbox.service.d.ts +6 -0
  40. package/dist/dts/services/inbox.service.d.ts.map +1 -1
  41. package/dist/dts/services/rule.service.d.ts +2 -1
  42. package/dist/dts/services/rule.service.d.ts.map +1 -1
  43. package/dist/dts/utils/alert-sound.d.ts +177 -0
  44. package/dist/dts/utils/alert-sound.d.ts.map +1 -0
  45. package/dist/dts/utils/alert-sound.test.d.ts +2 -0
  46. package/dist/dts/utils/alert-sound.test.d.ts.map +1 -0
  47. package/dist/esm/components/components.js +4 -0
  48. package/dist/esm/components/foundation-inbox/inbox-base/inbox-base.js +6 -0
  49. package/dist/esm/components/foundation-inbox/inbox.styles.js +15 -0
  50. package/dist/esm/components/foundation-inbox/inbox.template.js +16 -0
  51. package/dist/esm/components/foundation-inbox/inbox.types.js +1 -0
  52. package/dist/esm/components/foundation-notification-dashboard/alert-sound-attachment.js +218 -0
  53. package/dist/esm/components/foundation-notification-dashboard/components/rules/rule-dialog/rule-dialog.js +17 -2
  54. package/dist/esm/components/foundation-notification-dashboard/components/rules/rule-dialog/rule-dialog.template.js +3 -0
  55. package/dist/esm/components/foundation-notification-dashboard/components/rules/rule-management.js +5 -1
  56. package/dist/esm/components/foundation-notification-dashboard/components/templates/template-dialog/template-dialog.js +23 -2
  57. package/dist/esm/components/foundation-notification-dashboard/components/templates/template-dialog/template-dialog.template.js +3 -0
  58. package/dist/esm/components/foundation-notification-dashboard/components/templates/template-management.js +8 -1
  59. package/dist/esm/components/foundation-notification-dashboard/styles/dynamic-rule.styles.js +31 -1
  60. package/dist/esm/components/foundation-sounds/foundation-sounds-panel.js +283 -0
  61. package/dist/esm/components/foundation-sounds/foundation-sounds-panel.styles.js +200 -0
  62. package/dist/esm/components/foundation-sounds/foundation-sounds-panel.template.js +179 -0
  63. package/dist/esm/components/foundation-sounds/foundation-sounds.js +206 -0
  64. package/dist/esm/components/foundation-sounds/foundation-sounds.styles.js +7 -0
  65. package/dist/esm/index.js +3 -0
  66. package/dist/esm/services/inbox.service.js +6 -2
  67. package/dist/esm/services/rule.service.js +35 -0
  68. package/dist/esm/utils/alert-sound.js +955 -0
  69. package/dist/esm/utils/alert-sound.test.js +25 -0
  70. package/dist/esm/utils/index.js +3 -0
  71. package/dist/pbc-notify-ui.api.json +2859 -332
  72. package/dist/pbc-notify-ui.d.ts +281 -3
  73. package/dist/react.cjs +19 -5
  74. package/dist/react.mjs +16 -4
  75. package/dist/tsconfig.tsbuildinfo +1 -1
  76. package/package.json +23 -23
  77. package/src/components/components.ts +4 -0
  78. package/src/components/foundation-inbox/inbox-base/inbox-base.ts +7 -0
  79. package/src/components/foundation-inbox/inbox.styles.ts +15 -0
  80. package/src/components/foundation-inbox/inbox.template.ts +16 -0
  81. package/src/components/foundation-inbox/inbox.types.ts +3 -0
  82. package/src/components/foundation-notification-dashboard/alert-sound-attachment.ts +283 -0
  83. package/src/components/foundation-notification-dashboard/components/rules/rule-dialog/rule-dialog.template.ts +3 -0
  84. package/src/components/foundation-notification-dashboard/components/rules/rule-dialog/rule-dialog.ts +21 -2
  85. package/src/components/foundation-notification-dashboard/components/rules/rule-dialog/rule-dialog.types.ts +9 -0
  86. package/src/components/foundation-notification-dashboard/components/rules/rule-management.ts +5 -1
  87. package/src/components/foundation-notification-dashboard/components/templates/template-dialog/template-dialog.template.ts +3 -0
  88. package/src/components/foundation-notification-dashboard/components/templates/template-dialog/template-dialog.ts +25 -2
  89. package/src/components/foundation-notification-dashboard/components/templates/template-dialog/template-dialog.types.ts +2 -1
  90. package/src/components/foundation-notification-dashboard/components/templates/template-management.ts +8 -1
  91. package/src/components/foundation-notification-dashboard/styles/dynamic-rule.styles.ts +31 -1
  92. package/src/components/foundation-sounds/foundation-sounds-panel.styles.ts +201 -0
  93. package/src/components/foundation-sounds/foundation-sounds-panel.template.ts +188 -0
  94. package/src/components/foundation-sounds/foundation-sounds-panel.ts +280 -0
  95. package/src/components/foundation-sounds/foundation-sounds.styles.ts +8 -0
  96. package/src/components/foundation-sounds/foundation-sounds.ts +222 -0
  97. package/src/index.ts +8 -0
  98. package/src/services/inbox.service.ts +12 -2
  99. package/src/services/rule.service.ts +37 -1
  100. package/src/utils/alert-sound.test.ts +32 -0
  101. package/src/utils/alert-sound.ts +1135 -0
  102. package/src/utils/index.ts +3 -0
@@ -27,6 +27,8 @@ import type { TemplateConditionGroup as TemplateConditionGroupWC } from './compo
27
27
  import type { TemplateDialog as TemplateDialogWC } from './components/foundation-notification-dashboard/components/templates/template-dialog/template-dialog';
28
28
  import type { TemplateManagement as TemplateManagementWC } from './components/foundation-notification-dashboard/components/templates/template-management';
29
29
  import type { FoundationNotificationDashboard as FoundationNotificationDashboardWC } from './components/foundation-notification-dashboard/notification-dashboard';
30
+ import type { FoundationSoundsPanel as FoundationSoundsPanelWC } from './components/foundation-sounds/foundation-sounds-panel';
31
+ import type { FoundationSounds as FoundationSoundsWC } from './components/foundation-sounds/foundation-sounds';
30
32
  import type { ReconciliationSandbox as ReconciliationSandboxWC } from './sandbox';
31
33
  import type { ConditionBuilderEntity, ParameterBuilderEntity, ParameterEditedEventDetail } from '@genesislcap/pbc-notify-ui';
32
34
 
@@ -86,6 +88,24 @@ export declare const FoundationNotificationDashboard: React.ForwardRefExoticComp
86
88
  >;
87
89
  export type FoundationNotificationDashboardRef = FoundationNotificationDashboardWC;
88
90
 
91
+ export declare const FoundationSoundsPanel: React.ForwardRefExoticComponent<
92
+ React.PropsWithChildren<
93
+ Omit<PublicOf<FoundationSoundsPanelWC>, 'children' | 'style'> &
94
+ HTMLWCProps & {
95
+ }
96
+ > & React.RefAttributes<FoundationSoundsPanelWC>
97
+ >;
98
+ export type FoundationSoundsPanelRef = FoundationSoundsPanelWC;
99
+
100
+ export declare const FoundationSounds: React.ForwardRefExoticComponent<
101
+ React.PropsWithChildren<
102
+ Omit<PublicOf<FoundationSoundsWC>, 'children' | 'style'> &
103
+ HTMLWCProps & {
104
+ }
105
+ > & React.RefAttributes<FoundationSoundsWC>
106
+ >;
107
+ export type FoundationSoundsRef = FoundationSoundsWC;
108
+
89
109
  export declare const FoundationInboxCounter: React.ForwardRefExoticComponent<
90
110
  React.PropsWithChildren<
91
111
  Omit<PublicOf<FoundationInboxCounterWC>, 'children' | 'style'> &
@@ -235,17 +255,6 @@ export declare const TemplateDialog: React.ForwardRefExoticComponent<
235
255
  >;
236
256
  export type TemplateDialogRef = TemplateDialogWC;
237
257
 
238
- export declare const RuleConditionGroup: React.ForwardRefExoticComponent<
239
- React.PropsWithChildren<
240
- Omit<PublicOf<RuleConditionGroupWC>, 'children' | 'style'> &
241
- HTMLWCProps & {
242
- onDelete?: (event: CustomEvent<unknown>) => void;
243
- onEdit?: (event: CustomEvent<unknown>) => void;
244
- }
245
- > & React.RefAttributes<RuleConditionGroupWC>
246
- >;
247
- export type RuleConditionGroupRef = RuleConditionGroupWC;
248
-
249
258
  export declare const RuleConditionBuilder: React.ForwardRefExoticComponent<
250
259
  React.PropsWithChildren<
251
260
  Omit<PublicOf<RuleConditionBuilderWC>, 'children' | 'style'> &
@@ -257,6 +266,17 @@ export declare const RuleConditionBuilder: React.ForwardRefExoticComponent<
257
266
  >;
258
267
  export type RuleConditionBuilderRef = RuleConditionBuilderWC;
259
268
 
269
+ export declare const RuleConditionGroup: React.ForwardRefExoticComponent<
270
+ React.PropsWithChildren<
271
+ Omit<PublicOf<RuleConditionGroupWC>, 'children' | 'style'> &
272
+ HTMLWCProps & {
273
+ onDelete?: (event: CustomEvent<unknown>) => void;
274
+ onEdit?: (event: CustomEvent<unknown>) => void;
275
+ }
276
+ > & React.RefAttributes<RuleConditionGroupWC>
277
+ >;
278
+ export type RuleConditionGroupRef = RuleConditionGroupWC;
279
+
260
280
  export declare const ParameterBuilder: React.ForwardRefExoticComponent<
261
281
  React.PropsWithChildren<
262
282
  Omit<PublicOf<ParameterBuilderWC>, 'children' | 'style'> &
@@ -7,6 +7,12 @@ export declare const INBOX_PAGE_SIZE = 20;
7
7
  export interface FoundationInboxServiceStore {
8
8
  items: Array<any>;
9
9
  moreRows: boolean;
10
+ /**
11
+ * SEQUENCE_ID of the last message applied to {@link items}. `<= 1` means the
12
+ * message was (part of) the initial snapshot rather than a real-time update —
13
+ * mirrors the boundary `notification-listener` uses (`SEQUENCE_ID > 1`).
14
+ */
15
+ sequenceId: number;
10
16
  }
11
17
  export type SubscribeCallback = ({ store }: {
12
18
  store: FoundationInboxServiceStore;
@@ -1 +1 @@
1
- {"version":3,"file":"inbox.service.d.ts","sourceRoot":"","sources":["../../../src/services/inbox.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmC,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAIpG;;;GAGG;AACH,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,MAAM,iBAAiB,GAAG,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,2BAA2B,CAAA;CAAE,KAAK,GAAG,CAAC;AAE3F,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,kBAAkB,EAAE,kBAAkB,CAAC;CACxC,CAAC;AAEF,MAAM,WAAW,sBAAsB;IACrC;;;;;;;;OAQG;IACH,SAAS,CACP,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,GACd,IAAI,CAAC;IACR,WAAW,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC/C,WAAW,IAAI,IAAI,CAAC;CACrB;AAmGD,eAAO,MAAM,sBAAsB,8EAElC,CAAC"}
1
+ {"version":3,"file":"inbox.service.d.ts","sourceRoot":"","sources":["../../../src/services/inbox.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmC,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAIpG;;;GAGG;AACH,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,iBAAiB,GAAG,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,2BAA2B,CAAA;CAAE,KAAK,GAAG,CAAC;AAE3F,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,kBAAkB,EAAE,kBAAkB,CAAC;CACxC,CAAC;AAEF,MAAM,WAAW,sBAAsB;IACrC;;;;;;;;OAQG;IACH,SAAS,CACP,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,GACd,IAAI,CAAC;IACR,WAAW,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC/C,WAAW,IAAI,IAAI,CAAC;CACrB;AAuGD,eAAO,MAAM,sBAAsB,8EAElC,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { Message } from '@genesislcap/foundation-comms';
2
- import type { Rule } from '../components/foundation-notification-dashboard/components/rules/rule-dialog/rule-dialog.types';
2
+ import type { NotifyAlertSound, Rule } from '../components/foundation-notification-dashboard/components/rules/rule-dialog/rule-dialog.types';
3
3
  export interface RuleService {
4
4
  subscribeRule(dynamicRuleId: string, parameterDetails: any): any;
5
5
  unsubscribeRule(dynamicRuleId: string): any;
@@ -7,6 +7,7 @@ export interface RuleService {
7
7
  disableRule(dynamicRuleId: string): any;
8
8
  getRules(): Promise<any>;
9
9
  getRuleDetails(ruleId: string): Promise<Rule>;
10
+ getAlertSound(dynamicRuleId: string): Promise<NotifyAlertSound | null>;
10
11
  createRule(rule: Rule): Promise<Message>;
11
12
  updateRule(rule: Rule): Promise<Message>;
12
13
  deleteRule(ruleId: string): void;
@@ -1 +1 @@
1
- {"version":3,"file":"rule.service.d.ts","sourceRoot":"","sources":["../../../src/services/rule.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAEjE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gGAAgG,CAAC;AAE3H,MAAM,WAAW,WAAW;IAC1B,aAAa,CAAC,aAAa,EAAE,MAAM,EAAE,gBAAgB,EAAE,GAAG,OAAE;IAC5D,eAAe,CAAC,aAAa,EAAE,MAAM,OAAE;IACvC,UAAU,CAAC,aAAa,EAAE,MAAM,OAAE;IAClC,WAAW,CAAC,aAAa,EAAE,MAAM,OAAE;IACnC,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC;IACzB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzC,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzC,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAkHD,eAAO,MAAM,WAAW,mEAAuE,CAAC"}
1
+ {"version":3,"file":"rule.service.d.ts","sourceRoot":"","sources":["../../../src/services/rule.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAEjE,OAAO,KAAK,EACV,gBAAgB,EAChB,IAAI,EACL,MAAM,gGAAgG,CAAC;AAExG,MAAM,WAAW,WAAW;IAC1B,aAAa,CAAC,aAAa,EAAE,MAAM,EAAE,gBAAgB,EAAE,GAAG,OAAE;IAC5D,eAAe,CAAC,aAAa,EAAE,MAAM,OAAE;IACvC,UAAU,CAAC,aAAa,EAAE,MAAM,OAAE;IAClC,WAAW,CAAC,aAAa,EAAE,MAAM,OAAE;IACnC,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC;IACzB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,aAAa,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IACvE,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzC,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzC,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAkJD,eAAO,MAAM,WAAW,mEAAuE,CAAC"}
@@ -0,0 +1,177 @@
1
+ /** Maximum number of sound rules (MESSAGE/HEADER pattern, or notification rule). */
2
+ export declare const MAX_SOUND_RULES = 10;
3
+ /**
4
+ * Every rule's audio is held in one IndexedDB row, so a generous per-file limit
5
+ * still risks the origin storage quota once multiplied by {@link MAX_SOUND_RULES}.
6
+ */
7
+ export declare const MAX_SOUND_FILE_BYTES: number;
8
+ /** Encodes a File/Blob to a base64 string (no data-URL prefix). */
9
+ export declare function blobToBase64(blob: Blob): Promise<string>;
10
+ /** Decodes a base64 string to a Blob. */
11
+ export declare function base64ToBlob(base64: string, mimeType: string): Blob;
12
+ /** How a sound rule decides when to play. */
13
+ export type AlertSoundMatchMode = 'message' | 'header' | 'rule';
14
+ /** Notification rule option available for DYNAMIC_RULE_ID sound binding. */
15
+ export type AlertSoundSelectableRule = {
16
+ id: string;
17
+ name: string;
18
+ };
19
+ /**
20
+ * Resolves a playable MIME type. macOS often leaves `.aif` / `.aiff` with an
21
+ * empty `File.type`; falling back to `audio/mpeg` breaks restore + decode.
22
+ */
23
+ export declare function resolveAudioMimeType(name?: string | null, type?: string | null): string;
24
+ /** Formats a byte count for user-facing size limits (e.g. "2 MB"). */
25
+ export declare function formatBytes(bytes: number): string;
26
+ export type AlertSoundRuleView = {
27
+ index: number;
28
+ matchMode: AlertSoundMatchMode;
29
+ pattern: string;
30
+ templateId: string | null;
31
+ templateName: string | null;
32
+ fileName: string | null;
33
+ hasSound: boolean;
34
+ muted: boolean;
35
+ };
36
+ /**
37
+ * True when `text` contains `phrase` as a whole phrase (case-insensitive).
38
+ * Single words and multi-word phrases both work; edges use word boundaries so
39
+ * e.g. `g1` matches "test g1 test" but not "test g1h test".
40
+ *
41
+ * `\b` only asserts a word/non-word transition, so anchoring an edge whose own
42
+ * character is punctuation can never match — `\b\$100` fails on "cost $100".
43
+ * Each edge is therefore anchored only when the pattern ends in a word character.
44
+ *
45
+ * @internal Exported for unit tests.
46
+ */
47
+ export declare function textContainsWholeWord(text: string, phrase: string): boolean;
48
+ /**
49
+ * Plays user-selected alert sounds matched by MESSAGE/HEADER text (word or phrase)
50
+ * or notification rule DYNAMIC_RULE_ID. Persists up to {@link MAX_SOUND_RULES}
51
+ * rules in IndexedDB.
52
+ */
53
+ export declare class AlertSoundPlayer {
54
+ private slots;
55
+ private activeCount;
56
+ /** Linear gain 0–1 applied to every HTMLAudioElement. */
57
+ private volume;
58
+ private muted;
59
+ private listeners;
60
+ /** Coalesces concurrent restores from layout + Sounds tab mounts. */
61
+ private restorePromise;
62
+ private restored;
63
+ private persistChain;
64
+ /** In-memory cache of rule-attached sounds fetched from NOTIFY_ALERT_SOUND. */
65
+ private readonly serverSounds;
66
+ /** Shared player so the layout listener and inbox Sounds tab stay in sync. */
67
+ static readonly shared: AlertSoundPlayer;
68
+ get rules(): AlertSoundRuleView[];
69
+ get canAddRule(): boolean;
70
+ get canRemoveRule(): boolean;
71
+ /** Whole-number percent for the Sounds tab slider (0–100). */
72
+ get volumePercent(): number;
73
+ get isMuted(): boolean;
74
+ get isReady(): boolean;
75
+ subscribe(listener: () => void): () => void;
76
+ /**
77
+ * Loads rules from IndexedDB once per page session. Concurrent calls (layout
78
+ * listener + Sounds tab) share one promise so a late restore cannot wipe
79
+ * in-memory edits the user already made.
80
+ */
81
+ restore(): Promise<boolean>;
82
+ private restoreFromDb;
83
+ /** Sets global playback volume from a 0–100 percent (Sounds tab slider). */
84
+ setVolumePercent(percent: number): void;
85
+ setMuted(muted: boolean): void;
86
+ toggleMute(): void;
87
+ addRule(): boolean;
88
+ removeRule(index: number): void;
89
+ /** Clears match criteria and sound file for a rule without removing the row. */
90
+ resetRule(index: number): void;
91
+ setMatchMode(index: number, matchMode: AlertSoundMatchMode): void;
92
+ /**
93
+ * Deliberately does not notify: the value came from the pattern field itself,
94
+ * so re-rendering the rule rows mid-keystroke would only disturb the caret.
95
+ */
96
+ setPattern(index: number, pattern: string): void;
97
+ setRule(index: number, rule: Pick<AlertSoundSelectableRule, 'id' | 'name'> | null): void;
98
+ setSoundFromFile(index: number, file: File): void;
99
+ clearSound(index: number): void;
100
+ setRuleMuted(index: number, muted: boolean): void;
101
+ toggleRuleMute(index: number): void;
102
+ /** Plays the sound file for a specific rule (for UI testing). */
103
+ playRule(index: number): boolean;
104
+ /**
105
+ * Plays the first configured rule that matches the alert:
106
+ * - message: MESSAGE contains the pattern as a whole phrase (case-insensitive)
107
+ * - header: HEADER contains the pattern as a whole phrase (case-insensitive)
108
+ * - rule: alert DYNAMIC_RULE_ID equals the selected id
109
+ */
110
+ playForAlert(alert: {
111
+ message?: string | null;
112
+ header?: string | null;
113
+ dynamicRuleId?: string | null;
114
+ }): boolean;
115
+ /**
116
+ * Caches a server-backed rule sound (from NOTIFY_ALERT_SOUND) for playback.
117
+ * Replaces any previous cache entry for the same dynamicRuleId.
118
+ */
119
+ setServerSound(dynamicRuleId: string, options: {
120
+ blob: Blob;
121
+ fileName?: string;
122
+ mimeType?: string;
123
+ }): void;
124
+ /** True when a server sound for this rule is already cached. */
125
+ hasServerSound(dynamicRuleId: string): boolean;
126
+ /**
127
+ * Drops a cached server sound so the next alert re-fetches from
128
+ * NOTIFY_ALERT_SOUND (e.g. after replace/clear on the rule dialog).
129
+ */
130
+ clearServerSound(dynamicRuleId: string): void;
131
+ /**
132
+ * Clears every cached server-backed rule sound. Used when a template sound
133
+ * changes — subscribed instance ids are unknown on the client, and alerts
134
+ * look up by those instance ids.
135
+ */
136
+ clearAllServerSounds(): void;
137
+ /**
138
+ * Plays a cached server sound for the given DYNAMIC_RULE_ID.
139
+ * Respects global mute / volume. Returns false if missing or muted with no play.
140
+ */
141
+ playServerSound(dynamicRuleId: string): boolean;
142
+ private revokeServerSound;
143
+ private applyStoredMatch;
144
+ private matchFields;
145
+ /** Debounces config writes so holding down a key does not thrash IndexedDB. */
146
+ private readonly debouncedPersistConfig;
147
+ /** Schedules a debounced config write (pattern typing). */
148
+ private schedulePersist;
149
+ /** Writes config now, including anything a pending debounce had not yet flushed. */
150
+ private flushPersist;
151
+ private flushSettings;
152
+ /** Flushes config plus this rule's audio (sound file added/cleared/reset). */
153
+ private flushAudioPersist;
154
+ /** Chained so overlapping writes cannot land out of order. */
155
+ private queuePersist;
156
+ private applyStoredSettings;
157
+ private applyVolumeToSlots;
158
+ private applyVolumeToAudio;
159
+ private persistSettings;
160
+ /** Match config only — cheap enough to rewrite on every keystroke. */
161
+ private persistConfig;
162
+ /** One rule's audio, stored separately so a pattern edit never touches it. */
163
+ private persistAudio;
164
+ private applyFile;
165
+ private isValidIndex;
166
+ private notify;
167
+ private revokeObjectUrl;
168
+ }
169
+ /** Reads ALERT_ID from a streamed alert row. */
170
+ export declare function getAlertId(row: Record<string, unknown> | null | undefined): string | undefined;
171
+ /** Reads MESSAGE from a streamed alert row. */
172
+ export declare function getAlertMessage(row: Record<string, unknown> | null | undefined): string | undefined;
173
+ /** Reads HEADER from a streamed alert row. */
174
+ export declare function getAlertHeader(row: Record<string, unknown> | null | undefined): string | undefined;
175
+ /** Reads DYNAMIC_RULE_ID from a streamed alert row. */
176
+ export declare function getAlertDynamicRuleId(row: Record<string, unknown> | null | undefined): string | undefined;
177
+ //# sourceMappingURL=alert-sound.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alert-sound.d.ts","sourceRoot":"","sources":["../../../src/utils/alert-sound.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=alert-sound.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alert-sound.test.d.ts","sourceRoot":"","sources":["../../../src/utils/alert-sound.test.ts"],"names":[],"mappings":""}
@@ -22,11 +22,15 @@ import { TemplateConditionBuilder } from './foundation-notification-dashboard/co
22
22
  import { TemplateConditionGroup } from './foundation-notification-dashboard/components/templates/template-dialog/template-condition-group/template-condition-group';
23
23
  import { TemplateDialog } from './foundation-notification-dashboard/components/templates/template-dialog/template-dialog';
24
24
  import { TemplateManagement } from './foundation-notification-dashboard/components/templates/template-management';
25
+ import { FoundationSounds } from './foundation-sounds/foundation-sounds';
26
+ import { FoundationSoundsPanel } from './foundation-sounds/foundation-sounds-panel';
25
27
  // Components
26
28
  // #region Inbox
27
29
  FoundationInboxCounter;
28
30
  InboxSubscription;
29
31
  RuleParameter;
32
+ FoundationSounds;
33
+ FoundationSoundsPanel;
30
34
  // #endregion Inbox
31
35
  // #region Notification Dashboard
32
36
  RuleManagement;
@@ -129,6 +129,7 @@ export class FoundationInboxBase extends GenesisElement {
129
129
  // #region Tabs
130
130
  inboxTabChanged(inboxTab) {
131
131
  return __awaiter(this, void 0, void 0, function* () {
132
+ var _a;
132
133
  this.selectedTab = inboxTab;
133
134
  this.ruleTemplateDetails = null;
134
135
  switch (inboxTab) {
@@ -143,6 +144,11 @@ export class FoundationInboxBase extends GenesisElement {
143
144
  case InboxTab.SUBSCRIBE:
144
145
  yield this.refreshSubscribeData();
145
146
  break;
147
+ case InboxTab.SOUNDS: {
148
+ // Refresh rules so newly subscribed templates (which create rules) appear.
149
+ yield ((_a = this.sounds) === null || _a === void 0 ? void 0 : _a.reloadRules());
150
+ break;
151
+ }
146
152
  default:
147
153
  logger.error('Unexpected inboxTab:', inboxTab);
148
154
  break;
@@ -287,6 +287,21 @@ export const subscribe = css `
287
287
  flex: 1;
288
288
  overflow: auto;
289
289
  }
290
+
291
+ .sounds-tab-panel {
292
+ display: flex;
293
+ flex-direction: column;
294
+ min-height: 0;
295
+ flex: 1;
296
+ overflow: hidden;
297
+ }
298
+
299
+ .sounds-tab-panel foundation-sounds-panel {
300
+ display: flex;
301
+ flex: 1;
302
+ min-height: 0;
303
+ height: 100%;
304
+ }
290
305
  `;
291
306
  export const ruleAndTemplate = css `
292
307
  .template {
@@ -231,6 +231,14 @@ export const FoundationInboxTemplate = html `
231
231
  Subscribe
232
232
  </rapid-tab>
233
233
  `)}
234
+ <rapid-tab
235
+ slot="sounds-tab"
236
+ id=${InboxTab.SOUNDS}
237
+ @click=${(x) => x.inboxTabChanged(InboxTab.SOUNDS)}
238
+ appearance="secondary"
239
+ >
240
+ Sounds
241
+ </rapid-tab>
234
242
 
235
243
  <!-- Alerts -->
236
244
  ${(_) => alertsTab(InboxTab.ALERTS_NEW)} ${(_) => alertsTab(InboxTab.ALERT_HISTORY)}
@@ -318,6 +326,14 @@ export const FoundationInboxTemplate = html `
318
326
  </div>
319
327
  </rapid-tab-panel>
320
328
  `)}
329
+ <rapid-tab-panel
330
+ slot="sounds-tab"
331
+ class="sounds-tab-panel"
332
+ id=${InboxTab.SOUNDS}
333
+ style=${(x) => (x.selectedTab === InboxTab.SOUNDS ? '' : 'display: none;')}
334
+ >
335
+ <foundation-sounds-panel ${ref('sounds')}></foundation-sounds-panel>
336
+ </rapid-tab-panel>
321
337
  </rapid-tabs>
322
338
  </div>
323
339
  `;
@@ -13,4 +13,5 @@ export var InboxTab;
13
13
  InboxTab[InboxTab["ALERTS_NEW"] = 0] = "ALERTS_NEW";
14
14
  InboxTab[InboxTab["ALERT_HISTORY"] = 1] = "ALERT_HISTORY";
15
15
  InboxTab[InboxTab["SUBSCRIBE"] = 2] = "SUBSCRIBE";
16
+ InboxTab[InboxTab["SOUNDS"] = 3] = "SOUNDS";
16
17
  })(InboxTab || (InboxTab = {}));
@@ -0,0 +1,218 @@
1
+ import { __awaiter, __decorate } from "tslib";
2
+ import { showNotification } from '@genesislcap/foundation-notifications';
3
+ import { html, observable, ref, when } from '@genesislcap/web-core';
4
+ import { AlertSoundPlayer, base64ToBlob, blobToBase64, formatBytes, MAX_SOUND_FILE_BYTES, resolveAudioMimeType, } from '../../utils/alert-sound';
5
+ function showAlertSoundNotice(title, body) {
6
+ showNotification({
7
+ title,
8
+ body,
9
+ config: {
10
+ snackbar: {
11
+ type: 'error',
12
+ },
13
+ },
14
+ }, 'rapid');
15
+ }
16
+ /**
17
+ * Shared Attach/Replace/Test/Clear UI for rule and template dialogs.
18
+ * @public
19
+ */
20
+ export const alertSoundAttachmentTemplate = html `
21
+ <div class="alert-sound">
22
+ <input
23
+ ${ref('fileInput')}
24
+ class="file-input"
25
+ type="file"
26
+ accept="audio/*"
27
+ @change=${(x, c) => x.onAlertSoundSelected(c.event)}
28
+ />
29
+ <label>Alert sound</label>
30
+ <div class="alert-sound-row">
31
+ <span class="alert-sound-name" title=${(x) => x.alertSoundFileName || ''}>
32
+ ${(x) => x.alertSoundFileName || 'No sound attached'}
33
+ </span>
34
+ <rapid-button appearance="lightweight" @click=${(x) => x.openAlertSoundPicker()}>
35
+ ${(x) => (x.hasAlertSound ? 'Replace' : 'Attach')}
36
+ </rapid-button>
37
+ ${when((x) => x.hasAlertSound, html `
38
+ <rapid-button appearance="lightweight" @click=${(x) => x.testAttachedAlertSound()}>
39
+ Test
40
+ </rapid-button>
41
+ <rapid-button appearance="lightweight" @click=${(x) => x.clearAttachedAlertSound()}>
42
+ Clear
43
+ </rapid-button>
44
+ `)}
45
+ </div>
46
+ </div>
47
+ `;
48
+ /**
49
+ * Mixin: pick / test / clear / load server sound + apply fields to submit payload.
50
+ *
51
+ * Subclasses must provide {@link ruleService}, {@link isAlertSoundEditMode},
52
+ * {@link alertSoundEntityId}, and {@link alertSoundCacheSyncMode}.
53
+ *
54
+ * @public
55
+ */
56
+ export function AlertSoundAttachmentMixin(Base) {
57
+ class AlertSoundAttachment extends Base {
58
+ constructor() {
59
+ super(...arguments);
60
+ /** Display name of the attached / pending alert sound file. */
61
+ this.alertSoundFileName = '';
62
+ this.alertSoundMimeType = '';
63
+ /** True when edit loaded an existing server sound (or user attached one). */
64
+ this.hasAlertSound = false;
65
+ this.pendingAlertSoundData = null;
66
+ /** True when the user picked a new file this session (needs upload). */
67
+ this.alertSoundDirty = false;
68
+ this.clearAlertSound = false;
69
+ /** In-progress Test playback; not private — mixin return type is exported. */
70
+ this.testAudio = null;
71
+ }
72
+ loadExistingAlertSound(dynamicRuleId) {
73
+ return __awaiter(this, void 0, void 0, function* () {
74
+ if (!dynamicRuleId) {
75
+ return;
76
+ }
77
+ const sound = yield this.ruleService.getAlertSound(dynamicRuleId);
78
+ if (!(sound === null || sound === void 0 ? void 0 : sound.SOUND_DATA)) {
79
+ return;
80
+ }
81
+ this.alertSoundFileName = sound.ALERT_SOUND_FILE_NAME || 'Attached sound';
82
+ this.alertSoundMimeType = sound.ALERT_SOUND_MIME_TYPE || 'audio/mpeg';
83
+ this.pendingAlertSoundData = sound.SOUND_DATA;
84
+ this.hasAlertSound = true;
85
+ this.alertSoundDirty = false;
86
+ this.clearAlertSound = false;
87
+ });
88
+ }
89
+ openAlertSoundPicker() {
90
+ var _a;
91
+ (_a = this.fileInput) === null || _a === void 0 ? void 0 : _a.click();
92
+ }
93
+ onAlertSoundSelected(event) {
94
+ return __awaiter(this, void 0, void 0, function* () {
95
+ var _a;
96
+ const input = event.target;
97
+ const file = (_a = input.files) === null || _a === void 0 ? void 0 : _a[0];
98
+ input.value = '';
99
+ if (!file) {
100
+ return;
101
+ }
102
+ if (file.size > MAX_SOUND_FILE_BYTES) {
103
+ showAlertSoundNotice('Sound file too large', `Sound file must be ${formatBytes(MAX_SOUND_FILE_BYTES)} or smaller.`);
104
+ return;
105
+ }
106
+ try {
107
+ this.pendingAlertSoundData = yield blobToBase64(file);
108
+ this.alertSoundFileName = file.name;
109
+ this.alertSoundMimeType = resolveAudioMimeType(file.name, file.type);
110
+ this.hasAlertSound = true;
111
+ this.alertSoundDirty = true;
112
+ this.clearAlertSound = false;
113
+ }
114
+ catch (_b) {
115
+ showAlertSoundNotice('Could not read sound file', 'The selected file could not be read.');
116
+ }
117
+ });
118
+ }
119
+ clearAttachedAlertSound() {
120
+ this.stopTestSound();
121
+ this.resetAlertSoundState();
122
+ this.clearAlertSound = this.isAlertSoundEditMode;
123
+ }
124
+ testAttachedAlertSound() {
125
+ this.stopTestSound();
126
+ if (!this.pendingAlertSoundData) {
127
+ return;
128
+ }
129
+ const blob = base64ToBlob(this.pendingAlertSoundData, this.alertSoundMimeType || 'audio/mpeg');
130
+ const url = URL.createObjectURL(blob);
131
+ const audio = new Audio(url);
132
+ this.testAudio = audio;
133
+ void audio.play().catch(() => {
134
+ showAlertSoundNotice('Could not play sound', 'The browser could not play this file. Try MP3 or WAV.');
135
+ });
136
+ audio.addEventListener('ended', () => {
137
+ URL.revokeObjectURL(url);
138
+ if (this.testAudio === audio) {
139
+ this.testAudio = null;
140
+ }
141
+ }, { once: true });
142
+ }
143
+ stopTestSound() {
144
+ if (!this.testAudio) {
145
+ return;
146
+ }
147
+ this.testAudio.pause();
148
+ const src = this.testAudio.src;
149
+ if (src.startsWith('blob:')) {
150
+ URL.revokeObjectURL(src);
151
+ }
152
+ this.testAudio = null;
153
+ }
154
+ resetAlertSoundState() {
155
+ this.alertSoundFileName = '';
156
+ this.alertSoundMimeType = '';
157
+ this.pendingAlertSoundData = null;
158
+ this.hasAlertSound = false;
159
+ this.alertSoundDirty = false;
160
+ this.clearAlertSound = false;
161
+ if (this.fileInput) {
162
+ this.fileInput.value = '';
163
+ }
164
+ }
165
+ /** Merges pending sound / clear flag onto a create/update DETAILS object. */
166
+ applyAlertSoundToPayload(target) {
167
+ if (this.clearAlertSound) {
168
+ target.CLEAR_ALERT_SOUND = true;
169
+ }
170
+ else if (this.alertSoundDirty && this.pendingAlertSoundData && this.alertSoundFileName) {
171
+ target.ALERT_SOUND_FILE_NAME = this.alertSoundFileName;
172
+ target.ALERT_SOUND_MIME_TYPE = this.alertSoundMimeType;
173
+ target.ALERT_SOUND_DATA = this.pendingAlertSoundData;
174
+ }
175
+ return target;
176
+ }
177
+ /**
178
+ * Keep AlertSoundPlayer's in-memory NOTIFY_ALERT_SOUND cache in sync with
179
+ * what we just saved — otherwise the next alert still plays the old file.
180
+ */
181
+ syncAlertSoundCacheAfterSave() {
182
+ var _a;
183
+ if (this.alertSoundCacheSyncMode === 'all') {
184
+ if (!this.clearAlertSound && !this.alertSoundDirty) {
185
+ return;
186
+ }
187
+ AlertSoundPlayer.shared.clearAllServerSounds();
188
+ return;
189
+ }
190
+ const id = (_a = this.alertSoundEntityId) === null || _a === void 0 ? void 0 : _a.trim();
191
+ if (!id) {
192
+ return;
193
+ }
194
+ if (this.clearAlertSound) {
195
+ AlertSoundPlayer.shared.clearServerSound(id);
196
+ return;
197
+ }
198
+ if (this.alertSoundDirty && this.pendingAlertSoundData) {
199
+ const mimeType = this.alertSoundMimeType || 'audio/mpeg';
200
+ AlertSoundPlayer.shared.setServerSound(id, {
201
+ blob: base64ToBlob(this.pendingAlertSoundData, mimeType),
202
+ fileName: this.alertSoundFileName,
203
+ mimeType,
204
+ });
205
+ }
206
+ }
207
+ }
208
+ __decorate([
209
+ observable
210
+ ], AlertSoundAttachment.prototype, "alertSoundFileName", void 0);
211
+ __decorate([
212
+ observable
213
+ ], AlertSoundAttachment.prototype, "alertSoundMimeType", void 0);
214
+ __decorate([
215
+ observable
216
+ ], AlertSoundAttachment.prototype, "hasAlertSound", void 0);
217
+ return AlertSoundAttachment;
218
+ }