UNPKG

@kieler/klighd-core

Version:

Core KLighD diagram visualization with Sprotty

214 lines (205 loc) 8.72 kB
/* * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient * * http://rtsys.informatik.uni-kiel.de/kieler * * Copyright 2019-2023 by * + Kiel University * + Department of Computer Science * + Real-Time and Embedded Systems Group * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. * * SPDX-License-Identifier: EPL-2.0 */ import { KGraphData, SKGraphElement } from '@kieler/klighd-interactive/lib/constraint-classes' import { SModelRootImpl } from 'sprotty' import { isProxyRendering } from './proxy-view/proxy-view-util' import { isContainerRendering, isPolyline, isRendering, KPolyline, KRendering, K_POLYLINE, K_RENDERING_REF, SKEdge, SKLabel, SKNode, SKPort, } from './skgraph-models' /* global Element, SVGElement */ /** * Returns the SVG element in the DOM that represents the topmost KRendering in the hierarchy. * If an action should be triggered on the element, bubble up through the rendering hierarchy until the first actionable rendering.. * @param target The graph element the event is triggered on. * @param element The topmost SVG element clicked. * @param actionable Optional parameter to search for actionable renderings only. Defaults to false. */ export function getSemanticElement( target: SKGraphElement, element: EventTarget | null, actionable = false ): SVGElement | undefined { if (!(element instanceof SVGElement)) { return undefined } let currentElement: Element | null = element let semanticElement while (semanticElement === undefined && currentElement instanceof SVGElement) { // Check if the rendering has an action. let renderingHasAction = true if (currentElement.id !== '' && actionable) { const rendering = findRendering(target, currentElement) if (!rendering) { // If no rendering for this ID exists, we have gone too far up to the next graph element. Skip from here. return undefined } if (!hasAction(rendering)) { renderingHasAction = false } } // Choose this element if it is a defined element with an ID. // Also only use this if there is an action and we look for an actionable rendering (action => renderingHasAction) if (currentElement.id !== '' && (!actionable || renderingHasAction)) { semanticElement = currentElement } else { currentElement = currentElement.parentElement } } return semanticElement } /** * Returns if there is a KLighD action defined on the given rendering that needs to be handled. * * @param rendering the rendering that may define an action to be executed. * @param includeChildren if this should also search for actions in any of the rendering's children. * Defaults to false. * @returns if there is at least one action for this rendering. */ export function hasAction(rendering: KRendering, includeChildren = false): boolean { if (rendering.actions && rendering.actions.length !== 0) { return true } if (includeChildren && isContainerRendering(rendering)) { for (const child of rendering.children) { if (hasAction(child, includeChildren)) { return true } } if (isPolyline(rendering)) { if (hasAction(rendering.junctionPointRendering, includeChildren)) { return true } } } return false } /** * Finds the KRendering in the data of the SKGraphElement that matches the given ID. * @param graphElement The graph element to look in. * @param svgElement The SVG element that represents the rendering. */ export function findRendering(graphElement: SKGraphElement, svgElement: SVGElement): KRendering | undefined { const isProxy = isProxyRendering(svgElement, graphElement.id) const svgId = svgElement.id // The first rendering has to be extracted from the SKGraphElement. It is the first data object that is a KRendering. let data: KGraphData[] | undefined if (isProxy) { // For a proxy, the rendering may be in the graph element's properties. data = graphElement.properties['de.cau.cs.kieler.klighd.proxyView.proxyRendering'] as KGraphData[] } if (data === undefined) { // Non-proxies and proxies without an explicit proxy rendering just use the graph element's data. data = graphElement.data } let currentElement: KRendering = data.find((possibleRendering) => isRendering(possibleRendering)) as KRendering // The real rendering ID starts after the graph element ID prefix, delimited by a $$$. const renderingId = svgId.split('$$$')[1] ?? svgId if (renderingId === undefined) { return undefined } const idPath = renderingId.split('$') if (currentElement.type === K_RENDERING_REF) { // KRenderingRefs' ids always start with the identifying name of the reference and may continue with $<something> to refer to renderings within that reference. // Start with index 1 since the currentElement already contains the rendering with the identifying name. // for (let i = 1; i < idPath.length; i++) { // TODO:looking up renderings in rendering references is not supported yet. return undefined } // The rendering id is build hierarchically and the first rendering is already found, so start with index 1 as a $ sign can be skipped. for (let i = 1; i < idPath.length; i++) { let nextElement if (isContainerRendering(currentElement)) { // First, look for the ID in the child renderings. nextElement = currentElement.children.find((childRendering) => svgId.startsWith(childRendering.properties['klighd.lsp.rendering.id'] as string) ) as KRendering } if (nextElement === undefined && currentElement.type === K_POLYLINE) { // If the rendering was not found yet, take the junction point rendering. if ( svgId.startsWith( (currentElement as KPolyline).junctionPointRendering.properties['klighd.lsp.rendering.id'] as string ) ) { nextElement = (currentElement as KPolyline).junctionPointRendering } } if (nextElement === undefined) { // This ID does not exist in the renderings, therefore does not belong to them. return undefined } currentElement = nextElement } // Now the currentElement should be the element searched for by the id. if ((currentElement.properties['klighd.lsp.rendering.id'] as string) !== svgId) { console.error( `The found element does not match the searched id! id: ${svgId}, found element: ${currentElement}` ) return undefined } return currentElement } /** * Finds the SKGraphElement that matches the given ID. * @param root The root. * @param id The ID to search for. * @returns The element matching the given id or `undefined` if there is none. */ export function getElementByID(root: SModelRootImpl, id: string, suppressErrors = false): SKGraphElement | undefined { let curr = root as unknown as SKGraphElement const idPath = id.split('$') // The node id is build hierarchically and the root is already given, so start with i=1 ($root) for (let i = 1; i < idPath.length && curr; i++) { // $$ in id (e.g. comments in sccharts) if (idPath[i].length > 0) { const nextType = idPath[i].charAt(0) if (nextType === 'E') { // Edge curr = ((curr as SKNode | SKPort).outgoingEdges as SKEdge[]).find((edge) => id.startsWith(edge.id) ) as SKEdge } else if (['N', 'L', 'P'].includes(nextType) || idPath[i] === 'root') { // Node, label or port curr = curr.children.find((node) => id.startsWith(node.id)) as SKNode | SKLabel | SKPort } } } // Now currNode should be the node searched for by the id if (!curr) { if (!suppressErrors) { console.error('No node found matching the id:', id) } return undefined } if (curr.id !== id) { if (!suppressErrors) { console.error('The found node does not match the searched id! id:', id, ', found node:', curr) } return undefined } return curr }