@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.
- package/README.md +39 -0
- package/custom-elements-manifest.config.js +5 -0
- package/dist/custom-elements.json +1771 -321
- package/dist/dts/components/components.d.ts.map +1 -1
- package/dist/dts/components/foundation-inbox/inbox-base/inbox-base.d.ts +2 -0
- package/dist/dts/components/foundation-inbox/inbox-base/inbox-base.d.ts.map +1 -1
- package/dist/dts/components/foundation-inbox/inbox.styles.d.ts.map +1 -1
- package/dist/dts/components/foundation-inbox/inbox.template.d.ts.map +1 -1
- package/dist/dts/components/foundation-inbox/inbox.types.d.ts +4 -1
- package/dist/dts/components/foundation-inbox/inbox.types.d.ts.map +1 -1
- package/dist/dts/components/foundation-notification-dashboard/alert-sound-attachment.d.ts +86 -0
- package/dist/dts/components/foundation-notification-dashboard/alert-sound-attachment.d.ts.map +1 -0
- package/dist/dts/components/foundation-notification-dashboard/components/rules/rule-dialog/rule-dialog.d.ts +39 -1
- package/dist/dts/components/foundation-notification-dashboard/components/rules/rule-dialog/rule-dialog.d.ts.map +1 -1
- package/dist/dts/components/foundation-notification-dashboard/components/rules/rule-dialog/rule-dialog.template.d.ts.map +1 -1
- package/dist/dts/components/foundation-notification-dashboard/components/rules/rule-dialog/rule-dialog.types.d.ts +8 -0
- package/dist/dts/components/foundation-notification-dashboard/components/rules/rule-dialog/rule-dialog.types.d.ts.map +1 -1
- package/dist/dts/components/foundation-notification-dashboard/components/rules/rule-management.d.ts.map +1 -1
- package/dist/dts/components/foundation-notification-dashboard/components/templates/template-dialog/template-dialog.d.ts +41 -1
- package/dist/dts/components/foundation-notification-dashboard/components/templates/template-dialog/template-dialog.d.ts.map +1 -1
- package/dist/dts/components/foundation-notification-dashboard/components/templates/template-dialog/template-dialog.template.d.ts.map +1 -1
- package/dist/dts/components/foundation-notification-dashboard/components/templates/template-dialog/template-dialog.types.d.ts +2 -1
- package/dist/dts/components/foundation-notification-dashboard/components/templates/template-dialog/template-dialog.types.d.ts.map +1 -1
- package/dist/dts/components/foundation-notification-dashboard/components/templates/template-management.d.ts.map +1 -1
- package/dist/dts/components/foundation-notification-dashboard/styles/dynamic-rule.styles.d.ts.map +1 -1
- package/dist/dts/components/foundation-sounds/foundation-sounds-panel.d.ts +73 -0
- package/dist/dts/components/foundation-sounds/foundation-sounds-panel.d.ts.map +1 -0
- package/dist/dts/components/foundation-sounds/foundation-sounds-panel.styles.d.ts +2 -0
- package/dist/dts/components/foundation-sounds/foundation-sounds-panel.styles.d.ts.map +1 -0
- package/dist/dts/components/foundation-sounds/foundation-sounds-panel.template.d.ts +3 -0
- package/dist/dts/components/foundation-sounds/foundation-sounds-panel.template.d.ts.map +1 -0
- package/dist/dts/components/foundation-sounds/foundation-sounds.d.ts +34 -0
- package/dist/dts/components/foundation-sounds/foundation-sounds.d.ts.map +1 -0
- package/dist/dts/components/foundation-sounds/foundation-sounds.styles.d.ts +3 -0
- package/dist/dts/components/foundation-sounds/foundation-sounds.styles.d.ts.map +1 -0
- package/dist/dts/index.d.ts +4 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/react.d.ts +31 -11
- package/dist/dts/services/inbox.service.d.ts +6 -0
- package/dist/dts/services/inbox.service.d.ts.map +1 -1
- package/dist/dts/services/rule.service.d.ts +2 -1
- package/dist/dts/services/rule.service.d.ts.map +1 -1
- package/dist/dts/utils/alert-sound.d.ts +177 -0
- package/dist/dts/utils/alert-sound.d.ts.map +1 -0
- package/dist/dts/utils/alert-sound.test.d.ts +2 -0
- package/dist/dts/utils/alert-sound.test.d.ts.map +1 -0
- package/dist/esm/components/components.js +4 -0
- package/dist/esm/components/foundation-inbox/inbox-base/inbox-base.js +6 -0
- package/dist/esm/components/foundation-inbox/inbox.styles.js +15 -0
- package/dist/esm/components/foundation-inbox/inbox.template.js +16 -0
- package/dist/esm/components/foundation-inbox/inbox.types.js +1 -0
- package/dist/esm/components/foundation-notification-dashboard/alert-sound-attachment.js +218 -0
- package/dist/esm/components/foundation-notification-dashboard/components/rules/rule-dialog/rule-dialog.js +17 -2
- package/dist/esm/components/foundation-notification-dashboard/components/rules/rule-dialog/rule-dialog.template.js +3 -0
- package/dist/esm/components/foundation-notification-dashboard/components/rules/rule-management.js +5 -1
- package/dist/esm/components/foundation-notification-dashboard/components/templates/template-dialog/template-dialog.js +23 -2
- package/dist/esm/components/foundation-notification-dashboard/components/templates/template-dialog/template-dialog.template.js +3 -0
- package/dist/esm/components/foundation-notification-dashboard/components/templates/template-management.js +8 -1
- package/dist/esm/components/foundation-notification-dashboard/styles/dynamic-rule.styles.js +31 -1
- package/dist/esm/components/foundation-sounds/foundation-sounds-panel.js +283 -0
- package/dist/esm/components/foundation-sounds/foundation-sounds-panel.styles.js +200 -0
- package/dist/esm/components/foundation-sounds/foundation-sounds-panel.template.js +179 -0
- package/dist/esm/components/foundation-sounds/foundation-sounds.js +206 -0
- package/dist/esm/components/foundation-sounds/foundation-sounds.styles.js +7 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/services/inbox.service.js +6 -2
- package/dist/esm/services/rule.service.js +35 -0
- package/dist/esm/utils/alert-sound.js +955 -0
- package/dist/esm/utils/alert-sound.test.js +25 -0
- package/dist/esm/utils/index.js +3 -0
- package/dist/pbc-notify-ui.api.json +2859 -332
- package/dist/pbc-notify-ui.d.ts +281 -3
- package/dist/react.cjs +19 -5
- package/dist/react.mjs +16 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +23 -23
- package/src/components/components.ts +4 -0
- package/src/components/foundation-inbox/inbox-base/inbox-base.ts +7 -0
- package/src/components/foundation-inbox/inbox.styles.ts +15 -0
- package/src/components/foundation-inbox/inbox.template.ts +16 -0
- package/src/components/foundation-inbox/inbox.types.ts +3 -0
- package/src/components/foundation-notification-dashboard/alert-sound-attachment.ts +283 -0
- package/src/components/foundation-notification-dashboard/components/rules/rule-dialog/rule-dialog.template.ts +3 -0
- package/src/components/foundation-notification-dashboard/components/rules/rule-dialog/rule-dialog.ts +21 -2
- package/src/components/foundation-notification-dashboard/components/rules/rule-dialog/rule-dialog.types.ts +9 -0
- package/src/components/foundation-notification-dashboard/components/rules/rule-management.ts +5 -1
- package/src/components/foundation-notification-dashboard/components/templates/template-dialog/template-dialog.template.ts +3 -0
- package/src/components/foundation-notification-dashboard/components/templates/template-dialog/template-dialog.ts +25 -2
- package/src/components/foundation-notification-dashboard/components/templates/template-dialog/template-dialog.types.ts +2 -1
- package/src/components/foundation-notification-dashboard/components/templates/template-management.ts +8 -1
- package/src/components/foundation-notification-dashboard/styles/dynamic-rule.styles.ts +31 -1
- package/src/components/foundation-sounds/foundation-sounds-panel.styles.ts +201 -0
- package/src/components/foundation-sounds/foundation-sounds-panel.template.ts +188 -0
- package/src/components/foundation-sounds/foundation-sounds-panel.ts +280 -0
- package/src/components/foundation-sounds/foundation-sounds.styles.ts +8 -0
- package/src/components/foundation-sounds/foundation-sounds.ts +222 -0
- package/src/index.ts +8 -0
- package/src/services/inbox.service.ts +12 -2
- package/src/services/rule.service.ts +37 -1
- package/src/utils/alert-sound.test.ts +32 -0
- package/src/utils/alert-sound.ts +1135 -0
- package/src/utils/index.ts +3 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { css } from '@genesislcap/web-core';
|
|
2
|
+
import { scrollbarStyles } from '../../styles/scrollbar.styles';
|
|
3
|
+
export const FoundationSoundsPanelStyles = css `
|
|
4
|
+
:host {
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
height: 100%;
|
|
8
|
+
min-height: 0;
|
|
9
|
+
width: 100%;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.sounds-controls {
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
flex: 1;
|
|
16
|
+
min-height: 0;
|
|
17
|
+
gap: 12px;
|
|
18
|
+
padding: calc(var(--design-unit) * 3px);
|
|
19
|
+
box-sizing: border-box;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.sounds-help-disclosure {
|
|
23
|
+
flex: 0 0 auto;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.sounds-help-toggle {
|
|
27
|
+
margin-inline-start: calc(var(--design-unit) * 1px);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.sounds-help-toggle::before {
|
|
31
|
+
content: '+';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.sounds-help-disclosure[expanded] .sounds-help-toggle::before {
|
|
35
|
+
content: '−';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.sounds-help {
|
|
39
|
+
margin: 0;
|
|
40
|
+
font-size: var(--type-ramp-minus-1-font-size);
|
|
41
|
+
color: var(--neutral-foreground-hint);
|
|
42
|
+
text-align: left;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.volume-row {
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
flex: 0 0 auto;
|
|
49
|
+
gap: calc(var(--design-unit) * 2px);
|
|
50
|
+
min-width: 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.volume-icon {
|
|
54
|
+
flex: 0 0 auto;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.volume-label {
|
|
58
|
+
flex: 0 0 auto;
|
|
59
|
+
font-size: var(--type-ramp-minus-1-font-size);
|
|
60
|
+
color: var(--neutral-foreground-hint);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.volume-slider {
|
|
64
|
+
flex: 1 1 auto;
|
|
65
|
+
min-width: 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.volume-percent {
|
|
69
|
+
flex: 0 0 auto;
|
|
70
|
+
min-width: 3ch;
|
|
71
|
+
text-align: end;
|
|
72
|
+
font-size: var(--type-ramp-minus-1-font-size);
|
|
73
|
+
color: var(--neutral-foreground-hint);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.mute-all {
|
|
77
|
+
flex: 0 0 auto;
|
|
78
|
+
margin: 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.sound-rules-panel {
|
|
82
|
+
flex: 1 1 auto;
|
|
83
|
+
min-height: 0;
|
|
84
|
+
overflow: auto;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.sound-rules {
|
|
88
|
+
display: flex;
|
|
89
|
+
flex-direction: column;
|
|
90
|
+
gap: 12px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.sound-rule {
|
|
94
|
+
display: flex;
|
|
95
|
+
flex-direction: column;
|
|
96
|
+
gap: 8px;
|
|
97
|
+
padding: 12px;
|
|
98
|
+
border: calc(var(--stroke-width) * 1px) solid var(--neutral-stroke-divider-rest);
|
|
99
|
+
border-radius: calc(var(--control-corner-radius) * 1px);
|
|
100
|
+
|
|
101
|
+
/* Keep absolute listboxes from being clipped by the card itself. */
|
|
102
|
+
overflow: visible;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.sound-rule-header {
|
|
106
|
+
display: flex;
|
|
107
|
+
align-items: center;
|
|
108
|
+
justify-content: space-between;
|
|
109
|
+
font-weight: 700;
|
|
110
|
+
font-size: var(--type-ramp-minus-1-font-size);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.sound-rule-actions {
|
|
114
|
+
display: flex;
|
|
115
|
+
align-items: center;
|
|
116
|
+
gap: 2px;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.sound-rule-label {
|
|
120
|
+
display: flex;
|
|
121
|
+
flex-direction: column;
|
|
122
|
+
gap: 4px;
|
|
123
|
+
font-size: var(--type-ramp-minus-1-font-size);
|
|
124
|
+
color: var(--neutral-foreground-hint);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.pattern-input,
|
|
128
|
+
.match-mode-select,
|
|
129
|
+
.rule-select,
|
|
130
|
+
.sound-file-input {
|
|
131
|
+
width: 100%;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/*
|
|
135
|
+
* Anchor the listbox to the select host and use position:fixed so it escapes
|
|
136
|
+
* .sound-rules-panel overflow:auto (and the rule card). Each select sets a
|
|
137
|
+
* unique --sounds-select-anchor / anchor-name via inline style.
|
|
138
|
+
* !important beats FAST's internal absolute top/bottom for [open] selects.
|
|
139
|
+
*/
|
|
140
|
+
.match-mode-select[open]::part(listbox),
|
|
141
|
+
.rule-select[open]::part(listbox) {
|
|
142
|
+
position: fixed !important;
|
|
143
|
+
/* stylelint-disable-next-line property-no-unknown -- CSS anchor positioning */
|
|
144
|
+
position-anchor: var(--sounds-select-anchor);
|
|
145
|
+
inset: auto !important;
|
|
146
|
+
top: anchor(bottom) !important;
|
|
147
|
+
left: anchor(left) !important;
|
|
148
|
+
right: anchor(right) !important;
|
|
149
|
+
bottom: auto !important;
|
|
150
|
+
width: auto !important;
|
|
151
|
+
margin-top: calc(var(--design-unit) * 1px);
|
|
152
|
+
z-index: 10000;
|
|
153
|
+
max-height: min(240px, 40vh);
|
|
154
|
+
overflow-x: hidden;
|
|
155
|
+
overflow-y: auto;
|
|
156
|
+
box-shadow: 0 4px 16px rgb(0 0 0 / 24%);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.match-mode-select[open][position='above']::part(listbox),
|
|
160
|
+
.rule-select[open][position='above']::part(listbox) {
|
|
161
|
+
top: auto !important;
|
|
162
|
+
bottom: anchor(top) !important;
|
|
163
|
+
margin-top: 0;
|
|
164
|
+
margin-bottom: calc(var(--design-unit) * 1px);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.sound-file-row {
|
|
168
|
+
display: flex;
|
|
169
|
+
align-items: center;
|
|
170
|
+
gap: 4px;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.sound-file-row .sound-file-input {
|
|
174
|
+
flex: 1;
|
|
175
|
+
min-width: 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.choose-sound {
|
|
179
|
+
cursor: pointer;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.sound-rules-footer {
|
|
183
|
+
display: flex;
|
|
184
|
+
align-items: center;
|
|
185
|
+
justify-content: space-between;
|
|
186
|
+
flex: 0 0 auto;
|
|
187
|
+
gap: 8px;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.rule-count {
|
|
191
|
+
font-size: var(--type-ramp-minus-1-font-size);
|
|
192
|
+
color: var(--neutral-foreground-hint);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.file-input {
|
|
196
|
+
display: none;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
${scrollbarStyles}
|
|
200
|
+
`;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { html, ref, repeat } from '@genesislcap/web-core';
|
|
2
|
+
export const FoundationSoundsPanelTemplate = html `
|
|
3
|
+
<div class="sounds-controls" part="controls">
|
|
4
|
+
<input
|
|
5
|
+
${ref('fileInput')}
|
|
6
|
+
class="file-input"
|
|
7
|
+
type="file"
|
|
8
|
+
accept="audio/*"
|
|
9
|
+
@change=${(x, c) => x.onFileSelected(c.event)}
|
|
10
|
+
/>
|
|
11
|
+
<rapid-disclosure class="sounds-help-disclosure" appearance="lightweight">
|
|
12
|
+
<span slot="title">
|
|
13
|
+
Sound Alerts Information
|
|
14
|
+
<span class="sounds-help-toggle" aria-hidden="true"></span>
|
|
15
|
+
</span>
|
|
16
|
+
<p class="sounds-help">
|
|
17
|
+
Add up to ${(x) => x.maxRules} sounds.
|
|
18
|
+
<br />
|
|
19
|
+
Match text in MESSAGE or HEADER (a word or phrase), or a notification rule (including rules
|
|
20
|
+
created when you subscribe to a template).
|
|
21
|
+
<br />
|
|
22
|
+
The first matching rule is played.
|
|
23
|
+
</p>
|
|
24
|
+
</rapid-disclosure>
|
|
25
|
+
<div class="volume-row">
|
|
26
|
+
<rapid-icon
|
|
27
|
+
class="volume-icon"
|
|
28
|
+
name=${(x) => (x.muted || x.volumePercent === 0 ? 'volume-xmark' : 'volume-high')}
|
|
29
|
+
></rapid-icon>
|
|
30
|
+
<span class="volume-label">Volume</span>
|
|
31
|
+
<rapid-slider
|
|
32
|
+
class="volume-slider"
|
|
33
|
+
min="0"
|
|
34
|
+
max="100"
|
|
35
|
+
step="1"
|
|
36
|
+
:value=${(x) => String(x.volumePercent)}
|
|
37
|
+
@change=${(x, c) => x.onVolumeChange(c.event)}
|
|
38
|
+
></rapid-slider>
|
|
39
|
+
<span class="volume-percent">${(x) => `${x.volumePercent}%`}</span>
|
|
40
|
+
<rapid-button
|
|
41
|
+
class="mute-all"
|
|
42
|
+
appearance="lightweight"
|
|
43
|
+
title=${(x) => (x.muted ? 'Unmute alert sounds' : 'Mute all alert sounds')}
|
|
44
|
+
@click=${(x) => x.toggleMute()}
|
|
45
|
+
>
|
|
46
|
+
${(x) => (x.muted ? 'Unmute' : 'Mute all')}
|
|
47
|
+
</rapid-button>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="sound-rules-panel">
|
|
50
|
+
<div class="sound-rules">
|
|
51
|
+
${repeat((x) => x.rules, html `
|
|
52
|
+
<div class="sound-rule" data-rule-index=${(x) => x.index}>
|
|
53
|
+
<div class="sound-rule-header">
|
|
54
|
+
<span>Rule ${(x) => x.index + 1}</span>
|
|
55
|
+
<div class="sound-rule-actions">
|
|
56
|
+
<rapid-button
|
|
57
|
+
class="mute-rule"
|
|
58
|
+
appearance="stealth"
|
|
59
|
+
title=${(x) => (x.muted ? 'Unmute this rule' : 'Mute this rule')}
|
|
60
|
+
@click=${(x, c) => c.parent.toggleRuleMute(x.index)}
|
|
61
|
+
>
|
|
62
|
+
<rapid-icon
|
|
63
|
+
name=${(x) => (x.muted ? 'volume-xmark' : 'volume-high')}
|
|
64
|
+
></rapid-icon>
|
|
65
|
+
</rapid-button>
|
|
66
|
+
<rapid-button
|
|
67
|
+
class="remove-rule"
|
|
68
|
+
appearance="stealth"
|
|
69
|
+
title=${(_, c) => (c.parent.canRemoveRule ? 'Remove rule' : 'Reset rule')}
|
|
70
|
+
@click=${(x, c) => c.parent.removeRule(x.index)}
|
|
71
|
+
>
|
|
72
|
+
<rapid-icon name="trash"></rapid-icon>
|
|
73
|
+
</rapid-button>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
<label class="sound-rule-label">
|
|
77
|
+
Match by
|
|
78
|
+
<rapid-select
|
|
79
|
+
class="match-mode-select"
|
|
80
|
+
style=${(x) => `anchor-name: --sound-match-${x.index}; --sounds-select-anchor: --sound-match-${x.index}`}
|
|
81
|
+
:value=${(x) => x.matchMode}
|
|
82
|
+
@change=${(x, c) => c.parent.onMatchModeChange(x.index, c.event)}
|
|
83
|
+
>
|
|
84
|
+
<rapid-option value="message">MESSAGE text</rapid-option>
|
|
85
|
+
<rapid-option value="header">HEADER text</rapid-option>
|
|
86
|
+
<rapid-option value="rule">Notification rule</rapid-option>
|
|
87
|
+
</rapid-select>
|
|
88
|
+
</label>
|
|
89
|
+
<label
|
|
90
|
+
class="sound-rule-label"
|
|
91
|
+
style=${(x) => (x.matchMode === 'rule' ? 'display: none;' : '')}
|
|
92
|
+
>
|
|
93
|
+
${(x) => (x.matchMode === 'header' ? 'HEADER text' : 'MESSAGE text')}
|
|
94
|
+
<rapid-text-field
|
|
95
|
+
class="pattern-input"
|
|
96
|
+
placeholder=${(x) => x.matchMode === 'header' ? 'e.g. Price alert' : 'e.g. limit breached'}
|
|
97
|
+
:value=${(x) => x.pattern}
|
|
98
|
+
@input=${(x, c) => c.parent.onPatternInput(x.index, c.event)}
|
|
99
|
+
></rapid-text-field>
|
|
100
|
+
</label>
|
|
101
|
+
<label
|
|
102
|
+
class="sound-rule-label"
|
|
103
|
+
style=${(x) => (x.matchMode === 'rule' ? '' : 'display: none;')}
|
|
104
|
+
>
|
|
105
|
+
Notification rule
|
|
106
|
+
<rapid-select
|
|
107
|
+
class="rule-select"
|
|
108
|
+
style=${(x) => `anchor-name: --sound-rule-${x.index}; --sounds-select-anchor: --sound-rule-${x.index}`}
|
|
109
|
+
:value=${(x) => { var _a; return (_a = x.templateId) !== null && _a !== void 0 ? _a : ''; }}
|
|
110
|
+
@change=${(x, c) => c.parent.onRuleChange(x.index, c.event)}
|
|
111
|
+
>
|
|
112
|
+
<options-datasource
|
|
113
|
+
value-field="id"
|
|
114
|
+
label-field="name"
|
|
115
|
+
option-element="rapid-option"
|
|
116
|
+
:data=${(_, c) => c.parent.ruleSelectOptions}
|
|
117
|
+
></options-datasource>
|
|
118
|
+
</rapid-select>
|
|
119
|
+
</label>
|
|
120
|
+
<label class="sound-rule-label">
|
|
121
|
+
Sound file
|
|
122
|
+
<div class="sound-file-row">
|
|
123
|
+
<rapid-text-field
|
|
124
|
+
class="sound-file-input"
|
|
125
|
+
readonly
|
|
126
|
+
placeholder="No sound selected"
|
|
127
|
+
:value=${(x) => { var _a; return (_a = x.fileName) !== null && _a !== void 0 ? _a : ''; }}
|
|
128
|
+
title=${(x) => { var _a; return (_a = x.fileName) !== null && _a !== void 0 ? _a : 'No sound selected'; }}
|
|
129
|
+
>
|
|
130
|
+
<rapid-icon
|
|
131
|
+
slot="end"
|
|
132
|
+
class="choose-sound"
|
|
133
|
+
name="folder-open"
|
|
134
|
+
title="Choose alert sound"
|
|
135
|
+
@click=${(x, c) => {
|
|
136
|
+
c.event.stopPropagation();
|
|
137
|
+
return c.parent.openFilePicker(x.index);
|
|
138
|
+
}}
|
|
139
|
+
></rapid-icon>
|
|
140
|
+
</rapid-text-field>
|
|
141
|
+
<rapid-button
|
|
142
|
+
class="test-sound"
|
|
143
|
+
appearance="stealth"
|
|
144
|
+
title="Test play this sound"
|
|
145
|
+
?disabled=${(x) => !x.hasSound}
|
|
146
|
+
@click=${(x, c) => c.parent.testSound(x.index)}
|
|
147
|
+
>
|
|
148
|
+
<rapid-icon name="play"></rapid-icon>
|
|
149
|
+
</rapid-button>
|
|
150
|
+
<rapid-button
|
|
151
|
+
class="clear-sound"
|
|
152
|
+
appearance="stealth"
|
|
153
|
+
title="Clear sound file"
|
|
154
|
+
?disabled=${(x) => !x.hasSound}
|
|
155
|
+
@click=${(x, c) => c.parent.clearSound(x.index)}
|
|
156
|
+
>
|
|
157
|
+
<rapid-icon name="times"></rapid-icon>
|
|
158
|
+
</rapid-button>
|
|
159
|
+
</div>
|
|
160
|
+
</label>
|
|
161
|
+
</div>
|
|
162
|
+
`)}
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
<div class="sound-rules-footer">
|
|
166
|
+
<rapid-button
|
|
167
|
+
class="add-rule"
|
|
168
|
+
appearance="lightweight"
|
|
169
|
+
title="Add sound rule"
|
|
170
|
+
?disabled=${(x) => !x.canAddRule}
|
|
171
|
+
@click=${(x) => x.addRule()}
|
|
172
|
+
>
|
|
173
|
+
<rapid-icon name="plus"></rapid-icon>
|
|
174
|
+
Add rule
|
|
175
|
+
</rapid-button>
|
|
176
|
+
<span class="rule-count">${(x) => `${x.rules.length} / ${x.maxRules}`}</span>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
`;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { __awaiter, __decorate } from "tslib";
|
|
2
|
+
import { Connect } from '@genesislcap/foundation-comms';
|
|
3
|
+
import { attr, customElement, GenesisElement, html } from '@genesislcap/web-core';
|
|
4
|
+
import { FoundationInboxService } from '../../services/inbox.service';
|
|
5
|
+
import { RuleService } from '../../services/rule.service';
|
|
6
|
+
import { AlertSoundPlayer, base64ToBlob, getAlertDynamicRuleId, getAlertHeader, getAlertId, getAlertMessage, } from '../../utils/alert-sound';
|
|
7
|
+
import { logger } from '../../utils/logger';
|
|
8
|
+
import { AlertStatus } from '../foundation-inbox/inbox.types';
|
|
9
|
+
import { FoundationSoundsStyles } from './foundation-sounds.styles';
|
|
10
|
+
/**
|
|
11
|
+
* Caps the NEW-alert snapshot used to prime known IDs. Realtime inserts are
|
|
12
|
+
* still prepended by the inbox service after this ceiling.
|
|
13
|
+
*/
|
|
14
|
+
const SOUNDS_MAX_ROWS = 100;
|
|
15
|
+
/**
|
|
16
|
+
* Headless listener for inbox alert inserts. Plays configured sounds via the
|
|
17
|
+
* shared AlertSoundPlayer. Mount once in the app layout.
|
|
18
|
+
*
|
|
19
|
+
* Configure rules in the inbox Sounds tab with {@link FoundationSoundsPanel}.
|
|
20
|
+
*
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
23
|
+
let FoundationSounds = class FoundationSounds extends GenesisElement {
|
|
24
|
+
constructor() {
|
|
25
|
+
super(...arguments);
|
|
26
|
+
/**
|
|
27
|
+
* When false, new audible alerts are tracked but do not play.
|
|
28
|
+
*/
|
|
29
|
+
this.soundEnabled = true;
|
|
30
|
+
this.player = AlertSoundPlayer.shared;
|
|
31
|
+
this.knownAlertIds = new Set();
|
|
32
|
+
/** In-flight server sound fetches keyed by DYNAMIC_RULE_ID. */
|
|
33
|
+
this.serverSoundFetches = new Map();
|
|
34
|
+
this.subscriptionHandler = ({ store }) => {
|
|
35
|
+
var _a;
|
|
36
|
+
const items = (store === null || store === void 0 ? void 0 : store.items) || [];
|
|
37
|
+
const current = [];
|
|
38
|
+
for (const item of items) {
|
|
39
|
+
const id = getAlertId(item);
|
|
40
|
+
if (!id) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
current.push({
|
|
44
|
+
id,
|
|
45
|
+
message: getAlertMessage(item),
|
|
46
|
+
header: getAlertHeader(item),
|
|
47
|
+
dynamicRuleId: getAlertDynamicRuleId(item),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// SEQUENCE_ID <= 1 is (part of) the initial snapshot rather than a
|
|
51
|
+
// real-time update — same boundary as `notification-listener`. A
|
|
52
|
+
// first-callback latch instead would misfire on a rowless bookkeeping
|
|
53
|
+
// message (SOURCE_REF/MORE_ROWS only), or if the snapshot itself arrives
|
|
54
|
+
// split across more than one message.
|
|
55
|
+
if (((_a = store === null || store === void 0 ? void 0 : store.sequenceId) !== null && _a !== void 0 ? _a : 0) <= 1) {
|
|
56
|
+
for (const row of current) {
|
|
57
|
+
this.knownAlertIds.add(row.id);
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (!this.soundEnabled) {
|
|
62
|
+
for (const row of current) {
|
|
63
|
+
this.knownAlertIds.add(row.id);
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// Collect new rows first so known IDs stay in sync even while async
|
|
68
|
+
// server-sound fetches run. Play at most once per batch: user Sounds-tab
|
|
69
|
+
// match first, else server sound on DYNAMIC_RULE_ID — only latch after a
|
|
70
|
+
// successful play so a failed/in-flight fetch can fall through to later rows.
|
|
71
|
+
const newRows = [];
|
|
72
|
+
for (const row of current) {
|
|
73
|
+
if (this.knownAlertIds.has(row.id)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
this.knownAlertIds.add(row.id);
|
|
77
|
+
newRows.push(row);
|
|
78
|
+
}
|
|
79
|
+
if (newRows.length) {
|
|
80
|
+
void this.playForNewAlerts(newRows);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
connectedCallback() {
|
|
85
|
+
const _super = Object.create(null, {
|
|
86
|
+
connectedCallback: { get: () => super.connectedCallback }
|
|
87
|
+
});
|
|
88
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
89
|
+
_super.connectedCallback.call(this);
|
|
90
|
+
yield this.player.restore();
|
|
91
|
+
// The await above can outlive this element (mount → unmount → mount, e.g.
|
|
92
|
+
// via PBCElementsRenderer re-rendering its layout target). Without this
|
|
93
|
+
// guard we'd wire up a subscription nothing will ever tear down, and a
|
|
94
|
+
// later mount adds a second one — two overlapping sounds per alert.
|
|
95
|
+
if (!this.isConnected) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
this.connectionSub = this.connect.isConnected$.subscribe((isConnected) => {
|
|
99
|
+
if (isConnected) {
|
|
100
|
+
this.subscribeToAlerts();
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
disconnectedCallback() {
|
|
106
|
+
var _a;
|
|
107
|
+
super.disconnectedCallback();
|
|
108
|
+
this.inboxService.unsubscribe(this.subscriptionHandler);
|
|
109
|
+
(_a = this.connectionSub) === null || _a === void 0 ? void 0 : _a.unsubscribe();
|
|
110
|
+
this.connectionSub = undefined;
|
|
111
|
+
}
|
|
112
|
+
subscribeToAlerts() {
|
|
113
|
+
this.inboxService.unsubscribe(this.subscriptionHandler);
|
|
114
|
+
this.knownAlertIds = new Set();
|
|
115
|
+
this.inboxService.subscribe(this.subscriptionHandler, false, `ALERT_STATUS=='${AlertStatus.NEW}'`, SOUNDS_MAX_ROWS, 'ALERT_ID,MESSAGE,HEADER,DYNAMIC_RULE_ID');
|
|
116
|
+
}
|
|
117
|
+
playForNewAlerts(rows) {
|
|
118
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
119
|
+
for (const row of rows) {
|
|
120
|
+
if (this.player.playForAlert(row)) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (row.dynamicRuleId && !this.player.isMuted) {
|
|
124
|
+
// Sequential: stop on first successful play; do not race later rows.
|
|
125
|
+
// eslint-disable-next-line no-await-in-loop -- try each alert until one sound plays
|
|
126
|
+
if (yield this.tryPlayServerSound(row.dynamicRuleId)) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
tryPlayServerSound(dynamicRuleId) {
|
|
134
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
135
|
+
const id = dynamicRuleId === null || dynamicRuleId === void 0 ? void 0 : dynamicRuleId.trim();
|
|
136
|
+
if (!id || this.player.isMuted) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
if (this.player.hasServerSound(id)) {
|
|
140
|
+
return this.player.playServerSound(id);
|
|
141
|
+
}
|
|
142
|
+
let fetchPromise = this.serverSoundFetches.get(id);
|
|
143
|
+
if (!fetchPromise) {
|
|
144
|
+
fetchPromise = this.fetchAndCacheServerSound(id);
|
|
145
|
+
this.serverSoundFetches.set(id, fetchPromise);
|
|
146
|
+
try {
|
|
147
|
+
return yield fetchPromise;
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
this.serverSoundFetches.delete(id);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return fetchPromise;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
fetchAndCacheServerSound(dynamicRuleId) {
|
|
157
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
158
|
+
const sound = yield this.ruleService.getAlertSound(dynamicRuleId);
|
|
159
|
+
if (!(sound === null || sound === void 0 ? void 0 : sound.SOUND_DATA)) {
|
|
160
|
+
// Rule may have cleared its sound since we last played — drop stale cache.
|
|
161
|
+
this.player.clearServerSound(dynamicRuleId);
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
const mimeType = sound.ALERT_SOUND_MIME_TYPE || 'audio/mpeg';
|
|
165
|
+
let blob;
|
|
166
|
+
try {
|
|
167
|
+
blob = base64ToBlob(sound.SOUND_DATA, mimeType);
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
// Malformed base64 must not reject playForNewAlerts — that aborts the
|
|
171
|
+
// rest of the batch as an unhandled rejection.
|
|
172
|
+
logger.error(`Invalid alert sound data for rule ${dynamicRuleId}`, error);
|
|
173
|
+
this.player.clearServerSound(dynamicRuleId);
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
this.player.setServerSound(dynamicRuleId, {
|
|
177
|
+
blob,
|
|
178
|
+
fileName: sound.ALERT_SOUND_FILE_NAME,
|
|
179
|
+
mimeType,
|
|
180
|
+
});
|
|
181
|
+
return this.player.playServerSound(dynamicRuleId);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
__decorate([
|
|
186
|
+
Connect
|
|
187
|
+
], FoundationSounds.prototype, "connect", void 0);
|
|
188
|
+
__decorate([
|
|
189
|
+
FoundationInboxService
|
|
190
|
+
], FoundationSounds.prototype, "inboxService", void 0);
|
|
191
|
+
__decorate([
|
|
192
|
+
RuleService
|
|
193
|
+
], FoundationSounds.prototype, "ruleService", void 0);
|
|
194
|
+
__decorate([
|
|
195
|
+
attr({ attribute: 'sound-enabled', mode: 'boolean' })
|
|
196
|
+
], FoundationSounds.prototype, "soundEnabled", void 0);
|
|
197
|
+
FoundationSounds = __decorate([
|
|
198
|
+
customElement({
|
|
199
|
+
name: 'foundation-sounds',
|
|
200
|
+
template: html `
|
|
201
|
+
<slot></slot>
|
|
202
|
+
`,
|
|
203
|
+
styles: FoundationSoundsStyles,
|
|
204
|
+
})
|
|
205
|
+
], FoundationSounds);
|
|
206
|
+
export { FoundationSounds };
|
package/dist/esm/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export * from './components/foundation-inbox/inbox';
|
|
2
2
|
export * from './components/foundation-inbox/components/foundation-inbox-flyout/foundation-inbox-flyout';
|
|
3
3
|
export * from './components/foundation-inbox/components/foundation-inbox-counter/foundation-inbox-counter';
|
|
4
|
+
export * from './components/foundation-sounds/foundation-sounds';
|
|
5
|
+
export * from './components/foundation-sounds/foundation-sounds-panel';
|
|
4
6
|
export * from './components/foundation-notification-dashboard';
|
|
7
|
+
export { AlertSoundPlayer, MAX_SOUND_RULES, MAX_SOUND_FILE_BYTES } from './utils/alert-sound';
|
|
5
8
|
export { AlertStatus } from './components/foundation-inbox/inbox.types';
|
|
@@ -15,6 +15,7 @@ class FoundationInboxServiceImpl {
|
|
|
15
15
|
this.store = {
|
|
16
16
|
items: [],
|
|
17
17
|
moreRows: false,
|
|
18
|
+
sequenceId: 0,
|
|
18
19
|
};
|
|
19
20
|
}
|
|
20
21
|
getStream(reverse, criteriaMatch, maxRows, fields) {
|
|
@@ -27,6 +28,9 @@ class FoundationInboxServiceImpl {
|
|
|
27
28
|
if (result.SOURCE_REF) {
|
|
28
29
|
this.sourceRef = result.SOURCE_REF;
|
|
29
30
|
}
|
|
31
|
+
if (typeof result.SEQUENCE_ID === 'number') {
|
|
32
|
+
this.store.sequenceId = result.SEQUENCE_ID;
|
|
33
|
+
}
|
|
30
34
|
this.store.moreRows = Boolean(result.MORE_ROWS);
|
|
31
35
|
// getMoreRows batches continue the existing order, so append them
|
|
32
36
|
// rather than prepending like real-time inserts.
|
|
@@ -61,13 +65,13 @@ class FoundationInboxServiceImpl {
|
|
|
61
65
|
const found = this.subscriptions.splice(index, 1);
|
|
62
66
|
found.forEach((subscription) => {
|
|
63
67
|
subscription.socketSubscription.unsubscribe();
|
|
64
|
-
subscription.callback({ store: { items: [], moreRows: false } });
|
|
68
|
+
subscription.callback({ store: { items: [], moreRows: false, sequenceId: 0 } });
|
|
65
69
|
});
|
|
66
70
|
}
|
|
67
71
|
this.stream = undefined;
|
|
68
72
|
this.sourceRef = null;
|
|
69
73
|
this.fetchingMore = false;
|
|
70
|
-
this.store = { items: [], moreRows: false };
|
|
74
|
+
this.store = { items: [], moreRows: false, sequenceId: 0 };
|
|
71
75
|
}
|
|
72
76
|
}
|
|
73
77
|
__decorate([
|
|
@@ -21,6 +21,41 @@ class RuleServiceImpl {
|
|
|
21
21
|
return response.REPLY[0];
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
|
+
getAlertSound(dynamicRuleId) {
|
|
25
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
26
|
+
var _a, _b, _c, _d, _e, _f;
|
|
27
|
+
if (!dynamicRuleId) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const response = yield this.connect.request('REQ_NOTIFY_ALERT_SOUND', {
|
|
32
|
+
REQUEST: {
|
|
33
|
+
DYNAMIC_RULE_ID: dynamicRuleId,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
const reply = response === null || response === void 0 ? void 0 : response.REPLY;
|
|
37
|
+
if (!Array.isArray(reply) || !reply.length) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const row = reply[0];
|
|
41
|
+
const soundData = (_a = row.SOUND_DATA) !== null && _a !== void 0 ? _a : row.soundData;
|
|
42
|
+
if (!soundData) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
ALERT_SOUND_ID: (_b = row.ALERT_SOUND_ID) !== null && _b !== void 0 ? _b : row.alertSoundId,
|
|
47
|
+
DYNAMIC_RULE_ID: (_d = (_c = row.DYNAMIC_RULE_ID) !== null && _c !== void 0 ? _c : row.dynamicRuleId) !== null && _d !== void 0 ? _d : dynamicRuleId,
|
|
48
|
+
ALERT_SOUND_FILE_NAME: (_e = row.ALERT_SOUND_FILE_NAME) !== null && _e !== void 0 ? _e : row.alertSoundFileName,
|
|
49
|
+
ALERT_SOUND_MIME_TYPE: (_f = row.ALERT_SOUND_MIME_TYPE) !== null && _f !== void 0 ? _f : row.alertSoundMimeType,
|
|
50
|
+
SOUND_DATA: soundData,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error('Failed to fetch alert sound for rule', dynamicRuleId, error);
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
24
59
|
createRule(rule) {
|
|
25
60
|
return __awaiter(this, void 0, void 0, function* () {
|
|
26
61
|
return this.connect.commitEvent('EVENT_NOTIFICATION_RULE_CREATE', {
|