import { css , html , LitElement , property , query , state , TemplateResult , } from 'lit-element' ; import panzoom from 'panzoom' ; import { identity , getPathNameAttribute , newWizardEvent , SCLTag , getNameAttribute , getDescriptionAttribute } from '../foundation.js' ; import { compareNames , } from '../foundation.js' ; import { getAbsolutePosition , createTerminalElement , createBusBarElement , createVoltageLevelElement , createBayElement , createConductingEquipmentElement , createConnectivityNodeElement , getBusBarLength , createPowerTransformerElement , getAbsolutePositionBusBar , drawBusBarRoute , getDirections , getAbsolutePositionTerminal , drawCNodeConnections , getConnectivityNodesDrawingPosition , createSubstationElement , } from './singlelinediagram/sld-drawing.js' ; import { isBusBar , getConnectedTerminals , getCommonParentElement , } from './singlelinediagram/foundation.js' ; import { isSCLNamespace } from "../schemas.js" ; import { wizards } from './singlelinediagram/wizards/wizard-library.js' ; import { SingleSelectedEvent } from "@material/mwc-list/mwc-list-foundation" ; import { translate } from "lit-translate" ; import '@material/mwc-list/mwc-list-item' ; import '@material/mwc-select' ; import '@material/mwc-textfield' ; /**
 * Main class plugin for Single Line Diagram editor.
 */ export default class SingleLineDiagramPlugin extends LitElement { @ property ( { attribute : false } ) doc ! : XMLDocument ; @ state ( ) selectedSubstation : Element | undefined ; @ query ( '#panzoom' ) panzoomContainer ! : HTMLElement ; @ query ( '#svg' ) svg ! : SVGGraphicsElement ; private get substations ( ) : Element [ ] { return Array . from ( this . doc . querySelectorAll ( ':root > Substation' ) ) . sort ( ( a , b ) => compareNames ( a , b ) ) ; } /**
     * Get all the Power Transformers from an element.
     */ private getPowerTransformers ( parentElement ) { return Array . from ( parentElement . querySelectorAll ( 'PowerTransformer' ) ) . filter ( isSCLNamespace ) ; } /**
     * Get all the Voltage Levels from the substation.
     */ private getVoltageLevels ( substationElement ) { return Array . from ( substationElement . querySelectorAll ( 'VoltageLevel' ) ) . filter ( isSCLNamespace ) ; } /**
     * Get all the BusBars from the voltage level.
     */ private getBusBars ( voltageLevelElement ) { return Array . from ( voltageLevelElement . querySelectorAll ( 'Bay' ) ) . filter ( isSCLNamespace ) . filter ( bay => isBusBar ( bay ) ) ; } /**
     * Get all the bays from the voltage level.
     */ private getBays ( voltageLevelElement ) { return Array . from ( voltageLevelElement . querySelectorAll ( 'Bay' ) ) . filter ( isSCLNamespace ) . filter ( bay => ! isBusBar ( bay ) ) ; } param bayElement private getConductingEquipments ( bayElement ) { return Array . from ( bayElement . querySelectorAll ( 'ConductingEquipment' ) ) . filter ( isSCLNamespace ) ; } param bayElement private getConnectivityNode ( bayElement ) { return Array . from ( bayElement . querySelectorAll ( 'ConnectivityNode' ) ) . filter ( isSCLNamespace ) . filter ( cNode => cNode . getAttribute ( 'name' ) !== 'grounded' ) ; } param parentElement param pathName private findEquipment ( parentElement , pathName ) { return Array . from ( parentElement . querySelectorAll ( 'ConductingEquipment, PowerTransformer' ) ) . filter ( isSCLNamespace ) . filter ( element => element . querySelector ( `Terminal[connectivityNode="${ pathName }"]` ) ) ; } /**
     * Draw all equipment and connections of the selected Substation.
     */ private drawSubstation ( ) { const substationGroup = createSubstationElement ( this . selectedSubstation ! ) ; this . svg . appendChild ( substationGroup ) ; this . drawPowerTransformers ( this . selectedSubstation ! , substationGroup ) ; this . drawVoltageLevels ( this . selectedSubstation ! , substationGroup ) ; } param parentElement param parentGroup private drawPowerTransformers ( parentElement , parentGroup ) { this . getPowerTransformers ( parentElement ) . forEach ( powerTransformerElement => this . drawPowerTransformer ( parentGroup , powerTransformerElement ) ) ; } param parentGroup param powerTransformerElement private drawPowerTransformer ( parentGroup , powerTransformerElement ) { const powerTransformerGroup = createPowerTransformerElement ( powerTransformerElement , ( event ) => this . openEditWizard ( event , powerTransformerElement ! ) ) ; parentGroup . appendChild ( powerTransformerGroup ) ; } param substationElement param substationGroup private drawVoltageLevels ( substationElement , substationGroup ) { this . getVoltageLevels ( substationElement ) . forEach ( voltageLevelElement => { const voltageLevelGroup = createVoltageLevelElement ( voltageLevelElement ) ; substationGroup . appendChild ( voltageLevelGroup ) ; this . drawPowerTransformers ( voltageLevelElement , voltageLevelGroup ) ; this . drawBays ( voltageLevelElement , voltageLevelGroup ) ; this . drawBusBars ( voltageLevelElement , voltageLevelGroup ) ; } ) ; this . getVoltageLevels ( substationElement ) . forEach ( voltageLevelElement => { this . getBusBars ( voltageLevelElement ) . forEach ( busbarElement => { this . drawBusBarConnections ( substationElement , this . svg , busbarElement ) ; } ) ; this . getBays ( voltageLevelElement ) . forEach ( bayElement => { this . drawBayConnections ( substationElement , this . svg , bayElement ) ; } ) ; } ) ; } param voltageLevelElement param voltageLevelGroup private drawBays ( voltageLevelElement , voltageLevelGroup ) { this . getBays ( voltageLevelElement ) . forEach ( bayElement => { const bayGroup = createBayElement ( bayElement ) ; voltageLevelGroup . appendChild ( bayGroup ) ; this . drawPowerTransformers ( bayElement , bayGroup ) ; this . drawConductingEquipments ( bayElement , bayGroup ) ; this . drawConnectivityNodes ( bayElement , bayGroup ) ; } ) ; } param bayElement param bayGroup private drawConductingEquipments ( bayElement , bayGroup ) { this . getConductingEquipments ( bayElement ) . filter ( conductingEquipmentElement => Array . from ( conductingEquipmentElement . querySelectorAll ( 'Terminal' ) ) . filter ( terminal => terminal . getAttribute ( 'cNodeName' ) !== 'grounded' ) . length !== 0 ) . forEach ( conductingEquipmentElement => { const conductingEquipmentGroup = createConductingEquipmentElement ( conductingEquipmentElement , ( event ) => this . openEditWizard ( event , conductingEquipmentElement ! ) ) ; bayGroup . appendChild ( conductingEquipmentGroup ) ; } ) ; } param bayElement param bayGroup private drawConnectivityNodes ( bayElement , bayGroup ) { this . getConnectivityNode ( bayElement ) . filter ( cNode => getConnectedTerminals ( cNode ) . length > 0 ) . forEach ( cNode => { const cNodegroup = createConnectivityNodeElement ( cNode , ( event ) => this . openEditWizard ( event , cNode ) ) ; bayGroup . appendChild ( cNodegroup ) ; } ) ; } param rootElement param rootGroup param bayElement private drawBayConnections ( rootElement , rootGroup , bayElement ) { this . getConnectivityNode ( bayElement ) . forEach ( cNode => { this . findEquipment ( rootElement , getPathNameAttribute ( cNode ) ) . forEach ( equipmentElement => { const commonParentElement = getCommonParentElement ( cNode , equipmentElement , bayElement ) ; const sides = getDirections ( equipmentElement , cNode ) ; const elementsTerminalPosition = getAbsolutePositionTerminal ( equipmentElement , sides . startDirection ) ; const cNodePosition = getConnectivityNodesDrawingPosition ( cNode , sides . endDirection ) ; rootGroup . querySelectorAll ( `g[id="${ identity ( commonParentElement ) }"]` ) . forEach ( eq => drawCNodeConnections ( cNodePosition , elementsTerminalPosition , < SVGElement > eq ) ) ; const terminalElement = equipmentElement . querySelector ( `Terminal[connectivityNode="${ cNode . getAttribute ( 'pathName' ) }"]` ) ; const terminal = createTerminalElement ( terminalElement ! , sides . startDirection , ( event ) => this . openEditWizard ( event , terminalElement ! ) ) ; rootGroup . querySelectorAll ( `g[id="${ identity ( equipmentElement ) }"]` ) . forEach ( eq => eq . appendChild ( terminal ) ) ; } ) ; } ) ; } param voltageLevelElement param voltageLevelGroup private drawBusBars ( voltageLevelElement , voltageLevelGroup ) { this . getBusBars ( voltageLevelElement ) . forEach ( busbarElement => this . drawBusBar ( voltageLevelElement , voltageLevelGroup , busbarElement ) ) ; } param parentElement param parentGroup param busbarElement private drawBusBar ( parentElement , parentGroup , busbarElement ) { const busBarGroup = createBusBarElement ( busbarElement , getBusBarLength ( parentElement ) , ( event ) => this . openEditWizard ( event , busbarElement ) ) ; parentGroup . appendChild ( busBarGroup ) ; } param rootElement param rootGroup param busbarElement private drawBusBarConnections ( rootElement , rootGroup , busbarElement ) { const pathName = getPathNameAttribute ( busbarElement . children [ 0 ] ) ; const busBarPosition = getAbsolutePositionBusBar ( busbarElement ) ; this . findEquipment ( rootElement , pathName ) . forEach ( element => { const parentElement = element . parentElement ; const elementPosition = getAbsolutePosition ( element ) ; const elementsTerminalSide = busBarPosition . y < elementPosition . y ? 'top' : 'bottom' ; const elementsTerminalPosition = getAbsolutePositionTerminal ( element , elementsTerminalSide ) ; const busbarTerminalPosition = { x : elementsTerminalPosition . x , y : busBarPosition . y , } ; const terminalElement = element . querySelector ( `Terminal[connectivityNode="${ pathName }"]` ) ; rootGroup . querySelectorAll ( `g[id="${ identity ( parentElement ) }"]` ) . forEach ( eq => drawBusBarRoute ( busbarTerminalPosition , elementsTerminalPosition , < SVGElement > eq ) ) ; const terminal = createTerminalElement ( terminalElement ! , elementsTerminalSide , ( event ) => this . openEditWizard ( event , terminalElement ! ) ) ; rootGroup . querySelectorAll ( `g[id="${ identity ( element ) }"]` ) . forEach ( eq => eq . appendChild ( terminal ) ) ; } ) ; } /**
     * Remove all the child elements (and descendants) from the SVG Element, to have a clean start.
     */ private clearSVG ( ) { while ( this . svg . firstChild ) { this . svg . removeChild ( this . svg . lastChild ! ) ; } } /**
     * Draw all the elements of the selected Substation.
     */ drawSVGElements ( ) { this . clearSVG ( ) ; this . drawSubstation ( ) ; const bbox = this . svg . getBBox ( ) ; this . svg . setAttribute ( "viewBox" , ( bbox . x - 10 ) + " " + ( bbox . y - 10 ) + " " + ( bbox . width + 20 ) + " " + ( bbox . height + 20 ) ) ; this . svg . setAttribute ( "width" , ( bbox . width + 20 ) + "px" ) ; this . svg . setAttribute ( "height" , ( bbox . height + 20 ) + "px" ) ; panzoom ( this . panzoomContainer , { zoomSpeed : 0.2 , maxZoom : 1.5 , minZoom : 0.2 , initialZoom : 0.5 , } ) ; } param element openEditWizard ( event , element ) { const wizard = wizards [ < SCLTag > element . tagName ] . edit ( element ) ; if ( wizard ) { this . dispatchEvent ( newWizardEvent ( wizard ) ) ; event . stopPropagation ( ) ; } } firstUpdated ( ) { this . drawSVGElements ( ) ; } onSelect ( event ) { this . selectedSubstation = this . substations [ event . detail . index ] ; this . drawSVGElements ( ) ; } private renderSubstationSelector ( ) { const substationList = this . substations ; if ( substationList . length > 0 ) { if ( this . selectedSubstation === undefined ) { this . selectedSubstation = this . substations [ 0 ] ; } if ( substationList . length > 1 ) { const selectedSubstationName = getNameAttribute ( this . selectedSubstation ) ; return html `
          <mwc-select id="substationSelector"
                      label="${ translate ( "sld.substationSelector" ) }"
                      @selected=${ this . onSelect }>
            ${ this . substations . map ( substation => { const name = getNameAttribute ( substation ) ; const description = getDescriptionAttribute ( substation ) ; return html `
                  <mwc-list-item value="${ name }"
                                 ?selected=${ name === selectedSubstationName }>
                    ${ name }${ description !== undefined ? ' (' + description + ')' : '' }
                  </mwc-list-item>` ; } ) }
          </mwc-select>
        ` ; } const name = getNameAttribute ( this . selectedSubstation ) ; const description = getDescriptionAttribute ( this . selectedSubstation ) ; return html `
        <mwc-textfield label="${ translate ( 'substation.name' ) }"
                       value="${ name }${ description !== undefined ? ' (' + description + ')' : '' }"
                       id="selectedSubstation"
                       readonly
                       disabled>
        </mwc-textfield>
      ` ; } return html `
      <h1>
        <span id="noSubstationSelector">${ translate ( 'substation.missing' ) }</span>
      </h1>
    ` ; } render ( ) { return html `
      ${ this . renderSubstationSelector ( ) }

      <div class="sldContainer">
        <div id="panzoom">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            id="svg"
          ></svg>
        </div>
      </div>` ; } static styles = css `
    h1 {
      color: var(--mdc-theme-on-surface);
      font-family: 'Roboto', sans-serif;
      font-weight: 300;
      overflow: hidden;
      white-space: nowrap;
      text-overflow: ellipsis;
      margin: 0px;
      line-height: 48px;
      padding-left: 0.3em;
    }

    #substationSelector {
      width: 30vw;
      margin: 0.67em 0 0 0.67em;
    }

    #selectedSubstation {
      width: 30vw;
      margin: 0.67em 0 0 0.67em;
    }

    #noSubstationSelector {
      color: var(--base1)
    }

    .sldContainer {
      overflow: hidden;
    }

    g {
      pointer-events: bounding-box;
    }

    g[type='Busbar']:hover,
    g[type='ConductingEquipment']:hover,
    g[type='ConnectivityNode']:hover,
    g[type='PowerTransformer']:hover,
    g[type='Terminal']:hover {
      outline: 2px dashed var(--mdc-theme-primary);
      transition: transform 200ms linear, box-shadow 250ms linear;
    }
  ` ; }