/* eslint-disable import/no-cycle */
/* eslint-disable no-bitwise */
/* eslint-disable guard-for-in */
/* eslint-disable no-restricted-syntax */
/* eslint-disable no-restricted-globals */
/* eslint-disable consistent-return */
/* eslint-disable func-names */
/* eslint-disable array-callback-return */
/* eslint-disable no-use-before-define */
/* eslint-disable no-param-reassign */
/* eslint-disable no-alert */
import CircuitElement from './circuitElement';
import plotArea from './plotArea';
import simulationArea, { changeClockTime } from './simulationArea';
import {
stripTags,
uniq,
showMessage,
showError,
truncateString,
escapeHtml,
} from './utils';
import { findDimensions, dots } from './canvasApi';
import { updateRestrictedElementsList } from './restrictedElementDiv';
import { scheduleBackup } from './data/backupCircuit';
import { showProperties } from './ux';
import {
scheduleUpdate, updateSimulationSet,
updateCanvasSet, updateSubcircuitSet,
forceResetNodesSet, changeLightMode,
} from './engine';
import { toggleLayoutMode, layoutModeGet } from './layoutMode';
import { setProjectName, getProjectName } from './data/save';
import { changeClockEnable } from './sequential';
import { changeInputSize } from './modules';
import { verilogModeGet, verilogModeSet } from './Verilog2CV';
import { updateTestbenchUI } from './testbench';
import load from './data/load';
export const circuitProperty = {
toggleLayoutMode, setProjectName, changeCircuitName, changeClockTime, deleteCurrentCircuit, changeClockEnable, changeInputSize, changeLightMode,
};
export var scopeList = {};
export function resetScopeList() {
scopeList = {};
}
/**
* Function used to change the current focusedCircuit
* Disables layoutMode if enabled
* Changes UI tab etc
* Sets flags to make updates, resets most of the things
* @param {string} id - identifier for circuit
* @category circuit
*/
export function switchCircuit(id) {
if (layoutModeGet()) { toggleLayoutMode(); }
if (verilogModeGet()) { verilogModeSet(false);}
// globalScope.fixLayout();
scheduleBackup();
if (id === globalScope.id) return;
$('.circuits').removeClass('current');
simulationArea.lastSelected = undefined;
simulationArea.multipleObjectSelections = [];
simulationArea.copyList = [];
globalScope = scopeList[id];
if (globalScope.verilogMetadata.isVerilogCircuit) {
verilogModeSet(true);
}
if (globalScope.isVisible()) {
$(`#${id}`).addClass('current');
}
updateSimulationSet(true);
updateSubcircuitSet(true);
forceResetNodesSet(true);
dots(false);
simulationArea.lastSelected = globalScope.root;
if (!embed) {
showProperties(simulationArea.lastSelected);
updateTestbenchUI();
plotArea.reset();
}
updateCanvasSet(true);
scheduleUpdate();
// to update the restricted elements information
updateRestrictedElementsList();
}
/**
* Deletes the current circuit
* Ensures that at least one circuit is there
* Ensures that no circuit depends on the current circuit
* Switched to a random circuit
* @category circuit
*/
function deleteCurrentCircuit(scopeId = globalScope.id) {
const scope = scopeList[scopeId];
if (Object.keys(scopeList).length <= 1) {
showError('At least 2 circuits need to be there in order to delete a circuit.');
return;
}
let dependencies = '';
for (id in scopeList) {
if (id != scope.id && scopeList[id].checkDependency(scope.id)) {
if (dependencies === '') {
dependencies = scopeList[id].name;
} else {
dependencies += `, ${scopeList[id].name}`;
}
}
}
if (dependencies) {
dependencies = `\nThe following circuits are depending on '${scope.name}': ${dependencies}\nDelete subcircuits of ${scope.name} before trying to delete ${scope.name}`;
alert(dependencies);
return;
}
const confirmation = confirm(`Are you sure want to close: ${scope.name}\nThis cannot be undone.`);
if (confirmation) {
if (scope.verilogMetadata.isVerilogCircuit) {
scope.initialize();
for (var id in scope.verilogMetadata.subCircuitScopeIds)
delete scopeList[id];
}
$(`#${scope.id}`).remove();
delete scopeList[scope.id];
switchCircuit(Object.keys(scopeList)[0]);
showMessage('Circuit was successfully closed');
} else { showMessage('Circuit was not closed'); }
}
/**
* Wrapper function around newCircuit to be called from + button on UI
*/
export function createNewCircuitScope() {
simulationArea.lastSelected = undefined;
const scope = newCircuit();
if (!embed) {
showProperties(simulationArea.lastSelected);
updateTestbenchUI();
plotArea.reset();
}
}
/**
* Function to create new circuit
* Function creates button in tab, creates scope and switches to this circuit
* @param {string} name - name of the new circuit
* @param {string} id - identifier for circuit
* @category circuit
*/
export function newCircuit(name, id, isVerilog = false, isVerilogMain = false) {
if (layoutModeGet()) { toggleLayoutMode(); }
if (verilogModeGet()) { verilogModeSet(false); }
name = name || prompt('Enter circuit name:', 'Untitled-Circuit');
name = escapeHtml(stripTags(name));
if (!name) return;
const scope = new Scope(name);
if (id) scope.id = id;
scopeList[scope.id] = scope;
if (isVerilog) {
scope.verilogMetadata.isVerilogCircuit = true;
scope.verilogMetadata.isMainCircuit = isVerilogMain;
}
globalScope = scope;
$('.circuits').removeClass('current');
if (!isVerilog || isVerilogMain) {
if (embed) {
var html = `<div style='' class='circuits toolbarButton current' draggable='true' id='${scope.id}'><span class='circuitName noSelect'>${truncateString(name, 18)}</span></div>`;
$('#tabsBar').append(html);
$("#tabsBar").addClass('embed-tabs');
} else {
var html = `<div style='' class='circuits toolbarButton current' draggable='true' id='${scope.id}'><span class='circuitName noSelect'>${truncateString(name, 18)}</span><span class ='tabsCloseButton' id='${scope.id}' >x</span></div>`;
$('#tabsBar').children().last().before(html);
}
// Remove listeners
$('.circuits').off('click');
$('.circuitName').off('click');
$('.tabsCloseButton').off('click');
// Add listeners
$('.circuits').on('click',function () {
switchCircuit(this.id);
});
$('.circuitName').on('click',(e) => {
simulationArea.lastSelected = globalScope.root;
setTimeout(() => {
document.getElementById('circname').select();
}, 100);
});
$('.tabsCloseButton').on('click',function (e) {
e.stopPropagation();
deleteCurrentCircuit(this.id);
});
if (!embed) {
showProperties(scope.root);
}
dots(false);
}
return scope;
}
/**
* Used to change name of a circuit
* @param {string} name - new name
* @param {string} id - id of the circuit
* @category circuit
*/
export function changeCircuitName(name, id = globalScope.id) {
name = name || 'Untitled';
name = escapeHtml(stripTags(name));
$(`#${id} .circuitName`).html(`${truncateString(name, 18)}`);
scopeList[id].name = name;
}
/**
* Class representing a Scope
* @class
* @param {string} name - name of the circuit
* @param {number=} id - a random id for the circuit
* @category circuit
*/
export default class Scope {
constructor(name = 'localScope', id = undefined) {
this.restrictedCircuitElementsUsed = [];
this.id = id || Math.floor((Math.random() * 100000000000) + 1);
this.CircuitElement = [];
this.name = name;
// root object for referring to main canvas - intermediate node uses this
this.root = new CircuitElement(0, 0, this, 'RIGHT', 1);
this.backups = [];
// maintaining a state (history) for redo function
this.history = [];
this.timeStamp = new Date().getTime();
this.verilogMetadata = {
isVerilogCircuit: false,
isMainCircuit: false,
code: '// Write Some Verilog Code Here!',
subCircuitScopeIds: [],
};
this.ox = 0;
this.oy = 0;
this.scale = DPR;
this.stack = [];
this.initialize();
// Setting default layout
this.layout = { // default position
width: 100,
height: 40,
title_x: 50,
title_y: 13,
titleEnabled: true,
};
}
isVisible() {
if (!this.verilogMetadata.isVerilogCircuit) return true;
return this.verilogMetadata.isMainCircuit;
}
initialize() {
this.tunnelList = {};
this.pending = [];
this.nodes = []; // intermediate nodes only
this.allNodes = [];
this.wires = [];
// Creating arrays for other module elements
for (let i = 0; i < moduleList.length; i++) {
this[moduleList[i]] = [];
}
}
/**
* Resets all nodes recursively
*/
reset() {
for (let i = 0; i < this.allNodes.length; i++) { this.allNodes[i].reset(); }
for (let i = 0; i < this.Splitter.length; i++) {
this.Splitter[i].reset();
}
for (let i = 0; i < this.SubCircuit.length; i++) {
this.SubCircuit[i].reset();
}
}
/**
* Adds all inputs to simulationQueue
*/
addInputs() {
for (let i = 0; i < inputList.length; i++) {
for (var j = 0; j < this[inputList[i]].length; j++) {
simulationArea.simulationQueue.add(this[inputList[i]][j], 0);
}
}
for (let i = 0; i < this.SubCircuit.length; i++) { this.SubCircuit[i].addInputs(); }
}
/**
* Ticks clocks recursively -- needs to be deprecated and synchronize all clocks with a global clock
*/
clockTick() {
for (let i = 0; i < this.Clock.length; i++) { this.Clock[i].toggleState(); } // tick clock!
for (let i = 0; i < this.SubCircuit.length; i++) { this.SubCircuit[i].localScope.clockTick(); } // tick clock!
}
/**
* Checks if this circuit contains directly or indirectly scope with id
* Recursive nature
*/
checkDependency(id) {
if (id === this.id) return true;
for (let i = 0; i < this.SubCircuit.length; i++) { if (this.SubCircuit[i].id === id) return true; }
for (let i = 0; i < this.SubCircuit.length; i++) { if (scopeList[this.SubCircuit[i].id].checkDependency(id)) return true; }
return false;
}
/**
* Get dependency list - list of all circuits, this circuit depends on
*/
getDependencies() {
var list = [];
for (let i = 0; i < this.SubCircuit.length; i++) {
list.push(this.SubCircuit[i].id);
list.extend(scopeList[this.SubCircuit[i].id].getDependencies());
}
return uniq(list);
}
/**
* helper function to reduce layout size
*/
fixLayout() {
var maxY = 20;
for (let i = 0; i < this.Input.length; i++) { maxY = Math.max(this.Input[i].layoutProperties.y, maxY); }
for (let i = 0; i < this.Output.length; i++) { maxY = Math.max(this.Output[i].layoutProperties.y, maxY); }
if (maxY !== this.layout.height) { this.layout.height = maxY + 10; }
}
/**
* Function which centers the circuit to the correct zoom level
*/
centerFocus(zoomIn = true) {
if (layoutModeGet()) return;
findDimensions(this);
var ytoolbarOffset = embed ? 0 : 60 * DPR; // Some part ofcanvas is hidden behind the toolbar
var minX = simulationArea.minWidth || 0;
var minY = simulationArea.minHeight || 0;
var maxX = simulationArea.maxWidth || 0;
var maxY = simulationArea.maxHeight || 0;
var reqWidth = maxX - minX + 75 * DPR;
var reqHeight = maxY - minY + 75 * DPR;
this.scale = Math.min(width / reqWidth, (height - ytoolbarOffset) / reqHeight);
if (!zoomIn) { this.scale = Math.min(this.scale, DPR); }
this.scale = Math.max(this.scale, DPR / 10);
this.ox = (-minX) * this.scale + (width - (maxX - minX) * this.scale) / 2;
this.oy = (-minY) * this.scale + (height - ytoolbarOffset - (maxY - minY) * this.scale) / 2;
}
/**
* Function to load a circuit using circuit data
*/
loadCircuit(data) {
if (data) {
load(data);
} else {
alert('Invalid data');
}
}
/**
* Function to retrieve the previous stable state of the circuit
*/
previous() {
const autosaveData = localStorage.getItem('autosave');
if (autosaveData) {
const data = JSON.parse(autosaveData);
load(data);
localStorage.removeItem('autosave');
}
}
/**
* Function to detect the nodes that are creating cyclic paths in the circuit
*/
detectCycle() {
const obj = {};
const result = [];
for (let i = 0; i < globalScope.allNodes.length; i++) {
const nodeId = globalScope.allNodes[i].id;
for (let j = 0; j < globalScope.allNodes[i].connections.length; j++) {
const connection = globalScope.allNodes[i].connections[j].id;
if (obj[nodeId]) {
obj[nodeId].push(connection);
} else {
obj[nodeId] = [connection];
}
}
}
const newNestedArray = [];
const singleConnectionNodes = [];
for (const node in obj) {
const connections = obj[node];
if (connections.length === 1) {
singleConnectionNodes.push(node);
}
if (!isVisited(newNestedArray.flat(), node)) {
const connectedNodes = [node];
exploreNodes(obj, node, newNestedArray.flat(), connectedNodes);
newNestedArray.push(connectedNodes);
}
}
for (let i = 0; i < singleConnectionNodes.length - 1; i++) {
for (let j = i + 1; j < singleConnectionNodes.length; j++) {
if (areElementsInSameNode(newNestedArray, singleConnectionNodes[i], singleConnectionNodes[j])) {
const val1 = this.findNodeIndexById(singleConnectionNodes[i]);
const val2 = this.findNodeIndexById(singleConnectionNodes[j]);
if (globalScope.allNodes[val1].parent === globalScope.allNodes[val2].parent) {
result.push(dfs(obj, singleConnectionNodes[i], singleConnectionNodes[j]));
}
}
}
}
// Function to check whether a node is already visited
function isVisited(visited, node) {
return visited.includes(node);
}
// Function to explore interconnected nodes
function exploreNodes(graph, startNode, visited, connectedNodes) {
visited.push(startNode);
if (graph[startNode]) {
for (let i = 0; i < graph[startNode].length; i++) {
const connectedNode = graph[startNode][i];
if (!isVisited(visited, connectedNode)) {
connectedNodes.push(connectedNode);
exploreNodes(graph, connectedNode, visited, connectedNodes);
}
}
}
}
function areElementsInSameNode(nestedArray, element1, element2) {
for (const node of nestedArray) {
if (node.includes(element1) && node.includes(element2)) {
return true;
}
}
return false;
}
// Function to locate the nodes present between two specified nodes
function dfs(graph, start, end, path = []) {
if (start === end) {
path.push(end);
return path;
}
path.push(start);
for (const neighbor of graph[start]) {
if (!path.includes(neighbor)) {
const newPath = dfs(graph, neighbor, end, path);
if (newPath) {
return newPath;
}
}
}
path.pop();
return [];
}
if (result.length) {
this.highlightNodes(result);
return result;
}
return 'No cycle found';
}
/**
* Function to highlight the nodes in the currently selected circuit
* @param {Array<Array>} array - id's of the nodes, that we want to highlight
*/
highlightNodes(array) {
const Nodes = [];
array.forEach((innerArray) => {
const resultArray = innerArray.map(value => this.findNodeIndexById(value));
Nodes.push(resultArray);
});
Nodes.forEach((subArray) => {
subArray.forEach((node) => {
globalScope.allNodes[node].highlighted = true;
});
});
}
/**
* Function to find the index of a node in globalScope.allNodes
* @param {string} nodeId - id of a node
*/
findNodeIndexById(nodeId) {
for (let i = 0; i < globalScope.allNodes.length; i++) {
if (globalScope.allNodes[i].id === nodeId) {
return i;
}
}
return 'Not found';
}
/**
* Function to find the currently selected component on the canvas
*/
getCurrentlySelectedComponent() {
return simulationArea.lastSelected;
}
/**
* Function to find the currently selected components (when multiple components are selected) on the canvas
*/
getAllSelectedComponents() {
return simulationArea.multipleObjectSelections;
}
/**
* Function to modify the currently selected component's object in the globalScope
* takes the property which needs to modify
* and the modified value as parameters
*/
modifyCurrentlySelectedComponent(property, value) {
const selectedComponent = globalScope.getCurrentlySelectedComponent();
if (selectedComponent.objectType === 'Node') {
const nodeId = selectedComponent.id;
const nodeIndex = this.findNodeIndexById(nodeId);
globalScope.allNodes[nodeIndex][property] = value;
return;
}
for (const key in globalScope) {
const component = globalScope[key];
if (Array.isArray(component) && component.length) {
component.forEach((obj, index) => {
if (obj === selectedComponent) {
component[index][property] = value;
}
});
}
}
}
}
Source