@@ -3,6 +3,7 @@ import { get, translate } from 'lit-translate';
3
3
4
4
import '@material/mwc-button' ;
5
5
import '@material/mwc-list/mwc-list-item' ;
6
+ import '@material/mwc-list/mwc-check-list-item' ;
6
7
import { List } from '@material/mwc-list' ;
7
8
import { ListItemBase } from '@material/mwc-list/mwc-list-item-base' ;
8
9
import { SingleSelectedEvent } from '@material/mwc-list/mwc-list-foundation' ;
@@ -32,13 +33,15 @@ import {
32
33
WizardAction ,
33
34
MenuAction ,
34
35
} from '../foundation.js' ;
36
+ import { FilteredList } from '../filtered-list.js' ;
35
37
import { FinderList } from '../finder-list.js' ;
36
38
import { dataAttributePicker , iEDPicker } from './foundation/finder.js' ;
37
39
import { maxLength , patterns } from './foundation/limits.js' ;
38
40
import { editDataSetWizard } from './dataset.js' ;
39
41
import { newFCDA } from './fcda.js' ;
40
42
import { contentOptFieldsWizard , editOptFieldsWizard } from './optfields.js' ;
41
43
import { contentTrgOpsWizard , editTrgOpsWizard } from './trgops.js' ;
44
+ import { existFcdaReference } from '../foundation/scl.js' ;
42
45
43
46
interface ContentOptions {
44
47
name : string | null ;
@@ -407,6 +410,147 @@ function getRptEnabledAction(
407
410
} ;
408
411
}
409
412
413
+ function copyReportControlActions ( element : Element ) : WizardActor {
414
+ return ( _ : WizardInputElement [ ] , wizard : Element ) => {
415
+ const doc = element . ownerDocument ;
416
+
417
+ const iedItems = < ListItemBase [ ] > (
418
+ wizard . shadowRoot ?. querySelector < FilteredList > ( 'filtered-list' ) ?. selected
419
+ ) ;
420
+
421
+ const complexActions : ComplexAction [ ] = [ ] ;
422
+ iedItems . forEach ( iedItem => {
423
+ const ied = doc . querySelector ( selector ( 'IED' , iedItem . value ) ) ;
424
+ if ( ! ied ) return ;
425
+
426
+ const sinkLn0 = ied . querySelector ( 'LN0' ) ;
427
+ if ( ! sinkLn0 ) return [ ] ;
428
+
429
+ const sourceDataSet = element . parentElement ?. querySelector (
430
+ `DataSet[name="${ element . getAttribute ( 'datSet' ) } "]`
431
+ ) ;
432
+ if (
433
+ sourceDataSet &&
434
+ sinkLn0 . querySelector (
435
+ `DataSet[name="${ sourceDataSet ! . getAttribute ( 'name' ) } "]`
436
+ )
437
+ )
438
+ return [ ] ;
439
+
440
+ if (
441
+ sinkLn0 . querySelector (
442
+ `ReportControl[name="${ element . getAttribute ( 'name' ) } "]`
443
+ )
444
+ )
445
+ return [ ] ;
446
+
447
+ // clone DataSet and make sure that FCDA is valid in ied
448
+ const sinkDataSet = < Element > sourceDataSet ?. cloneNode ( true ) ;
449
+ Array . from ( sinkDataSet . querySelectorAll ( 'FCDA' ) ) . forEach ( fcda => {
450
+ if ( ! existFcdaReference ( fcda , ied ) ) sinkDataSet . removeChild ( fcda ) ;
451
+ } ) ;
452
+ if ( sinkDataSet . children . length === 0 ) return [ ] ; // when no data left no copy needed
453
+
454
+ const sinkReportControl = < Element > element . cloneNode ( true ) ;
455
+
456
+ const source = element . closest ( 'IED' ) ?. getAttribute ( 'name' ) ;
457
+ const sink = ied . getAttribute ( 'name' ) ;
458
+
459
+ complexActions . push ( {
460
+ title : `ReportControl copied from ${ source } to ${ sink } ` ,
461
+ actions : [
462
+ { new : { parent : sinkLn0 , element : sinkDataSet } } ,
463
+ { new : { parent : sinkLn0 , element : sinkReportControl } } ,
464
+ ] ,
465
+ } ) ;
466
+ } ) ;
467
+
468
+ return complexActions ;
469
+ } ;
470
+ }
471
+
472
+ function renderIedListItem ( sourceCb : Element , ied : Element ) : TemplateResult {
473
+ const sourceDataSet = sourceCb . parentElement ?. querySelector (
474
+ `DataSet[name="${ sourceCb . getAttribute ( 'datSet' ) } "]`
475
+ ) ;
476
+
477
+ const isSourceIed =
478
+ sourceCb . closest ( 'IED' ) ?. getAttribute ( 'name' ) === ied . getAttribute ( 'name' ) ;
479
+ const ln0 = ied . querySelector ( 'AccessPoint > Server > LDevice > LN0' ) ;
480
+ const hasCbNameConflict = ln0 ?. querySelector (
481
+ `ReportControl[name="${ sourceCb . getAttribute ( 'name' ) } "]`
482
+ )
483
+ ? true
484
+ : false ;
485
+ const hasDataSetConflict = ln0 ?. querySelector (
486
+ `DataSet[name="${ sourceDataSet ?. getAttribute ( 'name' ) } "]`
487
+ )
488
+ ? true
489
+ : false ;
490
+
491
+ // clone DataSet and make sure that FCDA is valid in ied
492
+ const sinkDataSet = < Element > sourceDataSet ?. cloneNode ( true ) ;
493
+ Array . from ( sinkDataSet . querySelectorAll ( 'FCDA' ) ) . forEach ( fcda => {
494
+ if ( ! existFcdaReference ( fcda , ied ) ) sinkDataSet . removeChild ( fcda ) ;
495
+ } ) ;
496
+ const hasDataMatch = sinkDataSet . children . length > 0 ;
497
+
498
+ const primSpan = ied . getAttribute ( 'name' ) ;
499
+ let secondSpan = '' ;
500
+ if ( isSourceIed ) secondSpan = get ( 'controlblock.hints.source' ) ;
501
+ else if ( ! ln0 ) secondSpan = get ( 'controlblock.hints.missingServer' ) ;
502
+ else if ( hasDataSetConflict && ! isSourceIed )
503
+ secondSpan = get ( 'controlblock.hints.exist' , {
504
+ type : 'RerportControl' ,
505
+ name : sourceCb . getAttribute ( 'name' ) ! ,
506
+ } ) ;
507
+ else if ( hasCbNameConflict && ! isSourceIed )
508
+ secondSpan = get ( 'controlblock.hints.exist' , {
509
+ type : 'DataSet' ,
510
+ name : sourceCb . getAttribute ( 'name' ) ! ,
511
+ } ) ;
512
+ else if ( ! hasDataMatch ) secondSpan = get ( 'controlblock.hints.noMatchingData' ) ;
513
+ else secondSpan = get ( 'controlBlock.hints.valid' ) ;
514
+
515
+ return html `< mwc-check-list-item
516
+ twoline
517
+ value ="${ identity ( ied ) } "
518
+ ?disabled =${ isSourceIed ||
519
+ ! ln0 ||
520
+ hasCbNameConflict ||
521
+ hasDataSetConflict ||
522
+ ! hasDataMatch }
523
+ > < span > ${ primSpan } </ span
524
+ > < span slot ="secondary "> ${ secondSpan } </ span > </ mwc-check-list-item
525
+ > ` ;
526
+ }
527
+
528
+ export function reportControlCopyToIedSelector ( element : Element ) : Wizard {
529
+ return [
530
+ {
531
+ title : get ( 'report.wizard.location' ) ,
532
+ primary : {
533
+ icon : 'save' ,
534
+ label : get ( 'save' ) ,
535
+ action : copyReportControlActions ( element ) ,
536
+ } ,
537
+ content : [
538
+ html `< filtered-list multi
539
+ > ${ Array . from ( element . ownerDocument . querySelectorAll ( 'IED' ) ) . map (
540
+ ied => renderIedListItem ( element , ied )
541
+ ) } </ filtered-list
542
+ > ` ,
543
+ ] ,
544
+ } ,
545
+ ] ;
546
+ }
547
+
548
+ function openIedsSelector ( element : Element ) : WizardMenuActor {
549
+ return ( ) : WizardAction [ ] => {
550
+ return [ ( ) => reportControlCopyToIedSelector ( element ) ] ;
551
+ } ;
552
+ }
553
+
410
554
export function removeReportControl ( element : Element ) : WizardMenuActor {
411
555
return ( ) : WizardAction [ ] => {
412
556
const complexAction = removeReportControlAction ( element ) ;
@@ -538,6 +682,12 @@ export function editReportControlWizard(element: Element): Wizard {
538
682
action : openOptFieldsWizard ( optFields ) ,
539
683
} ) ;
540
684
685
+ menuActions . push ( {
686
+ icon : 'copy' ,
687
+ label : get ( 'controlblock.label.copy' ) ,
688
+ action : openIedsSelector ( element ) ,
689
+ } ) ;
690
+
541
691
return [
542
692
{
543
693
title : get ( 'wizard.title.edit' , { tagName : element . tagName } ) ,
0 commit comments