import { scopeList } from '../circuit';
import { resetup } from '../setup';
import { update, updateSubcircuitSet } from '../engine';
import { stripTags, showMessage } from '../utils';
import { backUp } from './backupCircuit';
import simulationArea from '../simulationArea';
import backgroundArea from '../backgroundArea';
import { findDimensions } from '../canvasApi';
import { projectSavedSet } from './project';
import { colors } from '../themer/themer';
import {layoutModeGet, toggleLayoutMode} from '../layoutMode';
import {verilogModeGet} from '../Verilog2CV';
import domtoimage from 'dom-to-image';
import C2S from '../canvas2svg';
var projectName = undefined;
/**
* Function to set the name of project.
* @param {string} name - name for project
* @category data
*/
export function setProjectName(name) {
if(name == undefined) {
$('#projectName').html('Untitled');
return;
}
name = stripTags(name);
projectName = name;
$('#projectName').html(name);
}
/**
* Function to set the name of project.
* @param {string} name - name for project
* @category data
*/
export function getProjectName() {
return projectName;
}
/**
* Helper function to save canvas as image based on image type
* @param {string} name -name of the circuit
* @param {string} imgType - image type ex: png,jpg etc.
* @category data
*/
function downloadAsImg(name, imgType) {
const gh = simulationArea.canvas.toDataURL(`image/${imgType}`);
const anchor = document.createElement('a');
anchor.href = gh;
anchor.download = `${name}.${imgType}`;
anchor.click();
}
/**
* Returns the order of tabs in the project
*/
export function getTabsOrder() {
var tabs = $("#tabsBar").children().not('button');
var order = [];
for (let i = 0; i < tabs.length; i++) {
order.push(tabs[i].id);
}
return order
}
/**
* Generates JSON of the entire project
* @param {string} name - the name of project
* @return {JSON}
* @category data
*/
export function generateSaveData(name, setName = true) {
data = {};
// Prompts for name, defaults to Untitled
name = getProjectName() || name || prompt('Enter Project Name:') || 'Untitled';
data.name = stripTags(name);
if (setName) setProjectName(data.name);
// Save project details
data.timePeriod = simulationArea.timePeriod;
data.clockEnabled = simulationArea.clockEnabled;
data.projectId = projectId;
data.focussedCircuit = globalScope.id;
data.orderedTabs = getTabsOrder();
// Project Circuits, each scope is one circuit
data.scopes = [];
const dependencyList = {};
const 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 (let i = 0; i < dependencyList[id].length; i++) {
// Save inner subcircuits
saveScope(dependencyList[id][i]);
}
completed[id] = true;
// This update is very important.
// if a scope's input/output changes and the user saves without going
// to circuits where this circuit is used as a subcircuit. It will
// break the code since the Subcircuit will have different number of
// in/out nodes compared to the localscope input/output objects.
update(scopeList[id], true); // For any pending integrity checks on subcircuits
data.scopes.push(backUp(scopeList[id]));
}
// Save all circuits
for (let id in scopeList) { saveScope(id); }
// convert to text
data = JSON.stringify(data);
return data;
}
// Helper function to download text
export 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 generate image for the circuit
* @param {string} imgType - ex: png,jpg etc.
* @param {string} view - view type ex: full
* @param {boolean} transparent - tranparent bg or not
* @param {number} resolution - resolution of the image
* @param {boolean=} down - will download if true
* @category data
*/
export function generateImage(imgType, view, transparent, resolution, down = true) {
// Backup all data
const backUpOx = globalScope.ox;
const backUpOy = globalScope.oy;
const backUpWidth = width;
const backUpHeight = height;
const backUpScale = globalScope.scale;
const backUpContextBackground = backgroundArea.context;
const 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;
const scope = globalScope;
// Focus circuit
var flag = 1;
if (flag) {
if (view === 'full') {
findDimensions();
const minX = simulationArea.minWidth;
const minY = simulationArea.minHeight;
const maxX = simulationArea.maxWidth;
const 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 = colors["canvas_fill"];
simulationArea.context.rect(0, 0, width, height);
simulationArea.context.fill();
}
// Draw circuits, why is it updateOrder and not renderOrder?
for (let i = 0; i < renderOrder.length; i++) {
for (let j = 0; j < scope[renderOrder[i]].length; j++) { scope[renderOrder[i]][j].draw(); }
}
let returnData;
// If circuit is to be downloaded, download, other wise return dataURL
if (down) {
if (imgType === 'svg') {
const 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 {
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;
}
async function crop(dataURL, w, h) {
//get empty second canvas
var myCanvas = document.createElement("CANVAS");
myCanvas.width = w;
myCanvas.height = h;
var myContext = myCanvas.getContext('2d');
var myImage;
var img = new Image();
return new Promise (function (resolved, rejected) {
img.src = dataURL;
img.onload = () => {
myContext.drawImage(img, 0, 0, w, h,0,0, w ,h);
myContext.save();
//create a new data URL
myImage = myCanvas.toDataURL('image/jpeg');
resolved(myImage);}
})
}
/**
* Function that is used to save image for display in the website
* @return {JSON}
* @category data
*/
async function generateImageForOnline() {
// Verilog Mode -> Different logic
// Fix aspect ratio to 1.6
// Ensure image is approximately 700 x 440
var ratio = 1.6
if(verilogModeGet()) {
var node = document.getElementsByClassName('CodeMirror')[0];
// var node = document.getElementsByClassName('CodeMirror')[0];
var prevHeight = $(node).css('height');
var prevWidth = $(node).css('width');
var baseWidth = 500;
var baseHeight = Math.round(baseWidth / ratio);
$(node).css('height', baseHeight);
$(node).css('width', baseWidth);
var data = await domtoimage.toJpeg(node);
$(node).css('width', prevWidth);
$(node).css('height', prevHeight);
data = await crop(data, baseWidth, baseHeight);
return data;
}
simulationArea.lastSelected = undefined; // Unselect any selections
// Fix aspect ratio to 1.6
if (width > height * ratio) {
height = width / ratio;
} else {
width = height * 1.6;
}
// Center circuits
globalScope.centerFocus();
// Ensure image is approximately 700 x 440
const 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 called when you save acircuit online
* @category data
* @exports save
*/
export default async function save() {
if(layoutModeGet())
toggleLayoutMode();
projectSavedSet(true);
$('.loadingIcon').fadeIn();
const data = generateSaveData();
const projectName = getProjectName();
var imageData = await generateImageForOnline();
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();
// eslint-disable-next-line camelcase
} else if (__logix_project_id == "0") {
// Create new project - this part needs to be improved and optimised
const 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: imageData,
}),
);
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',
contentType: 'application/json',
beforeSend(xhr) {
xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
},
data: JSON.stringify({
data,
id: __logix_project_id,
image: imageData,
name: projectName,
}),
success(response) {
showMessage(`We have saved your project: ${projectName} in our servers.`);
$('.loadingIcon').fadeOut();
localStorage.removeItem('recover');
},
failure(err) {
showMessage("There was an error, we couldn't save to our servers");
$('.loadingIcon').fadeOut();
},
});
}
// Restore everything
resetup();
}
/**
* Function to autosave the data of circuit
* @category data
* @exports save
*/
var checkForAutosave = 1;
export function autosave() {
var circuitData = generateSaveData('Untitled');
localStorage.setItem('autosave', circuitData);
}
export function checkBackups() {
if (checkForAutosave < globalScope.backups.length) {
autosave();
checkForAutosave = globalScope.backups.length;
}
}
// Please do not enable autosave. It will not work
// in the current state and breaks other things.
// setInterval(checkBackups, 3000); // disabled
Source