@@ -13,6 +13,7 @@ import {
13
13
14
14
import { styled , css } from '../../styles' ;
15
15
import { Icon } from '../../icons' ;
16
+ import { UnreachableCheck } from '../../util/error' ;
16
17
17
18
import { getMethodColor , getSummaryColour } from '../../model/events/categorization' ;
18
19
import {
@@ -54,7 +55,7 @@ import { HandlerSelector } from './handler-selection';
54
55
import { HandlerConfiguration } from './handler-config' ;
55
56
import { DragHandle } from './mock-drag-handle' ;
56
57
import { IconMenu , IconMenuButton } from './mock-item-menu' ;
57
- import { UnreachableCheck } from '../../util/error ' ;
58
+ import { RuleTitle , EditableRuleTitle } from './mock-rule-title ' ;
58
59
59
60
const RowContainer = styled ( LittleCard ) < {
60
61
deactivated ?: boolean ,
@@ -73,6 +74,7 @@ const RowContainer = styled(LittleCard)<{
73
74
}
74
75
75
76
display: flex;
77
+ flex-wrap: wrap;
76
78
flex-direction: row;
77
79
align-items: center;
78
80
justify-content: space-between;
@@ -196,13 +198,15 @@ const RuleMenu = (p: {
196
198
isCollapsed : boolean ,
197
199
isNewRule : boolean ,
198
200
hasUnsavedChanges : boolean ,
199
- onToggleCollapse : ( event : React . MouseEvent ) => void ,
200
- onSave : ( event : React . MouseEvent ) => void ,
201
- onReset : ( event : React . MouseEvent ) => void ,
202
- onClone : ( event : React . MouseEvent ) => void ,
201
+ isEditingTitle : boolean ,
202
+ onSetCustomTitle : ( event : React . UIEvent ) => void ,
203
+ onToggleCollapse : ( event : React . UIEvent ) => void ,
204
+ onSave : ( event : React . UIEvent ) => void ,
205
+ onReset : ( event : React . UIEvent ) => void ,
206
+ onClone : ( event : React . UIEvent ) => void ,
203
207
toggleState : boolean ,
204
- onToggleActivation : ( event : React . MouseEvent ) => void ,
205
- onDelete : ( event : React . MouseEvent ) => void ,
208
+ onToggleActivation : ( event : React . UIEvent ) => void ,
209
+ onDelete : ( event : React . UIEvent ) => void ,
206
210
} ) => < RuleMenuContainer topOffset = { 7 } >
207
211
< IconMenuButton
208
212
title = 'Delete this rule'
@@ -219,6 +223,12 @@ const RuleMenu = (p: {
219
223
icon = { [ 'fas' , p . toggleState ? 'toggle-on' : 'toggle-off' ] }
220
224
onClick = { p . onToggleActivation }
221
225
/>
226
+ < IconMenuButton
227
+ title = 'Give this rule a custom name'
228
+ icon = { [ 'fas' , 'edit' ] }
229
+ disabled = { p . isEditingTitle }
230
+ onClick = { p . onSetCustomTitle }
231
+ />
222
232
< IconMenuButton
223
233
title = 'Revert this rule to the last saved version'
224
234
icon = { [ 'fas' , 'undo' ] }
@@ -291,6 +301,9 @@ export class RuleRow extends React.Component<{
291
301
initialMatcherSelect = React . createRef < HTMLSelectElement > ( ) ;
292
302
containerRef : HTMLElement | null = null ;
293
303
304
+ @observable
305
+ private titleEditState : undefined | { originalTitle ?: string } ;
306
+
294
307
render ( ) {
295
308
const {
296
309
index,
@@ -337,6 +350,10 @@ export class RuleRow extends React.Component<{
337
350
? [ rule . handler ]
338
351
: rule . steps ;
339
352
353
+ // We show the summary by default, but if you set a custom title, we only show it when expanded:
354
+ const shouldShowSummary = ! collapsed || ( ! rule . title && ! this . titleEditState ) ;
355
+ const isEditingTitle = ! ! this . titleEditState && ! collapsed ;
356
+
340
357
return < Draggable
341
358
draggableId = { rule . id }
342
359
index = { index }
@@ -369,13 +386,36 @@ export class RuleRow extends React.Component<{
369
386
onToggleActivation = { this . toggleActivation }
370
387
onClone = { this . cloneRule }
371
388
onDelete = { this . deleteRule }
389
+ isEditingTitle = { isEditingTitle }
390
+ onSetCustomTitle = { this . startEnteringCustomTitle }
372
391
/>
373
392
< DragHandle { ...provided . dragHandleProps } />
374
393
394
+ { rule . title && ! isEditingTitle &&
395
+ < RuleTitle >
396
+ { rule . title }
397
+ </ RuleTitle >
398
+ }
399
+
400
+ { isEditingTitle &&
401
+ < EditableRuleTitle
402
+ value = { rule . title || '' }
403
+ onEditTitle = { this . editTitle }
404
+ onSave = { this . saveRule }
405
+ onCancel = {
406
+ this . titleEditState ! . originalTitle !== this . props . rule . title
407
+ ? this . cancelEditingTitle
408
+ : undefined
409
+ }
410
+ />
411
+ }
412
+
375
413
< MatcherOrHandler >
376
- < Summary collapsed = { collapsed } title = { summarizeMatcher ( rule ) } >
377
- { summarizeMatcher ( rule ) }
378
- </ Summary >
414
+ { shouldShowSummary &&
415
+ < Summary collapsed = { collapsed } title = { summarizeMatcher ( rule ) } >
416
+ { summarizeMatcher ( rule ) }
417
+ </ Summary >
418
+ }
379
419
380
420
{
381
421
! collapsed && < Details >
@@ -411,12 +451,17 @@ export class RuleRow extends React.Component<{
411
451
}
412
452
</ MatcherOrHandler >
413
453
414
- < ArrowIcon />
454
+
455
+ { shouldShowSummary &&
456
+ < ArrowIcon />
457
+ }
415
458
416
459
< MatcherOrHandler >
417
- < Summary collapsed = { collapsed } title = { summarizeHandler ( rule ) } >
418
- { summarizeHandler ( rule ) }
419
- </ Summary >
460
+ { shouldShowSummary &&
461
+ < Summary collapsed = { collapsed } title = { summarizeHandler ( rule ) } >
462
+ { summarizeHandler ( rule ) }
463
+ </ Summary >
464
+ }
420
465
421
466
{
422
467
! collapsed && < Details >
@@ -443,13 +488,19 @@ export class RuleRow extends React.Component<{
443
488
} </ Observer > } </ Draggable > ;
444
489
}
445
490
446
- saveRule = noPropagation ( ( ) => this . props . saveRule ( this . props . path ) ) ;
447
- resetRule = noPropagation ( ( ) => this . props . resetRule ( this . props . path ) ) ;
491
+ saveRule = noPropagation ( ( ) => {
492
+ this . stopEditingTitle ( ) ;
493
+ this . props . saveRule ( this . props . path ) ;
494
+ } ) ;
495
+ resetRule = noPropagation ( ( ) => {
496
+ this . stopEditingTitle ( ) ;
497
+ this . props . resetRule ( this . props . path ) ;
498
+ } ) ;
448
499
deleteRule = noPropagation ( ( ) => this . props . deleteRule ( this . props . path ) ) ;
449
500
cloneRule = noPropagation ( ( ) => this . props . cloneRule ( this . props . path ) ) ;
450
501
451
502
@action . bound
452
- toggleActivation ( event : React . MouseEvent ) {
503
+ toggleActivation ( event : React . UIEvent ) {
453
504
const { rule } = this . props ;
454
505
rule . activated = ! rule . activated ;
455
506
event . stopPropagation ( ) ;
@@ -466,10 +517,13 @@ export class RuleRow extends React.Component<{
466
517
}
467
518
if ( this . initialMatcherSelect . current ) {
468
519
this . initialMatcherSelect . current . focus ( ) ;
520
+ // Clear selection too (sometimes clicking fast selects the rule title)
521
+ getSelection ( ) ?. empty ( ) ;
469
522
}
470
523
} ) ;
471
524
472
525
this . props . toggleRuleCollapsed ( this . props . rule . id ) ;
526
+ this . stopEditingTitle ( ) ;
473
527
} ) ;
474
528
475
529
@action . bound
@@ -554,6 +608,40 @@ export class RuleRow extends React.Component<{
554
608
}
555
609
}
556
610
}
611
+
612
+ @action . bound
613
+ startEnteringCustomTitle ( event : React . UIEvent ) {
614
+ this . titleEditState = { originalTitle : this . props . rule . title } ;
615
+ // We expand the row, but not with toggleCollapsed, because we don't want
616
+ // to auto-focus the matcher like normal:
617
+ if ( this . props . collapsed ) this . props . toggleRuleCollapsed ( this . props . rule . id ) ;
618
+ event . stopPropagation ( ) ;
619
+ }
620
+
621
+ @action . bound
622
+ editTitle ( newTitle : string | undefined ) {
623
+ this . props . rule . title = newTitle || undefined ;
624
+ }
625
+
626
+ @action . bound
627
+ cancelEditingTitle ( ) {
628
+ if ( ! this . titleEditState ) return ;
629
+ this . editTitle ( this . titleEditState . originalTitle ) ;
630
+ this . titleEditState = undefined ;
631
+ }
632
+
633
+ @action . bound
634
+ stopEditingTitle ( ) {
635
+ if ( ! this . titleEditState ) return ;
636
+
637
+ // Trim titles on save, so that e.g. space-only titles are dropped:
638
+ if ( this . props . rule . title !== this . titleEditState . originalTitle ) {
639
+ this . props . rule . title = this . props . rule . title ?. trim ( ) || undefined ;
640
+ }
641
+
642
+ this . titleEditState = undefined ;
643
+ }
644
+
557
645
}
558
646
559
647
@observer
0 commit comments