1024 lines
33 KiB
JavaScript
1024 lines
33 KiB
JavaScript
|
// Function to create new circuit
|
||
|
// Function creates button in tab, creates scope and switches to this circuit
|
||
|
function newCircuit(name, id) {
|
||
|
name = name || prompt("Enter circuit name:");
|
||
|
name = stripTags(name);
|
||
|
if (!name) return;
|
||
|
var scope = new Scope(name);
|
||
|
if (id) scope.id = id;
|
||
|
scopeList[scope.id] = scope;
|
||
|
globalScope = scope;
|
||
|
var $windowsize = $('body').width();
|
||
|
var $sideBarsize = $('.side').width();
|
||
|
var $maxwidth = ($windowsize - $sideBarsize);
|
||
|
var resizer = "max-width:" + ($maxwidth - 30);
|
||
|
$('.circuits').removeClass("current");
|
||
|
$('#tabsBar').append("<a href = '#' data-toggle = 'tooltip' title='" + name + "'><div class='circuits toolbarButton current' id='" + scope.id + "' style='" + resizer + "'>" + name + "<span class ='tabsCloseButton' onclick='deleteCurrentCircuit()' ><i class='fa fa-times'></i></span></div></a>");
|
||
|
$('.circuits').click(function() {
|
||
|
switchCircuit(this.id)
|
||
|
});
|
||
|
if (!embed) {
|
||
|
showProperties(scope.root);
|
||
|
}
|
||
|
|
||
|
dots(true, false);
|
||
|
|
||
|
return scope;
|
||
|
}
|
||
|
|
||
|
|
||
|
function changeCircuitName(name, id = globalScope.id) {
|
||
|
name = name || "Untitled";
|
||
|
name = stripTags(name);
|
||
|
$('#' + id).html(name + "<span class ='tabsCloseButton' onclick='deleteCurrentCircuit()' ><i class='fa fa-times'></i></span>");
|
||
|
scopeList[id].name = name;
|
||
|
}
|
||
|
|
||
|
function setProjectName(name) {
|
||
|
name = stripTags(name);
|
||
|
projectName = name;
|
||
|
$('#projectName').html(name);
|
||
|
}
|
||
|
|
||
|
function clearProject() {
|
||
|
if (confirm("Would you like to clear the project?")){
|
||
|
globalScope = undefined;
|
||
|
scopeList = {};
|
||
|
$('.circuits').remove();
|
||
|
newCircuit("main");
|
||
|
showMessage("Your project is as good as new!");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Function used to start a new project while prompting confirmation from the user
|
||
|
function newProject(verify) {
|
||
|
|
||
|
if (verify || projectSaved || !checkToSave() || confirm("What you like to start a new project? Any unsaved changes will be lost.")) {
|
||
|
clearProject();
|
||
|
localStorage.removeItem("recover");
|
||
|
window.location = "/simulator";
|
||
|
|
||
|
projectName = undefined;
|
||
|
projectId = generateId();
|
||
|
showMessage("New Project has been created!");
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// Helper function used to generate SVG - only used for internal purposes to generate the icons
|
||
|
function generateSvg() {
|
||
|
resolution = 1;
|
||
|
view = "full"
|
||
|
|
||
|
var backUpOx = globalScope.ox;
|
||
|
var backUpOy = globalScope.oy;
|
||
|
var backUpWidth = width
|
||
|
var backUpHeight = height;
|
||
|
var backUpScale = globalScope.scale;
|
||
|
backUpContextBackground = backgroundArea.context;
|
||
|
backUpContextSimulation = simulationArea.context;
|
||
|
backgroundArea.context = simulationArea.context;
|
||
|
globalScope.ox *= resolution / backUpScale;
|
||
|
globalScope.oy *= resolution / backUpScale;
|
||
|
|
||
|
globalScope.scale = resolution;
|
||
|
|
||
|
var scope = globalScope;
|
||
|
|
||
|
if (view == "full") {
|
||
|
var minX = 10000000;
|
||
|
var minY = 10000000;
|
||
|
var maxX = -10000000;
|
||
|
var maxY = -10000000;
|
||
|
var maxDimension = 0;
|
||
|
for (var i = 0; i < updateOrder.length; i++)
|
||
|
for (var j = 0; j < scope[updateOrder[i]].length; j++) {
|
||
|
if (scope[updateOrder[i]][j].objectType !== "Wire") {
|
||
|
minX = Math.min(minX, scope[updateOrder[i]][j].absX());
|
||
|
maxX = Math.max(maxX, scope[updateOrder[i]][j].absX());
|
||
|
minY = Math.min(minY, scope[updateOrder[i]][j].absY());
|
||
|
maxY = Math.max(maxY, scope[updateOrder[i]][j].absY());
|
||
|
maxDimension = Math.max(maxDimension, scope[updateOrder[i]][j].leftDimensionX)
|
||
|
maxDimension = Math.max(maxDimension, scope[updateOrder[i]][j].rightDimensionX)
|
||
|
maxDimension = Math.max(maxDimension, scope[updateOrder[i]][j].upDimensionY)
|
||
|
maxDimension = Math.max(maxDimension, scope[updateOrder[i]][j].downDimensionY)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// width = (maxX - minX + 60) * resolution;
|
||
|
// height = (maxY - minY + 60) * resolution;
|
||
|
//
|
||
|
// globalScope.ox = (-minX + maxDimension+11)*resolution;
|
||
|
// globalScope.oy = (-minY + maxDimension-6)*resolution;
|
||
|
width = (maxX - minX + 2 * maxDimension + 10) * resolution;
|
||
|
height = (maxY - minY + 2 * maxDimension + 10) * resolution;
|
||
|
|
||
|
globalScope.ox = (-minX + maxDimension + 5) * resolution;
|
||
|
globalScope.oy = (-minY + maxDimension + 5) * resolution;
|
||
|
} else {
|
||
|
width = (width * resolution) / backUpScale;
|
||
|
height = (height * resolution) / backUpScale;
|
||
|
}
|
||
|
globalScope.ox = Math.round(globalScope.ox);
|
||
|
globalScope.oy = Math.round(globalScope.oy);
|
||
|
|
||
|
simulationArea.canvas.width = width;
|
||
|
simulationArea.canvas.height = height;
|
||
|
backgroundArea.canvas.width = width;
|
||
|
backgroundArea.canvas.height = height;
|
||
|
simulationArea.context = new C2S(width, height);
|
||
|
backgroundArea.context = simulationArea.context;
|
||
|
|
||
|
simulationArea.clear();
|
||
|
|
||
|
|
||
|
for (var i = 0; i < updateOrder.length; i++)
|
||
|
for (var j = 0; j < scope[updateOrder[i]].length; j++)
|
||
|
scope[updateOrder[i]][j].draw();
|
||
|
|
||
|
var mySerializedSVG = simulationArea.context.getSerializedSvg(); //true here, if you need to convert named to numbered entities.
|
||
|
download("test.svg", mySerializedSVG);
|
||
|
width = backUpWidth
|
||
|
height = backUpHeight
|
||
|
simulationArea.canvas.width = width;
|
||
|
simulationArea.canvas.height = height;
|
||
|
backgroundArea.canvas.width = width;
|
||
|
backgroundArea.canvas.height = height;
|
||
|
globalScope.scale = backUpScale;
|
||
|
backgroundArea.context = backUpContextBackground;
|
||
|
simulationArea.context = backUpContextSimulation;
|
||
|
globalScope.ox = backUpOx
|
||
|
globalScope.oy = backUpOy;
|
||
|
|
||
|
updateSimulation = true;
|
||
|
updateCanvas = true;
|
||
|
scheduleUpdate();
|
||
|
dots(true, false);
|
||
|
|
||
|
}
|
||
|
|
||
|
// 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
|
||
|
|
||
|
function switchCircuit(id) {
|
||
|
|
||
|
if (layoutMode)
|
||
|
toggleLayoutMode();
|
||
|
|
||
|
// globalScope.fixLayout();
|
||
|
scheduleBackup();
|
||
|
if (id == globalScope.id) return;
|
||
|
$('#' + globalScope.id).removeClass("current");
|
||
|
$('#' + id).addClass("current");
|
||
|
simulationArea.lastSelected = undefined;
|
||
|
simulationArea.multipleObjectSelections = [];
|
||
|
simulationArea.copyList = [];
|
||
|
globalScope = scopeList[id];
|
||
|
updateSimulation = true;
|
||
|
updateSubcircuit = true;
|
||
|
forceResetNodes = true;
|
||
|
dots(true, false);
|
||
|
simulationArea.lastSelected = globalScope.root;
|
||
|
if (!embed) {
|
||
|
showProperties(simulationArea.lastSelected);
|
||
|
}
|
||
|
updateCanvas=true;
|
||
|
scheduleUpdate();
|
||
|
|
||
|
// to update the restricted elements information
|
||
|
updateRestrictedElementsList();
|
||
|
}
|
||
|
|
||
|
// Helper function to save canvas as image based on image type
|
||
|
function downloadAsImg(name, imgType) {
|
||
|
|
||
|
var gh = simulationArea.canvas.toDataURL('image/' + imgType);
|
||
|
var anchor = document.createElement('a');
|
||
|
anchor.href = gh;
|
||
|
anchor.download = name + '.' + imgType;
|
||
|
anchor.click()
|
||
|
|
||
|
}
|
||
|
|
||
|
// Function to restore copy from backup
|
||
|
function undo(scope = globalScope) {
|
||
|
if(layoutMode)return;
|
||
|
if (scope.backups.length == 0) return;
|
||
|
var backupOx = globalScope.ox;
|
||
|
var backupOy = globalScope.oy;
|
||
|
var backupScale = globalScope.scale;
|
||
|
globalScope.ox = 0;
|
||
|
globalScope.oy = 0;
|
||
|
var tempScope = new Scope(scope.name);
|
||
|
loading = true;
|
||
|
loadScope(tempScope, JSON.parse(scope.backups.pop()));
|
||
|
tempScope.backups = scope.backups;
|
||
|
tempScope.id = scope.id;
|
||
|
tempScope.name = scope.name;
|
||
|
scopeList[scope.id] = tempScope;
|
||
|
globalScope = tempScope;
|
||
|
globalScope.ox = backupOx;
|
||
|
globalScope.oy = backupOy;
|
||
|
globalScope.scale = backupScale;
|
||
|
loading = false;
|
||
|
forceResetNodes = true;
|
||
|
|
||
|
// Updated restricted elements
|
||
|
updateRestrictedElementsInScope();
|
||
|
}
|
||
|
|
||
|
//helper fn
|
||
|
function extract(obj) {
|
||
|
return obj.saveObject();
|
||
|
}
|
||
|
|
||
|
// Check if there is anything to backup - to be deprecated
|
||
|
function checkIfBackup(scope) {
|
||
|
for (var i = 0; i < updateOrder.length; i++)
|
||
|
if (scope[updateOrder[i]].length) return true;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Function that should backup if there is a change, otherwise ignore
|
||
|
function scheduleBackup(scope = globalScope) {
|
||
|
|
||
|
var backup = JSON.stringify(backUp(scope));
|
||
|
if (scope.backups.length == 0 || scope.backups[scope.backups.length - 1] != backup) {
|
||
|
scope.backups.push(backup);
|
||
|
scope.timeStamp = new Date().getTime();
|
||
|
projectSaved = false;
|
||
|
}
|
||
|
|
||
|
return backup;
|
||
|
}
|
||
|
|
||
|
// Random ID generator
|
||
|
function generateId() {
|
||
|
var id = "";
|
||
|
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||
|
|
||
|
for (var i = 0; i < 20; i++)
|
||
|
id += possible.charAt(Math.floor(Math.random() * possible.length));
|
||
|
|
||
|
return id;
|
||
|
}
|
||
|
|
||
|
//fn to create save data of a specific circuit
|
||
|
function backUp(scope = globalScope) {
|
||
|
|
||
|
// Disconnection of subcircuits are needed because these are the connections between nodes
|
||
|
// in current scope and those in the subcircuit's scope
|
||
|
for (let i = 0; i < scope.SubCircuit.length; i++)
|
||
|
scope.SubCircuit[i].removeConnections();
|
||
|
|
||
|
var data = {};
|
||
|
|
||
|
// Storing layout
|
||
|
data["layout"] = scope.layout;
|
||
|
|
||
|
// Storing all nodes
|
||
|
data["allNodes"] = scope.allNodes.map(extract);
|
||
|
|
||
|
// Storing other details
|
||
|
data["id"] = scope.id;
|
||
|
data["name"] = scope.name;
|
||
|
|
||
|
// Storing details of all module objects
|
||
|
for (var i = 0; i < moduleList.length; i++) {
|
||
|
if (scope[moduleList[i]].length)
|
||
|
data[moduleList[i]] = scope[moduleList[i]].map(extract);
|
||
|
}
|
||
|
|
||
|
// Adding restricted circuit elements used in the save data
|
||
|
data["restrictedCircuitElementsUsed"] = scope.restrictedCircuitElementsUsed;
|
||
|
|
||
|
// Storing intermediate nodes (nodes in wires)
|
||
|
data["nodes"] = []
|
||
|
for (var i = 0; i < scope.nodes.length; i++)
|
||
|
data["nodes"].push(scope.allNodes.indexOf(scope.nodes[i]));
|
||
|
|
||
|
// Restoring the connections
|
||
|
for (let i = 0; i < scope.SubCircuit.length; i++)
|
||
|
scope.SubCircuit[i].makeConnections();
|
||
|
|
||
|
return data
|
||
|
}
|
||
|
|
||
|
// Generating the order in which the circuits should be stored so that they can be loaded correctly
|
||
|
// POSET, Finish times
|
||
|
function generateDependencyOrder() {
|
||
|
var dependencyList = {};
|
||
|
var completed = {};
|
||
|
for (id in scopeList)
|
||
|
dependencyList[id] = scopeList[id].getDependencies();
|
||
|
|
||
|
function saveScope(id) {
|
||
|
if (completed[id]) return;
|
||
|
for (var i = 0; i < dependencyList[id].length; i++)
|
||
|
saveScope(dependencyList[id][i]);
|
||
|
completed[id] = true;
|
||
|
data.scopes.push(id);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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
|
||
|
|
||
|
function deleteCurrentCircuit() {
|
||
|
if (Object.keys(scopeList).length <= 1) {
|
||
|
showError("At least 2 circuits need to be there in order to delete a circuit.")
|
||
|
return;
|
||
|
}
|
||
|
var dependencies = "";
|
||
|
for (id in scopeList) {
|
||
|
if (id != globalScope.id && scopeList[id].checkDependency(globalScope.id)) {
|
||
|
if (dependencies == "")
|
||
|
dependencies = scopeList[id].name
|
||
|
else
|
||
|
dependencies += ", " + scopeList[id].name
|
||
|
}
|
||
|
|
||
|
}
|
||
|
if (dependencies) {
|
||
|
dependencies = "\nThe following circuits are depending on '" + globalScope.name + "': " + dependencies + "\nDelete subcircuits of " + globalScope.name + " before trying to delete " + globalScope.name
|
||
|
alert(dependencies)
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var confirmation = confirm("Are you sure want to delete: " + globalScope.name + "\nThis cannot be undone.");
|
||
|
if (confirmation) {
|
||
|
$('#' + globalScope.id).remove()
|
||
|
delete scopeList[globalScope.id]
|
||
|
switchCircuit(Object.keys(scopeList)[0])
|
||
|
showMessage("Circuit was successfully deleted")
|
||
|
} else
|
||
|
showMessage("Circuit was not deleted")
|
||
|
}
|
||
|
|
||
|
// Generates JSON of the entire project
|
||
|
function generateSaveData(name) {
|
||
|
|
||
|
data = {};
|
||
|
|
||
|
// Prompts for name, defaults to Untitled
|
||
|
name = projectName || name || prompt("Enter Project Name:") || "Untitled";
|
||
|
data["name"] = stripTags(name)
|
||
|
projectName = data["name"];
|
||
|
setProjectName(projectName)
|
||
|
|
||
|
// Save project details
|
||
|
data["timePeriod"] = simulationArea.timePeriod;
|
||
|
data["clockEnabled"] = simulationArea.clockEnabled;
|
||
|
data["projectId"] = projectId;
|
||
|
data["focussedCircuit"] = globalScope.id;
|
||
|
|
||
|
// Project Circuits, each scope is one circuit
|
||
|
data.scopes = []
|
||
|
var dependencyList = {};
|
||
|
var completed = {};
|
||
|
|
||
|
// Getting list of dependencies for each circuit
|
||
|
for (id in scopeList)
|
||
|
dependencyList[id] = scopeList[id].getDependencies();
|
||
|
|
||
|
// Helper function to save Scope
|
||
|
// Recursively saves inner subcircuits first, before saving parent circuits
|
||
|
function saveScope(id) {
|
||
|
|
||
|
if (completed[id]) return;
|
||
|
|
||
|
for (var i = 0; i < dependencyList[id].length; i++) // Save inner subcircuits
|
||
|
saveScope(dependencyList[id][i]);
|
||
|
|
||
|
completed[id] = true;
|
||
|
|
||
|
update(scopeList[id], true); // For any pending integrity checks on subcircuits
|
||
|
|
||
|
data.scopes.push(backUp(scopeList[id]));
|
||
|
|
||
|
}
|
||
|
|
||
|
// Save all circuits
|
||
|
for (id in scopeList)
|
||
|
saveScope(id);
|
||
|
|
||
|
//convert to text
|
||
|
data = JSON.stringify(data);
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
// Function that is used to save image for display in the website
|
||
|
function generateImageForOnline() {
|
||
|
|
||
|
simulationArea.lastSelected = undefined; // Unselect any selections
|
||
|
|
||
|
// Fix aspect ratio to 1.6
|
||
|
if (width > height * 1.6) {
|
||
|
height = width / 1.6;
|
||
|
} else {
|
||
|
width = height * 1.6;
|
||
|
}
|
||
|
|
||
|
// Center circuits
|
||
|
globalScope.centerFocus();
|
||
|
|
||
|
// Ensure image is approximately 700 x 440
|
||
|
resolution = Math.min(700 / (simulationArea.maxWidth - simulationArea.minWidth), 440 / (simulationArea.maxHeight - simulationArea.minHeight));
|
||
|
|
||
|
data = generateImage("jpeg", "current", false, resolution, false);
|
||
|
|
||
|
// Restores Focus
|
||
|
globalScope.centerFocus(false);
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
// Function to save data online
|
||
|
function save() {
|
||
|
projectSaved = true;
|
||
|
|
||
|
$('.loadingIcon').fadeIn();
|
||
|
var data = generateSaveData();
|
||
|
|
||
|
if (!userSignedIn) {
|
||
|
// user not signed in, save locally temporarily and force user to sign in
|
||
|
localStorage.setItem("recover_login", data);
|
||
|
// Asking user whether they want to login.
|
||
|
if(confirm("You have to login to save the project, you will be redirected to the login page.")) window.location.href = "/users/sign_in";
|
||
|
else $('.loadingIcon').fadeOut();
|
||
|
} else if (logix_project_id == 0) {
|
||
|
|
||
|
// Create new project - this part needs to be improved and optimised
|
||
|
var form = $("<form/>", {
|
||
|
action: '/simulator/create_data',
|
||
|
method: "post"
|
||
|
});
|
||
|
form.append(
|
||
|
$("<input>", {
|
||
|
type: 'hidden',
|
||
|
name: 'authenticity_token',
|
||
|
value: $('meta[name="csrf-token"]').attr('content')
|
||
|
})
|
||
|
);
|
||
|
form.append(
|
||
|
$("<input>", {
|
||
|
type: 'text',
|
||
|
name: 'data',
|
||
|
value: data
|
||
|
})
|
||
|
);
|
||
|
form.append(
|
||
|
$("<input>", {
|
||
|
type: 'text',
|
||
|
name: 'image',
|
||
|
value: generateImageForOnline()
|
||
|
})
|
||
|
);
|
||
|
|
||
|
form.append(
|
||
|
$("<input>", {
|
||
|
type: 'text',
|
||
|
name: 'name',
|
||
|
value: projectName,
|
||
|
})
|
||
|
);
|
||
|
|
||
|
$('body').append(form);
|
||
|
form.submit();
|
||
|
} else {
|
||
|
|
||
|
// updates project - this part needs to be improved and optimised
|
||
|
$.ajax({
|
||
|
url: '/simulator/update_data',
|
||
|
type: 'POST',
|
||
|
beforeSend: function(xhr) {
|
||
|
xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))
|
||
|
},
|
||
|
data: {
|
||
|
"data": data,
|
||
|
"id": logix_project_id,
|
||
|
"image": generateImageForOnline(),
|
||
|
name: projectName
|
||
|
},
|
||
|
success: function(response) {
|
||
|
showMessage("We have saved your project: " + projectName + " in our servers.")
|
||
|
$('.loadingIcon').fadeOut();
|
||
|
localStorage.removeItem("recover");
|
||
|
},
|
||
|
failure: function(err) {
|
||
|
showMessage("There was an error, we couldn't save to our servers")
|
||
|
$('.loadingIcon').fadeOut();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Restore everything
|
||
|
resetup();
|
||
|
|
||
|
}
|
||
|
|
||
|
// Function to load project from data
|
||
|
function load(data) {
|
||
|
|
||
|
// If project is new and no data is there, then just set project name
|
||
|
if (!data) {
|
||
|
setProjectName(projectName)
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
projectId = data.projectId;
|
||
|
projectName = data.name;
|
||
|
|
||
|
if (data.name == "Untitled")
|
||
|
projectName = undefined;
|
||
|
else
|
||
|
setProjectName(data.name);
|
||
|
|
||
|
globalScope = undefined;
|
||
|
scopeList = {}; // Remove default scope
|
||
|
$('.circuits').remove(); // Delete default scope
|
||
|
|
||
|
// Load all circuits according to the dependency order
|
||
|
for (var i = 0; i < data.scopes.length; i++) {
|
||
|
|
||
|
// Create new circuit
|
||
|
var scope = newCircuit(data.scopes[i].name || "Untitled", data.scopes[i].id);
|
||
|
|
||
|
// Load circuit data
|
||
|
loadScope(scope, data.scopes[i]);
|
||
|
|
||
|
// Focus circuit
|
||
|
globalScope = scope;
|
||
|
|
||
|
// Center circuit
|
||
|
if (embed)
|
||
|
globalScope.centerFocus(true);
|
||
|
else
|
||
|
globalScope.centerFocus(false);
|
||
|
|
||
|
// update and backup circuit once
|
||
|
update(globalScope, true);
|
||
|
|
||
|
// Updating restricted element list initially on loading
|
||
|
updateRestrictedElementsInScope();
|
||
|
|
||
|
scheduleBackup();
|
||
|
|
||
|
}
|
||
|
|
||
|
// Restore clock
|
||
|
simulationArea.changeClockTime(data["timePeriod"] || 500);
|
||
|
simulationArea.clockEnabled = data["clockEnabled"] == undefined ? true : data["clockEnabled"];
|
||
|
|
||
|
|
||
|
if (!embed)
|
||
|
showProperties(simulationArea.lastSelected)
|
||
|
|
||
|
// Switch to last focussedCircuit
|
||
|
if (data["focussedCircuit"]) switchCircuit(data["focussedCircuit"])
|
||
|
|
||
|
|
||
|
updateSimulation = true;
|
||
|
updateCanvas = true;
|
||
|
gridUpdate = true
|
||
|
scheduleUpdate();
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
// Backward compatibility - needs to be deprecated
|
||
|
function rectifyObjectType(obj) {
|
||
|
|
||
|
var rectify = {
|
||
|
"FlipFlop": "DflipFlop",
|
||
|
"Ram": "Rom"
|
||
|
};
|
||
|
return rectify[obj] || obj;
|
||
|
|
||
|
}
|
||
|
|
||
|
// Function to load CircuitElements
|
||
|
function loadModule(data, scope) {
|
||
|
|
||
|
// Create circuit element
|
||
|
var obj = new window[rectifyObjectType(data["objectType"])](data["x"], data["y"], scope, ...data.customData["constructorParamaters"] || []);
|
||
|
|
||
|
// Sets directions
|
||
|
obj.label = data["label"];
|
||
|
obj.labelDirection = data["labelDirection"] || oppositeDirection[fixDirection[obj.direction]];
|
||
|
|
||
|
// Sets delay
|
||
|
obj.propagationDelay = data["propagationDelay"] || obj.propagationDelay;
|
||
|
obj.fixDirection();
|
||
|
|
||
|
// Restore other values
|
||
|
if (data.customData["values"])
|
||
|
for (prop in data.customData["values"]) {
|
||
|
obj[prop] = data.customData["values"][prop];
|
||
|
}
|
||
|
|
||
|
// Replace new nodes with the correct old nodes (with connections)
|
||
|
if (data.customData["nodes"])
|
||
|
for (node in data.customData["nodes"]) {
|
||
|
var n = data.customData["nodes"][node]
|
||
|
if (n instanceof Array) {
|
||
|
for (var i = 0; i < n.length; i++) {
|
||
|
obj[node][i] = replace(obj[node][i], n[i]);
|
||
|
}
|
||
|
} else {
|
||
|
obj[node] = replace(obj[node], n);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// Helper function to download text
|
||
|
function download(filename, text) {
|
||
|
var pom = document.createElement('a');
|
||
|
pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
|
||
|
pom.setAttribute('download', filename);
|
||
|
|
||
|
|
||
|
if (document.createEvent) {
|
||
|
var event = document.createEvent('MouseEvents');
|
||
|
event.initEvent('click', true, true);
|
||
|
pom.dispatchEvent(event);
|
||
|
} else {
|
||
|
pom.click();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Function to load a full circuit
|
||
|
function loadScope(scope, data) {
|
||
|
|
||
|
var ML = moduleList.slice(); // Module List copy
|
||
|
scope.restrictedCircuitElementsUsed = data["restrictedCircuitElementsUsed"];
|
||
|
|
||
|
// Load all nodes
|
||
|
data["allNodes"].map(function(x) {
|
||
|
return loadNode(x, scope)
|
||
|
});
|
||
|
|
||
|
// Make all connections
|
||
|
for (var i = 0; i < data["allNodes"].length; i++)
|
||
|
constructNodeConnections(scope.allNodes[i], data["allNodes"][i]);
|
||
|
|
||
|
// Load all modules
|
||
|
for (var i = 0; i < ML.length; i++) {
|
||
|
if (data[ML[i]]) {
|
||
|
|
||
|
if (ML[i] == "SubCircuit") {
|
||
|
// Load subcircuits differently
|
||
|
for (var j = 0; j < data[ML[i]].length; j++)
|
||
|
loadSubCircuit(data[ML[i]][j], scope);
|
||
|
|
||
|
} else {
|
||
|
|
||
|
// Load everything else similarly
|
||
|
for (var j = 0; j < data[ML[i]].length; j++) {
|
||
|
loadModule(data[ML[i]][j], scope);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Update wires according
|
||
|
|
||
|
scope.wires.map(function(x) {
|
||
|
x.updateData(scope)
|
||
|
});
|
||
|
|
||
|
removeBugNodes(scope); // To be deprecated
|
||
|
|
||
|
// If layout exists, then restore
|
||
|
if (data["layout"])
|
||
|
scope.layout = data["layout"]
|
||
|
|
||
|
// Else generate new layout according to how it would have been otherwise (backward compatibility)
|
||
|
else {
|
||
|
scope.layout = {}
|
||
|
scope.layout.width = 100;
|
||
|
scope.layout.height = Math.max(scope.Input.length, scope.Output.length) * 20 + 20;
|
||
|
scope.layout.title_x = 50;
|
||
|
scope.layout.title_y = 13;
|
||
|
for (var i = 0; i < scope.Input.length; i++) {
|
||
|
scope.Input[i].layoutProperties = {
|
||
|
x: 0,
|
||
|
y: scope.layout.height / 2 - scope.Input.length * 10 + 20 * i + 10,
|
||
|
id: generateId()
|
||
|
}
|
||
|
}
|
||
|
for (var i = 0; i < scope.Output.length; i++)
|
||
|
scope.Output[i].layoutProperties = {
|
||
|
x: scope.layout.width,
|
||
|
y: scope.layout.height / 2 - scope.Output.length * 10 + 20 * i + 10,
|
||
|
id: generateId()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Backward compatibility
|
||
|
if(scope.layout.titleEnabled==undefined)
|
||
|
scope.layout.titleEnabled=true;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
// This fn shouldn't ideally exist. But temporary fix
|
||
|
function removeBugNodes(scope = globalScope) {
|
||
|
var x = scope.allNodes.length
|
||
|
for (var i = 0; i < x; i++) {
|
||
|
if (scope.allNodes[i].type != 2 && scope.allNodes[i].parent.objectType == "CircuitElement")
|
||
|
scope.allNodes[i].delete();
|
||
|
if (scope.allNodes.length != x) {
|
||
|
i = 0;
|
||
|
x = scope.allNodes.length;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Helper function to show prompt to save image
|
||
|
// Options - resolution, image type, view
|
||
|
createSaveAsImgPrompt = function(scope = globalScope) {
|
||
|
$('#saveImageDialog').dialog({
|
||
|
width: "auto",
|
||
|
buttons: [{
|
||
|
text: "Render Circuit Image",
|
||
|
click: function() {
|
||
|
generateImage($('input[name=imgType]:checked').val(), $('input[name=view]:checked').val(), $('input[name=transparent]:checked').val(), $('input[name=resolution]:checked').val());
|
||
|
$(this).dialog("close");
|
||
|
},
|
||
|
}]
|
||
|
|
||
|
});
|
||
|
$("input[name=imgType]").change(function() {
|
||
|
$('input[name=resolution]').prop("disabled", false);
|
||
|
$('input[name=transparent]').prop("disabled", false);
|
||
|
var imgType = $('input[name=imgType]:checked').val();
|
||
|
if (imgType == 'svg') {
|
||
|
$('input[name=resolution][value=1]').click();
|
||
|
$('input[name=view][value="full"]').click();
|
||
|
$('input[name=resolution]').prop("disabled", true);
|
||
|
$('input[name=view]').prop("disabled", true);
|
||
|
} else if (imgType != 'png') {
|
||
|
$('input[name=transparent]').attr('checked', false);
|
||
|
$('input[name=transparent]').prop("disabled", true);
|
||
|
$('input[name=view]').prop("disabled", false);
|
||
|
} else {
|
||
|
$('input[name=view]').prop("disabled", false);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Function to delete offline project of selected id
|
||
|
deleteOfflineProject = function (projectId) {
|
||
|
var projectList = JSON.parse(localStorage.getItem("projectList"));
|
||
|
var confirmation = confirm("Are You Sure You Want To Delete Project " + projectList[projectId] + " ?");
|
||
|
if (confirmation) {
|
||
|
delete projectList[projectId];
|
||
|
localStorage.removeItem(projectId);
|
||
|
localStorage.setItem("projectList", JSON.stringify(projectList));
|
||
|
$('#openProjectDialog').empty();
|
||
|
for (id in projectList) {
|
||
|
$('#openProjectDialog').append('<label class="option"><input type="radio" name="projectId" value="' + id + '" />' + projectList[id] + '<i class="fa fa-times deleteOfflineProject" onclick="deleteOfflineProject(\'' + id + '\')"></i></label>');
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Prompt to restore from localStorage
|
||
|
createOpenLocalPrompt = function() {
|
||
|
$('#openProjectDialog').empty();
|
||
|
var projectList = JSON.parse(localStorage.getItem("projectList"));
|
||
|
var flag = true;
|
||
|
for (id in projectList) {
|
||
|
flag = false;
|
||
|
$('#openProjectDialog').append('<label class="option"><input type="radio" name="projectId" value="' + id + '" />' + projectList[id] + '<i class="fa fa-times deleteOfflineProject" onclick="deleteOfflineProject(\'' + id + '\')"></i></label>');
|
||
|
}
|
||
|
if (flag) $('#openProjectDialog').append('<p>Looks like no circuit has been saved yet. Create a new one and save it!</p>')
|
||
|
$('#openProjectDialog').dialog({
|
||
|
width: "auto",
|
||
|
buttons: !flag ? [{
|
||
|
text: "Open Project",
|
||
|
click: function() {
|
||
|
if (!$("input[name=projectId]:checked").val()) return;
|
||
|
load(JSON.parse(localStorage.getItem($("input[name=projectId]:checked").val())));
|
||
|
$(this).dialog("close");
|
||
|
},
|
||
|
}] : []
|
||
|
|
||
|
});
|
||
|
|
||
|
}
|
||
|
|
||
|
// Prompt to create subcircuit, shows list of circuits which dont depend on the current circuit
|
||
|
createSubCircuitPrompt = function(scope = globalScope) {
|
||
|
$('#insertSubcircuitDialog').empty();
|
||
|
var flag = true;
|
||
|
for (id in scopeList) {
|
||
|
if (!scopeList[id].checkDependency(scope.id)) {
|
||
|
flag = false;
|
||
|
$('#insertSubcircuitDialog').append('<label class="option"><input type="radio" name="subCircuitId" value="' + id + '" />' + scopeList[id].name + '</label>');
|
||
|
}
|
||
|
}
|
||
|
if (flag) $('#insertSubcircuitDialog').append('<p>Looks like there are no other circuits which doesn\'t have this circuit as a dependency. Create a new one!</p>')
|
||
|
$('#insertSubcircuitDialog').dialog({
|
||
|
maxHeight: 350,
|
||
|
width: 250,
|
||
|
maxWidth: 250,
|
||
|
minWidth: 250,
|
||
|
buttons: !flag ? [{
|
||
|
text: "Insert SubCircuit",
|
||
|
click: function() {
|
||
|
if (!$("input[name=subCircuitId]:checked").val()) return;
|
||
|
simulationArea.lastSelected = new SubCircuit(undefined, undefined, globalScope, $("input[name=subCircuitId]:checked").val());
|
||
|
$(this).dialog("close");
|
||
|
},
|
||
|
}] : []
|
||
|
|
||
|
});
|
||
|
|
||
|
}
|
||
|
|
||
|
// Helper function to store to localStorage -- needs to be deprecated/removed
|
||
|
function saveOffline() {
|
||
|
projectSaved = true;
|
||
|
var data = generateSaveData();
|
||
|
localStorage.setItem(projectId, data);
|
||
|
var temp = JSON.parse(localStorage.getItem("projectList")) || {};
|
||
|
temp[projectId] = projectName;
|
||
|
localStorage.setItem("projectList", JSON.stringify(temp));
|
||
|
showMessage("We have saved your project: " + projectName + " in your browser's localStorage");
|
||
|
}
|
||
|
|
||
|
// Checks if any circuit has unsaved data
|
||
|
function checkToSave() {
|
||
|
var save = false
|
||
|
for (id in scopeList) {
|
||
|
save |= checkIfBackup(scopeList[id]);
|
||
|
}
|
||
|
return save;
|
||
|
}
|
||
|
|
||
|
// Prompt user to save data if unsaved
|
||
|
window.onbeforeunload = function() {
|
||
|
if (projectSaved || embed) return;
|
||
|
|
||
|
if (!checkToSave()) return;
|
||
|
|
||
|
alert("You have unsaved changes on this page. Do you want to leave this page and discard your changes or stay on this page?");
|
||
|
var data = generateSaveData("Untitled");
|
||
|
localStorage.setItem("recover", data);
|
||
|
return "Are u sure u want to leave? Any unsaved changes may not be recoverable"
|
||
|
|
||
|
}
|
||
|
|
||
|
// Function to generate image and prompt download
|
||
|
function generateImage(imgType, view, transparent, resolution, down = true) {
|
||
|
|
||
|
// Backup all data
|
||
|
var backUpOx = globalScope.ox;
|
||
|
var backUpOy = globalScope.oy;
|
||
|
var backUpWidth = width
|
||
|
var backUpHeight = height;
|
||
|
var backUpScale = globalScope.scale;
|
||
|
backUpContextBackground = backgroundArea.context;
|
||
|
backUpContextSimulation = simulationArea.context;
|
||
|
|
||
|
backgroundArea.context = simulationArea.context;
|
||
|
|
||
|
globalScope.ox *= 1 / backUpScale;
|
||
|
globalScope.oy *= 1 / backUpScale;
|
||
|
|
||
|
// If SVG, create SVG context - using canvas2svg here
|
||
|
if (imgType == 'svg') {
|
||
|
simulationArea.context = new C2S(width, height);
|
||
|
resolution = 1;
|
||
|
} else if (imgType != 'png') {
|
||
|
transparent = false;
|
||
|
}
|
||
|
|
||
|
globalScope.scale = resolution;
|
||
|
|
||
|
var scope = globalScope;
|
||
|
|
||
|
// Focus circuit
|
||
|
if (view == "full") {
|
||
|
findDimensions();
|
||
|
var minX = simulationArea.minWidth;
|
||
|
var minY = simulationArea.minHeight;
|
||
|
var maxX = simulationArea.maxWidth;
|
||
|
var maxY = simulationArea.maxHeight;
|
||
|
width = (maxX - minX + 100) * resolution;
|
||
|
height = (maxY - minY + 100) * resolution;
|
||
|
|
||
|
globalScope.ox = (-minX + 50) * resolution;
|
||
|
globalScope.oy = (-minY + 50) * resolution;
|
||
|
} else {
|
||
|
globalScope.ox *= resolution;
|
||
|
globalScope.oy *= resolution;
|
||
|
width = (width * resolution) / backUpScale;
|
||
|
height = (height * resolution) / backUpScale;
|
||
|
}
|
||
|
|
||
|
globalScope.ox = Math.round(globalScope.ox);
|
||
|
globalScope.oy = Math.round(globalScope.oy);
|
||
|
|
||
|
simulationArea.canvas.width = width;
|
||
|
simulationArea.canvas.height = height;
|
||
|
backgroundArea.canvas.width = width;
|
||
|
backgroundArea.canvas.height = height;
|
||
|
|
||
|
|
||
|
backgroundArea.context = simulationArea.context;
|
||
|
|
||
|
simulationArea.clear();
|
||
|
|
||
|
// Background
|
||
|
if (!transparent) {
|
||
|
simulationArea.context.fillStyle = "white";
|
||
|
simulationArea.context.rect(0, 0, width, height);
|
||
|
simulationArea.context.fill();
|
||
|
}
|
||
|
|
||
|
// Draw circuits, why is it updateOrder and not renderOrder?
|
||
|
for (var i = 0; i < renderOrder.length; i++)
|
||
|
for (var j = 0; j < scope[renderOrder[i]].length; j++)
|
||
|
scope[renderOrder[i]][j].draw();
|
||
|
|
||
|
// If circuit is to be downloaded, download, other wise return dataURL
|
||
|
if (down) {
|
||
|
if (imgType == 'svg') {
|
||
|
var mySerializedSVG = simulationArea.context.getSerializedSvg(); //true here, if you need to convert named to numbered entities.
|
||
|
download(globalScope.name + ".svg", mySerializedSVG);
|
||
|
} else {
|
||
|
downloadAsImg(globalScope.name, imgType)
|
||
|
}
|
||
|
} else {
|
||
|
var returnData = simulationArea.canvas.toDataURL('image/' + imgType);
|
||
|
}
|
||
|
|
||
|
// Restore everything
|
||
|
width = backUpWidth
|
||
|
height = backUpHeight
|
||
|
simulationArea.canvas.width = width;
|
||
|
simulationArea.canvas.height = height;
|
||
|
backgroundArea.canvas.width = width;
|
||
|
backgroundArea.canvas.height = height;
|
||
|
globalScope.scale = backUpScale;
|
||
|
backgroundArea.context = backUpContextBackground;
|
||
|
simulationArea.context = backUpContextSimulation;
|
||
|
globalScope.ox = backUpOx
|
||
|
globalScope.oy = backUpOy;
|
||
|
|
||
|
resetup();
|
||
|
|
||
|
if (!down) return returnData
|
||
|
|
||
|
}
|
||
|
|
||
|
if (logix_project_id == 0)
|
||
|
setTimeout(promptSave,120000);
|
||
|
|
||
|
function promptSave(){
|
||
|
console.log("PROMPT")
|
||
|
if(confirm("You have not saved your creation! Would you like save your project online? "))
|
||
|
save()
|
||
|
}
|
||
|
|
||
|
async function postUserIssue(message) {
|
||
|
var img = generateImage("jpeg", "full", false, 1, false).split(',')[1];
|
||
|
const result = await $.ajax({
|
||
|
url: 'https://api.imgur.com/3/image',
|
||
|
type: 'POST',
|
||
|
data: {
|
||
|
image: img
|
||
|
},
|
||
|
dataType: 'json',
|
||
|
headers: {
|
||
|
Authorization: 'Client-ID 9a33b3b370f1054'
|
||
|
},
|
||
|
});
|
||
|
|
||
|
message += "\n" + result.data.link;
|
||
|
|
||
|
$.ajax({
|
||
|
url: '/simulator/post_issue',
|
||
|
type: 'POST',
|
||
|
beforeSend: function(xhr) {
|
||
|
xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))
|
||
|
},
|
||
|
data: {
|
||
|
"text": message,
|
||
|
},
|
||
|
success: function(response) {
|
||
|
$('#result').html("<i class='fa fa-check' style='color:green'></i> You've successfully submitted the issue. Thanks for improving our platform.");
|
||
|
},
|
||
|
failure: function(err) {
|
||
|
$('#result').html("<i class='fa fa-check' style='color:red'></i> There seems to be a network issue. Please reach out to us at support@ciruitverse.org");
|
||
|
}
|
||
|
});
|
||
|
}
|