Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import {
createLNodeElementInIED,
createServerElementWithAuth,
getExistingServer,
getExistingLDevice,
createLDeviceElement,
getOrCreateLDeviceElement,
hasLNodeInTargetDoc
} from './elements'

Expand Down Expand Up @@ -52,13 +51,14 @@ function ensureLDevice(
doc: XMLDocument,
sourceFunction: ConductingEquipmentTemplate | FunctionTemplate
): { lDevice: Element; edit: Insert | undefined } {
const existingLDevice = getExistingLDevice(server, sourceFunction)
if (existingLDevice) {
return { lDevice: existingLDevice, edit: undefined }
const lDevice = getOrCreateLDeviceElement(doc, sourceFunction, server)

// If lDevice already existed in server, no edit needed
const alreadyExists = Array.from(server.children).includes(lDevice)
if (alreadyExists) {
return { lDevice, edit: undefined }
}

const lDevice = createLDeviceElement(doc, sourceFunction)

const edit: Insert = {
node: lDevice,
parent: server,
Expand Down Expand Up @@ -89,11 +89,8 @@ export function createLNodesInAccessPoint({
lNodes,
iedName,
accessPoint
}: CreateLNodesParams): void {
const editor = pluginGlobalStore.editor
if (!editor) {
throw new Error('No editor found')
}
}: CreateLNodesParams): Insert[] {
const edits: Insert[] = []

if (!pluginGlobalStore.xmlDocument) {
throw new Error('No XML document found')
Expand All @@ -113,10 +110,8 @@ export function createLNodesInAccessPoint({

if (lNodesToAdd.length === 0) {
console.info('[createLNodesInAccessPoint] No new lNodes to add')
return
return edits
}

const edits: Insert[] = []
const { serverElement, edit: serverEdit } = ensureServer(accessPoint, doc)
const { lDevice, edit: lDeviceEdit } = ensureLDevice(
serverElement,
Expand All @@ -135,7 +130,5 @@ export function createLNodesInAccessPoint({
edits.push(lNodeEdit)
}

editor.commit(edits, {
title: `Add LNodes to IED ${iedName}`
})
return edits
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i need to change this back to your version

Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function generateLDeviceInst(
return functionName
}

export function getExistingLDevice(
function getExistingLDevice(
server: Element,
sourceFunction: ConductingEquipmentTemplate | FunctionTemplate
): Element | undefined {
Expand All @@ -47,10 +47,16 @@ export function getExistingLDevice(
)
}

export function createLDeviceElement(
export function getOrCreateLDeviceElement(
doc: XMLDocument,
sourceFunction: ConductingEquipmentTemplate | FunctionTemplate
sourceFunction: ConductingEquipmentTemplate | FunctionTemplate,
server: Element
): Element {
const existingLDevice = getExistingLDevice(server, sourceFunction)
if (existingLDevice) {
return existingLDevice
}

const { functionName, conductingEquipmentName } =
extractFunctionNames(sourceFunction)
const lDevice = doc.createElement('LDevice')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import type { SetAttributes } from '@openscd/oscd-api'
import type {
EqFunctionTemplate,
FunctionTemplate,
LNodeTemplate
} from '@/headless/common-types'
import { getDocumentAndEditor } from '../utils'
import { bayStore } from '@/headless/stores'

type UpdateBayLNodesParams = {
scdBay: Element
lNodes: LNodeTemplate[]
iedName: string
sourceFunction: EqFunctionTemplate | FunctionTemplate
equipmentUuid?: string
}

function findMatchingLNodeElement(
scdBay: Element,
lNode: LNodeTemplate,
sourceFunction: EqFunctionTemplate | FunctionTemplate,
equipmentUuid?: string
): Element | null {
let functionElements: Element[] = []

// EqFunctions are inside ConductingEquipment
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this also looks really complex, perhaps break this down into smaller chunks?

if (equipmentUuid) {
const match = bayStore.equipmentMatches.find(
(m) => m.templateEquipment.uuid === equipmentUuid
)

if (match) {
const targetEquipment = match.scdElement
const eqFunctions = Array.from(
targetEquipment.querySelectorAll('EqFunction')
)
functionElements = eqFunctions.filter((eqFunc) => {
const name = eqFunc.getAttribute('name')
return name === sourceFunction.name
})
}
} else {
const functions = Array.from(
scdBay.querySelectorAll(':scope > Function')
)
functionElements = functions.filter((func) => {
const name = func.getAttribute('name')
return name === sourceFunction.name
})
}

for (const functionElement of functionElements) {
const lnodeElements = Array.from(
functionElement.querySelectorAll('LNode')
)

const matches = lnodeElements.filter((element) => {
const lnClass = element.getAttribute('lnClass')
const lnType = element.getAttribute('lnType')
const lnInst = element.getAttribute('lnInst')

return (
lnClass === lNode.lnClass &&
lnType === lNode.lnType &&
lnInst === lNode.lnInst
)
})

const unassignedMatch = matches.find(
(element) => !element.getAttribute('iedName')
)
if (unassignedMatch) {
return unassignedMatch
}

if (matches.length > 0) {
return matches[0]
}
}

return null
}

export function updateBayLNodeIedNames({
scdBay,
lNodes,
iedName,
sourceFunction,
equipmentUuid
}: UpdateBayLNodesParams): SetAttributes[] {
const edits: SetAttributes[] = []

for (const lNode of lNodes) {
const lnodeElement = findMatchingLNodeElement(
scdBay,
lNode,
sourceFunction,
equipmentUuid
)
if (!lnodeElement) {
continue
}

const currentIedName = lnodeElement.getAttribute('iedName')
if (currentIedName) {
continue
}
edits.push({
element: lnodeElement,
attributes: {
iedName
},
attributesNS: {}
})
}

return edits
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ export function applyBayTypeSelection(bayName: string): void {
title: `Assign BayType "${bayType.name}" to Bay "${bayName}"`
})

bayStore.assigendBayType = selectedBayTypeName
bayStore.equipmentMatches = matches

const allLNodeTemplates: LNodeTemplate[] = []

for (const match of matches) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,5 @@ export function createLNodeElement(
lnodeElement.setAttribute('lnInst', lnodeTemplate.lnInst)
lnodeElement.setAttribute('lnType', lnodeTemplate.lnType)

if (lnodeTemplate.iedName) {
lnodeElement.setAttribute('iedName', lnodeTemplate.iedName)
}

return lnodeElement
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { getDocumentAndEditor } from '@/headless/utils'
import type { EquipmentMatch } from '@/headless/matching/types'

class UseBayStore {
selectedBay: string | null = $state<string | null>(null)
selectedBayUuid = $state<string | null>(null)
assigendBayType: string | null = null // As soon as any lnode is assigned
assigendBayType = $state<string | null>(null)
pendingBayTypeApply = $state<string | null>(null)
equipmentMatches: EquipmentMatch[] = $state<EquipmentMatch[]>([])

scdBay = $derived.by(() => {
if (!this.selectedBay) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@ import type {
FunctionTemplate,
LNodeTemplate
} from '../common-types'
import type { Insert, SetAttributes } from '@openscd/oscd-api'
import { pluginGlobalStore } from '@oscd-plugins/core-ui-svelte'
import { createLNodesInAccessPoint } from '../ied/create-lNode-in-access-point'
import { updateBayLNodeIedNames } from '../ied/update-bay-lnodes'
import { applyBayTypeSelection } from '../matching'
import { bayStore } from './bay.store.svelte'
import { bayTypesStore } from './bay-types.store.svelte'
import { equipmentMatchingStore } from './equipment-matching.store.svelte'

type DraggedItem = {
type: 'equipmentFunction' | 'functionTemplate' | 'lNode'
lNodes: LNodeTemplate[]
sourceFunction: EqFunctionTemplate | FunctionTemplate
equipmentName?: string
equipmentUuid?: string
}

class UseDndStore {
Expand Down Expand Up @@ -41,22 +48,87 @@ class UseDndStore {
}

try {
createLNodesInAccessPoint({
const hasAssignedBayType = !!bayStore.assigendBayType
const hasSelectedBay = !!bayStore.selectedBay
const validation = equipmentMatchingStore.validationResult

let didApplyBayType = false

if (!hasAssignedBayType && hasSelectedBay) {
const requiresManualMatching =
validation?.requiresManualMatching ?? false

const hasValidAutoSelection =
!!bayTypesStore.selectedBayType &&
!!validation?.isValid &&
!requiresManualMatching

const hasPendingManualSelection =
!!bayStore.pendingBayTypeApply && requiresManualMatching

if (hasPendingManualSelection) {
bayTypesStore.selectedBayType = bayStore.pendingBayTypeApply
}

if (hasValidAutoSelection || hasPendingManualSelection) {
applyBayTypeSelection(bayStore.selectedBay)
bayStore.assigendBayType = bayTypesStore.selectedBayType
bayStore.pendingBayTypeApply = null
equipmentMatchingStore.clearValidationResult()
didApplyBayType = true
}
}

const allEdits: (Insert | SetAttributes)[] = []

const iedEdits = createLNodesInAccessPoint({
sourceFunction: functionFromSSD,
lNodes,
iedName: targetSIedName,
accessPoint: targetAccessPoint
})
allEdits.push(...iedEdits)

if (bayStore.scdBay && (hasAssignedBayType || didApplyBayType)) {
const bayEdits = updateBayLNodeIedNames({
scdBay: bayStore.scdBay,
lNodes,
iedName: targetSIedName,
sourceFunction: functionFromSSD,
equipmentUuid: this.draggedItem.equipmentUuid
})
allEdits.push(...bayEdits)
}

if (allEdits.length > 0) {
const editor = pluginGlobalStore.editor
if (!editor) {
throw new Error('No editor found')
}

const functionName = functionFromSSD.name
const lnodeInfo =
lNodes.length === 1
? `${lNodes[0].lnClass}`
: `${lNodes.length} LNodes`

const title = didApplyBayType
? `Apply BayType and assign ${lnodeInfo} from ${functionName} to IED ${targetSIedName}`
: `Assign ${lnodeInfo} from ${functionName} to IED ${targetSIedName}`

editor.commit(
allEdits,
didApplyBayType ? { squash: true } : { title }
)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks really complex, lets break this down into multiple functions with more descriptive names, so we know whats happening

} catch (error) {
console.error('[DnD] Error creating LNodes:', error)
}

this.handleDragEnd()
}

get currentDraggedItem() {
return this.draggedItem
}
}

export const dndStore = new UseDndStore()
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const {
{#each conductingEquipmentTemplates as equipment}
{#if equipment.eqFunctions && equipment.eqFunctions.length > 0}
{#each equipment.eqFunctions as eqFunc}
<EqFunctionType eqFunction={eqFunc} equipmentName={equipment.name} />
<EqFunctionType eqFunction={eqFunc} {equipment} />
{/each}
{/if}
{/each}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
} from '@/headless/stores'
import { Button } from '@oscd-plugins/core-ui-svelte'
import EquipmentMatching from '@/ui/components/columns/bay-type/equipment-matching.svelte'
import { applyBayTypeSelection } from '@/headless/matching'

const {
bayTypeError
Expand All @@ -15,13 +14,12 @@ const {
} = $props()

function handleApplyBayType() {
if (!bayStore.selectedBay) {
if (!bayStore.selectedBay || !canApply) {
return
}

try {
applyBayTypeSelection(bayStore.selectedBay)
equipmentMatchingStore.clearValidationResult()
bayStore.pendingBayTypeApply = bayTypesStore.selectedBayType
} catch (error) {
console.error('[handleApplyBayType] Error:', error)
}
Expand Down
Loading