GraphEditor/js/undo_redo.js
2025-08-16 08:37:11 +00:00

543 lines
26 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Scripting by Oleg Okhotnikov, contact - svoboda200786@gmail.com
let poupup = document.querySelector(".poupup-menu");
let actionsHist = [];
let actionsIndex = -1;
let myUndo = document.getElementById('undo-btn');
let myRedo = document.getElementById('redo-btn');
let group = {groupName: undefined};
myUndo.onclick = function() {
if(actionsIndex >= 0){
undo_redo('undo');
actionsIndex--;
var ctx = canvas.contextTop;
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
};
myRedo.onclick = function () {
if(actionsIndex < actionsHist.length - 1){
actionsIndex++;
undo_redo('redo');
var ctx = canvas.contextTop;
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
};
function onObjectAdded(obj){
let item = obj.target;
if(!file_loaded || item.clonned){ // в этом условии объекты создаются при загрузке сохраненного файла или клонировании
let shape_type = /shape.*/.test(item.groupName); // если groupName содержит "shape", то это примитив
if(shape_type){ // если примитив
item.parent = item;
item.parent.added = true; // для составных объектов параметр added задается в functions.js
// также added задается в controls.js и editor.js в add_image
index++;
item.index = index;
shape[index] = item;
item.clonned = false;
item.groupName = /[a-zA-Z\_]+/.exec(item.groupName) + index; // перезаписываем groupName, добавляя новый индекс
}
if(!item.parent){
item.parent = item;
}
oldParam(item);
}
else if(!/legend.*/.test(item.groupName)){
if(item.parent && !item.parent.zoom_size){
item.parent.zoom_size = zoom_size;
}
if(item.type == "textbox"){
canvas.setActiveObject(item);
}
}
if(canvas.isDrawingMode == 1){
item.groupName = "shape_pencil" + index;
item.parent = item;
item.perPixelTargetFind = true;
item.parent.zoom_size = zoom_size;
}
let shape_type = /shape.*/.test(item.groupName); // если groupName shape, то это примитив
if(shape_type && !item.parent.added){ // если примитив, объект создается после загрузки сцены
index++;
item.index = index;
shape[index] = item;
}
shape_type = /shape_img.*/.test(item.groupName);
if((shape_type || canvas.isDrawingMode == 1) && !item.parent.added){ // если создали картинку или
// карандашную линию, то есть объекты которые создаются без срабатывания обработчика canvas.on('mouse:down'),
// записываем в истории тут - для остальных см. controls.js функцию endOfCreating
item.parent.added = true;
oldParam(item);
changes(item, "4");
actionsHist[actionsIndex].flag = 'add';
actionsHist.index = item.index;
}
// возможно так будет быстрее, чем через groupName, потому что в последнем применяется регулярка (пока реализовано только в геометрических ф-ях)
item.groupType = /[a-zA-Z\_]+/.exec(item.groupName)[0];
}
function onObjectSelected(obj){
let item = obj.target;
// // выделена или нажата группа
if(item != undefined){
if(item.type == "activeSelection"){ // объект является выделением
item.hasControls = false;
item.lockScalingX = true;
item.lockScalingY = true;
item.lockRotation = true;
item.parent.zoom_size = item._objects[0].parent.zoom_size;
}
if(canvas.isDrawingMode == 0 && item != null && (item.type != "activeSelection" || item.shapes == 1) ){
if(palette.active){
palette.active.classList.remove("color-cell-active");
}
palette.active = document.querySelector("[color='" + item.parent.stroke + "']") || document.querySelector("[color='" + item.parent.fill + "']");
if(palette.active){
palette.active.classList.add("color-cell-active");
}
if(item.parent && item.parent.zoom_size != undefined){
var size = item.parent.zoom_size + 1;
setTimeout(function(){
scale_indicator.setAttribute("scale_value", size + "x");
}, 10);
}
}
}
}
function calcObjects(item, obj){
let prop = "_objects";
let array = obj[prop];
for(var i = 0; i < array.length; i++){
item.objects_temp.push(array[i]);
if(array[i][prop] != undefined){
calcObjects(item, array[i]);
}
}
}
function changes(item, num){
if(item.index != undefined || item.type == "activeSelection" ){
//console.log("num: " + num);
actionsIndex++;
actionsHist.splice(actionsIndex, actionsHist.length - actionsIndex);
histAdd(item);
}
group.param_saved = false;
}
function onObjectModified(obj){
if(!/back/.test(obj.target.groupName) && !/legend/.test(obj.target.groupName)){
changes(obj.target, "2");
}
}
function onObjectRemoved(item){
if(item != undefined){
changes(item, "3");
actionsHist.index = item.index;
}
}
function onSelectionCleared(item){
if(typeof selection_temp2 != "undefined" && selection_temp2 ){
var line = item.deselected[0].parent;
line.x1 = line.arrow.left;
line.y1 = line.arrow.top;
line.x2 = line.arrow2.left;
line.y2 = line.arrow2.top;
selection_temp2 = null;
}
poupup.classList.add("mfp-hide");
}
document.addEventListener("click", function(e){
// алгоритм для выделения нескольких объектов находится в fabric.js
let item = canvas.getActiveObject();
if(item != undefined && item.type == "activeSelection"){
item.parent = item;
// oldParam(item);
}
if(e.target.className == "upper-canvas " && !move_flag && item != undefined
&& item.parent.objects != undefined && item.type != "i-text"){
}
else if(move_flag){
let obj = {};
obj.target = item;
// onObjectSelected(obj);
}
move_flag = false;
});
canvas.on('mouse:down:before', function(e){
var obj = e.target;
if(obj && editor_command || text_editing){
drawing_object = true;
}
if(obj){
if(drawing_object){
let array = obj.parent.objects;
for(var i in array){
obj.parent[array[i]].selectable = false;
}
}
else{
let array = obj.parent.objects;
for(var i in array){
var name = obj.parent[array[i]].name;
if( (name == "rect" && obj.parent[array[i]].groupType == "socket")
|| name == "arrow" || name == "arrow2" || name == "arrow_part1" || name == "arrow_part2" || name == "line"){
obj.parent[array[i]].selectable = true;
}
}
}
}
if(group.cloneOne){ // __onMouseDown в fabric.js, там установлен getActiveObject().setCoords()
let item = canvas.getActiveObject();
editor_obj.setClone(item);
// item.hasControls = true;
// canvas.setActiveObject(item);
canvas.renderAll();
element_cursor();
}
});
canvas.on('selection:created', onObjectSelected);
canvas.on('selection:updated', onObjectSelected);
canvas.on('selection:cleared', onSelectionCleared);
canvas.on('mouse:down', onObjectSelected);
canvas.on('object:modified', onObjectModified);
canvas.on('object:changed', onObjectModified);
canvas.on('object:added', onObjectAdded);
///////////////////////////////// oldParam /////////////////////////////////
/* Записывает в свойства с посфиксом old текущие значения - при отмене в них можно будет вернуться */
// Параметр old нужен потому, что предыдущее действик могло быть с другим объектом, поэтому
// нельзя просто взять старое свойство из actionIndex -1
function oldParam(item){
if (item != null){
let shape_type = /shape.*/.test(item.groupName);
if(item.parent.objects || item.parent._objects){
if(item.type == "activeSelection"){
var group_deltaX = item.width / 2 + item.left; // у объектов в группе отсчет координат относительно центра выделения
var group_deltaY = item.height / 2 + item.top; // у объектов в группе отсчет координат относительно центра выделения
var prop = "_objects";
}
else{
var group_deltaX = 0;
var group_deltaY = 0;
var prop = "objects";
}
let array = item.parent[prop]; // записываем части составного объекта в массив
for(var i in array){
if(item.type == "activeSelection"){ // у объектов в группе отсчет координат относительно центра выделения
var obj = array[i];
}
else{
var obj = item.parent[array[i]];
}
obj.set({
"x1_old": obj.x1,
"x2_old": obj.x2,
"y1_old": obj.y1,
"y2_old": obj.y2,
"left_old": obj.left + group_deltaX,
"top_old": obj.top + group_deltaY,
"width_old": obj.width,
"height_old": obj.height,
"originX_old": obj.originX,
"originY_old": obj.originY,
"scaleX_old": obj.scaleX,
"scaleY_old": obj.scaleY,
"angle_old": obj.angle,
"text_old": obj.text,
"fill_old": obj.fill,
"fontWeight_old": obj.fontWeight,
"fontSize_old": obj.fontSize,
"stroke_old": obj.stroke,
"strokeWidth_old": obj.strokeWidth,
"visible_old": obj.visible,
"zoom_size_old": obj.zoom_size
})
}
}
else{
if(item.x1){
item.set({
"x1_old": item.x1,
"x2_old": item.x2,
"y1_old": item.y1,
"y2_old": item.y2
})
}
item.set({
"left_old": item.left,
"top_old": item.top,
"width_old": item.width,
"height_old": item.height,
"originX_old": item.originX,
"originY_old": item.originY,
"scaleX_old": item.scaleX,
"scaleY_old": item.scaleY,
"angle_old": item.angle,
"text_old": item.text,
"fill_old": item.fill,
"fontWeight_old": item.fontWeight,
"fontSize_old": item.fontSize,
"stroke_old": item.stroke,
"strokeWidth_old": item.strokeWidth,
"visible_old": item.visible,
"zoom_size_old": item.zoom_size
})
}
}
}
///////////////////////////////// histAdd /////////////////////////////////
/* Добавляет в историю объект, с текущими параметрами объекта fabric */
function histAdd(item){
if (item != undefined){
let shape_type = /shape.*/.test(item.groupName);
// Пушим в историю все составные части
if(!shape_type || item.type == "activeSelection"){ // если не примитив
actionsHist.push([]); // создаем новый элемент в истории
if(item.type == "activeSelection"){
var group_deltaX = item.width / 2 + item.left; // у объектов в группе отсчет координат относительно центра выделения
var group_deltaY = item.height / 2 + item.top; // у объектов в группе отсчет координат относительно центра выделения
var prop = "_objects";
}
else{
var group_deltaX = 0;
var group_deltaY = 0;
var prop = "objects";
}
let array = item.parent[prop]; // записываем части составного объекта в массив
let actionsHistEl = actionsHist[actionsHist.length-1]; // даем новому эелементу истории название
// actionsHistEl.objects = []; // в это свойсто будут записываться старые и текущие параметры части
// составного объекта
for(var i in array){
if(item.type == "activeSelection"){ // у объектов в группе отсчет координат относительно центра выделения
var obj = array[i];
}
else{
var obj = item.parent[array[i]];
}
actionsHistEl.push({
"name": obj.name,
"index": obj.parent.index,
"left_old": obj.left_old,
"top_old": obj.top_old,
"width_old": obj.width_old,
"height_old": obj.height_old,
"originX_old": obj.originX_old,
"originY_old": obj.originY_old,
"scaleX_old": obj.scaleX_old,
"scaleY_old": obj.scaleY_old,
"angle_old": obj.angle_old,
'x1_old': obj.x1_old,
'y1_old': obj.y1_old,
'x2_old': obj.x2_old,
'y2_old': obj.y2_old,
"text_old": obj.text_old,
"fill_old": obj.fill_old,
"fontWeight_old": obj.fontWeight_old,
"fontSize_old": obj.fontSize_old,
"stroke_old": obj.stroke_old,
"strokeWidth_old": obj.strokeWidth_old,
"visible_old": obj.visible_old,
"zoom_size_old": obj.zoom_size_old,
"left": obj.left + group_deltaX,
"top": obj.top + group_deltaY,
"width": obj.width,
"height": obj.height,
"originX": obj.originX,
"originY": obj.originY,
"scaleX": obj.scaleX,
"scaleY": obj.scaleY,
"angle": obj.angle,
'x1': obj.x1,
'y1': obj.y1,
'x2': obj.x2,
'y2': obj.y2,
"text": obj.text,
"fill": obj.fill,
"fontWeight": obj.fontWeight,
"fontSize": obj.fontSize,
"stroke": obj.stroke,
"strokeWidth": obj.strokeWidth,
"visible": obj.visible,
"zoom_size": obj.zoom_size
});
}
}
else{
actionsHist.push([]); // создаем новый элемент в истории
let actionsHistEl = actionsHist[actionsHist.length-1];
actionsHistEl.push({
"index": item.index,
"left_old": item.left_old,
"top_old": item.top_old,
"width_old": item.width_old,
"height_old": item.height_old,
"scaleX_old": item.scaleX_old,
"scaleY_old": item.scaleY_old,
"originX_old": item.originX_old,
"originY_old": item.originY_old,
"angle_old": item.angle_old,
"text_old": item.text_old,
"fill_old": item.fill_old,
"fontWeight_old": item.fontWeight_old,
"fontSize_old": item.fontSize_old,
"stroke_old": item.stroke_old,
"strokeWidth_old": item.strokeWidth_old,
"visible_old": item.visible_old,
"zoom_size_old": item.zoom_size_old,
"left": item.left,
"top": item.top,
"width": item.width,
"height": item.height,
"originX": item.originX,
"originY": item.originY,
"scaleX": item.scaleX,
"scaleY": item.scaleY,
"angle": item.angle,
"text": item.text,
"fill": item.fill,
"fontWeight": item.fontWeight,
"fontSize": item.fontSize,
"stroke": item.stroke,
"strokeWidth": item.strokeWidth,
"visible": item.visible,
"zoom_size": item.zoom_size
});
}
}
}
///////////////////////////////// undo_redo /////////////////////////////////
/* Возврат - повтор */
function undo_redo(flag){
if(Boolean(actionsHist[actionsIndex])){
// let item = shape[actionsHist[actionsIndex].index];
for (var obj of actionsHist[actionsIndex]){
if(obj.name){
var item = shape[obj.index][obj.name];
}
else{
var item = shape[obj.index];
}
if(actionsHist[actionsIndex].flag == "add" && flag == 'undo'){ // если объект был создан в этот момент истории
deleteObj(item, true);
}
else{
canvas.discardActiveObject();
canvas.requestRenderAll();
if(flag == 'undo'){
setHistoryParam(item, "_old", obj);
}
else{
setHistoryParam(item, "", obj);
}
}
}
// shape[actionsHist[actionsIndex].index].setCoords();
}
canvas.discardActiveObject().renderAll();
}
///////////////////////////////// setHistoryParam /////////////////////////////////
/* Устанавливает значения , записанные при помощи oldParam */
function setHistoryParam(item, postfix, obj){
if(item.name){
item.set({
"x1": obj["x1" + postfix],
"x2": obj["x2" + postfix],
"y1": obj["y1" + postfix],
"y2": obj["y2" + postfix],
"left": obj["left" + postfix],
"top": obj["top" + postfix],
"width": obj["width" + postfix],
"height": obj["height" + postfix],
"originX": obj["originX" + postfix],
"originY": obj["originY" + postfix],
"scaleX": obj["scaleX" + postfix],
"scaleY": obj["scaleY" + postfix],
"angle": obj["angle" + postfix],
"text": obj["text" + postfix],
"fill": obj["fill" + postfix],
"fontWeight": obj["fontWeight" + postfix],
"fontSize": obj["fontSize" + postfix],
"stroke": obj["stroke" + postfix],
"strokeWidth": obj["strokeWidth" + postfix],
"visible": obj["visible" + postfix],
"zoom_size": obj["zoom_size" + postfix]
});
item.setCoords();
}
// Примитивы:
// Если выделено несколько объектов, втом числе составных,
// то все будут расчитываться как сумма примитиввов, потому что обсчет идет
// для item, в который записывается результат canvas.getActiveObject() - это будет выделение
// или (activeSelection)
else{
item.set({
"left": obj["left" + postfix],
"top": obj["top" + postfix],
"width": obj["width" + postfix],
"height": obj["height" + postfix],
"originX": obj["originX" + postfix],
"originY": obj["originY" + postfix],
"scaleX": obj["scaleX" + postfix],
"scaleY": obj["scaleY" + postfix],
"angle": obj["angle" + postfix],
"text": obj["text" + postfix],
"fill": obj["fill" + postfix],
"fontWeight": obj["fontWeight" + postfix],
"fontSize": obj["fontSize" + postfix],
"stroke": obj["stroke" + postfix],
"strokeWidth": obj["strokeWidth" + postfix],
"visible": obj["visible" + postfix],
"zoom_size": obj["zoom_size" + postfix]
});
shape_type = /shape_svg.*/.test(item.groupName);
if(shape_type){
editor_obj.add_legend();
}
canvas.renderAll();
}
// if(item.parent._objects != undefined){ // выделение или группа
// setHistoryParam_subfunction(item, postfix);
// }
}
function setHistoryParam_subfunction(item, postfix, array){
/* подфункция для объектов, создаваемых с помощью выделения или группы */
// if(shape[actionsHist[actionsIndex].index].type == "activeSelection"){
// var obj = shape[actionsHist[actionsIndex].index].objects_temp;
// }
// else{
var obj = shape[actionsHist[actionsIndex].index]._objects;
// }
if(array == undefined){
var array = actionsHist[actionsIndex]["_objects"];
}
for(var i in array){
obj[i].set({
"fill": array[i]["fill" + postfix],
"fontWeight": array[i]["fontWeight" + postfix],
"fontSize": array[i]["fontSize" + postfix],
"stroke": array[i]["stroke" + postfix],
"strokeWidth": array[i]["strokeWidth" + postfix],
"scaleX": array[i]["scaleX" + postfix],
"scaleY": array[i]["scaleY" + postfix],
"visible": array[i]["visible" + postfix],
"zoom_size": array[i]["zoom_size" + postfix]
});
shape_type = /shape_svg.*/.test(obj[i].groupName);
if(shape_type){
editor_obj.add_legend();
}
}
}