@@ -15,10 +15,18 @@ limitations under the License.
15
15
*/
16
16
17
17
import React , { ReactNode } from "react" ;
18
- import { IAnnotatedPushRule , IPusher , PushRuleAction , PushRuleKind , RuleId } from "matrix-js-sdk/src/@types/PushRules" ;
18
+ import {
19
+ IAnnotatedPushRule ,
20
+ IPusher ,
21
+ PushRuleAction ,
22
+ IPushRule ,
23
+ PushRuleKind ,
24
+ RuleId ,
25
+ } from "matrix-js-sdk/src/@types/PushRules" ;
19
26
import { IThreepid , ThreepidMedium } from "matrix-js-sdk/src/@types/threepids" ;
20
27
import { logger } from "matrix-js-sdk/src/logger" ;
21
28
import { LocalNotificationSettings } from "matrix-js-sdk/src/@types/local_notifications" ;
29
+ import { PushProcessor } from "matrix-js-sdk/src/pushprocessor" ;
22
30
23
31
import Spinner from "../elements/Spinner" ;
24
32
import { MatrixClientPeg } from "../../../MatrixClientPeg" ;
@@ -92,6 +100,9 @@ interface IVectorPushRule {
92
100
rule ?: IAnnotatedPushRule ;
93
101
description : TranslatedString | string ;
94
102
vectorState : VectorState ;
103
+ // loudest vectorState of a rule and its synced rules
104
+ // undefined when rule has no synced rules
105
+ syncedVectorState ?: VectorState ;
95
106
}
96
107
97
108
interface IProps { }
@@ -115,9 +126,68 @@ interface IState {
115
126
116
127
clearingNotifications : boolean ;
117
128
}
129
+ const findInDefaultRules = (
130
+ ruleId : RuleId | string ,
131
+ defaultRules : {
132
+ [ k in RuleClass ] : IAnnotatedPushRule [ ] ;
133
+ } ,
134
+ ) : IAnnotatedPushRule | undefined => {
135
+ for ( const category in defaultRules ) {
136
+ const rule : IAnnotatedPushRule | undefined = defaultRules [ category as RuleClass ] . find (
137
+ ( rule ) => rule . rule_id === ruleId ,
138
+ ) ;
139
+ if ( rule ) {
140
+ return rule ;
141
+ }
142
+ }
143
+ } ;
144
+
145
+ // Vector notification states ordered by loudness in ascending order
146
+ const OrderedVectorStates = [ VectorState . Off , VectorState . On , VectorState . Loud ] ;
147
+
148
+ /**
149
+ * Find the 'loudest' vector state assigned to a rule
150
+ * and it's synced rules
151
+ * If rules have fallen out of sync,
152
+ * the loudest rule can determine the display value
153
+ * @param defaultRules
154
+ * @param rule - parent rule
155
+ * @param definition - definition of parent rule
156
+ * @returns VectorState - the maximum/loudest state for the parent and synced rules
157
+ */
158
+ const maximumVectorState = (
159
+ defaultRules : {
160
+ [ k in RuleClass ] : IAnnotatedPushRule [ ] ;
161
+ } ,
162
+ rule : IAnnotatedPushRule ,
163
+ definition : VectorPushRuleDefinition ,
164
+ ) : VectorState | undefined => {
165
+ if ( ! definition . syncedRuleIds ?. length ) {
166
+ return undefined ;
167
+ }
168
+ const vectorState = definition . syncedRuleIds . reduce < VectorState > ( ( maxVectorState , ruleId ) => {
169
+ // already set to maximum
170
+ if ( maxVectorState === VectorState . Loud ) {
171
+ return maxVectorState ;
172
+ }
173
+ const syncedRule = findInDefaultRules ( ruleId , defaultRules ) ;
174
+ if ( syncedRule ) {
175
+ const syncedRuleVectorState = definition . ruleToVectorState ( syncedRule ) ;
176
+ // if syncedRule is 'louder' than current maximum
177
+ // set maximum to louder vectorState
178
+ if ( OrderedVectorStates . indexOf ( syncedRuleVectorState ) > OrderedVectorStates . indexOf ( maxVectorState ) ) {
179
+ return syncedRuleVectorState ;
180
+ }
181
+ }
182
+ return maxVectorState ;
183
+ } , definition . ruleToVectorState ( rule ) ) ;
184
+
185
+ return vectorState ;
186
+ } ;
118
187
119
188
export default class Notifications extends React . PureComponent < IProps , IState > {
120
189
private settingWatchers : string [ ] ;
190
+ private pushProcessor : PushProcessor ;
121
191
122
192
public constructor ( props : IProps ) {
123
193
super ( props ) ;
@@ -145,6 +215,8 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
145
215
this . setState ( { audioNotifications : value as boolean } ) ,
146
216
) ,
147
217
] ;
218
+
219
+ this . pushProcessor = new PushProcessor ( MatrixClientPeg . get ( ) ) ;
148
220
}
149
221
150
222
private get isInhibited ( ) : boolean {
@@ -281,6 +353,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
281
353
ruleId : rule . rule_id ,
282
354
rule,
283
355
vectorState,
356
+ syncedVectorState : maximumVectorState ( defaultRules , rule , definition ) ,
284
357
description : _t ( definition . description ) ,
285
358
} ) ;
286
359
}
@@ -388,6 +461,43 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
388
461
await SettingsStore . setValue ( "audioNotificationsEnabled" , null , SettingLevel . DEVICE , checked ) ;
389
462
} ;
390
463
464
+ private setPushRuleActions = async (
465
+ ruleId : IPushRule [ "rule_id" ] ,
466
+ kind : PushRuleKind ,
467
+ actions ?: PushRuleAction [ ] ,
468
+ ) : Promise < void > => {
469
+ const cli = MatrixClientPeg . get ( ) ;
470
+ if ( ! actions ) {
471
+ await cli . setPushRuleEnabled ( "global" , kind , ruleId , false ) ;
472
+ } else {
473
+ await cli . setPushRuleActions ( "global" , kind , ruleId , actions ) ;
474
+ await cli . setPushRuleEnabled ( "global" , kind , ruleId , true ) ;
475
+ }
476
+ } ;
477
+
478
+ /**
479
+ * Updated syncedRuleIds from rule definition
480
+ * If a rule does not exist it is ignored
481
+ * Synced rules are updated sequentially
482
+ * and stop at first error
483
+ */
484
+ private updateSyncedRules = async (
485
+ syncedRuleIds : VectorPushRuleDefinition [ "syncedRuleIds" ] ,
486
+ actions ?: PushRuleAction [ ] ,
487
+ ) : Promise < void > => {
488
+ // get synced rules that exist for user
489
+ const syncedRules : ReturnType < PushProcessor [ "getPushRuleAndKindById" ] > [ ] = syncedRuleIds
490
+ ?. map ( ( ruleId ) => this . pushProcessor . getPushRuleAndKindById ( ruleId ) )
491
+ . filter ( Boolean ) ;
492
+
493
+ if ( ! syncedRules ?. length ) {
494
+ return ;
495
+ }
496
+ for ( const { kind, rule : syncedRule } of syncedRules ) {
497
+ await this . setPushRuleActions ( syncedRule . rule_id , kind , actions ) ;
498
+ }
499
+ } ;
500
+
391
501
private onRadioChecked = async ( rule : IVectorPushRule , checkedState : VectorState ) : Promise < void > => {
392
502
this . setState ( { phase : Phase . Persisting } ) ;
393
503
@@ -428,12 +538,8 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
428
538
} else {
429
539
const definition : VectorPushRuleDefinition = VectorPushRulesDefinitions [ rule . ruleId ] ;
430
540
const actions = definition . vectorStateToActions [ checkedState ] ;
431
- if ( ! actions ) {
432
- await cli . setPushRuleEnabled ( "global" , rule . rule . kind , rule . rule . rule_id , false ) ;
433
- } else {
434
- await cli . setPushRuleActions ( "global" , rule . rule . kind , rule . rule . rule_id , actions ) ;
435
- await cli . setPushRuleEnabled ( "global" , rule . rule . kind , rule . rule . rule_id , true ) ;
436
- }
541
+ await this . setPushRuleActions ( rule . rule . rule_id , rule . rule . kind , actions ) ;
542
+ await this . updateSyncedRules ( definition . syncedRuleIds , actions ) ;
437
543
}
438
544
439
545
await this . refreshFromServer ( ) ;
@@ -684,7 +790,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
684
790
< StyledRadioButton
685
791
key = { r . ruleId + s }
686
792
name = { r . ruleId }
687
- checked = { r . vectorState === s }
793
+ checked = { ( r . syncedVectorState ?? r . vectorState ) === s }
688
794
onChange = { this . onRadioChecked . bind ( this , r , s ) }
689
795
disabled = { this . state . phase === Phase . Persisting }
690
796
aria-label = { VectorStateToLabel [ s ] }
0 commit comments