Подтвердить что ты не робот

Функция отмены отмены в Fabric.js

Есть ли встроенная поддержка для отмены/повтора в Fabric.js? Не могли бы вы посоветовать мне, как вы использовали этот отменить и повторить в [http://printio.ru/] [1]

4b9b3361

Ответ 1

В http://jsfiddle.net/SpgGV/9/ переместите объект и измените его размер. Если состояние объекта изменилось, а затем мы отменим/повторим, его предыдущее состояние будет удалено при следующем изменении. Это упрощает отмену/повтор. Все события холста должны быть вызваны до того, как любой элемент будет добавлен в холст. Я не добавил здесь событие object:remove. Вы можете добавить его сами. Если один элемент удален, состояние и список должны быть недопустимыми, если этот элемент находится в этом массиве. Более простой способ - установить state и list = [] и index = 0.

Это очистит состояние вашей очереди отмены/повтора. Если вы хотите сохранить все состояния, например add/remove, мое предложение состоит в том, чтобы добавить дополнительные свойства к элементу вашего массива состояний. Например, state = [{"data":object.originalState, "event": "added"}, ....]. "Событие" может быть "изменено" или "добавлено" и установлено в соответствующем обработчике событий.

Если вы добавили один объект, установите state[index].event="added", чтобы в следующий раз, когда вы используете отмену, вы проверяете его. Если он "добавлен", то все равно удалите его. Или когда вы используете повтор, если целевой "добавлен", вы добавили его. Недавно я был очень занят. Я добавлю коды в jsfiddle.net позже.

Обновление: добавлено setCoords();

var current;
var list = [];
var state = [];
var index = 0;
var index2 = 0;
var action = false;
var refresh = true;

canvas.on("object:added", function (e) {
    var object = e.target;
    console.log('object:modified');

    if (action === true) {
        state = [state[index2]];
        list = [list[index2]];

        action = false;
        console.log(state);
        index = 1;
    }
    object.saveState();

    console.log(object.originalState);
    state[index] = JSON.stringify(object.originalState);
    list[index] = object;
    index++;
    index2 = index - 1;



    refresh = true;
});

canvas.on("object:modified", function (e) {
    var object = e.target;
    console.log('object:modified');

    if (action === true) {
        state = [state[index2]];
        list = [list[index2]];

        action = false;
        console.log(state);
        index = 1;
    }

    object.saveState();

    state[index] = JSON.stringify(object.originalState);
    list[index] = object;
    index++;
    index2 = index - 1;

    console.log(state);
    refresh = true;
});

function undo() {

    if (index <= 0) {
        index = 0;
        return;
    }

    if (refresh === true) {
        index--;
        refresh = false;
    }

    console.log('undo');

    index2 = index - 1;
    current = list[index2];
    current.setOptions(JSON.parse(state[index2]));

    index--;
    current.setCoords();
    canvas.renderAll();
    action = true;
}

function redo() {

    action = true;
    if (index >= state.length - 1) {
        return;
    }

    console.log('redo');

    index2 = index + 1;
    current = list[index2];
    current.setOptions(JSON.parse(state[index2]));

    index++;
    current.setCoords();
    canvas.renderAll();
}

Обновление: лучшее решение для учета алгоритма истории изменений. Здесь мы можем использовать Editing.getInst().set(item), где элемент может быть {action, object, state}; Например, {"add", object, "{JSON....}"}.

/**
 * Editing : we will save element states into an queue, and the length of queue 
 * is fixed amount, for example, 0..99, each element will be insert into the top 
 * of queue, queue.push, and when the queue is full, we will shift the queue, 
 * to remove the oldest element from the queue, queue.shift, and then we will 
 * do push. 
 * 
 * So the latest state will be at the top of queue, and the oldest one will be 
 * at the bottom of the queue (0), and the top of queue is changed, could be 
 * 1..99.
 * 
 * The initialized action is "set", it will insert item into the top of queue,
 * even if it arrived the length of queue, it will queue.shift, but still do
 * the same thing, and queue only abandon the oldest element this time. When
 * the current is changed and new state is coming, then this time, top will be
 * current + 1.
 *
 * The prev action is to fetch "previous state" of the element, and it will use
 * "current" to do this job, first, we will --current, and then we will return
 * the item of it, because "current" always represent the "current state" of
 * element. When the current is equal 0, that means, we have fetched the last
 * element of the queue, and then it arrived at the bottom of the queue.
 *
 * The next action is to fetch "next state" after current element, and it will
 * use "current++" to do the job, when the current is equal to "top", it means
 * we have fetched the latest element, so we should stop.
 *
 * If the action changed from prev/next to "set", then we should reset top to
 * "current", and abandon all rest after that...
 *
 * Here we should know that, if we keep the reference in the queue, the item
 * in the queue will never be released.
 *
 *
 * @constructor
 */
function Editing() {

    this.queue = [];
    this.length = 4;
    this.bottom = 0;
    this.top = 0;
    this.current = 0;
    this.empty = true;

    // At the Begin of Queue
    this.BOQ = true;

    // At the End of Queue
    this.EOQ = true;

    // 0: set, 1: prev, 2: next
    this._action = 0;
    this._round = 0;
}

Editing.sharedInst = null;
Editing.getInst = function (owner) {

    if (Editing.sharedInst === null) {
        Editing.sharedInst = new Editing(owner);
    }

    return Editing.sharedInst;
};

/**
 * To set the item into the editing queue, and mark the EOQ, BOQ, so we know
 * the current position.
 *
 * @param item
 */
Editing.prototype.set = function (item) {

    console.log("=== Editing.set");

    var result = null;

    if (this._action != 0) {
        this.top = this.current + 1;
    }

    if (this.top >= this.length) {
        result = this.queue.shift();
        this.top = this.length - 1;
    }

    this._action = 0;
    this.queue[this.top] = item;
    this.current = this.top;
    this.top++;

    this.empty = false;
    this.EOQ = true;
    this.BOQ = false;

    console.log("==> INFO : ");
    console.log(item);
    console.log("===========");
    console.log("current: ", 0 + this.current);
    console.log("start: ", 0 + this.bottom);
    console.log("end: ", 0 + this.top);

    return result;

};

/**
 * To fetch the previous item just before current one
 *
 * @returns {item|boolean}
 */
Editing.prototype.prev = function () {

    console.log("=== Editing.prev");

    if (this.empty) {
        return false;
    }

    if (this.BOQ) {
        return false;
    }

    this._action = 1;

    this.current--;

    if (this.current == this.bottom) {
        this.BOQ = true;
    }

    var item = this.queue[this.current];
    this.EOQ = false;

    console.log("==> INFO : ");
    console.log(item);
    console.log("===========");
    console.log("current: ", 0 + this.current);
    console.log("start: ", 0 + this.bottom);
    console.log("end: ", 0 + this.top);

    return item;
};

/**
 * To fetch the next item just after the current one
 *
 * @returns {*|boolean}
 */
Editing.prototype.next = function () {

    console.log("=== Editing.next");

    if (this.empty) {
        return false;
    }

    if (this.EOQ) {
        return false;
    }

    this.current++;

    if (this.current == this.top - 1 && this.top < this.length) {
        this.EOQ = true;
    }

    if (this.current == this.top - 1 && this.top == this.length) {
        this.EOQ = true;
    }

    this._action = 2;

    var item = this.queue[this.current];
    this.BOQ = false;

    console.log("==> INFO : ");
    console.log(item);
    console.log("===========");
    console.log("current: ", 0 + this.current);
    console.log("start: ", 0 + this.bottom);
    console.log("end: ", 0 + this.top);

    return item;
};


/**
 * To empty the editing and reset all state
 */
Editing.prototype.clear = function () {

    this.queue = [];
    this.bottom = 0;
    this.top = 0;
    this.current = 0;
    this.empty = true;
    this.BOQ = true;
    this.EOQ = false;
};

Ответ 2

Вот решение, которое начиналось с этого более простого ответа по аналогичному вопросу, Отменить историю повтора для Canvas FabricJs.

Мой ответ соответствует тем же строкам, что ответ Tom и другой , которые являются модификациями ответа Тома.

Чтобы отслеживать состояние, я использую JSON.stringify(canvas) и canvas.loadFromJSON() как другие ответы и регистрирую событие на object:modified, чтобы зафиксировать состояние.

Важно то, что окончательный canvas.renderAll() должен быть вызван в обратном вызове, переданном второму параметру loadFromJSON(), как этот

canvas.loadFromJSON(state, function() {
    canvas.renderAll();
}

Это связано с тем, что для разбора и загрузки JSON может потребоваться несколько миллисекунд, и вам нужно подождать, пока это будет сделано до рендеринга. Также важно отключить кнопки отмены и повтора, как только они будут нажаты, и только повторное включение в том же обратном вызове. Что-то вроде этого

$('#undo').prop('disabled', true);
$('#redo').prop('disabled', true);    
canvas.loadFromJSON(state, function() {
    canvas.renderAll();
    // now turn buttons back on appropriately
    ...
    (see full code below)
}

У меня есть откат и повторный стек и глобальный для последнего неизмененного состояния. Когда происходит какая-то модификация, предыдущее состояние вставляется в стек отмены и текущее состояние повторно захватывается.

Когда пользователь хочет отменить, текущее состояние переводится в стек повтора. Затем я удаляю последнее отменить и оба устанавливают его в текущее состояние и отображают на холсте.

Аналогично, когда пользователь хочет выполнить повтор, текущее состояние помещается в стек отмены. Затем я вывожу последний повтор и оба устанавливают его в текущее состояние и отображают его на холсте.

Код

         // Fabric.js Canvas object
        var canvas;
         // current unsaved state
        var state;
         // past states
        var undo = [];
         // reverted states
        var redo = [];

        /**
         * Push the current state into the undo stack and then capture the current state
         */
        function save() {
          // clear the redo stack
          redo = [];
          $('#redo').prop('disabled', true);
          // initial call won't have a state
          if (state) {
            undo.push(state);
            $('#undo').prop('disabled', false);
          }
          state = JSON.stringify(canvas);
        }

        /**
         * Save the current state in the redo stack, reset to a state in the undo stack, and enable the buttons accordingly.
         * Or, do the opposite (redo vs. undo)
         * @param playStack which stack to get the last state from and to then render the canvas as
         * @param saveStack which stack to push current state into
         * @param buttonsOn jQuery selector. Enable these buttons.
         * @param buttonsOff jQuery selector. Disable these buttons.
         */
        function replay(playStack, saveStack, buttonsOn, buttonsOff) {
          saveStack.push(state);
          state = playStack.pop();
          var on = $(buttonsOn);
          var off = $(buttonsOff);
          // turn both buttons off for the moment to prevent rapid clicking
          on.prop('disabled', true);
          off.prop('disabled', true);
          canvas.clear();
          canvas.loadFromJSON(state, function() {
            canvas.renderAll();
            // now turn the buttons back on if applicable
            on.prop('disabled', false);
            if (playStack.length) {
              off.prop('disabled', false);
            }
          });
        }

        $(function() {
          ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
          // Set up the canvas
          canvas = new fabric.Canvas('canvas');
          canvas.setWidth(500);
          canvas.setHeight(500);
          // save initial state
          save();
          // register event listener for user actions
          canvas.on('object:modified', function() {
            save();
          });
          // draw button
          $('#draw').click(function() {
            var imgObj = new fabric.Circle({
              fill: '#' + Math.floor(Math.random() * 16777215).toString(16),
              radius: Math.random() * 250,
              left: Math.random() * 250,
              top: Math.random() * 250
            });
            canvas.add(imgObj);
            canvas.renderAll();
            save();
          });
          // undo and redo buttons
          $('#undo').click(function() {
            replay(undo, redo, '#redo', this);
          });
          $('#redo').click(function() {
            replay(redo, undo, '#undo', this);
          })
        });
<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js" type="text/javascript"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.5.0/fabric.min.js" type="text/javascript"></script>
</head>

<body>
  <button id="draw">circle</button>
  <button id="undo" disabled>undo</button>
  <button id="redo" disabled>redo</button>
  <canvas id="canvas" style="border: solid 1px black;"></canvas>
</body>

Ответ 3

Я разрешаю пользователю удалить последний добавленный путь (в моем приложении для рисования), это отлично работает для меня:

var lastItemIndex = (fabricCanvas.getObjects().length - 1);
var item = fabricCanvas.item(lastItemIndex);

if(item.get('type') === 'path') {
  fabricCanvas.remove(item);
  fabricCanvas.renderAll();
}

Но вы также можете удалить оператор IF и позволить людям удалять что-либо.

Ответ 4

Я знаю, что поздно ответить, но это моя версия реализации этого. Может быть кому-то полезно.

Я реализовал эту функцию, сохраняя состояния холста как JSON. Всякий раз, когда пользователь добавляет или изменяет объект в Canvas, он сохраняет сохраненное состояние холста и сохраняет его в array. Этот array затем обрабатывается всякий раз, когда пользователь нажимает кнопку "Отменить" или "Повторить".

Взгляните на эту ссылку. Я также предоставил рабочий Демо-код.

https://github.com/abhi06991/Undo-Redo-Fabricjs

HTML:

<canvas id="canvas" width="400" height="400"></canvas> 
<button type="button" id="undo" >Undo</button>
<button type="button" id="redo" disabled>Redo</button>

JS:

var canvasDemo = (function(){
  var _canvasObject = new fabric.Canvas('canvas',{backgroundColor : "#f5deb3"});
    var _config = {
        canvasState             : [],
        currentStateIndex       : -1,
        undoStatus              : false,
        redoStatus              : false,
        undoFinishedStatus      : 1,
        redoFinishedStatus      : 1,
    undoButton              : document.getElementById('undo'),
        redoButton              : document.getElementById('redo'),
    };
    _canvasObject.on(
        'object:modified', function(){
            updateCanvasState();
        }
    );

  _canvasObject.on(
        'object:added', function(){
            updateCanvasState();
        }
    );

  var addObject = function(){
     var rect = new fabric.Rect({
            left   : 100,
            top    : 100,
            fill   : 'red',
            width  : 200,
            height : 200
    });
        _canvasObject.add(rect);
        _canvasObject.setActiveObject(rect);
    _canvasObject.renderAll();
  }

    var updateCanvasState = function() {
        if((_config.undoStatus == false && _config.redoStatus == false)){
            var jsonData        = _canvasObject.toJSON();
            var canvasAsJson        = JSON.stringify(jsonData);
            if(_config.currentStateIndex < _config.canvasState.length-1){
                var indexToBeInserted                  = _config.currentStateIndex+1;
                _config.canvasState[indexToBeInserted] = canvasAsJson;
                var numberOfElementsToRetain           = indexToBeInserted+1;
                _config.canvasState                    = _config.canvasState.splice(0,numberOfElementsToRetain);
            }else{
            _config.canvasState.push(canvasAsJson);
            }
        _config.currentStateIndex = _config.canvasState.length-1;
      if((_config.currentStateIndex == _config.canvasState.length-1) && _config.currentStateIndex != -1){
        _config.redoButton.disabled= "disabled";
      }
        }
    }


    var undo = function() {
        if(_config.undoFinishedStatus){
            if(_config.currentStateIndex == -1){
            _config.undoStatus = false;
            }
            else{
            if (_config.canvasState.length >= 1) {
            _config.undoFinishedStatus = 0;
              if(_config.currentStateIndex != 0){
                    _config.undoStatus = true;
                  _canvasObject.loadFromJSON(_config.canvasState[_config.currentStateIndex-1],function(){
                                var jsonData = JSON.parse(_config.canvasState[_config.currentStateIndex-1]);
                            _canvasObject.renderAll();
                        _config.undoStatus = false;
                        _config.currentStateIndex -= 1;
                                _config.undoButton.removeAttribute("disabled");
                                if(_config.currentStateIndex !== _config.canvasState.length-1){
                                    _config.redoButton.removeAttribute('disabled');
                                }
                            _config.undoFinishedStatus = 1;
                });
              }
              else if(_config.currentStateIndex == 0){
                _canvasObject.clear();
                        _config.undoFinishedStatus = 1;
                        _config.undoButton.disabled= "disabled";
                        _config.redoButton.removeAttribute('disabled');
                _config.currentStateIndex -= 1;
              }
            }
            }
        }
    }

    var redo = function() {
        if(_config.redoFinishedStatus){
            if((_config.currentStateIndex == _config.canvasState.length-1) && _config.currentStateIndex != -1){
                _config.redoButton.disabled= "disabled";
            }else{
            if (_config.canvasState.length > _config.currentStateIndex && _config.canvasState.length != 0){
                    _config.redoFinishedStatus = 0;
                _config.redoStatus = true;
              _canvasObject.loadFromJSON(_config.canvasState[_config.currentStateIndex+1],function(){
                            var jsonData = JSON.parse(_config.canvasState[_config.currentStateIndex+1]);
                        _canvasObject.renderAll();
                        _config.redoStatus = false;
                    _config.currentStateIndex += 1;
                            if(_config.currentStateIndex != -1){
                                _config.undoButton.removeAttribute('disabled');
                            }
                        _config.redoFinishedStatus = 1;
            if((_config.currentStateIndex == _config.canvasState.length-1) && _config.currentStateIndex != -1){
              _config.redoButton.disabled= "disabled";
            }
              });
            }
            }
        }
    }


    return {
        addObject  : addObject,
        undoButton : _config.undoButton,
        redoButton : _config.redoButton,
        undo       : undo,
        redo       : redo,
  }


  })();



  canvasDemo.undoButton.addEventListener('click',function(){
        canvasDemo.undo();
    });

    canvasDemo.redoButton.addEventListener('click',function(){
        canvasDemo.redo();
    });
  canvasDemo.addObject();

Ответ 6

Я знаю, что ответ уже выбран, но вот моя версия, script сжимается, также добавлено reset в исходное состояние. После любого события, которое вы хотите сохранить, просто вызовите saveState(); jsFiddle

    canvas = new fabric.Canvas('canvas', {
        selection: false
    });
function saveState(currentAction) {
    currentAction = currentAction || '';
    // if (currentAction !== '' && lastAction !== currentAction) {
        $(".redo").val($(".undo").val());
        $(".undo").val(JSON.stringify(canvas));
        console.log("Saving After " + currentAction);
        lastAction = currentAction;
    // }
    var objects = canvas.getObjects();
    for (i in objects) {
        if (objects.hasOwnProperty(i)) {
            objects[i].setCoords();
        }
    }
}
canvas.on('object:modified', function (e) {
   saveState("modified");
});
// Undo Canvas Change
function undo() {
    canvas.loadFromJSON($(".redo").val(), canvas.renderAll.bind(canvas));
}
// Redo Canvas Change
function redo() {
    canvas.loadFromJSON($(".undo").val(), canvas.renderAll.bind(canvas));
};
$("#reset").click(function () {
    canvas.loadFromJSON($("#original_canvas").val(),canvas.renderAll.bind(canvas));
});

var bgnd = new fabric.Image.fromURL('https://s3-eu-west-1.amazonaws.com/kienzle.dev.cors/img/image2.png', function(oImg){
    oImg.hasBorders = false;
    oImg.hasControls = false;
    // ... Modify other attributes
    canvas.insertAt(oImg,0);
    canvas.setActiveObject(oImg);
    myImg = canvas.getActiveObject();
    saveState("render");
    $("#original_canvas").val(JSON.stringify(canvas.toJSON()));
});

$("#undoButton").click(function () {
    undo();
});
$("#redoButton").click(function () {
    redo();
});

Ответ 7

Мой вариант использования заключался в рисовании простых фигур, похожих на чертежи, поэтому мне не пришлось беспокоиться о накладных расходах на сохранение всего состояния холста. Если вы находитесь в такой же ситуации, это очень легко сделать. В этом коде предполагается, что у вас есть div-обертка вокруг холста, и что вы хотите, чтобы функциональные возможности отмены/возврата были привязаны к стандартным сочетаниям клавиш Windows: "CTRL + Z" и "CTRL + Y".

Цель переменной 'pause_saving' состояла в том, чтобы учесть тот факт, что при повторной визуализации холста он, казалось бы, создавал каждый объект один за другим снова, и мы не хотим перехватывать эти события, так как они не ДЕЙСТВИТЕЛЬНО новые события.

//variables for undo/redo
let pause_saving = false;
let undo_stack = []
let redo_stack = []

canvas.on('object:added', function(event){
    if (!pause_saving) {
        undo_stack.push(JSON.stringify(canvas));
        redo_stack = [];
        console.log('Object added, state saved', undo_stack);
    }

});
canvas.on('object:modified', function(event){
    if (!pause_saving) {
        undo_stack.push(JSON.stringify(canvas));
        redo_stack = [];
        console.log('Object modified, state saved', undo_stack);
    }
});
canvas.on('object:removed', function(event){
    if (!pause_saving) {
        undo_stack.push(JSON.stringify(canvas));
        redo_stack = [];
        console.log('Object removed, state saved', undo_stack);
    }
});

//Listen for undo/redo 
wrapper.addEventListener('keydown', function(event){
    //Undo - CTRL+Z
    if (event.ctrlKey && event.keyCode == 90) {
        pause_saving=true;
        redo_stack.push(undo_stack.pop());
        let previous_state = undo_stack[undo_stack.length-1];
        if (previous_state == null) {
            previous_state = '{}';
        }
        canvas.loadFromJSON(previous_state,function(){
            canvas.renderAll();
        })
        pause_saving=false;
    }
    //Redo - CTRL+Y
    else if (event.ctrlKey && event.keyCode == 89) {
        pause_saving=true;
        state = redo_stack.pop();
        if (state != null) {
            undo_stack.push(state);
            canvas.loadFromJSON(state,function(){
                canvas.renderAll();
            })
            pause_saving=false;
        }
    }
});

Ответ 8

Я разработал для вас небольшой script, надеюсь, что это вам поможет. Посмотрите эту демонстрацию Fiddle хотя повтор не идеален, вам нужно нажать минимум два раза на кнопку отмены, а затем выполнить повторную работу. Вы можете легко решить эту проблему, предоставив простые условия для повторного кода. //Html

<canvas id="c" width="400" height="200" style=" border-radius:25px 25px 25px 25px"></canvas>
  <br>
   <br>
 <input type="button" id="addtext" value="Add Text"/>
<input type="button" id="undo" value="Undo"/>
<input type="button" id="redo" value="redo"/>
<input type="button" id="clear" value="Clear Canvas"/>   

//script

  var canvas = new fabric.Canvas('c');
    var text = new fabric.Text('Sample', {
   fontFamily: 'Hoefler Text',
    left: 50,
   top: 30,
    //textAlign: 'center',
    fill: 'navy',

});
canvas.add(text);
var vall=10;    
var l=0;
var flag=0;
var k=1;
var yourJSONString = new Array();
canvas.observe('object:selected', function(e) {
    //yourJSONString = JSON.stringify(canvas);
    if(k!=10)
{   
yourJSONString[k] = JSON.stringify(canvas);
k++;
}
j = k;
    var activeObject = canvas.getActiveObject();
    });
$("#undo").click(function(){
     if(k-1!=0)
     {
     canvas.clear();
     canvas.loadFromJSON(yourJSONString[k-1]);
     k--;
     l++;
     } 
  canvas.renderAll();   
 });

$("#redo").click(function(){
    if(l > 1)
     {  
      canvas.clear();
      canvas.loadFromJSON(yourJSONString[k+1]);
       k++;
       l--;
      canvas.renderAll();   
     }
 });

$("#clear").click(function(){
    canvas.clear();
  });
 $("#addtext").click(function(){
var text = new fabric.Text('Sample', {
   fontFamily: 'Hoefler Text',
    left: 100,
    top: 100,
    //textAlign: 'center',
    fill: 'navy',

});
canvas.add(text);
}); 

Ответ 9

У меня есть ответ на все ваши вопросы:) Получите улыбку проверьте эту ссылку.. все это сделано... скопируйте и вставьте: P http://jsfiddle.net/SpgGV/27/

var canvas = new fabric.Canvas('c');
var current;
var list = [];
var state = [];
var index = 0;
var index2 = 0;
var action = false;
var refresh = true;
state[0] = JSON.stringify(canvas.toDatalessJSON());
console.log(JSON.stringify(canvas.toDatalessJSON()));
$("#clear").click(function(){
canvas.clear();
index=0;
});
$("#addtext").click(function(){
++index;
action=true;  
var text = new fabric.Text('Sample', {
fontFamily: 'Hoefler Text',
left: 100,
top: 100,
//textAlign: 'center',
fill: 'navy',

});
canvas.add(text);   
}); 
canvas.on("object:added", function (e) {

if(action===true){

var object = e.target;
console.log(JSON.stringify(canvas.toDatalessJSON()));
state[index] = JSON.stringify(canvas.toDatalessJSON());
refresh = true;
action=false;
canvas.renderAll();
}
});
function undo() {

if (index < 0) {
index = 0;
canvas.loadFromJSON(state[index]);
canvas.renderAll();
return;
}


console.log('undo');

canvas.loadFromJSON(state[index]);

console.log(JSON.stringify(canvas.toDatalessJSON()));
canvas.renderAll();
action = false;
}

function redo() {

action = false;
if (index >= state.length - 1) {
canvas.loadFromJSON(state[index]);
canvas.renderAll();
return;
}

console.log('redo');

canvas.loadFromJSON(state[index]);

console.log(JSON.stringify(canvas.toDatalessJSON()));
canvas.renderAll();

canvas.renderAll();

}

canvas.on("object:modified", function (e) {
var object = e.target;
console.log('object:modified');
console.log(JSON.stringify(canvas.toDatalessJSON()));
state[++index] = JSON.stringify(canvas.toDatalessJSON());
action=false;
});

$('#undo').click(function () {
index--;
undo();
});
$('#redo').click(function () {
index++;
redo();
});