@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
|
@@ -261,6 +261,14 @@ export const FoundationInboxTemplate = html<FoundationInbox>`
|
|
|
261
261
|
</rapid-tab>
|
|
262
262
|
`,
|
|
263
263
|
)}
|
|
264
|
+
<rapid-tab
|
|
265
|
+
slot="sounds-tab"
|
|
266
|
+
id=${InboxTab.SOUNDS}
|
|
267
|
+
@click=${(x) => x.inboxTabChanged(InboxTab.SOUNDS)}
|
|
268
|
+
appearance="secondary"
|
|
269
|
+
>
|
|
270
|
+
Sounds
|
|
271
|
+
</rapid-tab>
|
|
264
272
|
|
|
265
273
|
<!-- Alerts -->
|
|
266
274
|
${(_) => alertsTab(InboxTab.ALERTS_NEW)} ${(_) => alertsTab(InboxTab.ALERT_HISTORY)}
|
|
@@ -372,6 +380,14 @@ export const FoundationInboxTemplate = html<FoundationInbox>`
|
|
|
372
380
|
</rapid-tab-panel>
|
|
373
381
|
`,
|
|
374
382
|
)}
|
|
383
|
+
<rapid-tab-panel
|
|
384
|
+
slot="sounds-tab"
|
|
385
|
+
class="sounds-tab-panel"
|
|
386
|
+
id=${InboxTab.SOUNDS}
|
|
387
|
+
style=${(x) => (x.selectedTab === InboxTab.SOUNDS ? '' : 'display: none;')}
|
|
388
|
+
>
|
|
389
|
+
<foundation-sounds-panel ${ref('sounds')}></foundation-sounds-panel>
|
|
390
|
+
</rapid-tab-panel>
|
|
375
391
|
</rapid-tabs>
|
|
376
392
|
</div>
|
|
377
393
|
`;
|
|
@@ -9,6 +9,8 @@ export interface Alert {
|
|
|
9
9
|
NOTIFY_SEVERITY: string;
|
|
10
10
|
TABLE_ENTITY_ID: string;
|
|
11
11
|
TOPIC: string;
|
|
12
|
+
/** Source notify template/rule that produced this alert. */
|
|
13
|
+
DYNAMIC_RULE_ID?: string;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export enum AlertStatus {
|
|
@@ -87,4 +89,5 @@ export enum InboxTab {
|
|
|
87
89
|
ALERTS_NEW,
|
|
88
90
|
ALERT_HISTORY,
|
|
89
91
|
SUBSCRIBE,
|
|
92
|
+
SOUNDS,
|
|
90
93
|
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { showNotification } from '@genesislcap/foundation-notifications';
|
|
2
|
+
import { GenesisElement, html, observable, ref, when } from '@genesislcap/web-core';
|
|
3
|
+
import type { RuleService } from '../../services/rule.service';
|
|
4
|
+
import {
|
|
5
|
+
AlertSoundPlayer,
|
|
6
|
+
base64ToBlob,
|
|
7
|
+
blobToBase64,
|
|
8
|
+
formatBytes,
|
|
9
|
+
MAX_SOUND_FILE_BYTES,
|
|
10
|
+
resolveAudioMimeType,
|
|
11
|
+
} from '../../utils/alert-sound';
|
|
12
|
+
|
|
13
|
+
type Constructor<T = object> = abstract new (...args: any[]) => T;
|
|
14
|
+
|
|
15
|
+
/** Sound fields included on rule/template create & update event payloads. */
|
|
16
|
+
export type AlertSoundPayloadFields = {
|
|
17
|
+
ALERT_SOUND_FILE_NAME?: string;
|
|
18
|
+
ALERT_SOUND_MIME_TYPE?: string;
|
|
19
|
+
ALERT_SOUND_DATA?: string;
|
|
20
|
+
CLEAR_ALERT_SOUND?: boolean;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function showAlertSoundNotice(title: string, body: string) {
|
|
24
|
+
showNotification(
|
|
25
|
+
{
|
|
26
|
+
title,
|
|
27
|
+
body,
|
|
28
|
+
config: {
|
|
29
|
+
snackbar: {
|
|
30
|
+
type: 'error',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
'rapid',
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Host surface required by {@link alertSoundAttachmentTemplate} and the mixin.
|
|
40
|
+
* @public
|
|
41
|
+
*/
|
|
42
|
+
export interface AlertSoundAttachmentHost {
|
|
43
|
+
alertSoundFileName: string;
|
|
44
|
+
hasAlertSound: boolean;
|
|
45
|
+
fileInput: HTMLInputElement;
|
|
46
|
+
openAlertSoundPicker(): void;
|
|
47
|
+
onAlertSoundSelected(event: Event): void | Promise<void>;
|
|
48
|
+
testAttachedAlertSound(): void;
|
|
49
|
+
clearAttachedAlertSound(): void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Shared Attach/Replace/Test/Clear UI for rule and template dialogs.
|
|
54
|
+
* @public
|
|
55
|
+
*/
|
|
56
|
+
export const alertSoundAttachmentTemplate = html<AlertSoundAttachmentHost>`
|
|
57
|
+
<div class="alert-sound">
|
|
58
|
+
<input
|
|
59
|
+
${ref('fileInput')}
|
|
60
|
+
class="file-input"
|
|
61
|
+
type="file"
|
|
62
|
+
accept="audio/*"
|
|
63
|
+
@change=${(x, c) => x.onAlertSoundSelected(c.event)}
|
|
64
|
+
/>
|
|
65
|
+
<label>Alert sound</label>
|
|
66
|
+
<div class="alert-sound-row">
|
|
67
|
+
<span class="alert-sound-name" title=${(x) => x.alertSoundFileName || ''}>
|
|
68
|
+
${(x) => x.alertSoundFileName || 'No sound attached'}
|
|
69
|
+
</span>
|
|
70
|
+
<rapid-button appearance="lightweight" @click=${(x) => x.openAlertSoundPicker()}>
|
|
71
|
+
${(x) => (x.hasAlertSound ? 'Replace' : 'Attach')}
|
|
72
|
+
</rapid-button>
|
|
73
|
+
${when(
|
|
74
|
+
(x) => x.hasAlertSound,
|
|
75
|
+
html<AlertSoundAttachmentHost>`
|
|
76
|
+
<rapid-button appearance="lightweight" @click=${(x) => x.testAttachedAlertSound()}>
|
|
77
|
+
Test
|
|
78
|
+
</rapid-button>
|
|
79
|
+
<rapid-button appearance="lightweight" @click=${(x) => x.clearAttachedAlertSound()}>
|
|
80
|
+
Clear
|
|
81
|
+
</rapid-button>
|
|
82
|
+
`,
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
`;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Mixin: pick / test / clear / load server sound + apply fields to submit payload.
|
|
90
|
+
*
|
|
91
|
+
* Subclasses must provide {@link ruleService}, {@link isAlertSoundEditMode},
|
|
92
|
+
* {@link alertSoundEntityId}, and {@link alertSoundCacheSyncMode}.
|
|
93
|
+
*
|
|
94
|
+
* @public
|
|
95
|
+
*/
|
|
96
|
+
export function AlertSoundAttachmentMixin<TBase extends Constructor<GenesisElement>>(Base: TBase) {
|
|
97
|
+
abstract class AlertSoundAttachment extends Base implements AlertSoundAttachmentHost {
|
|
98
|
+
abstract ruleService: RuleService;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* True when editing an existing entity (clear should set CLEAR_ALERT_SOUND).
|
|
102
|
+
*/
|
|
103
|
+
abstract get isAlertSoundEditMode(): boolean;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* DYNAMIC_RULE_ID of the entity being edited (null on create).
|
|
107
|
+
* Used when {@link alertSoundCacheSyncMode} is `'entity'`.
|
|
108
|
+
*/
|
|
109
|
+
abstract get alertSoundEntityId(): string | null;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* `'entity'` — update/clear cache for {@link alertSoundEntityId} (rules).
|
|
113
|
+
* `'all'` — drop every cached server sound (templates; instance ids unknown).
|
|
114
|
+
*/
|
|
115
|
+
abstract get alertSoundCacheSyncMode(): 'entity' | 'all';
|
|
116
|
+
|
|
117
|
+
/** Display name of the attached / pending alert sound file. */
|
|
118
|
+
@observable alertSoundFileName: string = '';
|
|
119
|
+
@observable alertSoundMimeType: string = '';
|
|
120
|
+
/** True when edit loaded an existing server sound (or user attached one). */
|
|
121
|
+
@observable hasAlertSound = false;
|
|
122
|
+
fileInput: HTMLInputElement;
|
|
123
|
+
pendingAlertSoundData: string | null = null;
|
|
124
|
+
/** True when the user picked a new file this session (needs upload). */
|
|
125
|
+
alertSoundDirty = false;
|
|
126
|
+
clearAlertSound = false;
|
|
127
|
+
/** In-progress Test playback; not private — mixin return type is exported. */
|
|
128
|
+
testAudio: HTMLAudioElement | null = null;
|
|
129
|
+
|
|
130
|
+
async loadExistingAlertSound(dynamicRuleId: string) {
|
|
131
|
+
if (!dynamicRuleId) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const sound = await this.ruleService.getAlertSound(dynamicRuleId);
|
|
135
|
+
if (!sound?.SOUND_DATA) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
this.alertSoundFileName = sound.ALERT_SOUND_FILE_NAME || 'Attached sound';
|
|
139
|
+
this.alertSoundMimeType = sound.ALERT_SOUND_MIME_TYPE || 'audio/mpeg';
|
|
140
|
+
this.pendingAlertSoundData = sound.SOUND_DATA;
|
|
141
|
+
this.hasAlertSound = true;
|
|
142
|
+
this.alertSoundDirty = false;
|
|
143
|
+
this.clearAlertSound = false;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
openAlertSoundPicker() {
|
|
147
|
+
this.fileInput?.click();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async onAlertSoundSelected(event: Event) {
|
|
151
|
+
const input = event.target as HTMLInputElement;
|
|
152
|
+
const file = input.files?.[0];
|
|
153
|
+
input.value = '';
|
|
154
|
+
if (!file) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (file.size > MAX_SOUND_FILE_BYTES) {
|
|
158
|
+
showAlertSoundNotice(
|
|
159
|
+
'Sound file too large',
|
|
160
|
+
`Sound file must be ${formatBytes(MAX_SOUND_FILE_BYTES)} or smaller.`,
|
|
161
|
+
);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
this.pendingAlertSoundData = await blobToBase64(file);
|
|
166
|
+
this.alertSoundFileName = file.name;
|
|
167
|
+
this.alertSoundMimeType = resolveAudioMimeType(file.name, file.type);
|
|
168
|
+
this.hasAlertSound = true;
|
|
169
|
+
this.alertSoundDirty = true;
|
|
170
|
+
this.clearAlertSound = false;
|
|
171
|
+
} catch {
|
|
172
|
+
showAlertSoundNotice('Could not read sound file', 'The selected file could not be read.');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
clearAttachedAlertSound() {
|
|
177
|
+
this.stopTestSound();
|
|
178
|
+
this.resetAlertSoundState();
|
|
179
|
+
this.clearAlertSound = this.isAlertSoundEditMode;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
testAttachedAlertSound() {
|
|
183
|
+
this.stopTestSound();
|
|
184
|
+
if (!this.pendingAlertSoundData) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const blob = base64ToBlob(
|
|
188
|
+
this.pendingAlertSoundData,
|
|
189
|
+
this.alertSoundMimeType || 'audio/mpeg',
|
|
190
|
+
);
|
|
191
|
+
const url = URL.createObjectURL(blob);
|
|
192
|
+
const audio = new Audio(url);
|
|
193
|
+
this.testAudio = audio;
|
|
194
|
+
void audio.play().catch(() => {
|
|
195
|
+
showAlertSoundNotice(
|
|
196
|
+
'Could not play sound',
|
|
197
|
+
'The browser could not play this file. Try MP3 or WAV.',
|
|
198
|
+
);
|
|
199
|
+
});
|
|
200
|
+
audio.addEventListener(
|
|
201
|
+
'ended',
|
|
202
|
+
() => {
|
|
203
|
+
URL.revokeObjectURL(url);
|
|
204
|
+
if (this.testAudio === audio) {
|
|
205
|
+
this.testAudio = null;
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
{ once: true },
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
stopTestSound() {
|
|
213
|
+
if (!this.testAudio) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
this.testAudio.pause();
|
|
217
|
+
const src = this.testAudio.src;
|
|
218
|
+
if (src.startsWith('blob:')) {
|
|
219
|
+
URL.revokeObjectURL(src);
|
|
220
|
+
}
|
|
221
|
+
this.testAudio = null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
resetAlertSoundState() {
|
|
225
|
+
this.alertSoundFileName = '';
|
|
226
|
+
this.alertSoundMimeType = '';
|
|
227
|
+
this.pendingAlertSoundData = null;
|
|
228
|
+
this.hasAlertSound = false;
|
|
229
|
+
this.alertSoundDirty = false;
|
|
230
|
+
this.clearAlertSound = false;
|
|
231
|
+
if (this.fileInput) {
|
|
232
|
+
this.fileInput.value = '';
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Merges pending sound / clear flag onto a create/update DETAILS object. */
|
|
237
|
+
applyAlertSoundToPayload<T extends AlertSoundPayloadFields>(target: T): T {
|
|
238
|
+
if (this.clearAlertSound) {
|
|
239
|
+
target.CLEAR_ALERT_SOUND = true;
|
|
240
|
+
} else if (this.alertSoundDirty && this.pendingAlertSoundData && this.alertSoundFileName) {
|
|
241
|
+
target.ALERT_SOUND_FILE_NAME = this.alertSoundFileName;
|
|
242
|
+
target.ALERT_SOUND_MIME_TYPE = this.alertSoundMimeType;
|
|
243
|
+
target.ALERT_SOUND_DATA = this.pendingAlertSoundData;
|
|
244
|
+
}
|
|
245
|
+
return target;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Keep AlertSoundPlayer's in-memory NOTIFY_ALERT_SOUND cache in sync with
|
|
250
|
+
* what we just saved — otherwise the next alert still plays the old file.
|
|
251
|
+
*/
|
|
252
|
+
syncAlertSoundCacheAfterSave() {
|
|
253
|
+
if (this.alertSoundCacheSyncMode === 'all') {
|
|
254
|
+
if (!this.clearAlertSound && !this.alertSoundDirty) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
AlertSoundPlayer.shared.clearAllServerSounds();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const id = this.alertSoundEntityId?.trim();
|
|
262
|
+
if (!id) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (this.clearAlertSound) {
|
|
267
|
+
AlertSoundPlayer.shared.clearServerSound(id);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (this.alertSoundDirty && this.pendingAlertSoundData) {
|
|
272
|
+
const mimeType = this.alertSoundMimeType || 'audio/mpeg';
|
|
273
|
+
AlertSoundPlayer.shared.setServerSound(id, {
|
|
274
|
+
blob: base64ToBlob(this.pendingAlertSoundData, mimeType),
|
|
275
|
+
fileName: this.alertSoundFileName,
|
|
276
|
+
mimeType,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return AlertSoundAttachment;
|
|
283
|
+
}
|
|
@@ -2,6 +2,7 @@ import { sync } from '@genesislcap/foundation-utils';
|
|
|
2
2
|
import { html, ref, repeat, when } from '@genesislcap/web-core';
|
|
3
3
|
import { ConditionNode, Severity, UpdateType } from '../../../../../notify.types';
|
|
4
4
|
import { eventDetail } from '../../../../../utils/eventDetail';
|
|
5
|
+
import { alertSoundAttachmentTemplate } from '../../../alert-sound-attachment';
|
|
5
6
|
import type { RuleDialog } from './rule-dialog';
|
|
6
7
|
|
|
7
8
|
const isGroup = (node: ConditionNode) => Array.isArray((node as any).CHILDREN);
|
|
@@ -124,6 +125,8 @@ export const RuleDialogTemplate = html<RuleDialog>`
|
|
|
124
125
|
</div>
|
|
125
126
|
</div>
|
|
126
127
|
|
|
128
|
+
${alertSoundAttachmentTemplate}
|
|
129
|
+
|
|
127
130
|
<div class="control">
|
|
128
131
|
<label>Condition(s)</label>
|
|
129
132
|
<div class="items">
|
package/src/components/foundation-notification-dashboard/components/rules/rule-dialog/rule-dialog.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { NotifyService } from '../../../../../services/notify.service';
|
|
|
11
11
|
import { RuleService } from '../../../../../services/rule.service';
|
|
12
12
|
import { Field, SystemService } from '../../../../../services/system.service';
|
|
13
13
|
import { humanize } from '../../../../../utils';
|
|
14
|
+
import { AlertSoundAttachmentMixin } from '../../../alert-sound-attachment';
|
|
14
15
|
import {
|
|
15
16
|
DynamicRuleUtils,
|
|
16
17
|
RuleUtils,
|
|
@@ -32,7 +33,7 @@ import { Rule, RuleDialogMode, RuleDialogParams } from './rule-dialog.types';
|
|
|
32
33
|
template: RuleDialogTemplate,
|
|
33
34
|
styles: DynamicRuleDialogStyles,
|
|
34
35
|
})
|
|
35
|
-
export class RuleDialog extends GenesisElement {
|
|
36
|
+
export class RuleDialog extends AlertSoundAttachmentMixin(GenesisElement) {
|
|
36
37
|
@RuleService ruleService: RuleService;
|
|
37
38
|
@SystemService systemService: SystemService;
|
|
38
39
|
@NotifyService notifyService: NotifyService;
|
|
@@ -60,6 +61,18 @@ export class RuleDialog extends GenesisElement {
|
|
|
60
61
|
@observable fields: Array<Field> = [];
|
|
61
62
|
@observable topics: string[] = [];
|
|
62
63
|
|
|
64
|
+
get isAlertSoundEditMode(): boolean {
|
|
65
|
+
return this.ruleDialogMode === RuleDialogMode.EDIT;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get alertSoundEntityId(): string | null {
|
|
69
|
+
return this.ruleId;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get alertSoundCacheSyncMode(): 'entity' | 'all' {
|
|
73
|
+
return 'entity';
|
|
74
|
+
}
|
|
75
|
+
|
|
63
76
|
public async openDialog(params: RuleDialogParams) {
|
|
64
77
|
this.topics = await this.notifyService.getNotifyRouteTopics();
|
|
65
78
|
this.resources = await this.systemService.getResources();
|
|
@@ -80,6 +93,7 @@ export class RuleDialog extends GenesisElement {
|
|
|
80
93
|
}
|
|
81
94
|
|
|
82
95
|
close() {
|
|
96
|
+
this.stopTestSound();
|
|
83
97
|
// Objects
|
|
84
98
|
this.ruleId = null;
|
|
85
99
|
this.name = null;
|
|
@@ -90,6 +104,7 @@ export class RuleDialog extends GenesisElement {
|
|
|
90
104
|
this.topic = null;
|
|
91
105
|
this.severity = null;
|
|
92
106
|
this.ruleDialogMode = null;
|
|
107
|
+
this.resetAlertSoundState();
|
|
93
108
|
|
|
94
109
|
// Arrays
|
|
95
110
|
this.resources = [];
|
|
@@ -132,6 +147,7 @@ export class RuleDialog extends GenesisElement {
|
|
|
132
147
|
this.conditions = DynamicRuleUtils.resolveLeftValues(loadedConditions, this.fields);
|
|
133
148
|
this.refreshExpressionPreview();
|
|
134
149
|
this.attributeAssignments(rule.RESULT_EXPRESSION?.ASSIGNMENTS);
|
|
150
|
+
await this.loadExistingAlertSound(rule.DYNAMIC_RULE_ID);
|
|
135
151
|
}
|
|
136
152
|
|
|
137
153
|
private refreshExpressionPreview() {
|
|
@@ -184,7 +200,7 @@ export class RuleDialog extends GenesisElement {
|
|
|
184
200
|
DynamicRuleUtils.hasOrJoin(this.conditions) ||
|
|
185
201
|
DynamicRuleUtils.hasGroup(this.conditions) ||
|
|
186
202
|
DynamicRuleUtils.hasUnsupportedOperator(this.conditions);
|
|
187
|
-
|
|
203
|
+
const rule: Rule = {
|
|
188
204
|
...(this.ruleDialogMode === RuleDialogMode.EDIT && { DYNAMIC_RULE_ID: this.ruleId }),
|
|
189
205
|
RULE_NAME: this.name,
|
|
190
206
|
RULE_DESCRIPTION: this.description,
|
|
@@ -205,10 +221,13 @@ export class RuleDialog extends GenesisElement {
|
|
|
205
221
|
],
|
|
206
222
|
},
|
|
207
223
|
};
|
|
224
|
+
|
|
225
|
+
return this.applyAlertSoundToPayload(rule);
|
|
208
226
|
}
|
|
209
227
|
|
|
210
228
|
validateResponse(response) {
|
|
211
229
|
if (response.MESSAGE_TYPE === 'EVENT_ACK') {
|
|
230
|
+
this.syncAlertSoundCacheAfterSave();
|
|
212
231
|
this.close();
|
|
213
232
|
return;
|
|
214
233
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { UpdateType } from '../../../../../notify.types';
|
|
2
|
+
import type { AlertSoundPayloadFields } from '../../../alert-sound-attachment';
|
|
2
3
|
|
|
3
4
|
export const RuleDialogMode = {
|
|
4
5
|
CREATE: 'create',
|
|
@@ -25,4 +26,12 @@ export type Rule = {
|
|
|
25
26
|
RESULT_EXPRESSION?: {
|
|
26
27
|
ASSIGNMENTS: Array<any>;
|
|
27
28
|
};
|
|
29
|
+
} & AlertSoundPayloadFields;
|
|
30
|
+
|
|
31
|
+
export type NotifyAlertSound = {
|
|
32
|
+
ALERT_SOUND_ID: string;
|
|
33
|
+
DYNAMIC_RULE_ID: string;
|
|
34
|
+
ALERT_SOUND_FILE_NAME?: string;
|
|
35
|
+
ALERT_SOUND_MIME_TYPE?: string;
|
|
36
|
+
SOUND_DATA: string;
|
|
28
37
|
};
|
package/src/components/foundation-notification-dashboard/components/rules/rule-management.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { showNotificationDialog } from '@genesislcap/foundation-notifications';
|
|
2
2
|
import { customElement, GenesisElement, html, ref, repeat } from '@genesislcap/web-core';
|
|
3
3
|
import { RuleService } from '../../../../services/rule.service';
|
|
4
|
+
import { AlertSoundPlayer } from '../../../../utils/alert-sound';
|
|
4
5
|
import { DynamicRuleManagementStyles } from '../../styles/dynamic-rule.styles';
|
|
5
6
|
import { RULE_MANAGEMENT_COLUMNS } from './columns';
|
|
6
7
|
import { RuleDialog } from './rule-dialog/rule-dialog';
|
|
@@ -61,7 +62,10 @@ export class RuleManagement extends GenesisElement {
|
|
|
61
62
|
confirmingActions: [
|
|
62
63
|
{
|
|
63
64
|
label: 'Confirm',
|
|
64
|
-
action: () =>
|
|
65
|
+
action: () => {
|
|
66
|
+
AlertSoundPlayer.shared.clearServerSound(rule.ID);
|
|
67
|
+
this.ruleService.deleteRule(rule.ID);
|
|
68
|
+
},
|
|
65
69
|
},
|
|
66
70
|
],
|
|
67
71
|
},
|
|
@@ -2,6 +2,7 @@ import { sync } from '@genesislcap/foundation-utils';
|
|
|
2
2
|
import { html, ref, repeat, when } from '@genesislcap/web-core';
|
|
3
3
|
import { ConditionNode, Severity, UpdateType } from '../../../../../notify.types';
|
|
4
4
|
import { eventDetail } from '../../../../../utils/eventDetail';
|
|
5
|
+
import { alertSoundAttachmentTemplate } from '../../../alert-sound-attachment';
|
|
5
6
|
import type { TemplateDialog } from './template-dialog';
|
|
6
7
|
|
|
7
8
|
const isGroup = (node: ConditionNode) => Array.isArray((node as any).CHILDREN);
|
|
@@ -109,6 +110,8 @@ export const TemplateDialogTemplate = html<TemplateDialog>`
|
|
|
109
110
|
</div>
|
|
110
111
|
</div>
|
|
111
112
|
|
|
113
|
+
${alertSoundAttachmentTemplate}
|
|
114
|
+
|
|
112
115
|
<div class="control">
|
|
113
116
|
<label>Condition(s)</label>
|
|
114
117
|
<div class="items">
|
|
@@ -8,9 +8,11 @@ import {
|
|
|
8
8
|
UpdateType,
|
|
9
9
|
} from '../../../../../notify.types';
|
|
10
10
|
import { NotifyService } from '../../../../../services/notify.service';
|
|
11
|
+
import { RuleService } from '../../../../../services/rule.service';
|
|
11
12
|
import { Field, SystemService } from '../../../../../services/system.service';
|
|
12
13
|
import { TemplateService } from '../../../../../services/template.service';
|
|
13
14
|
import { humanize, logger } from '../../../../../utils';
|
|
15
|
+
import { AlertSoundAttachmentMixin } from '../../../alert-sound-attachment';
|
|
14
16
|
import {
|
|
15
17
|
DynamicRuleUtils,
|
|
16
18
|
showNotificationError,
|
|
@@ -32,10 +34,11 @@ import { ParameterBuilderEntity, Template, TemplateDialogMode } from './template
|
|
|
32
34
|
template: TemplateDialogTemplate,
|
|
33
35
|
styles: DynamicRuleDialogStyles,
|
|
34
36
|
})
|
|
35
|
-
export class TemplateDialog extends GenesisElement {
|
|
37
|
+
export class TemplateDialog extends AlertSoundAttachmentMixin(GenesisElement) {
|
|
36
38
|
@TemplateService templateService: TemplateService;
|
|
37
39
|
@SystemService systemService: SystemService;
|
|
38
40
|
@NotifyService notifyService: NotifyService;
|
|
41
|
+
@RuleService ruleService: RuleService;
|
|
39
42
|
|
|
40
43
|
dialog: Modal;
|
|
41
44
|
@observable templateDialogMode: (typeof TemplateDialogMode)[keyof typeof TemplateDialogMode] =
|
|
@@ -64,6 +67,20 @@ export class TemplateDialog extends GenesisElement {
|
|
|
64
67
|
|
|
65
68
|
private requiredFields = () => [this.name, this.description, this.header, this.message];
|
|
66
69
|
|
|
70
|
+
get isAlertSoundEditMode(): boolean {
|
|
71
|
+
return this.templateDialogMode === TemplateDialogMode.EDIT;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
get alertSoundEntityId(): string | null {
|
|
75
|
+
return this.templateId;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
get alertSoundCacheSyncMode(): 'entity' | 'all' {
|
|
79
|
+
// Template sound updates propagate to instance rules by NAME on the server;
|
|
80
|
+
// bust the whole client cache so those instance ids re-fetch.
|
|
81
|
+
return 'all';
|
|
82
|
+
}
|
|
83
|
+
|
|
67
84
|
public async openDialog(params: any) {
|
|
68
85
|
this.topics = await this.notifyService.getNotifyRouteTopics();
|
|
69
86
|
this.resources = await this.systemService.getResources(); // TODO: Tables + Views
|
|
@@ -85,6 +102,7 @@ export class TemplateDialog extends GenesisElement {
|
|
|
85
102
|
}
|
|
86
103
|
|
|
87
104
|
close() {
|
|
105
|
+
this.stopTestSound();
|
|
88
106
|
// Objects
|
|
89
107
|
this.templateId = null;
|
|
90
108
|
this.name = null;
|
|
@@ -95,6 +113,7 @@ export class TemplateDialog extends GenesisElement {
|
|
|
95
113
|
this.topic = null;
|
|
96
114
|
this.severity = null;
|
|
97
115
|
this.templateDialogMode = null;
|
|
116
|
+
this.resetAlertSoundState();
|
|
98
117
|
|
|
99
118
|
// Arrays
|
|
100
119
|
this.resources = [];
|
|
@@ -139,6 +158,7 @@ export class TemplateDialog extends GenesisElement {
|
|
|
139
158
|
this.conditions = DynamicRuleUtils.resolveLeftValues(loadedConditions, this.fields);
|
|
140
159
|
this.refreshExpressionPreview();
|
|
141
160
|
this.attributeAssignments(template.RESULT_EXPRESSION?.ASSIGNMENTS);
|
|
161
|
+
await this.loadExistingAlertSound(template.DYNAMIC_RULE_ID);
|
|
142
162
|
}
|
|
143
163
|
|
|
144
164
|
private refreshExpressionPreview() {
|
|
@@ -203,7 +223,7 @@ export class TemplateDialog extends GenesisElement {
|
|
|
203
223
|
DynamicRuleUtils.hasOrJoin(this.conditions) ||
|
|
204
224
|
DynamicRuleUtils.hasGroup(this.conditions) ||
|
|
205
225
|
DynamicRuleUtils.hasUnsupportedOperator(this.conditions);
|
|
206
|
-
|
|
226
|
+
const template: Template = {
|
|
207
227
|
...(this.templateDialogMode === TemplateDialogMode.EDIT && {
|
|
208
228
|
DYNAMIC_RULE_ID: this.templateId,
|
|
209
229
|
}),
|
|
@@ -227,10 +247,13 @@ export class TemplateDialog extends GenesisElement {
|
|
|
227
247
|
},
|
|
228
248
|
PARAMETER_DETAILS: useRaw ? {} : TemplateUtils.createParameterDetails(this.parameters),
|
|
229
249
|
};
|
|
250
|
+
|
|
251
|
+
return this.applyAlertSoundToPayload(template);
|
|
230
252
|
}
|
|
231
253
|
|
|
232
254
|
validateResponse(response) {
|
|
233
255
|
if (response.MESSAGE_TYPE === 'EVENT_ACK') {
|
|
256
|
+
this.syncAlertSoundCacheAfterSave();
|
|
234
257
|
this.close();
|
|
235
258
|
return;
|
|
236
259
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { UpdateType } from '../../../../../notify.types';
|
|
2
|
+
import type { AlertSoundPayloadFields } from '../../../alert-sound-attachment';
|
|
2
3
|
|
|
3
4
|
export const TemplateDialogMode = {
|
|
4
5
|
CREATE: 'create',
|
|
@@ -26,7 +27,7 @@ export type Template = {
|
|
|
26
27
|
ASSIGNMENTS: Array<any>;
|
|
27
28
|
};
|
|
28
29
|
PARAMETER_DETAILS: any;
|
|
29
|
-
};
|
|
30
|
+
} & AlertSoundPayloadFields;
|
|
30
31
|
|
|
31
32
|
export type ParameterBuilderEntity = {
|
|
32
33
|
UUID?: string;
|
package/src/components/foundation-notification-dashboard/components/templates/template-management.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { showNotificationDialog } from '@genesislcap/foundation-notifications';
|
|
2
2
|
import { customElement, GenesisElement, html, ref, repeat } from '@genesislcap/web-core';
|
|
3
3
|
import { TemplateService } from '../../../../services/template.service';
|
|
4
|
+
import { AlertSoundPlayer } from '../../../../utils/alert-sound';
|
|
4
5
|
import { DynamicRuleManagementStyles } from '../../styles/dynamic-rule.styles';
|
|
5
6
|
import { TEMPLATE_MANAGEMENT_COLUMNS } from './columns';
|
|
6
7
|
import type { TemplateDialog } from './template-dialog/template-dialog';
|
|
@@ -61,7 +62,13 @@ export class TemplateManagement extends GenesisElement {
|
|
|
61
62
|
confirmingActions: [
|
|
62
63
|
{
|
|
63
64
|
label: 'Confirm',
|
|
64
|
-
action: () =>
|
|
65
|
+
action: () => {
|
|
66
|
+
// Template sounds propagate to instance rules by NAME on the
|
|
67
|
+
// server; the client cache is keyed by those instance ids, not
|
|
68
|
+
// template.ID — bust everything so deleted audio stops playing.
|
|
69
|
+
AlertSoundPlayer.shared.clearAllServerSounds();
|
|
70
|
+
this.templateService.deleteRuleTemplate(template.ID);
|
|
71
|
+
},
|
|
65
72
|
},
|
|
66
73
|
],
|
|
67
74
|
},
|
|
@@ -97,7 +97,7 @@ export const DynamicRuleDialogStyles = css`
|
|
|
97
97
|
display: flex;
|
|
98
98
|
flex-direction: column;
|
|
99
99
|
width: ${String(modalWidthPx)}px;
|
|
100
|
-
max-height:
|
|
100
|
+
max-height: 600px;
|
|
101
101
|
overflow-y: scroll;
|
|
102
102
|
gap: calc(var(--design-unit) * 3px);
|
|
103
103
|
padding: calc(var(--design-unit) * 4px);
|
|
@@ -168,5 +168,35 @@ export const DynamicRuleDialogStyles = css`
|
|
|
168
168
|
min-height: calc(var(--design-unit) * 12px);
|
|
169
169
|
resize: vertical;
|
|
170
170
|
}
|
|
171
|
+
|
|
172
|
+
.alert-sound {
|
|
173
|
+
display: flex;
|
|
174
|
+
flex-direction: column;
|
|
175
|
+
gap: calc(var(--design-unit) * 2px);
|
|
176
|
+
margin-top: calc(var(--design-unit) * 2px);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.alert-sound .file-input {
|
|
180
|
+
display: none;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.alert-sound-row {
|
|
184
|
+
display: flex;
|
|
185
|
+
align-items: center;
|
|
186
|
+
gap: calc(var(--design-unit) * 2px);
|
|
187
|
+
flex-wrap: wrap;
|
|
188
|
+
border: calc(var(--stroke-width) * 1px) solid var(--neutral-stroke-divider-rest);
|
|
189
|
+
border-radius: calc(var(--control-corner-radius) * 1px);
|
|
190
|
+
padding: calc(var(--design-unit) * 2px);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.alert-sound-name {
|
|
194
|
+
flex: 1;
|
|
195
|
+
min-width: 120px;
|
|
196
|
+
color: var(--neutral-foreground-hint);
|
|
197
|
+
overflow: hidden;
|
|
198
|
+
text-overflow: ellipsis;
|
|
199
|
+
white-space: nowrap;
|
|
200
|
+
}
|
|
171
201
|
`;
|
|
172
202
|
// #endregion
|