/* eslint new-cap: ["error", {"newIsCapExceptionPattern": "^mx"}] */
import 'mxgraph/javascript/src/css/common.css'
import { useSelector } from 'react-redux'
import mxGraphFactory from 'mxgraph'
import { styleToObject } from '../../../utils/GalleryUtils'
import { getPortType, InputPort, OutputPort } from './ComponentDrag'
import { portSize, getParameter } from './SvgParser'
let graph
let undoManager
const {
mxPrintPreview,
mxConstants,
mxRectangle,
mxUtils,
mxUndoManager,
mxEvent,
mxCodec,
mxPoint
} = new mxGraphFactory()
const DEBUG_PORT = false
export default function toolbarTools (grid) {
graph = grid
undoManager = new mxUndoManager()
const listener = function (sender, evt) {
undoManager.undoableEditHappened(evt.getProperty('edit'))
}
graph.getModel().addListener(mxEvent.UNDO, listener)
graph.getView().addListener(mxEvent.UNDO, listener)
}
// SAVE
export function saveXml (description = '') {
const enc = new mxCodec(mxUtils.createXmlDocument())
const model = graph.getModel()
const firstCell = model.cells[0]
firstCell.appname = process.env.REACT_APP_NAME
firstCell.description = description
const node = enc.encode(model)
const pins = node.querySelectorAll('Object[as="errorFields"],Object[as="pins"]')
pins.forEach(pin => { pin.remove() })
const xcosDiagram = document.createElementNS('', 'XcosDiagram')
xcosDiagram.setAttribute('background', '-1')
xcosDiagram.setAttribute('finalIntegrationTime', '0.5')
xcosDiagram.setAttribute('title', 'basic')
const contextArray = graph.contextArray
if (contextArray) {
const node0 = enc.encode(contextArray)
xcosDiagram.appendChild(node0)
}
xcosDiagram.appendChild(node)
const value = mxUtils.getXml(xcosDiagram)
return value
}
// UNDO
export function editorUndo () {
undoManager.undo()
}
// REDO
export function editorRedo () {
undoManager.redo()
}
// Zoom IN
export function editorZoomIn () {
graph.zoomIn()
}
// ZOOM OUT
export function editorZoomOut () {
graph.zoomOut()
}
// ZOOM ACTUAL
export function editorZoomAct () {
graph.zoomActual()
}
// DELETE COMPONENT
export function deleteComp () {
graph.removeCells()
}
// CLEAR WHOLE GRID
export function ClearGrid () {
graph.removeCells(graph.getChildVertices(graph.getDefaultParent()))
}
// ROTATE COMPONENT
export function Rotate () {
const cell = graph.getSelectionCell()
if (cell != null && cell.CellType === 'Component') {
const view = graph.getView()
const state = view.getState(cell, true)
const vHandler = graph.createVertexHandler(state)
vHandler.rotateCell(cell, 90, cell.getParent())
vHandler.destroy()
}
}
// PRINT PREVIEW OF SCHEMATIC
export function PrintPreview () {
const title = useSelector(state => state.saveSchematic.title)
// Matches actual printer paper size and avoids blank pages
const scale = 0.8
const headerSize = 50
const footerSize = 50
// Applies scale to page
const pageFormat = { x: 0, y: 0, width: 1169, height: 827 }
const pf = mxRectangle.fromRectangle(pageFormat || mxConstants.PAGE_FORMAT_A4_LANDSCAPE)
pf.width = Math.round(pf.width * scale * graph.pageScale)
pf.height = Math.round(pf.height * scale * graph.pageScale)
// Finds top left corner of top left page
const bounds = mxRectangle.fromRectangle(graph.getGraphBounds())
bounds.x -= graph.view.translate.x * graph.view.scale
bounds.y -= graph.view.translate.y * graph.view.scale
const x0 = Math.floor(bounds.x / pf.width) * pf.width
const y0 = Math.floor(bounds.y / pf.height) * pf.height
const preview = new mxPrintPreview(graph, scale, pf, 0, -x0, -y0)
preview.marginTop = headerSize * scale * graph.pageScale
preview.marginBottom = footerSize * scale * graph.pageScale
preview.autoOrigin = false
const oldRenderPage = preview.renderPage
preview.renderPage = function (w, h, x, y, content, pageNumber) {
const div = oldRenderPage.apply(this, arguments)
const header = document.createElement('div')
header.style.position = 'absolute'
header.style.boxSizing = 'border-box'
header.style.fontFamily = 'Arial,Helvetica'
header.style.height = (this.marginTop - 10) + 'px'
header.style.textAlign = 'center'
header.style.verticalAlign = 'middle'
header.style.marginTop = 'auto'
header.style.fontSize = '12px'
header.style.width = '100%'
header.style.fontWeight = '100'
// Vertical centering for text in header/footer
header.style.lineHeight = (this.marginTop - 10) + 'px'
const footer = header.cloneNode(true)
mxUtils.write(header, title + ' - ' + process.env.REACT_APP_NAME + ' on Cloud')
header.style.borderBottom = '1px solid blue'
header.style.top = '0px'
mxUtils.write(footer, 'Made with ' + process.env.REACT_APP_DIAGRAM_NAME + ' Editor - ' + pageNumber + ' - ' + process.env.REACT_APP_NAME + ' on Cloud')
footer.style.borderTop = '1px solid blue'
footer.style.bottom = '0px'
div.firstChild.appendChild(footer)
div.firstChild.appendChild(header)
return div
}
preview.open()
}
// ERC CHECK FOR SCHEMATIC
export function ErcCheck () {
const NoAddition = 'No ' + process.env.REACT_APP_BLOCK_NAME + ' added'
const list = graph.getModel().cells // mapping the grid
let vertexCount = 0
let errorCount = 0
let PinNC = 0
const ground = 0
for (const property in list) {
const cell = list[property]
if (cell.CellType === 'Component') {
for (const child in cell.children) {
const childVertex = cell.children[child]
if (childVertex.CellType === 'Pin' && childVertex.edges === null) { // Checking if connections exist from a given pin
++PinNC
++errorCount
} else {
for (const w in childVertex.edges) {
if (childVertex.edges[w].source === null || childVertex.edges[w].target === null) {
++PinNC
}
}
}
}
++vertexCount
}
}
if (vertexCount === 0) {
alert(NoAddition)
++errorCount
} else if (PinNC !== 0) {
alert('Pins not connected')
} else if (ground === 0) {
alert('Ground not connected')
} else {
if (errorCount === 0) {
alert('ERC Check completed')
}
}
}
export function renderXML () {
graph.view.refresh()
const xml = 'null'
const xmlDoc = mxUtils.parseXml(xml)
parseXmlToGraph(xmlDoc, graph)
}
const PORTDIRECTIONS = {
UNK: 0,
LOR: 4,
L2T: 5,
L2R: 6,
L2B: 7,
TOB: 12,
T2R: 13,
T2B: 14,
T2L: 15
}
export function getRotationParameters (stylename) {
let rotatename
if (stylename === 'ImplicitInputPort') {
rotatename = 'ExplicitInputPort'
} else if (stylename === 'ImplicitOutputPort') {
rotatename = 'ExplicitOutputPort'
} else {
rotatename = stylename
}
let portdirection = PORTDIRECTIONS.UNK
if (rotatename === 'ExplicitInputPort' || rotatename === 'ExplicitOutputPort') {
portdirection = PORTDIRECTIONS.LOR
} else if (rotatename === 'ControlPort' || rotatename === 'CommandPort') {
portdirection = PORTDIRECTIONS.TOB
}
return { rotatename, portdirection }
}
export function getPins (portOrientation, v1) {
let pins
switch (portOrientation) {
case 'ExplicitInputPort':
v1.explicitInputPorts += 1
pins = v1.pins?.explicitInputPorts
break
case 'ImplicitInputPort':
v1.implicitInputPorts += 1
pins = v1.pins?.implicitInputPorts
break
case 'ControlPort':
v1.controlPorts += 1
pins = v1.pins?.controlPorts
break
case 'ExplicitOutputPort':
v1.explicitOutputPorts += 1
pins = v1.pins?.explicitOutputPorts
break
case 'ImplicitOutputPort':
v1.implicitOutputPorts += 1
pins = v1.pins?.implicitOutputPorts
break
case 'CommandPort':
v1.commandPorts += 1
pins = v1.pins?.commandPorts
break
default:
pins = null
break
}
return pins
}
export function getPointXY (rotationParameters, blockname) {
let pointX
let pointY
switch (rotationParameters.rotatename) {
case 'ExplicitInputPort':
if (blockname === 'Ground') {
pointX = -portSize / 2
pointY = -portSize
} else {
pointX = -portSize
pointY = -portSize / 2
}
break
case 'ControlPort':
pointX = -portSize / 2
pointY = -portSize
break
case 'ExplicitOutputPort':
pointX = 0
pointY = -portSize / 2
break
case 'CommandPort':
pointX = -portSize / 2
pointY = 0
break
default:
pointX = -portSize / 2
pointY = -portSize / 2
break
}
return { pointX, pointY }
}
export function getSuperBlockDiagram (xml) {
xml = '' + xml + ''
const superBlockDiagram = mxUtils.parseXml(xml)
return superBlockDiagram.getElementsByTagName('SuperBlockDiagram')[0]
}
function parseXmlToGraph (xmlDoc, graph) {
const parent = graph.getDefaultParent()
let v1
const model = graph.getModel()
model.beginUpdate()
let oldcellslength = 0
graph.contextArray = xmlDoc.querySelector('Array[as="context"]')
let cells = xmlDoc.getElementsByTagName('root')[0].children
let cellslength = cells.length
let remainingcells = []
let portCount
let blockname
try {
console.log('cellslength1=', cellslength)
while (cellslength > 0 && cellslength !== oldcellslength) {
for (let i = 0; i < cellslength; i++) {
const cell = cells[i]
const cellAttrs = cell.attributes
const cellChildren = cell.children
if (cellAttrs.CellType?.value === 'Component') { // is component
portCount = {
ExplicitInputPort: 0,
ImplicitInputPort: 0,
ControlPort: 0,
ExplicitOutputPort: 0,
ImplicitOutputPort: 0,
CommandPort: 0
}
const style = cellAttrs.style.value
const styleObject = styleToObject(style)
const stylename = styleObject.default
blockname = stylename
const vertexId = cellAttrs.id.value
const geom = cellChildren[0].attributes
const xPos = (geom.x !== undefined) ? Number(geom.x.value) : 0
const yPos = (geom.y !== undefined) ? Number(geom.y.value) : 0
const height = Number(geom.height.value)
const width = Number(geom.width.value)
if (DEBUG_PORT && stylename === 'TEXT_f') {
continue
}
v1 = graph.insertVertex(parent, vertexId, null, xPos, yPos, width, height, style)
v1.connectable = 0
v1.CellType = 'Component'
v1.blockprefix = cellAttrs.blockprefix.value
const blockportSet = []
const cellChildrenBlockportSet = cellChildren[1].children[0]
if (cellChildrenBlockportSet !== undefined) {
for (const b of cellChildrenBlockportSet.children) {
const bc = {}
for (let i = 0, n = b.attributes.length; i < n; i++) {
const key = b.attributes[i].nodeName
const value = b.attributes[i].nodeValue
bc[key] = value
}
blockportSet.push(bc)
}
}
v1.displayProperties = {
display_parameter: cellChildren[1].attributes.display_parameter.value
}
const parameterValues = {}
const cellChildrenAttributes = cellChildren[2].attributes
if (cellChildrenAttributes !== undefined) {
const pattern = /^p[0-9]{3}_value$/
const parameterCount = Object.values(cellChildrenAttributes).filter((value) => {
return pattern.test(value.name)
}).length
for (let i = 0; i < parameterCount; i++) {
const p = getParameter(i) + '_value'
if (cellChildrenAttributes[p] !== undefined) {
parameterValues[p] = cellChildrenAttributes[p].value
}
}
}
v1.parameter_values = parameterValues
// Todo set v1.errorFields
v1.explicitInputPorts = 0
v1.implicitInputPorts = 0
v1.explicitOutputPorts = 0
v1.implicitOutputPorts = 0
v1.controlPorts = 0
v1.commandPorts = 0
v1.simulationFunction = cellAttrs.simulationFunction?.value
v1.pins = {
explicitInputPorts: [],
implicitInputPorts: [],
controlPorts: [],
explicitOutputPorts: [],
implicitOutputPorts: [],
commandPorts: []
}
let SuperBlockDiagram = cell.querySelector('SuperBlockDiagram')
if (SuperBlockDiagram !== null) {
v1.SuperBlockDiagram = SuperBlockDiagram
} else {
SuperBlockDiagram = cellAttrs.SuperBlockDiagram?.value
if (SuperBlockDiagram !== null) {
SuperBlockDiagram = '' + SuperBlockDiagram + ''
const superblock = mxUtils.parseXml(SuperBlockDiagram)
const superblock2 = superblock.getElementsByTagName('SuperBlockDiagram')[0]
v1.SuperBlockDiagram = superblock2
}
}
} else if (cellAttrs.CellType?.value === 'Pin') {
const style = cellAttrs.style.value
const styleObject = styleToObject(style)
const stylename = styleObject.default
const vertexId = cellAttrs.id.value
const geom = cellChildren[0].attributes
const xPos = (geom.x !== undefined) ? Number(geom.x.value) : 0
const yPos = (geom.y !== undefined) ? Number(geom.y.value) : 0
const rotationParameters = getRotationParameters(stylename)
const pins = getPins(stylename, v1)
const pointXY = getPointXY(rotationParameters, blockname)
const pointX = pointXY.pointX
const pointY = pointXY.pointY
const point = new mxPoint(pointX, pointY)
const vp = graph.insertVertex(v1, vertexId, null, xPos, yPos, portSize, portSize, style)
vp.geometry.relative = true
vp.geometry.offset = point
vp.CellType = 'Pin'
let orderingname
if (stylename === 'ImplicitInputPort') {
orderingname = 'ExplicitInputPort'
} else if (stylename === 'ImplicitOutputPort') {
orderingname = 'ExplicitOutputPort'
} else {
orderingname = stylename
}
portCount[orderingname] += 1
let ordering
if (cellAttrs.ordering) {
ordering = cellAttrs.ordering.value
} else {
ordering = portCount[orderingname]
}
vp.ordering = ordering
vp.ParentComponent = v1.id
if (pins != null) {
pins.push(vp)
}
} else if (cellAttrs.edge) { // is edge
if (DEBUG_PORT) {
continue
}
const edgeId = cellAttrs.id.value
const source = cellAttrs.sourceVertex.value
const target = cellAttrs.targetVertex.value
const sourceCell = model.getCell(source)
const targetCell = model.getCell(target)
if (sourceCell == null || targetCell == null) {
remainingcells.push(cell)
continue
}
const firstChild = cellChildren[0].querySelector('Array[as=points]')
const points = []
if (firstChild !== null) {
const plist = firstChild.children
for (const a of plist) {
try {
const point = new mxPoint(Number(a.attributes.x.value), Number(a.attributes.y.value))
points.push(point)
} catch (e) { console.error('error', e) }
}
}
const sourceType = getPortType(sourceCell)
const targetType = getPortType(targetCell)
if (sourceType.type2 !== OutputPort && targetType.type2 !== InputPort) {
console.log('switch', source, target)
// const tmp = source
// source = target
// target = tmp
// const tmpCell = sourceCell
// sourceCell = targetCell
// targetCell = tmpCell
// const tmpsourcePoint = sourcePoint
// sourcePoint = targetPoint
// targetPoint = tmpsourcePoint
// points.reverse()
}
try {
const edge = graph.insertEdge(parent, edgeId, null, sourceCell, targetCell)
edge.tarx = cellAttrs.tarx.value
edge.tary = cellAttrs.tary.value
edge.tar2x = cellAttrs.tar2x.value
edge.tar2y = cellAttrs.tar2y.value
edge.sourceVertex = cellAttrs.sourceVertex.value
edge.targetVertex = cellAttrs.targetVertex.value
const x1 = Number(cellAttrs.tarx.value)
const y1 = Number(cellAttrs.tary.value)
if (sourceCell && sourceCell.edge && (x1 !== 0 || y1 !== 0)) {
const terminalPoint = new mxPoint(x1, y1)
edge.geometry.setTerminalPoint(terminalPoint, true)
}
const x2 = Number(cellAttrs.tar2x.value)
const y2 = Number(cellAttrs.tar2y.value)
if (targetCell && targetCell.edge && (x2 !== 0 || y2 !== 0)) {
const terminalPoint2 = new mxPoint(x2, y2)
edge.geometry.setTerminalPoint(terminalPoint2, false)
}
edge.geometry.points = points
} catch (e) {
console.log(sourceCell)
console.log(targetCell)
console.error('error', e)
}
}
}
oldcellslength = cellslength
cells = remainingcells
cellslength = remainingcells.length
remainingcells = []
console.log('cellslength=', cellslength, ', oldcellslength=', oldcellslength)
}
graph.view.refresh()
} finally {
model.endUpdate()
}
}
export function renderGalleryXML (xml) {
if (!graph) {
console.log('graph is not initialized')
return
}
const parent = graph.getDefaultParent()
graph.removeCells(graph.getChildVertices(parent))
graph.removeCells(graph.getChildEdges(parent))
graph.view.refresh()
const xmlDoc = mxUtils.parseXml(xml)
parseXmlToGraph(xmlDoc, graph)
}