// Resize the canvas
function resizeCanvas() {
  canvas.discardActiveObject();
  canvas.setHeight($('#canvas-area').height());
  canvas.setWidth($('#canvas-area').width());
  artboard.set({
    left: canvas.get('width') / 2 - artboard.get('width') / 2,
    top: canvas.get('height') / 2 - artboard.get('height') / 2,
  });
  canvas.renderAll();
  animate(false, currenttime);
  initLines();
}
window.addEventListener('resize', resizeCanvas, false);
resizeCanvas();

// Highlight layers when selecting objects in the canvas, scroll into view if needed
function updateSelection(e) {
  if (e.target.type == 'activeSelection') {
    $('.layer-selected').removeClass('layer-selected');
    canvas.getActiveObjects().forEach(function (object) {
      if (
        $('.layer').length > 0 &&
        $(".layer[data-object='" + object.get('id') + "']").length > 0
      ) {
        $(".layer[data-object='" + object.get('id') + "']").addClass(
          'layer-selected'
        );
        if (e.e != undefined) {
          document
            .getElementsByClassName('layer-selected')[0]
            .scrollIntoView();
        }
      }
    });
  } else {
    if (
      $('.layer').length > 0 &&
      $(".layer[data-object='" + e.target.get('id') + "']").length > 0
    ) {
      $('.layer-selected').removeClass('layer-selected');
      $(".layer[data-object='" + e.target.get('id') + "']").addClass(
        'layer-selected'
      );
      if (e.e != undefined) {
        document
          .getElementsByClassName('layer-selected')[0]
          .scrollIntoView();
      }
    }
  }
}

// Object has been modified, automatically add a keyframe
function autoKeyframe(object, e, multi) {
  if (e.action == 'drag') {
    newKeyframe(
      'left',
      object,
      currenttime,
      object.get('left'),
      true
    );
    newKeyframe('top', object, currenttime, object.get('top'), true);
  } else if (e.action == 'scale') {
    newKeyframe(
      'scaleX',
      object,
      currenttime,
      object.get('scaleX'),
      true
    );
    newKeyframe(
      'scaleY',
      object,
      currenttime,
      object.get('scaleY'),
      true
    );
    newKeyframe(
      'left',
      object,
      currenttime,
      object.get('left'),
      true
    );
    newKeyframe('top', object, currenttime, object.get('top'), true);
    newKeyframe(
      'width',
      object,
      currenttime,
      object.get('width'),
      true
    );
    newKeyframe(
      'height',
      object,
      currenttime,
      object.get('height'),
      true
    );
  } else if (e.action == 'rotate') {
    newKeyframe(
      'angle',
      object,
      currenttime,
      object.get('angle'),
      true
    );
    if (multi) {
      newKeyframe(
        'scaleX',
        object,
        currenttime,
        object.get('scaleX'),
        true
      );
      newKeyframe(
        'scaleY',
        object,
        currenttime,
        object.get('scaleY'),
        true
      );
      newKeyframe(
        'width',
        object,
        currenttime,
        object.get('width'),
        true
      );
      newKeyframe(
        'left',
        object,
        currenttime,
        object.get('left'),
        true
      );
      newKeyframe(
        'top',
        object,
        currenttime,
        object.get('top'),
        true
      );
    }
  } else if (
    e.action == 'resizing' ||
    e.action == 'scaleX' ||
    e.action == 'scaleY'
  ) {
    newKeyframe(
      'scaleX',
      object,
      currenttime,
      object.get('scaleX'),
      true
    );
    newKeyframe(
      'scaleY',
      object,
      currenttime,
      object.get('scaleY'),
      true
    );
    newKeyframe(
      'left',
      object,
      currenttime,
      object.get('left'),
      true
    );
    newKeyframe(
      'width',
      object,
      currenttime,
      object.get('width'),
      true
    );
    newKeyframe('top', object, currenttime, object.get('top'), true);
    newKeyframe(
      'height',
      object,
      currenttime,
      object.get('height'),
      true
    );
  }
}

// Reselect
function reselect(selection) {
  tempselection = false;
  if (selection.type == 'activeSelection') {
    var objs = [];
    for (let so of selection._objects) {
      for (let obj of canvas.getObjects()) {
        if (obj.get('id') === so.get('id')) {
          objs.push(obj);
          break;
        }
      }
    }
    canvas.setActiveObject(
      new fabric.ActiveSelection(objs, {
        canvas: canvas,
      })
    );
    canvas.renderAll();
  } else {
    if (selection.get('type') == 'group') {
      canvas.setActiveObject(canvas.getItemById(selection.get('id')));
    } else {
      canvas.setActiveObject(selection);
    }
    canvas.renderAll();
  }
}

// Group objects
function group() {
  var objects = canvas.getActiveObjects();
  var object_ids = [];
  var newgroup = new fabric.Group();
  objects.forEach(function (object) {
    newgroup.addWithUpdate(object);
    object.set({ inGroup: true });
    $(".layer[data-object='" + object.get('id') + "']").remove();
    object_ids.push(object.get('id'));
    $('#' + object.get('id')).remove();
    canvas.remove(object);
  });
  //var newgroup = canvas.getActiveObject().toGroup();
  newgroup.set({
    id: 'Group' + layer_count,
    objectCaching: false,
    isGroup: true,
    color: '#F1890E',
    type: 'group',
    stroke: '#000',
    strokeUniform: true,
    strokeWidth: 0,
    paintFirst: 'stroke',
    absolutePositioned: true,
    inGroup: false,
    strokeDashArray: false,
    objectCaching: true,
    shadow: {
      color: 'black',
      offsetX: 0,
      offsetY: 0,
      blur: 0,
      opacity: 0,
    },
  });
  groups.push({ id: newgroup.get('id'), objects: object_ids });
  canvas.renderAll();
  newLayer(newgroup);
  canvas.setActiveObject(newgroup);
  keyframes.sort(function (a, b) {
    if (a.id.indexOf('Group') >= 0 && b.id.indexOf('Group') == -1) {
      return 1;
    } else if (
      b.id.indexOf('Group') >= 0 &&
      a.id.indexOf('Group') == -1
    ) {
      return -1;
    } else {
      return 0;
    }
  });
  save();
}
$(document).on('click', '#group-objects', group);

// Ungroup SVG
function unGroup(group) {
  canvas.discardActiveObject();
  canvas.renderAll();
  tempgroup = group._objects;
  group._restoreObjectsState();
  $(".layer[data-object='" + group.get('id') + "']").remove();
  $('#' + group.get('id')).remove();
  canvas.remove(group);
  keyframes = $.grep(keyframes, function (e) {
    return e.id != group.get('id');
  });
  p_keyframes = $.grep(p_keyframes, function (e) {
    return e.id != group.get('id');
  });
  objects = $.grep(objects, function (e) {
    return e.id != group.get('id');
  });
  canvas.renderAll();
  for (var i = 0; i < tempgroup.length; i++) {
    if (tempgroup[i].inGroup) {
      canvas.add(tempgroup[i]);
      renderLayer(tempgroup[i]);
      props.forEach(function (prop) {
        if (
          prop != 'top' &&
          prop != 'scaleY' &&
          prop != 'width' &&
          prop != 'height' &&
          prop != 'shadow.offsetX' &&
          prop != 'shadow.offsetY' &&
          prop != 'shadow.opacity' &&
          prop != 'shadow.blur' &&
          prop != 'lineHeight'
        ) {
          renderProp(prop, tempgroup[i]);
        }
      });
      const keyarr = $.grep(keyframes, function (e) {
        return e.id == tempgroup[i].id;
      });
      keyarr.forEach(function (keyframe) {
        if (
          keyframe.name != 'top' &&
          keyframe.name != 'scaleY' &&
          keyframe.name != 'width' &&
          keyframe.name != 'height' &&
          keyframe.name != 'shadow.offsetX' &&
          keyframe.name != 'shadow.offsetY' &&
          keyframe.name != 'shadow.opacity' &&
          keyframe.name != 'shadow.blur' &&
          keyframe.name != 'lineHeight'
        ) {
          renderKeyframe(
            canvas.getItemById(keyframe.id),
            keyframe.name,
            keyframe.t
          );
        }
      });
    }
  }
  canvas.renderAll();
  save();
}
$(document).on('click', '#ungroup-objects', function () {
  if (canvas.getActiveObject()) {
    unGroup(canvas.getActiveObject());
  }
});

// Regroup SVG
function reGroup(id) {
  var group = [];
  var objects = [];
  groups
    .find((x) => x.id == id)
    .objects.forEach(function (object) {
      objects.push(canvas.getItemById(object));
    });
  var activeselection = new fabric.ActiveSelection(objects);
  var newgroup = activeselection.toGroup();
  newgroup.set({
    id: id,
    objectCaching: false,
  });
  canvas.add(newgroup);
  canvas.renderAll();
}

// Keep record canvas up to date
function updateRecordCanvas() {
  canvasrecord.setWidth(artboard.width);
  canvasrecord.setHeight(artboard.height);
  canvasrecord.width = artboard.width;
  canvasrecord.height = artboard.height;
  canvas.clipPath = null;
  objects.forEach(async function (object) {
    var obj = canvas.getItemById(object.id);
    if (obj.filters) {
      if (obj.filters.length > 0) {
        object.filters = [];
        obj.filters.forEach(function (filter) {
          if (
            filter.type == 'BlackWhite' ||
            filter.type == 'Invert' ||
            filter.type == 'Sepia' ||
            filter.type == 'Kodachrome' ||
            filter.type == 'Polaroid' ||
            filter.type == 'Technicolor' ||
            filter.type == 'Brownie' ||
            filter.type == 'Vintage'
          ) {
            object.filters.push({ type: filter.type });
          } else if (filter.type == 'Brightness') {
            object.filters.push({
              type: filter.type,
              value: filter.brightness,
            });
          } else if (filter.type == 'Contrast') {
            object.filters.push({
              type: filter.type,
              value: filter.contrast,
            });
          } else if (filter.type == 'Vibrance') {
            object.filters.push({
              type: filter.type,
              value: filter.vibrance,
            });
          } else if (filter.type == 'Saturation') {
            object.filters.push({
              type: filter.type,
              value: filter.saturation,
            });
          } else if (filter.type == 'HueRotation') {
            object.filters.push({
              type: filter.type,
              value: filter.rotation,
            });
          } else if (filter.type == 'Blur') {
            object.filters.push({
              type: filter.type,
              value: filter.blur,
            });
          } else if (filter.type == 'Noise') {
            object.filters.push({
              type: filter.type,
              value: filter.noise,
            });
          } else if (filter.type == 'RemoveColor') {
            object.filters.push({
              type: filter.type,
              distance: filter.distance,
              color: filter.color,
            });
          }
        });
        obj.filters = [];
        obj.applyFilters();
        var backend = fabric.filterBackend;
        if (backend && backend.evictCachesForKey) {
          backend.evictCachesForKey(obj.cacheKey);
          backend.evictCachesForKey(obj.cacheKey + '_filtered');
        }
        if (
          obj.filters.length > 0 &&
          obj.get('id').indexOf('Video') >= 0
        ) {
          await obj.setElement(obj.saveElem);
        }
      } else {
        object.filters = [];
      }
    } else {
      object.filters = [];
    }
  });
  const canvassave = canvas.toJSON([
    'volume',
    'audioSrc',
    'defaultLeft',
    'defaultTop',
    'defaultScaleX',
    'defaultScaleY',
    'notnew',
    'starttime',
    'top',
    'left',
    'width',
    'height',
    'scaleX',
    'scaleY',
    'flipX',
    'flipY',
    'originX',
    'originY',
    'transformMatrix',
    'stroke',
    'strokeWidth',
    'strokeDashArray',
    'strokeLineCap',
    'strokeDashOffset',
    'strokeLineJoin',
    'strokeMiterLimit',
    'angle',
    'opacity',
    'fill',
    'globalCompositeOperation',
    'shadow',
    'clipTo',
    'visible',
    'backgroundColor',
    'skewX',
    'skewY',
    'fillRule',
    'paintFirst',
    'clipPath',
    'strokeUniform',
    'rx',
    'ry',
    'selectable',
    'hasControls',
    'subTargetCheck',
    'id',
    'hoverCursor',
    'defaultCursor',
    'isEditing',
    'source',
    'assetType',
    'duration',
    'inGroup',
  ]);
  canvas.clipPath = artboard;
  canvasrecord.loadFromJSON(canvassave, function () {
    if (canvasrecord.getItemById('center_h')) {
      canvasrecord.remove(canvasrecord.getItemById('center_h'));
      canvasrecord.remove(canvasrecord.getItemById('center_v'));
    }
    if (canvasrecord.getItemById('line_h')) {
      canvasrecord.remove(canvasrecord.getItemById('line_h'));
      canvasrecord.remove(canvasrecord.getItemById('line_v'));
    }
    canvasrecord.renderAll();
    canvasrecord.setWidth(artboard.width);
    canvasrecord.setHeight(artboard.height);
    canvasrecord.width = artboard.width;
    canvasrecord.height = artboard.height;
    canvasrecord.renderAll();
    objects.forEach(function (object) {
      replaceSource(
        canvasrecord.getItemById(object.id),
        canvasrecord
      );
      replaceSource(canvas.getItemById(object.id), canvas);
    });
  });
}

// Download recording
function downloadRecording(chunks) {
  $('#download-real').html('Downloading...');
  if ($('input[name=radio]:checked').val() == 'webm') {
    var url = URL.createObjectURL(
      new Blob(chunks, {
        type: 'video/webm',
      })
    );
    const a = document.createElement('a');
    a.style.display = 'none';
    a.href = url;
    a.download = name;
    document.body.appendChild(a);
    a.click();
    recording = false;
    currenttime = 0;
    animate(false, 0);
    $('#seekbar').offset({
      left:
        offset_left +
        $('#inner-timeline').offset().left +
        currenttime / timelinetime,
    });
    canvas.renderAll();
    resizeCanvas();
    $('#download-real').html('Download');
    $('#download-real').removeClass('downloading');
    updateRecordCanvas();
  } else if ($('input[name=radio]:checked').val() == 'mp4') {
    type = 'video/mp4';
  } else {
    convertStreams(new Blob(chunks, { type: 'video/webm' }), 'gif');
  }
}

$('#download-real').on('click', record);

// Save everything in the canvas (for undo/redo/autosave)
function save() {
  redo = [];
  redoarr = [];
  if (state) {
    undo.push(state);
    undoarr.push(statearr);
  }
  canvas.clipPath = null;
  state = canvas.toJSON([
    'volume',
    'audioSrc',
    'top',
    'left',
    'width',
    'height',
    'scaleX',
    'scaleY',
    'flipX',
    'flipY',
    'originX',
    'originY',
    'transformMatrix',
    'stroke',
    'strokeWidth',
    'strokeDashArray',
    'strokeLineCap',
    'strokeDashOffset',
    'strokeLineJoin',
    'strokeMiterLimit',
    'angle',
    'opacity',
    'fill',
    'globalCompositeOperation',
    'shadow',
    'clipTo',
    'visible',
    'backgroundColor',
    'skewX',
    'skewY',
    'fillRule',
    'paintFirst',
    'clipPath',
    'strokeUniform',
    'rx',
    'ry',
    'selectable',
    'hasControls',
    'subTargetCheck',
    'id',
    'hoverCursor',
    'defaultCursor',
    'isEditing',
    'source',
    'assetType',
    'duration',
    'inGroup',
    'filters',
  ]);
  canvas.clipPath = artboard;
  statearr = {
    keyframes: JSON.parse(JSON.stringify(keyframes)),
    p_keyframes: JSON.parse(JSON.stringify(p_keyframes)),
    objects: JSON.parse(JSON.stringify(objects)),
    colormode: colormode,
    duration: duration,
    currenttime: currenttime,
  };
  if (undo.length >= 1) {
    $('#undo').addClass('history-active');
  } else {
    $('#undo').removeClass('history-active');
  }
  if (redo.length >= 1) {
    $('#redo').addClass('history-active');
  } else {
    $('#redo').removeClass('history-active');
  }

  updateRecordCanvas();
  autoSave();
}

// Duplicate object
function copyObject() {
  if (clipboard) {
    if (cliptype == 'object') {
      if (clipboard.type == 'activeSelection') {
        clipboard._objects.forEach(function (clone) {
          clone.clone(function (cloned) {
            cloned.set({
              id: 'Shape' + layer_count,
            });
            canvas.add(cloned);
            canvas.renderAll();
            newLayer(cloned);
            canvas.setActiveObject(cloned);
          });
        });
      } else {
        clipboard.clone(function (cloned) {
          cloned.set({
            id: 'Shape' + layer_count,
          });
          canvas.add(cloned);
          canvas.renderAll();
          newLayer(cloned);
          canvas.setActiveObject(cloned);
        });
      }
      save();
    } else {
      copyKeyframes();
    }
  }
}

// Replace the source of an object when reloading the canvas (since Fabric needs a DOM reference for the objects)
function replaceSource(object, canvas) {
  if (object == null) {
    return false;
  }
  if (object.get('type') != 'group') {
    if (object.type) {
      if (object.type == 'image') {
        if (object.get('id').indexOf('Video') >= 0) {
          var vidObj = document.createElement('video');
          var vidSrc = document.createElement('source');
          vidSrc.src = object.get('source');
          vidObj.crossOrigin = 'anonymous';
          vidObj.appendChild(vidSrc);
          vidObj.addEventListener('loadeddata', function () {
            vidObj.width = this.videoWidth;
            vidObj.height = this.videoHeight;
            vidObj.currentTime = 0;
            vidObj.muted = false;
            async function waitLoad() {
              if (vidObj.readyState >= 3) {
                object.setElement(vidObj);
                object.saveElem = vidObj;
                await canvas.renderAll();
                await animate(false, currenttime);
                if (
                  objects.find((x) => x.id == object.get('id'))
                    .filters
                ) {
                  if (
                    objects.find((x) => x.id == object.get('id'))
                      .filters.length > 0
                  ) {
                    objects
                      .find((x) => x.id == object.get('id'))
                      .filters.forEach(function (filter) {
                        if (filter.type == 'Sepia') {
                          object.filters.push(new f.Sepia());
                        } else if (filter.type == 'Invert') {
                          object.filters.push(new f.Invert());
                        } else if (filter.type == 'BlackWhite') {
                          object.filters.push(new f.BlackWhite());
                        } else if (filter.type == 'Kodachrome') {
                          object.filters.push(new f.Kodachrome());
                        } else if (filter.type == 'Polaroid') {
                          object.filters.push(new f.Polaroid());
                        } else if (filter.type == 'Technicolor') {
                          object.filters.push(new f.Technicolor());
                        } else if (filter.type == 'Vintage') {
                          object.filters.push(new f.Vintage());
                        } else if (filter.type == 'Brownie') {
                          object.filters.push(new f.Brownie());
                        } else if (filter.type == 'Brightness') {
                          object.filters.push(
                            new f.Brightness({
                              brightness: filter.value,
                            })
                          );
                        } else if (filter.type == 'Contrast') {
                          object.filters.push(
                            new f.Contrast({ contrast: filter.value })
                          );
                        } else if (filter.type == 'Saturation') {
                          object.filters.push(
                            new f.Saturation({
                              saturation: filter.value,
                            })
                          );
                        } else if (filter.type == 'Vibrance') {
                          object.filters.push(
                            new f.Vibrance({ vibrance: filter.value })
                          );
                        } else if (filter.type == 'HueRotation') {
                          object.filters.push(
                            new f.HueRotation({
                              rotation: filter.value,
                            })
                          );
                        } else if (filter.type == 'Noise') {
                          object.filters.push(
                            new f.Noise({ noise: filter.value })
                          );
                        } else if (filter.type == 'Blur') {
                          object.filters.push(
                            new f.Blur({ blur: filter.value })
                          );
                        } else if (filter.type == 'RemoveColor') {
                          object.filters.push(
                            new f.RemoveColor({
                              distance: filter.distance,
                              color: filter.color,
                            })
                          );
                        }
                      });
                    object.applyFilters();
                    canvas.renderAll();
                  }
                }
              } else {
                window.setTimeout(function () {
                  waitLoad();
                }, 100);
              }
            }
            window.setTimeout(function () {
              waitLoad();
            }, 100);
          });
          vidObj.currentTime = 0;
        } else {
          //var img = new Image();
          //img.onload = function(){
          //   object.setElement(img);
          //    canvas.renderAll();
          if (objects.find((x) => x.id == object.get('id')).filters) {
            if (
              objects.find((x) => x.id == object.get('id')).filters
                .length > 0
            ) {
              objects
                .find((x) => x.id == object.get('id'))
                .filters.forEach(function (filter) {
                  if (filter.type == 'Sepia') {
                    object.filters.push(new f.Sepia());
                  } else if (filter.type == 'Invert') {
                    object.filters.push(new f.Invert());
                  } else if (filter.type == 'BlackWhite') {
                    object.filters.push(new f.BlackWhite());
                  } else if (filter.type == 'Kodachrome') {
                    object.filters.push(new f.Kodachrome());
                  } else if (filter.type == 'Polaroid') {
                    object.filters.push(new f.Polaroid());
                  } else if (filter.type == 'Technicolor') {
                    object.filters.push(new f.Technicolor());
                  } else if (filter.type == 'Vintage') {
                    object.filters.push(new f.Vintage());
                  } else if (filter.type == 'Brownie') {
                    object.filters.push(new f.Brownie());
                  } else if (filter.type == 'Brightness') {
                    object.filters.push(
                      new f.Brightness({ brightness: filter.value })
                    );
                  } else if (filter.type == 'Contrast') {
                    object.filters.push(
                      new f.Contrast({ contrast: filter.value })
                    );
                  } else if (filter.type == 'Saturation') {
                    object.filters.push(
                      new f.Saturation({ saturation: filter.value })
                    );
                  } else if (filter.type == 'Vibrance') {
                    object.filters.push(
                      new f.Vibrance({ vibrance: filter.value })
                    );
                  } else if (filter.type == 'HueRotation') {
                    object.filters.push(
                      new f.HueRotation({ rotation: filter.value })
                    );
                  } else if (filter.type == 'Noise') {
                    object.filters.push(
                      new f.Noise({ noise: filter.value })
                    );
                  } else if (filter.type == 'Blur') {
                    object.filters.push(
                      new f.Blur({ blur: filter.value })
                    );
                  } else if (filter.type == 'RemoveColor') {
                    object.filters.push(
                      new f.RemoveColor({
                        distance: filter.distance,
                        color: filter.color,
                      })
                    );
                  }
                });
              object.applyFilters();
              canvas.renderAll();
            } else {
              object.filters = [];
              object.applyFilters();
              canvas.renderAll();
            }
          }

          //}
          //img.src = window.URL.createObjectURL(new Blob(files.find(x => x.name == object.get("id")).file, {type:"image/png"}));
        }
      }
    }
  }
}

// Perform undo/redo
function undoRedo(newState, saveState, newArrState, saveArrState) {
  saveState.push(state);
  saveArrState.push(statearr);
  statearr = newArrState.pop();
  state = newState.pop();
  keyframes = statearr.keyframes;
  p_keyframes = statearr.p_keyframes;
  objects = statearr.objects;
  colormode = statearr.colormode;
  duration = statearr.duration;
  currenttime = statearr.currenttime;
  canvas.clipPath = null;
  canvas.loadFromJSON(state, function () {
    canvas.clipPath = artboard;
    canvas.getItemById('line_h').set({ opacity: 0 });
    canvas.getItemById('line_v').set({ opacity: 0 });
    canvas.renderAll();
    $('.object-props').remove();
    $('.layer').remove();
    objects.forEach(function (object) {
      replaceSource(canvas.getItemById(object.id), canvas);
      renderLayer(canvas.getItemById(object.id));
      props.forEach(function (prop) {
        if (
          prop != 'top' &&
          prop != 'scaleY' &&
          prop != 'width' &&
          prop != 'height' &&
          prop != 'shadow.offsetX' &&
          prop != 'shadow.offsetY' &&
          prop != 'shadow.opacity' &&
          prop != 'shadow.blur' &&
          prop != 'lineHeight'
        ) {
          renderProp(prop, canvas.getItemById(object.id));
        }
      });
    });
    keyframes.forEach(function (keyframe) {
      if (
        keyframe.name != 'top' &&
        keyframe.name != 'scaleY' &&
        keyframe.name != 'width' &&
        keyframe.name != 'height' &&
        keyframe.name != 'shadow.offsetX' &&
        keyframe.name != 'shadow.offsetY' &&
        keyframe.name != 'shadow.opacity' &&
        keyframe.name != 'shadow.blur' &&
        keyframe.name != 'lineHeight'
      ) {
        renderKeyframe(
          canvas.getItemById(keyframe.id),
          keyframe.name,
          keyframe.t
        );
      }
    });
    animate(false, currenttime);
  });
  if (undo.length >= 1) {
    $('#undo').addClass('history-active');
  } else {
    $('#undo').removeClass('history-active');
  }
  if (redo.length >= 1) {
    $('#redo').addClass('history-active');
  } else {
    $('#redo').removeClass('history-active');
  }
}

// Undo/redo buttons
$(document).on('click', '#undo', function () {
  if (undo.length >= 1) {
    undoRedo(undo, redo, undoarr, redoarr);
  }
});
$(document).on('click', '#redo', function () {
  if (undo.length >= 1) {
    undoRedo(redo, undo, redoarr, undoarr);
  }
});

// Generate keyframes
function keyframeChanges(object, type, id, selection) {
  if (object.get('type') == 'rect') {
    object.set({
      rx: parseFloat($('#object-corners input').val()),
      ry: parseFloat($('#object-corners input').val()),
    });
  } else if (object.get('type') == 'textbox') {
    object.set({
      charSpacing: parseFloat($('#text-h input').val()) * 10,
      lineHeight: parseFloat($('#text-v input').val() / 100),
    });
    canvas.renderAll();
  }
  canvas.renderAll();
  if (type && type == 'opacity') {
    newKeyframe(
      'opacity',
      object,
      currenttime,
      object.get('opacity'),
      true
    );
  } else if (type && type == 'opacity3') {
    newKeyframe(
      'charSpacing',
      object,
      currenttime,
      object.get('charSpacing'),
      true
    );
    newKeyframe(
      'lineHeight',
      object,
      currenttime,
      object.get('lineHeight'),
      true
    );
  } else {
    if (id == 'object-x' || id == 'object-y') {
      newKeyframe(
        'left',
        object,
        currenttime,
        object.get('left'),
        true
      );
      newKeyframe(
        'top',
        object,
        currenttime,
        object.get('top'),
        true
      );
    } else if (id == 'object-w' || id == 'object-h') {
      newKeyframe(
        'scaleX',
        object,
        currenttime,
        object.get('scaleX'),
        true
      );
      newKeyframe(
        'scaleY',
        object,
        currenttime,
        object.get('scaleY'),
        true
      );
      newKeyframe(
        'width',
        object,
        currenttime,
        object.get('width'),
        true
      );
      newKeyframe(
        'height',
        object,
        currenttime,
        object.get('width'),
        true
      );
      if (selection) {
        newKeyframe(
          'left',
          object,
          currenttime,
          object.get('left'),
          true
        );
        newKeyframe(
          'top',
          object,
          currenttime,
          object.get('top'),
          true
        );
      }
    } else if (id == 'object-r') {
      newKeyframe(
        'angle',
        object,
        currenttime,
        object.get('angle'),
        true
      );
      if (selection) {
        newKeyframe(
          'left',
          object,
          currenttime,
          object.get('left'),
          true
        );
        newKeyframe(
          'top',
          object,
          currenttime,
          object.get('top'),
          true
        );
        newKeyframe(
          'scaleX',
          object,
          currenttime,
          object.get('scaleX'),
          true
        );
        newKeyframe(
          'scaleY',
          object,
          currenttime,
          object.get('scaleY'),
          true
        );
        newKeyframe(
          'width',
          object,
          currenttime,
          object.get('width'),
          true
        );
        newKeyframe(
          'height',
          object,
          currenttime,
          object.get('width'),
          true
        );
      }
    } else if (id == 'object-stroke') {
      newKeyframe(
        'strokeWidth',
        object,
        currenttime,
        object.get('strokeWidth'),
        true
      );
      newKeyframe(
        'stroke',
        object,
        currenttime,
        object.get('stroke'),
        true
      );
    } else if (
      id == 'object-shadow-x' ||
      id == 'object-shadow-y' ||
      id == 'object-blur' ||
      id == 'object-color-stroke-opacity'
    ) {
      newKeyframe(
        'shadow.color',
        object,
        currenttime,
        object.shadow.color,
        true
      );
      newKeyframe(
        'shadow.opacity',
        object,
        currenttime,
        object.shadow.opacity,
        true
      );
      newKeyframe(
        'shadow.offsetX',
        object,
        currenttime,
        object.shadow.offsetX,
        true
      );
      newKeyframe(
        'shadow.offsetY',
        object,
        currenttime,
        object.shadow.offsetY,
        true
      );
      newKeyframe(
        'shadow.blur',
        object,
        currenttime,
        object.shadow.blur,
        true
      );
    }
  }
  save();
}

// Play video
function play() {
  paused = false;
  animate(true, currenttime);
  $('#play-button').attr('src', 'assets/pause-button.svg');
}

// Pause video
function pause() {
  paused = true;
  $('#play-button').attr('src', 'assets/play-button.svg');
}

// Set object value (while animating)
function setObjectValue(prop, object, value, inst) {
  if (object.get('type') != 'group') {
    if (object.group) {
      var group = object.group;
      tempgroup = group._objects;
      group._restoreObjectsState();
      canvas.setActiveObject(group);
      inst.remove(canvas.getActiveObject());
      canvas.discardActiveObject();
      inst.renderAll();
      for (var i = 0; i < tempgroup.length; i++) {
        inst.add(tempgroup[i]);
      }
    }
  }
  if (prop == 'left' && !recording) {
    object.set(prop, value + artboard.get('left'));
  } else if (prop == 'top' && !recording) {
    object.set(prop, value + artboard.get('top'));
  } else if (prop == 'shadow.blur') {
    object.shadow.blur = value;
  } else if (prop == 'shadow.color') {
    object.shadow.color = value;
  } else if (prop == 'shadow.offsetX') {
    object.shadow.offsetX = value;
  } else if (prop == 'shadow.offsetY') {
    object.shadow.offsetY = value;
  } else if (prop == 'shadow.blur') {
    object.shadow.blur = value;
  } else if (object.get('type') != 'group') {
    object.set(prop, value);
  } else if (prop != 'width') {
    object.set(prop, value);
  }
  inst.renderAll();
}

// Find last keyframe in time from same object & property
function lastKeyframe(keyframe, index) {
  var temparr = keyframes.slice();
  temparr.sort(function (a, b) {
    return a.t - b.t;
  });
  temparr.length = temparr.findIndex((x) => x === keyframe);
  temparr.reverse();
  if (temparr.length == 0) {
    return false;
  } else {
    for (var i = 0; i < temparr.length; i++) {
      if (
        temparr[i].id == keyframe.id &&
        temparr[i].name == keyframe.name
      ) {
        return temparr[i];
        break;
      } else if (i == temparr.length - 1) {
        return false;
      }
    }
  }
}

// Check whether any keyframe exists for a certain property
function checkAnyKeyframe(id, prop, inst) {
  const object = inst.getItemById(id);
  if (object.get('assetType') == 'audio') {
    return false;
  }
  if (
    object.get('type') != 'textbox' &&
    (prop == 'charSpacing' || prop == 'lineHeight')
  ) {
    return false;
  }
  if (
    object.get('type') == 'group' &&
    (prop == 'shadow.opacity' ||
      prop == 'shadow.color' ||
      prop == 'shadow.offsetX' ||
      prop == 'shadow.offsetY' ||
      prop == 'shadow.blur')
  ) {
    return false;
  }
  const keyarr2 = $.grep(keyframes, function (e) {
    return e.id == id && e.name == prop;
  });
  if (keyarr2.length == 0) {
    const value = objects
      .find((x) => x.id == id)
      .defaults.find((x) => x.name == prop).value;
    setObjectValue(prop, object, value, inst);
  }
}

// Check if parameter is a DOM element
function isDomElem(el) {
  return el instanceof HTMLElement || el[0] instanceof HTMLElement
    ? true
    : false;
}

// Play videos when seeking/playing
async function playVideos(time) {
  objects.forEach(async function (object) {
    var object = canvas.getItemById(object.id);
    if (object == null) {
      return false;
    }
    var inst = canvas;
    var start = false;
    if (recording) {
      object = canvasrecord.getItemById(object.id);
      inst = canvasrecord;
    }
    if (
      object.get('id').indexOf('Video') >= 0 &&
      p_keyframes.find((x) => x.id == object.id).trimstart +
        p_keyframes.find((x) => x.id == object.id).start <=
        time &&
      p_keyframes.find((x) => x.id == object.id).end >= time
    ) {
      var tempfilters = object.filters;
      if (object.filters.length > 0) {
        object.filters = [];
        object.applyFilters();
        var image = object;
        var backend = fabric.filterBackend;
        if (backend && backend.evictCachesForKey) {
          backend.evictCachesForKey(image.cacheKey);
          backend.evictCachesForKey(image.cacheKey + '_filtered');
        }
        await object.setElement(object.saveElem);
      }
      object.set('visible', true);
      inst.renderAll();
      if ($(object.getElement())[0].paused == true) {
        $(object.getElement())[0].currentTime = parseFloat(
          (
            (time -
              p_keyframes.find((x) => x.id == object.id).start +
              p_keyframes.find((x) => x.id == object.id).trimstart) /
            1000
          ).toFixed(2)
        );
      }
      if (!recording) {
        var animation = {
          value: 0,
        };
        var instance = anime({
          targets: animation,
          value: [currenttime, duration],
          delay: 0,
          duration: duration,
          easing: 'linear',
          autoplay: true,
          update: async function () {
            if (!paused && start) {
              if (object.filters.length > 0) {
                object.filters = [];
                object.applyFilters();
                var image = object;
                var backend = fabric.filterBackend;
                if (backend && backend.evictCachesForKey) {
                  backend.evictCachesForKey(image.cacheKey);
                  backend.evictCachesForKey(
                    image.cacheKey + '_filtered'
                  );
                }
                await object.setElement(object.saveElem);
              }
              if ($(object.getElement())[0].tagName == 'VIDEO') {
                $(object.getElement())[0].play();
              }
              await inst.renderAll();
              if (tempfilters.length > 0) {
                object.filters = tempfilters;
                object.applyFilters();
                inst.renderAll();
              }
            } else if (paused) {
              if (
                isDomElem($(object.getElement())[0]) &&
                $(object.getElement())[0].tagName == 'VIDEO'
              ) {
                $(object.getElement())[0].pause();
              }
              animation.value = duration + 1;
              anime.remove(animation);
            }
          },
          changeBegin: function () {
            start = true;
          },
        });
        if (paused) {
          $(object.getElement())[0].currentTime = parseFloat(
            (
              (time -
                p_keyframes.find((x) => x.id == object.id).start +
                p_keyframes.find((x) => x.id == object.id)
                  .trimstart) /
              1000
            ).toFixed(2)
          );
          await inst.renderAll();
          if (tempfilters.length > 0) {
            object.filters = tempfilters;
            object.applyFilters();
            inst.renderAll();
          }
        }
      } else {
        if ($(object.getElement())[0].paused == true) {
          $(object.getElement())[0].play();
          inst.renderAll();
        }
      }
    } else if (object.get('id').indexOf('Video') >= 0) {
      $(object.getElement())[0].pause();
      object.set('visible', false);
      inst.renderAll();
    }
  });
}

// Play background audio
function playAudio(time) {
  objects.forEach(async function (object) {
    var start = false;
    var obj = canvas.getItemById(object.id);
    if (obj.get('assetType') == 'audio') {
      var flag = false;
      var animation = {
        value: 0,
      };
      var instance = anime({
        targets: animation,
        value: [currenttime, duration],
        delay: 0,
        duration: duration,
        easing: 'linear',
        autoplay: true,
        update: async function () {
          if (start && play && !paused) {
            if (
              !flag &&
              p_keyframes.find((x) => x.id == object.id).start <=
                currenttime &&
              p_keyframes.find((x) => x.id == object.id).end >=
                currenttime
            ) {
              if (obj.get('src')) {
                obj.get('src').currentTime =
                  (p_keyframes.find((x) => x.id == object.id)
                    .trimstart -
                    p_keyframes.find((x) => x.id == object.id).start +
                    currenttime) /
                  1000;
                obj.get('src').volume = obj.get('volume');
                obj.get('src').play();
                flag = true;
              } else {
                var audio = new Audio(obj.get('audioSrc'));
                obj.set('src', audio);
                audio.volume = obj.get('volume');
                audio.crossOrigin = 'anonymous';
                audio.currentTime =
                  (p_keyframes.find((x) => x.id == object.id)
                    .trimstart -
                    p_keyframes.find((x) => x.id == object.id).start +
                    currenttime) /
                  1000;
                audio.play();
                flag = true;
              }
            } else if (
              p_keyframes.find((x) => x.id == object.id).start >=
                currenttime ||
              p_keyframes.find((x) => x.id == object.id).end <=
                currenttime
            ) {
              if (obj.get('src')) {
                obj.get('src').pause();
              }
            }
          } else if (paused) {
            if (obj.get('src')) {
              obj.get('src').pause();
              anime.remove(animation);
            }
          }
        },
        changeBegin: function () {
          start = true;
        },
      });
    }
  });
}

// Temp animate with render callback
async function recordAnimate(time) {
  anime.speed = 1;
  //return new Promise(function(resolve){
  var inst = canvasrecord;
  if (animatedtext.length > 0) {
    animatedtext.forEach(function (text) {
      text.seek(time, inst);
      inst.renderAll();
    });
  }
  keyframes.forEach(function (keyframe, index) {
    // Regroup if needed (groups break to animate their children, then regroup after children have animated)
    if (groups.find((x) => x.id == keyframe.id)) {
      if (!canvas.getItemById(keyframe.id)) {
        reGroup(keyframe.id);
      }
      const object = canvas.getItemById(keyframe.id);
      if (
        currenttime <
        p_keyframes.find((x) => x.id == keyframe.id).trimstart +
          p_keyframes.find((x) => x.id == keyframe.id).start
      ) {
        object.set('visible', false);
        inst.renderAll();
      } else if (
        currenttime >
          p_keyframes.find((x) => x.id == keyframe.id).end ||
        currenttime > duration
      ) {
        object.set('visible', false);
        inst.renderAll();
      } else {
        object.set('visible', true);
        inst.renderAll();
      }
      if (
        currenttime >=
        p_keyframes.find((x) => x.id == keyframe.id).trimstart +
          p_keyframes.find((x) => x.id == keyframe.id).start
      ) {
        props.forEach(function (prop) {
          checkAnyKeyframe(keyframe.id, prop, inst);
        });
      }
    }

    // Copy of setObjectValue function, seems to perform better inside the function
    function setValue(prop, object, value, inst) {
      if (object.get('type') != 'group') {
        if (object.group) {
          var group = object.group;
          tempgroup = group._objects;
          group._restoreObjectsState();
          canvas.setActiveObject(group);
          inst.remove(canvas.getActiveObject());
          canvas.discardActiveObject();
          inst.renderAll();
          for (var i = 0; i < tempgroup.length; i++) {
            inst.add(tempgroup[i]);
          }
        }
      }
      if (prop == 'left' && !recording) {
        object.set(prop, value + artboard.get('left'));
      } else if (prop == 'top' && !recording) {
        object.set(prop, value + artboard.get('top'));
      } else if (prop == 'shadow.blur') {
        object.shadow.blur = value;
      } else if (prop == 'shadow.color') {
        object.shadow.color = value;
      } else if (prop == 'shadow.offsetX') {
        object.shadow.offsetX = value;
      } else if (prop == 'shadow.offsetY') {
        object.shadow.offsetY = value;
      } else if (prop == 'shadow.blur') {
        object.shadow.blur = value;
      } else if (object.get('type') != 'group') {
        object.set(prop, value);
      } else if (prop != 'width') {
        object.set(prop, value);
      }
      inst.renderAll();
    }

    // Find next keyframe in time from same object & property
    function nextKeyframe(keyframe, index) {
      var temparr = keyframes.slice();
      temparr.sort(function (a, b) {
        return a.t - b.t;
      });
      temparr.splice(0, temparr.findIndex((x) => x === keyframe) + 1);
      if (temparr.length == 0) {
        return false;
      } else {
        for (var i = 0; i < temparr.length; i++) {
          if (
            temparr[i].id == keyframe.id &&
            temparr[i].name == keyframe.name
          ) {
            return temparr[i];
            break;
          } else if (i == temparr.length - 1) {
            return false;
          }
        }
      }
    }

    var object = canvasrecord.getItemById(keyframe.id);
    if (
      keyframe.t >= time &&
      currenttime >=
        p_keyframes.find((x) => x.id == keyframe.id).trimstart +
          p_keyframes.find((x) => x.id == keyframe.id).start
    ) {
      var delay = 0;
      var start = false;
      var lasttime, lastprop;
      // Find last keyframe in time from same object & property
      var lastkey = lastKeyframe(keyframe, index);
      if (!lastkey) {
        lasttime = 0;
        lastprop = objects
          .find((x) => x.id == keyframe.id)
          .defaults.find((x) => x.name == keyframe.name).value;
      } else {
        lasttime = lastkey.t;
        lastprop = lastkey.value;
      }
      if (lastkey && lastkey.t >= time && !play) {
        return;
      }

      // Initiate the animation
      var animation = {
        value: lastprop,
      };
      var instance = anime({
        targets: animation,
        delay: delay,
        value: keyframe.value,
        duration: keyframe.t - lasttime,
        easing: keyframe.easing,
        autoplay: false,
        update: function () {
          if (start && paused) {
            anime.remove(animation);
          }
        },
        changeBegin: function () {
          start = true;
        },
      });

      if (time - lasttime <= 0) {
        instance.seek(0);
      } else {
        instance.seek(time - lasttime);
      }

      if (
        parseFloat(lasttime) <= parseFloat(time) &&
        parseFloat(keyframe.t) >= parseFloat(time)
      ) {
        setValue(keyframe.name, object, animation.value, inst);
      }
    } else if (keyframe.t < time && !nextKeyframe(keyframe, index)) {
      var prop = keyframe.name;
      if (prop == 'shadow.blur') {
        if (object.shadow.blur != keyframe.value) {
          setValue(keyframe.name, object, keyframe.value, inst);
        }
      } else if (prop == 'shadow.color') {
        if (object.shadow.color != keyframe.value) {
          setValue(keyframe.name, object, keyframe.value, inst);
        }
      } else if (prop == 'shadow.offsetX') {
        if (object.shadow.offsetX != keyframe.value) {
          setValue(keyframe.name, object, keyframe.value, inst);
        }
      } else if (prop == 'shadow.offsetY') {
        if (object.shadow.offsetY != keyframe.value) {
          setValue(keyframe.name, object, keyframe.value, inst);
        }
      } else {
        if (object.get(prop) != keyframe.value) {
          setValue(keyframe.name, object, keyframe.value, inst);
        }
      }
    }
  });

  objects.forEach(function (object) {
    if (object.id.indexOf('Group') == -1) {
      const object2 = canvas.getItemById(object.id);
      if (
        currenttime <
        p_keyframes.find((x) => x.id == object.id).trimstart +
          p_keyframes.find((x) => x.id == object.id).start
      ) {
        object2.set('visible', false);
      } else if (
        currenttime >
          p_keyframes.find((x) => x.id == object.id).end ||
        currenttime > duration
      ) {
        object2.set('visible', false);
      } else {
        object2.set('visible', true);
      }
      if (
        currenttime >=
        p_keyframes.find((x) => x.id == object.id).trimstart +
          p_keyframes.find((x) => x.id == object.id).start
      ) {
        props.forEach(function (prop) {
          checkAnyKeyframe(object.id, prop, inst);
        });
      }
    }
  });
  inst.renderAll();

  playVideos(time);
  //});
}

// Animate timeline (or seek to specific point in time)
async function animate(play, time) {
  anime.speed = speed;
  if (!draggingPanel) {
    var starttime = new Date();
    var offset = time;
    var inst = canvas;
    keyframes.forEach(function (keyframe, index) {
      // Find next keyframe in time from same object & property
      function nextKeyframe(keyframe, index) {
        var temparr = keyframes.slice();
        temparr.sort(function (a, b) {
          return a.t - b.t;
        });
        temparr.splice(
          0,
          temparr.findIndex((x) => x === keyframe) + 1
        );
        if (temparr.length == 0) {
          return false;
        } else {
          for (var i = 0; i < temparr.length; i++) {
            if (
              temparr[i].id == keyframe.id &&
              temparr[i].name == keyframe.name
            ) {
              return temparr[i];
              break;
            } else if (i == temparr.length - 1) {
              return false;
            }
          }
        }
      }
      // Regroup if needed (groups break to animate their children, then regroup after children have animated)
      if (groups.find((x) => x.id == keyframe.id)) {
        if (!canvas.getItemById(keyframe.id)) {
          reGroup(keyframe.id);
        }
        const object = canvas.getItemById(keyframe.id);
        if (
          currenttime <
          p_keyframes.find((x) => x.id == keyframe.id).trimstart +
            p_keyframes.find((x) => x.id == keyframe.id).start
        ) {
          object.set('visible', false);
          inst.renderAll();
        } else if (
          currenttime >
            p_keyframes.find((x) => x.id == keyframe.id).end ||
          currenttime > duration
        ) {
          object.set('visible', false);
          inst.renderAll();
        } else {
          object.set('visible', true);
          inst.renderAll();
        }
        if (
          currenttime >=
          p_keyframes.find((x) => x.id == keyframe.id).trimstart +
            p_keyframes.find((x) => x.id == keyframe.id).start
        ) {
          props.forEach(function (prop) {
            checkAnyKeyframe(keyframe.id, prop, inst);
          });
        }
      }

      // Copy of setObjectValue function, seems to perform better inside the function
      function setValue(prop, object, value, inst) {
        if (object.get('assetType') == 'audio' && play) {
          if (object.get('src')) {
            object.get('src').volume = value;
            object.set('volume', value);
          }
          return false;
        }
        if (object.get('type') != 'group') {
          if (object.group) {
            /*
                        var group = object.group;
                        tempgroup = group._objects;
                        group._restoreObjectsState();
                        canvas.setActiveObject(group);
                        inst.remove(canvas.getActiveObject());
                        canvas.discardActiveObject();
                        inst.renderAll();
                        for (var i = 0; i < tempgroup.length; i++) {
                            inst.add(tempgroup[i]);
                        }
												*/
          }
        }
        if (prop == 'left' && !recording) {
          object.set(prop, value + artboard.get('left'));
        } else if (prop == 'top' && !recording) {
          object.set(prop, value + artboard.get('top'));
        } else if (prop == 'shadow.blur') {
          object.shadow.blur = value;
        } else if (prop == 'shadow.color') {
          object.shadow.color = value;
        } else if (prop == 'shadow.offsetX') {
          object.shadow.offsetX = value;
        } else if (prop == 'shadow.offsetY') {
          object.shadow.offsetY = value;
        } else if (prop == 'shadow.blur') {
          object.shadow.blur = value;
        } else if (object.get('type') != 'group') {
          object.set(prop, value);
        } else if (prop != 'width') {
          object.set(prop, value);
        }
        inst.renderAll();
      }

      var object = canvas.getItemById(keyframe.id);
      if (
        keyframe.t >= time &&
        currenttime >=
          p_keyframes.find((x) => x.id == keyframe.id).trimstart +
            p_keyframes.find((x) => x.id == keyframe.id).start
      ) {
        var delay = 0;
        var start = false;
        var lasttime, lastprop;
        // Find last keyframe in time from same object & property
        var lastkey = lastKeyframe(keyframe, index);
        if (!lastkey) {
          lasttime = 0;
          lastprop = objects
            .find((x) => x.id == keyframe.id)
            .defaults.find((x) => x.name == keyframe.name).value;
        } else {
          lasttime = lastkey.t;
          lastprop = lastkey.value;
        }
        if (lastkey && lastkey.t >= time && !play) {
          return;
        }
        // Set delay for the animation if playing
        if (play) {
          if (lasttime > currenttime) {
            delay = lasttime - time;
          }
        }
        // Initiate the animation
        var animation = {
          value: lastprop,
        };
        var instance = anime({
          targets: animation,
          delay: delay,
          value: keyframe.value,
          duration: keyframe.t - lasttime,
          easing: keyframe.easing,
          autoplay: false,
          update: function () {
            if (start && !paused) {
              if (
                currenttime <
                  p_keyframes.find((x) => x.id == keyframe.id)
                    .trimstart +
                    p_keyframes.find((x) => x.id == keyframe.id)
                      .start ||
                currenttime >
                  p_keyframes.find((x) => x.id == keyframe.id).end ||
                currenttime > duration
              ) {
                object.set('visible', false);
                inst.renderAll();
              } else {
                setValue(
                  keyframe.name,
                  object,
                  animation.value,
                  inst
                );
                object.set('visible', true);
                inst.renderAll();
              }
            } else if (start && paused) {
              anime.remove(animation);
            }
          },
          changeBegin: function () {
            start = true;
          },
        });

        if (time - lasttime <= 0) {
          instance.seek(0);
        } else {
          instance.seek(time - lasttime);
        }

        if (play) {
          instance.play();
        } else if (
          parseFloat(lasttime) <= parseFloat(time) &&
          parseFloat(keyframe.t) >= parseFloat(time)
        ) {
          setValue(keyframe.name, object, animation.value, inst);
        }
      } else if (
        keyframe.t < time &&
        !nextKeyframe(keyframe, index)
      ) {
        var prop = keyframe.name;
        if (prop == 'left' && !recording) {
          if (
            object.get('left') - artboard.get('left') !=
            keyframe.value
          ) {
            setValue(keyframe.name, object, keyframe.value, inst);
          }
        } else if (prop == 'top' && !recording) {
          if (
            object.get('top') - artboard.get('top') !=
            keyframe.value
          ) {
            setValue(keyframe.name, object, keyframe.value, inst);
          }
        } else if (prop == 'shadow.blur') {
          if (object.shadow.blur != keyframe.value) {
            setValue(keyframe.name, object, keyframe.value, inst);
          }
        } else if (prop == 'shadow.color') {
          if (object.shadow.color != keyframe.value) {
            setValue(keyframe.name, object, keyframe.value, inst);
          }
        } else if (prop == 'shadow.offsetX') {
          if (object.shadow.offsetX != keyframe.value) {
            setValue(keyframe.name, object, keyframe.value, inst);
          }
        } else if (prop == 'shadow.offsetY') {
          if (object.shadow.offsetY != keyframe.value) {
            setValue(keyframe.name, object, keyframe.value, inst);
          }
        } else {
          if (object.get(prop) != keyframe.value) {
            setValue(keyframe.name, object, keyframe.value, inst);
          }
        }
      }
    });
    /*
        if (play) {
            p_keyframes.forEach(function(keyframe){
                inst.getItemById(keyframe.id).set("visible", false);
                window.setTimeout(function(){
                    if (!paused) {
                        inst.getItemById(keyframe.id).set("visible", true);
                        inst.renderAll();
                    }
                }, keyframe.start-time)
                window.setTimeout(function(){
                    if (!paused) {
                        inst.getItemById(keyframe.id).set("visible", false);
                        inst.renderAll();
                    }
                }, keyframe.end-time)
            })
        }
        */
    objects.forEach(function (object) {
      if (object.id.indexOf('Group') == -1) {
        const object2 = canvas.getItemById(object.id);
        if (
          currenttime <
          p_keyframes.find((x) => x.id == object.id).trimstart +
            p_keyframes.find((x) => x.id == object.id).start
        ) {
          object2.set('visible', false);
        } else if (
          currenttime >
            p_keyframes.find((x) => x.id == object.id).end ||
          currenttime > duration
        ) {
          object2.set('visible', false);
        } else {
          object2.set('visible', true);
        }
        if (
          currenttime >=
          p_keyframes.find((x) => x.id == object.id).trimstart +
            p_keyframes.find((x) => x.id == object.id).start
        ) {
          props.forEach(function (prop) {
            checkAnyKeyframe(object.id, prop, inst);
          });
        }
      }
      var obj = canvas.getItemById(object.id);
      if (obj.type == 'lottie') {
        obj.goToSeconds(currenttime);
        inst.renderAll();
      }
    });
    inst.renderAll();

    if (animatedtext.length > 0) {
      animatedtext.forEach(function (text) {
        text.seek(currenttime, canvas);
        inst.renderAll();
      });
    }

    playVideos(time);
    if (play) {
      playAudio(time);
    }
    if (play && !paused) {
      var animation = {
        value: 0,
      };
      var main_instance = anime({
        targets: animation,
        value: [currenttime, duration],
        duration: duration - currenttime,
        easing: 'linear',
        autoplay: true,
        update: function () {
          if (!paused) {
            currenttime = animation.value;
            if (animatedtext.length > 0) {
              animatedtext.forEach(function (text) {
                text.seek(currenttime, canvas);
                inst.renderAll();
              });
            }
            objects.forEach(function (object) {
              if (object.id.indexOf('Group') == -1) {
                const object2 = inst.getItemById(object.id);
                if (
                  currenttime <
                  p_keyframes.find((x) => x.id == object.id)
                    .trimstart +
                    p_keyframes.find((x) => x.id == object.id).start
                ) {
                  object2.set('visible', false);
                } else if (
                  currenttime >
                    p_keyframes.find((x) => x.id == object.id).end ||
                  currenttime > duration
                ) {
                  object2.set('visible', false);
                } else {
                  object2.set('visible', true);
                }
                if (
                  currenttime >=
                  p_keyframes.find((x) => x.id == object.id)
                    .trimstart +
                    p_keyframes.find((x) => x.id == object.id).start
                ) {
                  props.forEach(function (prop) {
                    checkAnyKeyframe(object.id, prop, inst);
                  });
                }
              }
              var obj = canvas.getItemById(object.id);
              if (obj.type == 'lottie') {
                obj.goToSeconds(currenttime);
                inst.renderAll();
              }
            });
            inst.renderAll();
            if (!recording) {
              renderTime();
              $('#seekbar').css({
                left: currenttime / timelinetime + offset_left,
              });
            }
          } else {
            pause();
            animation.value = duration + 1;
            anime.remove(animation);
          }
        },
        complete: function () {
          pause();
        },
      });
    } else if (paused) {
      currenttime = time;
    }
  }
}

// Render a keyframe
function renderKeyframe(object, prop, time) {
  const color = objects.find((x) => x.id == object.id).color;
  if (prop == 'shadow.color') {
    if (
      $('#' + object.get('id'))
        .find('.shadowcolor')
        .is(':visible')
    ) {
      time =
        time -
        parseFloat(
          p_keyframes.find((x) => x.id == object.get('id')).start
        );
    }
    $('#' + object.get('id'))
      .find('.shadowcolor')
      .prepend(
        "<div class='keyframe' data-time='" +
          time +
          "' data-object='" +
          object.get('id') +
          "' data-property='" +
          prop +
          "'></div>"
      );
    $('#' + object.get('id'))
      .find('.shadowcolor')
      .find("[data-time='" + time + "']")
      .css({ left: time / timelinetime, background: color });
  } else {
    if (
      $('#' + object.get('id'))
        .find('.' + prop)
        .is(':visible')
    ) {
      time =
        time -
        parseFloat(
          p_keyframes.find((x) => x.id == object.get('id')).start
        );
    }
    $('#' + object.get('id'))
      .find('.' + prop)
      .prepend(
        "<div class='keyframe' data-time='" +
          time +
          "' data-object='" +
          object.get('id') +
          "' data-property='" +
          prop +
          "'></div>"
      );
    $('#' + object.get('id'))
      .find('.' + prop)
      .find("[data-time='" + time + "']")
      .css({ left: time / timelinetime, background: color });
  }
}

// Create a keyframe
function newKeyframe(property, object, time, value, render) {
  // Check if property can be animated
  if (
    $.inArray(
      property,
      objects.find((x) => x.id == object.get('id')).animate
    ) != -1
  ) {
    const keyarr = $.grep(keyframes, function (e) {
      return (
        e.t == parseFloat(time) &&
        e.id == object.get('id') &&
        e.name == property
      );
    });
    const keyarr2 = $.grep(keyframes, function (e) {
      return e.id == object.get('id') && e.name == property;
    });
    if (keyarr2.length == 0) {
      if (property == 'left') {
        objects
          .find((x) => x.id == object.get('id'))
          .defaults.find((x) => x.name == property).value =
          object.get(property) - artboard.get('left');
      } else if (property == 'top') {
        objects
          .find((x) => x.id == object.get('id'))
          .defaults.find((x) => x.name == property).value =
          object.get(property) - artboard.get('top');
      } else {
        objects
          .find((x) => x.id == object.get('id'))
          .defaults.find((x) => x.name == property).value = value;
      }
    }
    if (keyarr.length == 0) {
      if (property == 'left') {
        keyframes.push({
          t: time,
          name: property,
          value: value - artboard.get('left'),
          id: object.get('id'),
          easing: 'linear',
        });
      } else if (property == 'top') {
        keyframes.push({
          t: time,
          name: property,
          value: value - artboard.get('top'),
          id: object.get('id'),
          easing: 'linear',
        });
      } else {
        keyframes.push({
          t: time,
          name: property,
          value: value,
          id: object.get('id'),
          easing: 'linear',
        });
      }
      if (
        render &&
        property != 'top' &&
        property != 'scaleY' &&
        property != 'width' &&
        property != 'height' &&
        property != 'stroke' &&
        property != 'shadow.opacity' &&
        property != 'shadow.offsetX' &&
        property != 'shadow.offsetY' &&
        property != 'shadow.blur' &&
        property != 'lineHeight'
      ) {
        renderKeyframe(object, property, time);
      }
      keyframes.sort(function (a, b) {
        if (
          a.id.indexOf('Group') >= 0 &&
          b.id.indexOf('Group') == -1
        ) {
          return 1;
        } else if (
          b.id.indexOf('Group') >= 0 &&
          a.id.indexOf('Group') == -1
        ) {
          return -1;
        } else {
          return 0;
        }
      });
    } else if (render) {
      if (
        property != 'top' &&
        property != 'scaleY' &&
        property != 'width' &&
        property != 'height' &&
        property != 'stroke' &&
        property != 'shadow.opacity' &&
        property != 'shadow.offsetX' &&
        property != 'shadow.offsetY' &&
        property != 'shadow.blur' &&
        property != 'lineHeight'
      ) {
        updateKeyframe(
          $('#' + object.get('id')).find(
            ".keyframe[data-time='" +
              time +
              "'][data-property='" +
              property +
              "']"
          ),
          true
        );
      }
    }
  } else {
    if (property == 'left') {
      objects
        .find((x) => x.id == object.get('id'))
        .defaults.find((x) => x.name == property).value =
        object.get(property) - artboard.get('left');
    } else if (property == 'top') {
      objects
        .find((x) => x.id == object.get('id'))
        .defaults.find((x) => x.name == property).value =
        object.get(property) - artboard.get('top');
    } else {
      objects
        .find((x) => x.id == object.get('id'))
        .defaults.find((x) => x.name == property).value = value;
    }
  }
}

// Create a keyframe (manually)
function manualKeyframe() {
  var prop = $(this).parent().attr('data-property');
  const object = canvas.getItemById(
    $(this).parent().parent().parent().attr('data-object')
  );
  if (prop == 'position') {
    prop = 'left';
    newKeyframe('top', object, currenttime, object.get('top'), true);
  } else if (prop == 'scale') {
    prop = 'scaleX';
    newKeyframe(
      'scaleY',
      object,
      currenttime,
      object.get('scaleY'),
      true
    );
    newKeyframe(
      'width',
      object,
      currenttime,
      object.get('width'),
      true
    );
    newKeyframe(
      'height',
      object,
      currenttime,
      object.get('height'),
      true
    );
  } else if (prop == 'stroke') {
    prop = 'strokeWidth';
    newKeyframe(
      'stroke',
      object,
      currenttime,
      object.get('stroke'),
      true
    );
  } else if (prop == 'shadow') {
    prop = 'shadow.color';
    newKeyframe(
      'shadow.opacity',
      object,
      currenttime,
      object.get('shadow.opacity'),
      true
    );
    newKeyframe(
      'shadow.offsetX',
      object,
      currenttime,
      object.get('shadow.offsetX'),
      true
    );
    newKeyframe(
      'shadow.offsetY',
      object,
      currenttime,
      object.get('shadow.offsetY'),
      true
    );
    newKeyframe(
      'shadow.blur',
      object,
      currenttime,
      object.get('shadow.blur'),
      true
    );
  } else if (prop == 'text') {
    prop = 'charSpacing';
    newKeyframe(
      'lineHeight',
      object,
      currenttime,
      object.get('lineHeight'),
      true
    );
  }
  newKeyframe(prop, object, currenttime, object.get(prop), true);
  save();
}
$(document).on('click', '.property-keyframe', manualKeyframe);

// Freeze all properties (this is counterintuitve because I initially programmed it to work the other way around)
function toggleAnimate(e) {
  e.stopPropagation();
  const object = canvas.getItemById(
    $(this).parent().parent().parent().attr('data-object')
  );
  // Turn off clock -> Stop animation
  if ($(this).hasClass('frozen')) {
    $(this).removeClass('frozen');
    $(this).attr('src', 'assets/freeze.svg');
    objects.find((x) => x.id == object.get('id')).animate = [];
    // Turn on clock -> Start animation
  } else {
    $(this).addClass('frozen');
    $(this).attr('src', 'assets/frozen.svg');
    objects.find((x) => x.id == object.get('id')).animate = [];
    if (object.get('assetType') == 'audio') {
      objects
        .find((x) => x.id == object.get('id'))
        .animate.push('volume');
      keyframes = $.grep(keyframes, function (e) {
        return e.id != object.get('id');
      });
      $(".keyframe[data-object='" + object.get('id') + "']").remove();
      $(this)
        .parent()
        .parent()
        .parent()
        .find('.freeze-prop')
        .removeClass('frozen');
      newKeyframe('volume', object, 0, 0.5, true);
    } else {
      props.forEach(function (prop) {
        objects
          .find((x) => x.id == object.get('id'))
          .animate.push(prop);
      });
      keyframes = $.grep(keyframes, function (e) {
        return e.id != object.get('id');
      });
      $(".keyframe[data-object='" + object.get('id') + "']").remove();
      $(this)
        .parent()
        .parent()
        .parent()
        .find('.freeze-prop')
        .removeClass('frozen');

      props.forEach(function (prop) {
        if (prop == 'lineHeight' || prop == 'charSpacing') {
          if (object.get('type') == 'textbox') {
            newKeyframe(prop, object, 0, object.get(prop), true);
          }
        } else if (
          prop == 'shadow.opacity' ||
          prop == 'shadow.blur' ||
          prop == 'shadow.offsetX' ||
          prop == 'shadow.offsetY' ||
          prop == 'shadow.color'
        ) {
          if (object.get('type') != 'group') {
            if (prop == 'shadow.color') {
              newKeyframe(prop, object, 0, object.shadow.color, true);
            } else if (prop == 'shadow.opacity') {
              newKeyframe(
                prop,
                object,
                0,
                object.shadow.opacity,
                true
              );
            } else if (prop == 'shadow.offsetX') {
              newKeyframe(
                prop,
                object,
                0,
                object.shadow.offsetX,
                true
              );
            } else if (prop == 'shadow.offsetY') {
              newKeyframe(
                prop,
                object,
                0,
                object.shadow.offsetY,
                true
              );
            } else if (prop == 'shadow.blur') {
              newKeyframe(prop, object, 0, object.shadow.blur, true);
            }
          }
        } else {
          newKeyframe(prop, object, 0, object.get(prop), true);
        }
      });
    }
  }
  save();
}
$(document).on('click', '.freeze', toggleAnimate);

function animateProp(prop, object) {
  objects.find((x) => x.id == object.get('id')).animate.push(prop);

  // Prop counterparts
  if (prop == 'left') {
    objects.find((x) => x.id == object.get('id')).animate.push('top');
    newKeyframe(
      'left',
      object,
      currenttime,
      object.get('left'),
      true
    );
    newKeyframe('top', object, currenttime, object.get('top'), true);
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == 'left').value =
      object.get('left') - artboard.get('left');
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == 'top').value =
      object.get('top') - artboard.get('top');
  } else if (prop == 'scaleX') {
    newKeyframe(
      'scaleY',
      object,
      currenttime,
      object.get('scaleY'),
      true
    );
    newKeyframe(
      'width',
      object,
      currenttime,
      object.get('width'),
      true
    );
    newKeyframe(
      'height',
      object,
      currenttime,
      object.get('height'),
      true
    );
    objects
      .find((x) => x.id == object.get('id'))
      .animate.push('scaleY');
    objects
      .find((x) => x.id == object.get('id'))
      .animate.push('width');
    objects
      .find((x) => x.id == object.get('id'))
      .animate.push('height');
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == 'height').value =
      object.get('height');
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == 'width').value =
      object.get('width');
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == 'scaleY').value =
      object.get('scaleY');
  } else if (prop == 'strokeWidth') {
    newKeyframe(
      'stroke',
      object,
      currenttime,
      object.get('stroke'),
      true
    );
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == 'stroke').value =
      object.get('stroke');
    objects
      .find((x) => x.id == object.get('id'))
      .animate.push('stroke');
  } else if (prop == 'shadow.color') {
    newKeyframe(
      'shadow.color',
      object,
      currenttime,
      object.shadow.color,
      true
    );
    newKeyframe(
      'shadow.opacity',
      object,
      currenttime,
      object.shadow.opacity,
      true
    );
    newKeyframe(
      'shadow.offsetX',
      object,
      currenttime,
      object.shadow.offsetX,
      true
    );
    newKeyframe(
      'shadow.offsetY',
      object,
      currenttime,
      object.shadow.offsetY,
      true
    );
    newKeyframe(
      'shadow.blur',
      object,
      currenttime,
      object.shadow.blur,
      true
    );
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == 'shadow.color').value =
      object.get('shadow.color');
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == 'shadow.opacity').value =
      object.get('shadow.opacity');
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == 'shadow.offsetX').value =
      object.get('shadow.offsetX');
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == 'shadow.offsetY').value =
      object.get('shadow.offsetY');
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == 'shadow.blur').value =
      object.get('shadow.blur');
    objects
      .find((x) => x.id == object.get('id'))
      .animate.push('shadow.opacity');
    objects
      .find((x) => x.id == object.get('id'))
      .animate.push('shadow.offsetX');
    objects
      .find((x) => x.id == object.get('id'))
      .animate.push('shadow.offsetY');
    objects
      .find((x) => x.id == object.get('id'))
      .animate.push('shadow.blur');
  } else if (prop == 'charSpacing') {
    newKeyframe(
      'lineHeight',
      object,
      currenttime,
      object.get('lineHeight'),
      true
    );
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == 'lineHeight').value =
      object.get('lineHeight');
    objects
      .find((x) => x.id == object.get('id'))
      .animate.push('lineHeight');
  }

  // Exception
  if (prop != 'left' && prop != 'shadow.color') {
    newKeyframe(prop, object, currenttime, object.get(prop), true);
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == prop).value = object.get(prop);
  }
}

function freezeProp(prop, object) {
  objects.find((x) => x.id == object.get('id')).animate = $.grep(
    objects.find((x) => x.id == object.get('id')).animate,
    function (e) {
      return e != prop;
    }
  );
  // Also add prop counterparts (should probably have done in a better way)
  if (prop == 'left') {
    objects.find((x) => x.id == object.get('id')).animate = $.grep(
      objects.find((x) => x.id == object.get('id')).animate,
      function (e) {
        return e != 'top';
      }
    );
    keyframes = $.grep(keyframes, function (e) {
      return e.id != object.get('id') || e.name != 'top';
    });
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == 'left').value =
      object.get('left') - artboard.get('left');
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == 'top').value =
      object.get('top') - artboard.get('top');
  } else if (prop == 'scaleX') {
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == 'height').value =
      object.get('height');
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == 'width').value =
      object.get('width');
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == 'scaleY').value =
      object.get('scaleY');
    objects.find((x) => x.id == object.get('id')).animate = $.grep(
      objects.find((x) => x.id == object.get('id')).animate,
      function (e) {
        return e != 'scaleY';
      }
    );
    keyframes = $.grep(keyframes, function (e) {
      return e.id != object.get('id') || e.name != 'scaleY';
    });
    objects.find((x) => x.id == object.get('id')).animate = $.grep(
      objects.find((x) => x.id == object.get('id')).animate,
      function (e) {
        return e != 'width';
      }
    );
    keyframes = $.grep(keyframes, function (e) {
      return e.id != object.get('id') || e.name != 'width';
    });
    objects.find((x) => x.id == object.get('id')).animate = $.grep(
      objects.find((x) => x.id == object.get('id')).animate,
      function (e) {
        return e != 'height';
      }
    );
    keyframes = $.grep(keyframes, function (e) {
      return e.id != object.get('id') || e.name != 'height';
    });
  } else if (prop == 'strokeWidth') {
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == 'stroke').value =
      object.get('stroke');
    objects.find((x) => x.id == object.get('id')).animate = $.grep(
      objects.find((x) => x.id == object.get('id')).animate,
      function (e) {
        return e != 'stroke';
      }
    );
    keyframes = $.grep(keyframes, function (e) {
      return e.id != object.get('id') || e.name != 'stroke';
    });
  } else if (prop == 'shadow.color') {
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == 'shadow.opacity').value =
      object.shadow.opacity;
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == 'shadow.offsetX').value =
      object.shadow.offsetX;
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == 'shadow.offsetY').value =
      object.shadow.offsetY;
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == 'shadow.blur').value =
      object.shadow.blur;
    keyframes = $.grep(keyframes, function (e) {
      return e.id != object.get('id') || e.name != 'shadow.opacity';
    });
    objects.find((x) => x.id == object.get('id')).animate = $.grep(
      objects.find((x) => x.id == object.get('id')).animate,
      function (e) {
        return e != 'shadow.opacity';
      }
    );
    keyframes = $.grep(keyframes, function (e) {
      return e.id != object.get('id') || e.name != 'shadow.offsetX';
    });
    objects.find((x) => x.id == object.get('id')).animate = $.grep(
      objects.find((x) => x.id == object.get('id')).animate,
      function (e) {
        return e != 'shadow.offsetX';
      }
    );
    keyframes = $.grep(keyframes, function (e) {
      return e.id != object.get('id') || e.name != 'shadow.offsetY';
    });
    objects.find((x) => x.id == object.get('id')).animate = $.grep(
      objects.find((x) => x.id == object.get('id')).animate,
      function (e) {
        return e != 'shadow.offsetY';
      }
    );
    keyframes = $.grep(keyframes, function (e) {
      return e.id != object.get('id') || e.name != 'shadow.blur';
    });
    objects.find((x) => x.id == object.get('id')).animate = $.grep(
      objects.find((x) => x.id == object.get('id')).animate,
      function (e) {
        return e != 'shadow.blur';
      }
    );
  } else if (prop == 'charSpacing') {
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == 'lineHeight').value =
      object.get('lineHeight');
    objects.find((x) => x.id == object.get('id')).animate = $.grep(
      objects.find((x) => x.id == object.get('id')).animate,
      function (e) {
        return e != 'lineHeight';
      }
    );
    keyframes = $.grep(keyframes, function (e) {
      return e.id != object.get('id') || e.name != 'lineHeight';
    });
  }

  keyframes = $.grep(keyframes, function (e) {
    return e.id != object.get('id') || e.name != prop;
  });

  // Exception
  if (prop != 'left' && prop != 'shadow.color') {
    objects
      .find((x) => x.id == object.get('id'))
      .defaults.find((x) => x.name == prop).value = object.get(prop);
  }

  $(
    ".keyframe[data-object='" +
      object.get('id') +
      "'][data-property='" +
      prop +
      "']"
  ).remove();
}

// Toggle animation mode for property
function toggleAnimateProp(e) {
  e.stopPropagation();
  var prop = $(this).parent().attr('data-property');
  const object = canvas.getItemById(
    $(this).parent().parent().parent().attr('data-object')
  );
  if (prop == 'position') {
    prop = 'left';
  } else if (prop == 'scale') {
    prop = 'scaleX';
  } else if (prop == 'stroke') {
    prop = 'strokeWidth';
  } else if (prop == 'shadow') {
    prop = 'shadow.color';
  } else if (prop == 'text') {
    prop = 'charSpacing';
  }

  // Check layer global "freezing" state
  if (
    $(this)
      .parent()
      .parent()
      .parent()
      .find('.freeze')
      .hasClass('frozen')
  ) {
    objects.find((x) => x.id == object.get('id')).animate = [];
    $(this)
      .parent()
      .parent()
      .parent()
      .find('.freeze')
      .removeClass('frozen');

    // Stop animating all props except selected
    var propmatch = [
      prop,
      'scaleY',
      'width',
      'height',
      'top',
      'stroke',
      'shadow.opacity',
      'shadow.offsetX',
      'shadow.offsetY',
      'shadow.blur',
      'lineHeight',
    ];
    props.forEach(function (p) {
      if ($.inArray(p, propmatch) == -1) {
        if (
          (object.get('type') == 'textbox' && p == 'charSpacing') ||
          p == 'lineHeight'
        ) {
          freezeProp(p, object);
        } else if (p != 'charSpacing' && p != 'lineHeight') {
          freezeProp(p, object);
        }
      }
    });
  }

  // Turn off clock -> Stop animating
  if ($(this).hasClass('frozen')) {
    $(this).removeClass('frozen');
    $(this).attr('src', 'assets/freeze.svg');
    freezeProp(prop, object);
    // Turn on clock -> Animate
  } else {
    $(this).addClass('frozen');
    $(this).attr('src', 'assets/frozen.svg');
    animateProp(prop, object);
  }
  save();
}
$(document).on('click', '.freeze-prop', toggleAnimateProp);

// Lock layer
function lockLayer(e) {
  e.stopPropagation();
  const object = canvas.getItemById(
    $(this).parent().parent().parent().attr('data-object')
  );
  if ($(this).hasClass('locked')) {
    $(this).removeClass('locked');
    $(this).attr('src', 'assets/lock.svg');
    object.selectable = true;
    $(this).parent().parent().parent().attr('draggable', true);
  } else {
    $(this).addClass('locked');
    $(this).attr('src', 'assets/locked.svg');
    object.selectable = false;
    if (canvas.getActiveObject() == object) {
      canvas.discardActiveObject();
      canvas.renderAll();
    }
    $(this).parent().parent().parent().attr('draggable', false);
  }
  save();
}
$(document).on('click', '.lock', lockLayer);

// Center an object in the canvas
function centerObject(object) {
  object.set('top', artboard.get('top') + artboard.get('height') / 2);
  object.set(
    'left',
    artboard.get('left') + artboard.get('width') / 2
  );
  canvas.renderAll();
  save();
}

// Render a layer
function renderLayer(object, animate = false) {
  $('#nolayers').addClass('yaylayers');
  const color = objects.find((x) => x.id == object.get('id')).color;
  var src = '';
  var classlock = '';
  var srclock = 'lock';
  var freeze = 'freeze';
  if (object.get('type') == 'textbox') {
    src = 'assets/text.svg';
  } else if (
    object.get('type') == 'rect' ||
    object.get('type') == 'group' ||
    object.get('type') == 'circle' ||
    object.get('type') == 'path'
  ) {
    src = 'assets/star.svg';
    if (object.get('assetType') == 'animatedText') {
      src = 'assets/text.svg';
    }
    if (object.get('assetType') == 'audio') {
      src = 'assets/audio.svg';
    }
  } else if (object.get('type') == 'image') {
    if (
      object.get('assetType') &&
      object.get('assetType') == 'video'
    ) {
      src = 'assets/video.svg';
    } else {
      src = 'assets/image.svg';
    }
  } else if (object.get('type') == 'lottie') {
    src = 'assets/zappy.svg';
  } else if (object.get('assetType') == 'audio') {
    src = 'assets/audio.svg';
  }
  if (object.selectable == false) {
    classlock = 'locked';
    srclock = 'locked';
  }
  if (animate != false) {
    freeze = 'frozen';
  }
  const leftoffset =
    p_keyframes.find((x) => x.id == object.get('id')).trimstart /
    timelinetime;
  const width =
    (p_keyframes.find((x) => x.id == object.get('id')).end -
      p_keyframes.find((x) => x.id == object.get('id')).trimstart) /
    timelinetime;
  $('#inner-timeline').prepend(
    "<div class='object-props' id='" +
      object.get('id') +
      "' style='width:" +
      (p_keyframes.find((x) => x.id == object.get('id')).end -
        p_keyframes.find((x) => x.id == object.get('id')).start) /
        timelinetime +
      "px'><div class='row main-row'><div class='row-el' style='background-color:" +
      color +
      "'><div class='trim-row' style='left:" +
      leftoffset +
      'px;width:' +
      width +
      "px'></div></div></div></div>"
  );
  if (object.get('assetType') == 'audio') {
    object.setControlsVisibility({
      mt: false,
      mb: false,
      ml: false,
      mr: false,
    });
    $('#layer-inner-list').prepend(
      "<div class='layer' data-object='" +
        object.get('id') +
        "'><div class='layer-name'><img class='droparrow' src='assets/drop-arrow.svg' ><img class='layer-icon' src=" +
        src +
        "><input class='layer-custom-name' value='" +
        objects.find((x) => x.id == object.get('id')).label +
        "' readonly></span><div class='layer-options'><img class='" +
        freeze +
        "' src='assets/" +
        freeze +
        ".svg' title='Toggle animation'></div></div><div class='properties'></div></div>"
    );
  } else {
    $('#layer-inner-list').prepend(
      "<div class='layer' data-object='" +
        object.get('id') +
        "'><div class='layer-name'><img class='droparrow' src='assets/drop-arrow.svg' ><img class='layer-icon' src=" +
        src +
        "><input class='layer-custom-name' value='" +
        objects.find((x) => x.id == object.get('id')).label +
        "' readonly></span><div class='layer-options'><img class='lock " +
        classlock +
        "' src='assets/" +
        srclock +
        ".svg' title='Lock layer'><img class='" +
        freeze +
        "' src='assets/" +
        freeze +
        ".svg' title='Toggle animation'></div></div><div class='properties'></div></div>"
    );
  }
  $(".layer[data-object='" + object.get('id') + "']")
    .find('.properties')
    .toggle();
  setTimelineZoom(timelinetime);
  sortable('#layer-inner-list', {
    placeholderClass: 'hovering',
    copy: true,
    customDragImage: (draggedElement, elementOffset, event) => {
      return {
        element: document.getElementById('nothing'),
        posX: event.pageX - elementOffset.left,
        posY: event.pageY - elementOffset.top,
      };
    },
  });
  if (object.selectable == false) {
    $(".layer[data-object='" + object.get('id') + "']").attr(
      'draggable',
      false
    );
  }
}

// Render a property
function renderProp(prop, object) {
  var classfreeze = '';
  srcfreeze = 'freeze';
  if (
    $.inArray(
      prop,
      objects.find((x) => x.id == object.get('id')).animate
    ) != -1
  ) {
    classfreeze = 'frozen';
    srcfreeze = 'frozen';
  }
  if (prop == 'shadow.color') {
    prop = 'shadowcolor';
  }
  $('#' + object.get('id')).append(
    "<div class='row " +
      prop +
      " keyframe-row' data-object='" +
      object.get('id') +
      "'><div class='row-el'></div></div>"
  );
  if (prop == 'left') {
    $(".layer[data-object='" + object.get('id') + "']")
      .find('.properties')
      .append(
        "<div class='property-name' data-property='position'><span class='property-keyframe' title='Create a new keyframe'></span>Position<img class='freeze-prop " +
          classfreeze +
          "' src='assets/" +
          srcfreeze +
          ".svg' title='Toggle animation'></div>"
      );
  } else if (prop == 'scaleX') {
    $(".layer[data-object='" + object.get('id') + "']")
      .find('.properties')
      .append(
        "<div class='property-name' data-property='scale'><span class='property-keyframe' title='Create a new keyframe'></span>Scale<img class='freeze-prop " +
          classfreeze +
          "' src='assets/" +
          srcfreeze +
          ".svg' title='Toggle animation'></div>"
      );
  } else if (prop == 'strokeWidth') {
    $(".layer[data-object='" + object.get('id') + "']")
      .find('.properties')
      .append(
        "<div class='property-name' data-property='stroke'><span class='property-keyframe' title='Create a new keyframe'></span>Stroke<img class='freeze-prop " +
          classfreeze +
          "' src='assets/" +
          srcfreeze +
          ".svg' title='Toggle animation'></div>"
      );
  } else if (prop == 'shadowcolor') {
    $(".layer[data-object='" + object.get('id') + "']")
      .find('.properties')
      .append(
        "<div class='property-name' data-property='shadow'><span class='property-keyframe' title='Create a new keyframe'></span>Shadow<img class='freeze-prop " +
          classfreeze +
          "' src='assets/" +
          srcfreeze +
          ".svg' title='Toggle animation'></div>"
      );
  } else if (prop == 'charSpacing') {
    $(".layer[data-object='" + object.get('id') + "']")
      .find('.properties')
      .append(
        "<div class='property-name' data-property='text'><span class='property-keyframe' title='Create a new keyframe'></span>Text<img class='freeze-prop " +
          classfreeze +
          "' src='assets/" +
          srcfreeze +
          ".svg' title='Toggle animation'></div>"
      );
  } else {
    $(".layer[data-object='" + object.get('id') + "']")
      .find('.properties')
      .append(
        "<div class='property-name' data-property='" +
          prop +
          "'><span class='property-keyframe' title='Create a new keyframe'></span>" +
          prop +
          "<img class='freeze-prop " +
          classfreeze +
          "' src='assets/" +
          srcfreeze +
          ".svg' title='Toggle animation'></div>"
      );
  }
  $('#' + object.get('id'))
    .find('.keyframe-row' + '.' + prop)
    .toggle();
}

// Create a layer
function newLayer(object) {
  layer_count++;
  var color;
  if (object.get('type') == 'image') {
    if (
      object.get('assetType') &&
      object.get('assetType') == 'video'
    ) {
      color = '#106CF6';
    } else {
      color = '#92F711';
    }
  } else if (object.get('type') == 'textbox') {
    color = '#F7119B';
  } else if (
    object.get('type') == 'rect' ||
    object.get('type') == 'group' ||
    object.get('type') == 'circle' ||
    object.get('type') == 'path'
  ) {
    color = '#9211F7';
    if (object.get('assetType') == 'animatedText') {
      color = '#F7119B';
    } else if (object.get('assetType') == 'audio') {
      color = '#11C0F7';
    }
  }
  if (
    (object.get('assetType') && object.get('assetType') == 'video') ||
    object.get('type') == 'lottie' ||
    object.get('assetType') == 'audio'
  ) {
    objects.push({
      object: object,
      id: object.get('id'),
      label: object.get('id'),
      color: color,
      defaults: [],
      locked: [],
      mask: 'none',
      start: 0,
      end: object.get('duration'),
    });
    if (object.get('duration') < duration) {
      p_keyframes.push({
        start: currenttime,
        end: object.get('duration') + currenttime,
        trimstart: 0,
        trimend: object.get('duration') + currenttime,
        object: object,
        id: object.get('id'),
      });
    } else {
      p_keyframes.push({
        start: currenttime,
        end: duration - currenttime,
        trimstart: 0,
        trimend: duration - currenttime,
        object: object,
        id: object.get('id'),
      });
    }
  } else {
    objects.push({
      object: object,
      id: object.get('id'),
      label: object.get('id'),
      color: color,
      defaults: [],
      locked: [],
      mask: 'none',
    });
    if (object.get('notnew')) {
      p_keyframes.push({
        start: object.get('starttime'),
        end: duration - object.get('starttime'),
        trimstart: 0,
        trimend: duration - currenttime,
        object: object,
        id: object.get('id'),
      });
    } else {
      p_keyframes.push({
        start: currenttime,
        end: duration - currenttime,
        trimstart: 0,
        trimend: duration - currenttime,
        object: object,
        id: object.get('id'),
      });
    }
  }
  renderLayer(object);
  if (
    !object.get('assetType') ||
    object.get('assetType') != 'audio'
  ) {
    props.forEach(function (prop) {
      if (prop == 'lineHeight' || prop == 'charSpacing') {
        if (object.get('type') == 'textbox') {
          if (prop != 'lineHeight') {
            renderProp(prop, object);
          }
          objects
            .find((x) => x.id == object.id)
            .defaults.push({ name: prop, value: object.get(prop) });
        }
      } else if (
        prop == 'shadow.opacity' ||
        prop == 'shadow.blur' ||
        prop == 'shadow.offsetX' ||
        prop == 'shadow.offsetY' ||
        prop == 'shadow.color'
      ) {
        if (object.get('type') != 'group') {
          if (prop == 'shadow.color') {
            renderProp(prop, object);
            objects
              .find((x) => x.id == object.id)
              .defaults.push({
                name: prop,
                value: object.shadow.color,
              });
          } else if (prop == 'shadow.blur') {
            objects
              .find((x) => x.id == object.id)
              .defaults.push({
                name: prop,
                value: object.shadow.blur,
              });
          } else if (prop == 'shadow.offsetX') {
            objects
              .find((x) => x.id == object.id)
              .defaults.push({
                name: prop,
                value: object.shadow.offsetX,
              });
          } else if (prop == 'shadow.offsetY') {
            objects
              .find((x) => x.id == object.id)
              .defaults.push({
                name: prop,
                value: object.shadow.offsetY,
              });
          } else if (prop == 'shadow.opacity') {
            objects
              .find((x) => x.id == object.id)
              .defaults.push({
                name: prop,
                value: object.shadow.opacity,
              });
          }
        }
      } else {
        if (
          prop != 'top' &&
          prop != 'scaleY' &&
          prop != 'stroke' &&
          prop != 'width' &&
          prop != 'height'
        ) {
          renderProp(prop, object);
        }
        objects
          .find((x) => x.id == object.id)
          .defaults.push({ name: prop, value: object.get(prop) });
      }
    });
  } else {
    renderProp('volume', object);
    objects
      .find((x) => x.id == object.id)
      .defaults.push({ name: 'volume', value: 0 });
  }
  $('.layer-selected').removeClass('layer-selected');
  $(".layer[data-object='" + object.get('id') + "']").addClass(
    'layer-selected'
  );
  document
    .getElementsByClassName('layer-selected')[0]
    .scrollIntoView();
  objects.find((x) => x.id == object.id).animate = [];
  animate(false, currenttime);
  save();
  checkFilter();
}

// Add a (complex) SVG shape to the canvas
function newSVG(svg, x, y, width, center) {
  var svggroup = [];
  fabric.loadSVGFromURL(svg, function (objects, options) {
    var newsvg = objects[0];
    if (objects.length > 1) {
      newsvg = fabric.util.groupSVGElements(objects, options);
    }
    newsvg.set({
      id: 'Shape' + layer_count,
      stroke: '#000',
      left: x,
      top: y,
      strokeWidth: 0,
      strokeUniform: true,
      originX: 'center',
      originY: 'center',
      strokeDashArray: false,
      absolutePositioned: true,
      paintFirst: 'stroke',
      objectCaching: true,
      sourcePath: svg,
      inGroup: false,
      shadow: {
        color: '#000',
        offsetX: 0,
        offsetY: 0,
        blur: 0,
        opacity: 0,
      },
    });
    newsvg.scaleToWidth(width);
    newsvg.set({
      scaleX: parseFloat(newsvg.get('scaleX').toFixed(2)),
      scaleY: parseFloat(newsvg.get('scaleY').toFixed(2)),
    });
    canvas.add(newsvg);
    newLayer(newsvg);
    canvas.setActiveObject(newsvg);
    canvas.bringToFront(newsvg);
    canvas.renderAll();
    if (center) {
      newsvg.set(
        'left',
        artboard.get('left') + artboard.get('width') / 2
      );
      newsvg.set(
        'top',
        artboard.get('top') + artboard.get('height') / 2
      );
      canvas.renderAll();
    }
  });
}

// Add a video to the canvas
function newVideo(file, src, x, y, duration, center) {
  var newvid = new fabric.Image(file, {
    left: x,
    top: y,
    width: file.width,
    height: file.height,
    originX: 'center',
    originY: 'center',
    backgroundColor: 'rgba(255,255,255,0)',
    cursorWidth: 1,
    stroke: '#000',
    strokeUniform: true,
    paintFirst: 'stroke',
    strokeWidth: 0,
    cursorDuration: 1,
    cursorDelay: 250,
    source: src,
    duration: duration * 1000,
    assetType: 'video',
    id: 'Video' + layer_count,
    objectCaching: false,
    strokeDashArray: false,
    inGroup: false,
    shadow: {
      color: '#000',
      offsetX: 0,
      offsetY: 0,
      blur: 0,
      opacity: 0,
    },
  });
  files.push({ name: newvid.get('id'), file: src });
  newvid.saveElem = newvid.getElement();
  canvas.add(newvid);
  if (newvid.get('width') > artboard.get('width')) {
    newvid.scaleToWidth(artboard.get('width'));
  }
  newvid.scaleToWidth(150);
  canvas.renderAll();
  if (window.duration < newvid.duration + currenttime) {
    window.duration =
      ((newvid.duration + currenttime) / 1000).toFixed(2) * 1000;
  }
  newLayer(newvid);
  canvas.setActiveObject(newvid);
  canvas.bringToFront(newvid);
  if (center) {
    newvid.set(
      'left',
      artboard.get('left') + artboard.get('width') / 2
    );
    newvid.set(
      'top',
      artboard.get('top') + artboard.get('height') / 2
    );
    canvas.renderAll();
  }
  $('#load-video').removeClass('loading-active');
}

// Load a video
function loadVideo(src, x, y, center) {
  var vidObj = document.createElement('video');
  var vidSrc = document.createElement('source');
  vidSrc.src = src;
  vidObj.crossOrigin = 'anonymous';
  vidObj.appendChild(vidSrc);
  vidObj.addEventListener('loadeddata', function () {
    vidObj.width = this.videoWidth;
    vidObj.height = this.videoHeight;
    vidObj.currentTime = 0;
    vidObj.muted = false;
    function waitLoad() {
      if (vidObj.readyState >= 3) {
        newVideo(vidObj, src, x, y, vidObj.duration, center);
      } else {
        window.setTimeout(function () {
          waitLoad();
        }, 100);
      }
    }
    window.setTimeout(function () {
      waitLoad();
    }, 100);
  });
  vidObj.currentTime = 0;
}

// Check that crop controls are inside image
function checkCrop(obj) {
  if (obj.isContainedWithinObject(cropobj)) {
    croptop = obj.get('top');
    cropleft = obj.get('left');
    cropscalex = obj.get('scaleX');
    cropscaley = obj.get('scaleY');
  } else {
    obj.top = croptop;
    obj.left = cropleft;
    obj.scaleX = cropscalex;
    obj.scaleY = cropscaley;
    obj.setCoords();
    obj.saveState();
  }
  obj.set({
    borderColor: '#51B9F9',
  });
  canvas.renderAll();
  crop(canvas.getItemById('cropped'));
}

// Perform a crop
function crop(obj) {
  var crop = canvas.getItemById('crop');
  cropobj.setCoords();
  crop.setCoords();
  var cleft =
    crop.get('left') - (crop.get('width') * crop.get('scaleX')) / 2;
  var ctop =
    crop.get('top') - (crop.get('height') * crop.get('scaleY')) / 2;
  var height =
    (crop.get('height') / cropobj.get('scaleY')) * crop.get('scaleY');
  var width =
    (crop.get('width') / cropobj.get('scaleX')) * crop.get('scaleX');
  var img_height = cropobj.get('height') * cropobj.get('scaleY');
  var img_width = cropobj.get('width') * cropobj.get('scaleX');
  var left =
    cleft -
    (cropobj.get('left') -
      (cropobj.get('width') * cropobj.get('scaleX')) / 2);
  var top =
    ctop -
    (cropobj.get('top') -
      (cropobj.get('height') * cropobj.get('scaleY')) / 2);
  if (left < 0 && top > 0) {
    obj
      .set({ cropY: top / cropobj.get('scaleY'), height: height })
      .setCoords();
    canvas.renderAll();
    obj.set({
      top: ctop + (obj.get('height') * obj.get('scaleY')) / 2,
    });
    canvas.renderAll();
  } else if (top < 0 && left > 0) {
    obj
      .set({ cropX: left / cropobj.get('scaleX'), width: width })
      .setCoords();
    canvas.renderAll();
    obj.set({
      left: cleft + (obj.get('width') * obj.get('scaleX')) / 2,
    });
    canvas.renderAll();
  } else if (top > 0 && left > 0) {
    obj
      .set({
        cropX: left / cropobj.get('scaleX'),
        cropY: top / cropobj.get('scaleY'),
        height: height,
        width: width,
      })
      .setCoords();
    canvas.renderAll();
    obj.set({
      left: cleft + (obj.get('width') * obj.get('scaleX')) / 2,
      top: ctop + (obj.get('height') * obj.get('scaleY')) / 2,
    });
    canvas.renderAll();
  }
  if (obj.get('id') != 'cropped') {
    canvas.remove(crop);
    canvas.remove(canvas.getItemById('overlay'));
    canvas.remove(canvas.getItemById('cropped'));
    cropping = false;
    resetControls();
    canvas.uniformScaling = true;
    canvas.renderAll();
    newKeyframe('scaleX', obj, currenttime, obj.get('scaleX'), true);
    newKeyframe('scaleY', obj, currenttime, obj.get('scaleY'), true);
    newKeyframe('width', obj, currenttime, obj.get('width'), true);
    newKeyframe('height', obj, currenttime, obj.get('width'), true);
    newKeyframe('left', obj, currenttime, obj.get('left'), true);
    newKeyframe('top', obj, currenttime, obj.get('top'), true);
    $('#properties-overlay').removeClass('properties-disabled');
    save();
  }
  canvas.renderAll();
}

var tlcrop = new Image();
tlcrop.src = 'assets/tlcrop.svg';
var trcrop = new Image();
trcrop.src = 'assets/trcrop.svg';
var blcrop = new Image();
blcrop.src = 'assets/blcrop.svg';
var brcrop = new Image();
brcrop.src = 'assets/brcrop.svg';

function overlay() {
  canvas.add(
    new fabric.Rect({
      left: artboard.left,
      top: artboard.top,
      originX: 'left',
      originY: 'top',
      width: artboard.width,
      height: artboard.height,
      fill: 'rgba(0,0,0,0.5)',
      selectable: false,
      id: 'overlay',
    })
  );
}

// Start cropping an image
function cropImage(object) {
  if (!cropping) {
    $('#properties-overlay').addClass('properties-disabled');
    cropping = true;
    cropobj = object;
    canvas.uniformScaling = false;
    cropobj.setCoords();
    var left =
      cropobj.get('left') -
      (cropobj.get('width') * cropobj.get('scaleX')) / 2;
    var top =
      cropobj.get('top') -
      (cropobj.get('height') * cropobj.get('scaleY')) / 2;
    var cropx = cropobj.get('cropX');
    var cropy = cropobj.get('cropY');
    overlay();
    var cropUI = new fabric.Rect({
      left: object.get('left'),
      top: object.get('top'),
      width: object.get('width') * object.get('scaleX') - 5,
      height: object.get('height') * object.get('scaleY') - 5,
      originX: 'center',
      originY: 'center',
      id: 'crop',
      fill: 'rgba(0,0,0,0)',
      shadow: {
        color: 'black',
        offsetX: 0,
        offsetY: 0,
        blur: 0,
        opacity: 0,
      },
    });
    cropobj.clone(function (cloned) {
      cloned.set({
        id: 'cropped',
        selectable: false,
        originX: 'center',
        originY: 'center',
      });
      canvas.add(cloned);
      canvas.bringToFront(cloned);
      canvas.bringToFront(cropUI);
      canvas.renderAll();
      cropobj = object;
    });
    cropobj
      .set({
        cropX: 0,
        cropY: 0,
        width: cropobj.get('ogWidth'),
        height: cropobj.get('ogHeight'),
      })
      .setCoords();
    canvas.renderAll();
    cropobj.set({
      left:
        left +
        (cropobj.get('width') * cropobj.get('scaleX')) / 2 -
        cropx * cropobj.get('scaleX'),
      top:
        top +
        (cropobj.get('height') * cropobj.get('scaleY')) / 2 -
        cropy * cropobj.get('scaleY'),
    });
    cropUI.setControlsVisibility({
      mt: false,
      mb: false,
      mr: false,
      ml: false,
      mtr: false,
    });
    cropUI.controls.tl = new fabric.Control({
      x: -0.5,
      y: -0.5,
      offsetX: 3,
      offsetY: 3,
      cursorStyleHandler:
        fabric.controlsUtils.scaleCursorStyleHandler,
      actionHandler: fabric.controlsUtils.scalingEqually,
      render: function (ctx, left, top, styleOverride, fabricObject) {
        const wsize = 27;
        const hsize = 27;
        ctx.save();
        ctx.translate(left, top);
        ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
        ctx.drawImage(tlcrop, -wsize / 2, -hsize / 2, wsize, hsize);
        ctx.restore();
      },
    });
    cropUI.controls.tr = new fabric.Control({
      x: 0.5,
      y: -0.5,
      offsetX: -3,
      offsetY: 3,
      cursorStyleHandler:
        fabric.controlsUtils.scaleCursorStyleHandler,
      actionHandler: fabric.controlsUtils.scalingEqually,
      render: function (ctx, left, top, styleOverride, fabricObject) {
        const wsize = 27;
        const hsize = 27;
        ctx.save();
        ctx.translate(left, top);
        ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
        ctx.drawImage(trcrop, -wsize / 2, -hsize / 2, wsize, hsize);
        ctx.restore();
      },
    });
    cropUI.controls.bl = new fabric.Control({
      x: -0.5,
      y: 0.5,
      offsetX: 3,
      offsetY: -3,
      cursorStyleHandler:
        fabric.controlsUtils.scaleCursorStyleHandler,
      actionHandler: fabric.controlsUtils.scalingEqually,
      render: function (ctx, left, top, styleOverride, fabricObject) {
        const wsize = 27;
        const hsize = 27;
        ctx.save();
        ctx.translate(left, top);
        ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
        ctx.drawImage(blcrop, -wsize / 2, -hsize / 2, wsize, hsize);
        ctx.restore();
      },
    });
    cropUI.controls.br = new fabric.Control({
      x: 0.5,
      y: 0.5,
      offsetX: -3,
      offsetY: -3,
      cursorStyleHandler:
        fabric.controlsUtils.scaleCursorStyleHandler,
      actionHandler: fabric.controlsUtils.scalingEqually,
      render: function (ctx, left, top, styleOverride, fabricObject) {
        const wsize = 27;
        const hsize = 27;
        ctx.save();
        ctx.translate(left, top);
        ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
        ctx.drawImage(brcrop, -wsize / 2, -hsize / 2, wsize, hsize);
        ctx.restore();
      },
    });
    canvas.add(cropUI);
    canvas.setActiveObject(cropUI);
    canvas.renderAll();
    cropleft = cropUI.get('left');
    croptop = cropUI.get('top');
    cropscalex = cropUI.get('scaleX') - 0.03;
    cropscaley = cropUI.get('scaleY') - 0.03;
  }
}
$(document).on('click', '#crop-image', function () {
  if (canvas.getActiveObject()) {
    cropImage(canvas.getActiveObject());
  }
});

// Add an image to the canvas
function newImage(file, x, y, width, center) {
  var newimg = new fabric.Image(file, {
    left: x,
    top: y,
    originX: 'center',
    originY: 'center',
    stroke: '#000',
    strokeUniform: true,
    strokeWidth: 0,
    paintFirst: 'stroke',
    absolutePositioned: true,
    id: 'Image' + layer_count,
    inGroup: false,
    strokeDashArray: false,
    objectCaching: true,
    shadow: {
      color: 'black',
      offsetX: 0,
      offsetY: 0,
      blur: 0,
      opacity: 0,
    },
  });
  files.push({ name: newimg.get('id'), file: file.src });
  canvas.add(newimg);
  newimg.scaleToWidth(width);
  newimg.set({
    scaleX: parseFloat(newimg.get('scaleX').toFixed(2)),
    scaleY: parseFloat(newimg.get('scaleY').toFixed(2)),
    ogWidth: newimg.get('width'),
    ogHeight: newimg.get('height'),
  });
  canvas.bringToFront(newimg);
  canvas.renderAll();
  newLayer(newimg);
  canvas.setActiveObject(newimg);
  if (center) {
    newimg.set(
      'left',
      artboard.get('left') + artboard.get('width') / 2
    );
    newimg.set(
      'top',
      artboard.get('top') + artboard.get('height') / 2
    );
    canvas.renderAll();
  }
  $('#load-image').removeClass('loading-active');
}

function loadImage(src, x, y, width, center) {
  var image = new Image();
  image.onload = function (img) {
    newImage(image, x, y, width, center);
  };
  image.src = src;
}

function createVideoThumbnail(file, max, seekTo = 0.0, isURL) {
  return new Promise((resolve, reject) => {
    const videoPlayer = document.createElement('video');
    if (isURL) {
      videoPlayer.setAttribute('src', file);
    } else {
      videoPlayer.setAttribute('src', URL.createObjectURL(file));
    }
    videoPlayer.setAttribute('crossorigin', 'anonymous');
    videoPlayer.load();
    videoPlayer.addEventListener('error', (ex) => {
      reject('error when loading video file', ex);
    });
    videoPlayer.addEventListener('loadedmetadata', () => {
      if (videoPlayer.duration < seekTo) {
        reject('video is too short.');
        return;
      }
      setTimeout(() => {
        videoPlayer.currentTime = seekTo;
      }, 200);
      videoPlayer.addEventListener('seeked', () => {
        var oc = document.createElement('canvas');
        var octx = oc.getContext('2d');
        oc.width = videoPlayer.videoWidth;
        oc.height = videoPlayer.videoheight;
        octx.drawImage(videoPlayer, 0, 0);
        if (videoPlayer.videoWidth > videoPlayer.videoHeight) {
          oc.height =
            (videoPlayer.videoHeight / videoPlayer.videoWidth) * max;
          oc.width = max;
        } else {
          oc.width =
            (videoPlayer.videoWidth / videoPlayer.videoHeight) * max;
          oc.height = max;
        }
        octx.drawImage(oc, 0, 0, oc.width, oc.height);
        octx.drawImage(videoPlayer, 0, 0, oc.width, oc.height);
        resolve(oc.toDataURL());
      });
    });
  });
}

function createThumbnail(file, max) {
  return new Promise(function (resolve, reject) {
    var reader = new FileReader();
    reader.onload = function (event) {
      var img = new Image();
      img.onload = function () {
        if (img.width > max) {
          var oc = document.createElement('canvas');
          var octx = oc.getContext('2d');
          oc.width = img.width;
          oc.height = img.height;
          octx.drawImage(img, 0, 0);
          if (img.width > img.height) {
            oc.height = (img.height / img.width) * max;
            oc.width = max;
          } else {
            oc.width = (img.width / img.height) * max;
            oc.height = max;
          }
          octx.drawImage(oc, 0, 0, oc.width, oc.height);
          octx.drawImage(img, 0, 0, oc.width, oc.height);
          resolve(oc.toDataURL());
        } else {
          resolve(img.src);
        }
      };
      img.src = event.target.result;
    };
    reader.readAsDataURL(file);
  });
}

function dataURItoBlob(dataURI) {
  var byteString = atob(dataURI.split(',')[1]);
  var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
  var ab = new ArrayBuffer(byteString.length);
  var ia = new Uint8Array(ab);
  for (var i = 0; i < byteString.length; i++) {
    ia[i] = byteString.charCodeAt(i);
  }
  var blob = new Blob([ab], { type: mimeString });
  return blob;
}

async function uploadFromURL() {
  var url = $('#upload-link-input').val();
  let file = await fetch(url).then((r) => r.blob());
  if (file.type.split('/')[0] === 'image') {
    $('#upload-link-input').val('');
    $('.upload-show').removeClass('upload-show');
    createThumbnail(file, 250).then(function (data) {
      saveFile(
        dataURItoBlob(data),
        file,
        file.type.split('/')[0],
        'temp',
        false,
        false
      );
    });
  } else if (file.type.split('/')[0] === 'video') {
    $('.upload-show').removeClass('upload-show');
    createVideoThumbnail(file, 250, 0, false).then(function (data) {
      saveFile(
        dataURItoBlob(data),
        file,
        file.type.split('/')[0],
        'temp',
        false,
        false
      );
    });
    $('#upload-link-input').val('');
  } else {
    alert('File type not accepted');
  }
}
$(document).on('click', '#upload-link-add', uploadFromURL);

function handleUpload(custom = false) {
  var files2;
  if (custom == false) {
    files2 = $('#filepick').get(0).files;
  } else {
    files2 = custom.originalEvent.dataTransfer.files;
  }
  if (files2) {
    Array.from(files2).forEach((file) => {
      uploading = true;
      if (file.size / 1024 / 1024 <= 10) {
        $('#upload-button').html(
          "<img src='assets/upload.svg'> Uploading..."
        );
        $('#upload-button').addClass('uploading');
        if (file['type'].split('/')[0] === 'image') {
          $('.upload-show').removeClass('upload-show');
          createThumbnail(file, 250).then(function (data) {
            saveFile(
              dataURItoBlob(data),
              file,
              file['type'].split('/')[0],
              'temp',
              false,
              false
            );
          });
        } else if (file['type'].split('/')[0] === 'video') {
          $('.upload-show').removeClass('upload-show');
          createVideoThumbnail(file, 250, 0, false).then(function (
            data
          ) {
            saveFile(
              dataURItoBlob(data),
              file,
              file['type'].split('/')[0],
              'temp',
              false,
              false
            );
          });
        } else {
          alert('File type not accepted');
        }
      } else {
        alert('File is too big');
      }
    });
    if (files2.length == 1) {
      if (files2[0]['type'].split('/')[0] === 'image') {
        $('.upload-tab-active').removeClass('upload-tab-active');
        $('#images-tab').addClass('upload-tab-active');
      } else if (files2[0]['type'].split('/')[0] === 'video') {
        $('.upload-tab-active').removeClass('upload-tab-active');
        $('#videos-tab').addClass('upload-tab-active');
      } else if (files2[0]['type'].split('/')[0] === 'audio') {
        $('.upload-tab-active').removeClass('upload-tab-active');
        $('#audio-tab').addClass('upload-tab-active');
      }
    }
  }
}
$(document).on('change', '#filepick', function () {
  handleUpload(false);
});

// Upload audio
function audioUpload() {
  const files = $('#filepick2').get(0).files;
  if (files) {
    if (files.length == 1) {
      if (files[0]['type'].split('/')[0] === 'audio') {
        if (files[0].size / 1024 / 1024 <= 10) {
          $('#audio-upload-button').html('Uploading...');
          $('#audio-upload-button').addClass('uploading');
          saveAudio(files[0]);
        } else {
          alert('File is too big');
        }
      } else {
        alert('Wrong file type');
      }
    }
  }
}
$(document).on('change', '#filepick2', audioUpload);

// Create a rectangle
function newRectangle(color) {
  var newrect = new fabric.Rect({
    left: 0,
    top: 0,
    originX: 'center',
    originY: 'center',
    width: 200,
    height: 200,
    stroke: '#000',
    strokeWidth: 0,
    strokeUniform: true,
    backgroundColor: 'rgba(255,255,255,0)',
    rx: 0,
    ry: 0,
    fill: color,
    cursorWidth: 1,
    cursorDuration: 1,
    paintFirst: 'stroke',
    cursorDelay: 250,
    strokeDashArray: false,
    inGroup: false,
    id: 'Shape' + layer_count,
    shadow: {
      color: '#000',
      offsetX: 0,
      offsetY: 0,
      blur: 0,
      opacity: 0,
    },
  });
  canvas.add(newrect);
  newLayer(newrect);
  canvas.setActiveObject(newrect);
  canvas.bringToFront(newrect);
  canvas.renderAll();
}

// Change text format
function formatText() {
  var isselected = false;
  if (!canvas.getActiveObject().isEditing) {
    canvas.getActiveObject().enterEditing();
    canvas.getActiveObject().selectAll();
    isselected = true;
  }
  if ($(this).hasClass('format-text-active')) {
    if ($(this).attr('id') == 'format-bold') {
      $(this).find('img').attr('src', 'assets/bold.svg');
      canvas
        .getActiveObject()
        .setSelectionStyles({ fontWeight: 'normal' });
    } else if ($(this).attr('id') == 'format-italic') {
      $(this).find('img').attr('src', 'assets/italic.svg');
      canvas
        .getActiveObject()
        .setSelectionStyles({ fontStyle: 'normal' });
    } else if ($(this).attr('id') == 'format-underline') {
      $(this).find('img').attr('src', 'assets/underline.svg');
      canvas
        .getActiveObject()
        .setSelectionStyles({ underline: false });
    } else {
      $(this).find('img').attr('src', 'assets/strike.svg');
      canvas
        .getActiveObject()
        .setSelectionStyles({ linethrough: false });
    }
    $(this).removeClass('format-text-active');
  } else {
    $(this).addClass('format-text-active');
    if ($(this).attr('id') == 'format-bold') {
      $(this).find('img').attr('src', 'assets/bold-active.svg');
      canvas
        .getActiveObject()
        .setSelectionStyles({ fontWeight: 'bold' });
    } else if ($(this).attr('id') == 'format-italic') {
      $(this).find('img').attr('src', 'assets/italic-active.svg');
      canvas
        .getActiveObject()
        .setSelectionStyles({ fontStyle: 'italic' });
    } else if ($(this).attr('id') == 'format-underline') {
      $(this).find('img').attr('src', 'assets/underline-active.svg');
      canvas
        .getActiveObject()
        .setSelectionStyles({ underline: true });
    } else {
      $(this).find('img').attr('src', 'assets/strike-active.svg');
      canvas
        .getActiveObject()
        .setSelectionStyles({ linethrough: true });
    }
  }
  if (isselected) {
    canvas.getActiveObject().exitEditing();
  }
  canvas.renderAll();
  save();
}
$(document).on('click', '.format-text', formatText);

// Change stroke type (e.g. dashed), ignore naming (used to be for line join)
function lineJoin() {
  if ($('.line-join-active').attr('id') == 'miter') {
    $('.line-join-active')
      .find('img')
      .attr('src', 'assets/miter.svg');
  } else if ($('.line-join-active').attr('id') == 'bevel') {
    $('.line-join-active')
      .find('img')
      .attr('src', 'assets/bevel.svg');
  } else if ($('.line-join-active').attr('id') == 'round') {
    $('.line-join-active')
      .find('img')
      .attr('src', 'assets/round.svg');
  } else if ($('.line-join-active').attr('id') == 'small-dash') {
    $('.line-join-active')
      .find('img')
      .attr('src', 'assets/dash2.svg');
  }
  $('.line-join-active').removeClass('line-join-active');
  $(this).addClass('line-join-active');
  if ($(this).attr('id') == 'miter') {
    $(this).find('img').attr('src', 'assets/miter-active.svg');
    canvas
      .getActiveObject()
      .set({ strokeWidth: 0, strokeDashArray: false });
    canvas.renderAll();
    updatePanelValues();
  } else if ($(this).attr('id') == 'bevel') {
    $(this).find('img').attr('src', 'assets/bevel-active.svg');
    canvas.getActiveObject().set({ strokeDashArray: false });
    if (canvas.getActiveObject().get('strokeWidth') == 0) {
      canvas.getActiveObject().set({ strokeWidth: 1 });
      canvas.renderAll();
      updatePanelValues();
    }
  } else if ($(this).attr('id') == 'round') {
    $(this).find('img').attr('src', 'assets/round-active.svg');
    canvas.getActiveObject().set({ strokeDashArray: [10, 5] });
    if (canvas.getActiveObject().get('strokeWidth') == 0) {
      canvas.getActiveObject().set({ strokeWidth: 1 });
      canvas.renderAll();
      updatePanelValues();
    }
  } else {
    $(this).find('img').attr('src', 'assets/dash2-active.svg');
    canvas.getActiveObject().set({ strokeDashArray: [3, 3] });
    if (canvas.getActiveObject().get('strokeWidth') == 0) {
      canvas.getActiveObject().set({ strokeWidth: 1 });
      canvas.renderAll();
      updatePanelValues();
    }
  }
  canvas.renderAll();
  save();
}
$(document).on('click', '.line-join', lineJoin);

// Change text alignment
function alignText() {
  var textalign;
  if ($('.align-text-active').attr('id') == 'align-text-left') {
    $('.align-text-active')
      .find('img')
      .attr('src', 'assets/align-text-left.svg');
  } else if (
    $('.align-text-active').attr('id') == 'align-text-center'
  ) {
    $('.align-text-active')
      .find('img')
      .attr('src', 'assets/align-text-center.svg');
  } else if (
    $('.align-text-active').attr('id') == 'align-text-right'
  ) {
    $('.align-text-active')
      .find('img')
      .attr('src', 'assets/align-text-right.svg');
  } else {
    $('.align-text-active')
      .find('img')
      .attr('src', 'assets/align-text-justify.svg');
  }
  $('.align-text-active').removeClass('align-text-active');
  $(this).addClass('align-text-active');
  if ($(this).attr('id') == 'align-text-left') {
    textalign = 'left';
    $(this)
      .find('img')
      .attr('src', 'assets/align-text-left-active.svg');
  } else if ($(this).attr('id') == 'align-text-center') {
    textalign = 'center';
    $(this)
      .find('img')
      .attr('src', 'assets/align-text-center-active.svg');
  } else if ($(this).attr('id') == 'align-text-right') {
    textalign = 'right';
    $(this)
      .find('img')
      .attr('src', 'assets/align-text-right-active.svg');
  } else {
    textalign = 'justify';
    $(this)
      .find('img')
      .attr('src', 'assets/align-text-justify-active.svg');
  }
  canvas.getActiveObject().set({ textAlign: textalign });
  canvas.renderAll();
  save();
}
$(document).on('click', '.align-text', alignText);

// Change font
function changeFont() {
  var font = $('#font-picker').val();
  if (canvas.getActiveObject().get('assetType')) {
    WebFont.load({
      google: {
        families: [font],
      },
      active: () => {
        var object = canvas.getActiveObject();
        animatedtext
          .find((x) => x.id == object.id)
          .reset(
            animatedtext.find((x) => x.id == object.id).text,
            $.extend(
              animatedtext.find((x) => x.id == object.id).props,
              { fontFamily: font }
            ),
            canvas
          );
        save();
      },
    });
    save();
  } else {
    WebFont.load({
      google: {
        families: [font],
      },
      active: () => {
        canvas.getActiveObject().set('fontFamily', font);
        canvas.renderAll();
        save();
      },
    });
  }
}
$(document).on('change', '#font-picker', changeFont);

// Calculate text width to display it in 1 line
function calculateTextWidth(text, font) {
  var ctx = canvas.getContext('2d');
  ctx.font = font;
  return ctx.measureText(text).width + 10;
}

// Create an audio layer
function newAudioLayer(src) {
  var audio = new Audio(src);
  audio.crossOrigin = 'anonymous';
  audio.addEventListener('loadeddata', () => {
    var nullobject = new fabric.Rect({
      id: 'Audio' + layer_count,
      width: 10,
      height: 10,
      audioSrc: src,
      duration: audio.duration * 1000,
      opacity: 0,
      selectable: false,
      volume: 0.5,
      assetType: 'audio',
      shadow: {
        color: '#000',
        offsetX: 0,
        offsetY: 0,
        blur: 0,
        opacity: 0,
      },
    });
    canvas.add(nullobject);
    newLayer(nullobject);
  });
}

// Create a textbox
function newTextbox(
  fontsize,
  fontweight,
  text,
  x,
  y,
  width,
  center,
  font
) {
  var newtext = new fabric.Textbox(text, {
    left: x,
    top: y,
    originX: 'center',
    originY: 'center',
    fontFamily: 'Inter',
    fill: '#000',
    fontSize: fontsize,
    fontWeight: fontweight,
    textAlign: 'center',
    cursorWidth: 1,
    stroke: '#000',
    strokeWidth: 0,
    cursorDuration: 1,
    paintFirst: 'stroke',
    objectCaching: false,
    absolutePositioned: true,
    strokeUniform: true,
    inGroup: false,
    cursorDelay: 250,
    strokeDashArray: false,
    width: calculateTextWidth(
      text,
      fontweight + ' ' + fontsize + 'px Inter'
    ),
    id: 'Text' + layer_count,
    shadow: {
      color: '#000',
      offsetX: 0,
      offsetY: 0,
      blur: 0,
      opacity: 0,
    },
  });
  newtext.setControlsVisibility({
    mt: false,
    mb: false,
  });
  canvas.add(newtext);
  newLayer(newtext);
  canvas.setActiveObject(newtext);
  canvas.bringToFront(newtext);
  newtext.enterEditing();
  newtext.selectAll();
  canvas.renderAll();
  if (center) {
    newtext.set(
      'left',
      artboard.get('left') + artboard.get('width') / 2
    );
    newtext.set(
      'top',
      artboard.get('top') + artboard.get('height') / 2
    );
    canvas.renderAll();
  }
  canvas.getActiveObject().set('fontFamily', font);
  canvas.renderAll();
}

function deleteObject(object, def = true) {
  if (object.get('assetType') == 'animatedText' && def) {
    animatedtext = $.grep(animatedtext, function (a) {
      return a.id != object.id;
    });
  }
  if (object.type == 'image') {
    var temp = files.find((x) => x.name == object.get('id'));
    files = $.grep(files, function (a) {
      return a != temp.name;
    });
  }
  $(".layer[data-object='" + object.get('id') + "']").remove();
  $('#' + object.get('id')).remove();
  keyframes = $.grep(keyframes, function (e) {
    return e.id != object.get('id');
  });
  p_keyframes = $.grep(p_keyframes, function (e) {
    return e.id != object.get('id');
  });
  objects = $.grep(objects, function (e) {
    return e.id != object.get('id');
  });
  canvas.remove(object);
  canvas.renderAll();
  canvas.discardActiveObject();
  save();
  if (objects.length == 0) {
    $('#nolayers').removeClass('yaylayers');
  }
}

// Delete selected object
function deleteSelection() {
  if (
    canvas.getActiveObject() &&
    !canvas.getActiveObject().isEditing
  ) {
    const selection = canvas.getActiveObject();
    if (selection.type == 'activeSelection') {
      canvas.discardActiveObject();
      selection._objects.forEach(function (object) {
        deleteObject(object);
      });
    } else {
      deleteObject(canvas.getActiveObject());
    }
  }
}

// Expand / collapse layer
function toggleLayer() {
  const layerid = $(this).parent().parent().attr('data-object');
  $(this).parent().parent().find('.properties').toggle();
  $(this).parent().parent().find('.droparrow').toggleClass('layeron');
  $(".keyframe-row[data-object='" + layerid + "']").toggle();
  setTimelineZoom(timelinetime);
}
$(document).on('click', '.droparrow', toggleLayer);

// Select layer
function selectLayer(e) {
  if (!$(e.target).hasClass('droparrow')) {
    const layerid = $(this).parent().attr('data-object');
    $('.layer-selected').removeClass('layer-selected');
    $(this).parent().addClass('layer-selected');
    canvas.setActiveObject(canvas.getItemById(layerid));
  }
}
$(document).on('click', '.layer-name', selectLayer);

// Set video duration
function setDuration(length) {
  $('#inner-timeline').css('width', length / timelinetime + 50);
  $('#inner-seekarea').css('width', length / timelinetime + 50);
  duration = length;
  var minutes = Math.floor(duration / 1000 / 60);
  var seconds = (duration / 1000 - minutes * 60).toFixed(2);
  $('#total-time input').val(
    ('0' + minutes).slice(-2) +
      ':' +
      ('0' + Math.floor(seconds)).slice(-2) +
      ':' +
      ('0' + Math.floor((seconds % 1) * 100)).slice(-2)
  );
  $('.object-props').each(function () {
    $(this).css(
      'width',
      duration / timelinetime -
        p_keyframes.find((x) => x.id == $(this).attr('id')).start /
          timelinetime +
        'px'
    );
    p_keyframes.find((x) => x.id == $(this).attr('id')).end =
      duration;
    if (
      p_keyframes.find((x) => x.id == $(this).attr('id')).trimend >
      p_keyframes.find((x) => x.id == $(this).attr('id')).end
    ) {
      p_keyframes.find((x) => x.id == $(this).attr('id')).trimend =
        duration;
      $(this)
        .find('.trim-row')
        .css(
          'width',
          duration / timelinetime -
            p_keyframes.find((x) => x.id == $(this).attr('id'))
              .trimstart /
              timelinetime +
            'px'
        );
    }
  });
  setTimelineZoom(timelinetime);
  save();
}

// Render time markers
function renderTimeMarkers() {
  var renderoffset = 1000 / timelinetime - 20;
  var timenumber = 0;
  var modulo = 1;
  if (timelinetime > 18) {
    modulo = 5;
  } else if (timelinetime > 12) {
    modulo = 2;
  }
  $('#time-numbers').html('');
  $('#time-numbers').append(
    "<div class='time-number' style='margin-left:" +
      offset_left +
      "px'>" +
      timenumber +
      's<span></span></div>'
  );
  timenumber++;
  while (timenumber * 1000 <= duration) {
    $('#time-numbers').append(
      "<div class='time-number' style='margin-left:" +
        renderoffset +
        "px'>" +
        timenumber +
        's<span></span></div>'
    );
    if (timenumber % modulo != 0) {
      $('.time-number:last-child()').css('opacity', '0');
    }
    timenumber++;
  }
}

// Change timeline zoom level
function setTimelineZoom(time) {
  $('.object-props').each(function () {
    $(this).offset({
      left:
        p_keyframes.find((x) => x.id == $(this).attr('id')).start /
          time +
        $('#inner-timeline').offset().left +
        offset_left,
    });
    $(this).css({ width: ($(this).width() * timelinetime) / time });
    $(this)
      .find('.trim-row')
      .css({
        left:
          p_keyframes.find((x) => x.id == $(this).attr('id'))
            .trimstart / time,
      });
    $(this)
      .find('.trim-row')
      .css({
        width:
          ($(this).find('.trim-row').width() * timelinetime) / time,
      });
  });
  timelinetime = time;
  $('.keyframe').each(function () {
    $(this).offset({
      left:
        $(this).attr('data-time') / timelinetime +
        $('#inner-timeline').offset().left +
        offset_left,
    });
  });
  $('#seekbar').offset({
    left:
      $('#inner-timeline').offset().left +
      currenttime / timelinetime +
      offset_left,
  });
  $('#inner-timeline').css({ width: duration / timelinetime + 50 });
  $('#inner-seekarea').css({
    minWidth: duration / timelinetime + 50,
  });
  renderTimeMarkers();
}
$(document).on('input', '#timeline-zoom', function () {
  setTimelineZoom($('#timeline-zoom').val());
});

function removeKeyframe() {
  keyframes = $.grep(keyframes, function (e) {
    return (
      e.t != selectedkeyframe.attr('data-time') ||
      e.id != selectedkeyframe.attr('data-object') ||
      e.name != selectedkeyframe.attr('data-property')
    );
  });
  if (selectedkeyframe.attr('data-property') == 'left') {
    keyframes = $.grep(keyframes, function (e) {
      return (
        e.t != selectedkeyframe.attr('data-time') ||
        e.id != selectedkeyframe.attr('data-object') ||
        e.name != 'top'
      );
    });
  } else if (selectedkeyframe.attr('data-property') == 'scaleX') {
    keyframes = $.grep(keyframes, function (e) {
      return (
        e.t != selectedkeyframe.attr('data-time') ||
        e.id != selectedkeyframe.attr('data-object') ||
        e.name != 'scaleY'
      );
    });
    keyframes = $.grep(keyframes, function (e) {
      return (
        e.t != selectedkeyframe.attr('data-time') ||
        e.id != selectedkeyframe.attr('data-object') ||
        e.name != 'width'
      );
    });
    keyframes = $.grep(keyframes, function (e) {
      return (
        e.t != selectedkeyframe.attr('data-time') ||
        e.id != selectedkeyframe.attr('data-object') ||
        e.name != 'height'
      );
    });
  } else if (
    selectedkeyframe.attr('data-property') == 'strokeWidth'
  ) {
    keyframes = $.grep(keyframes, function (e) {
      return (
        e.t != selectedkeyframe.attr('data-time') ||
        e.id != selectedkeyframe.attr('data-object') ||
        e.name != 'stroke'
      );
    });
  } else if (
    selectedkeyframe.attr('data-property') == 'shadow.color'
  ) {
    keyframes = $.grep(keyframes, function (e) {
      return (
        e.t != selectedkeyframe.attr('data-time') ||
        e.id != selectedkeyframe.attr('data-object') ||
        e.name != 'shadow.blur'
      );
    });
    keyframes = $.grep(keyframes, function (e) {
      return (
        e.t != selectedkeyframe.attr('data-time') ||
        e.id != selectedkeyframe.attr('data-object') ||
        e.name != 'shadow.offsetX'
      );
    });
    keyframes = $.grep(keyframes, function (e) {
      return (
        e.t != selectedkeyframe.attr('data-time') ||
        e.id != selectedkeyframe.attr('data-object') ||
        e.name != 'shadow.offsetY'
      );
    });
    keyframes = $.grep(keyframes, function (e) {
      return (
        e.t != selectedkeyframe.attr('data-time') ||
        e.id != selectedkeyframe.attr('data-object') ||
        e.name != 'shadow.opacity'
      );
    });
  } else if (
    selectedkeyframe.attr('data-property') == 'charSpacing'
  ) {
    keyframes = $.grep(keyframes, function (e) {
      return (
        e.t != selectedkeyframe.attr('data-time') ||
        e.id != selectedkeyframe.attr('data-object') ||
        e.name != 'lineHeight'
      );
    });
  }
  selectedkeyframe.remove();
  $('#keyframe-properties').removeClass('show-properties');
}

// Delete a keyframe
function deleteKeyframe() {
  if (shiftkeys.length > 0) {
    shiftkeys.forEach(function (key) {
      selectedkeyframe = $(key.keyframe);
      removeKeyframe();
    });
    shiftkeys = [];
  } else {
    removeKeyframe();
  }
  animate(false, currenttime);
  save();
}
$(document).on('click', '#delete-keyframe', deleteKeyframe);

// Copy keyframes
function copyKeyframes() {
  clipboard.sort(function (a, b) {
    return a.t - b.t;
  });
  var inittime = clipboard[0].t;
  clipboard.forEach(function (keyframe) {
    var newtime = keyframe.t - inittime + currenttime;
    newKeyframe(
      keyframe.name,
      canvas.getItemById(keyframe.id),
      newtime,
      keyframe.value,
      true
    );
    var keyprop = keyframe.name;
    if (keyprop == 'left') {
      const keyarr2 = $.grep(keyframes, function (e) {
        return (
          e.t == keyframe.t && e.id == keyframe.id && e.name == 'top'
        );
      });
      newKeyframe(
        'top',
        canvas.getItemById(keyframe.id),
        newtime,
        keyarr2[0].value,
        true
      );
    } else if (keyprop == 'scaleX') {
      var keyarr2 = $.grep(keyframes, function (e) {
        return (
          e.t == keyframe.t &&
          e.id == keyframe.id &&
          e.name == 'scaleY'
        );
      });
      newKeyframe(
        'scaleY',
        canvas.getItemById(keyframe.id),
        newtime,
        keyarr2[0].value,
        true
      );
      var keyarr2 = $.grep(keyframes, function (e) {
        return (
          e.t == keyframe.t &&
          e.id == keyframe.id &&
          e.name == 'width'
        );
      });
      if (keyarr2.length > 0) {
        newKeyframe(
          'width',
          canvas.getItemById(keyframe.id),
          newtime,
          keyarr2[0].value,
          true
        );
      }
      var keyarr2 = $.grep(keyframes, function (e) {
        return (
          e.t == keyframe.t &&
          e.id == keyframe.id &&
          e.name == 'height'
        );
      });
      if (keyarr2.length > 0) {
        newKeyframe(
          'height',
          canvas.getItemById(keyframe.id),
          newtime,
          keyarr2[0].value,
          true
        );
      }
    } else if (keyprop == 'strokeWidth') {
      const keyarr2 = $.grep(keyframes, function (e) {
        return (
          e.t == keyframe.t &&
          e.id == keyframe.id &&
          e.name == 'stroke'
        );
      });
      newKeyframe(
        'stroke',
        canvas.getItemById(keyframe.id),
        newtime,
        keyarr2[0].value,
        true
      );
    } else if (keyprop == 'charSpacing') {
      const keyarr2 = $.grep(keyframes, function (e) {
        return (
          e.t == keyframe.t &&
          e.id == keyframe.id &&
          e.name == 'lineHeight'
        );
      });
      newKeyframe(
        'lineHeight',
        canvas.getItemByid(keyframe.id),
        newtime,
        keyarr2[0].value,
        true
      );
    } else if (keyprop == 'shadow.color') {
      var keyarr2 = $.grep(keyframes, function (e) {
        return (
          e.t == keyframe.t &&
          e.id == keyframe.id &&
          e.name == 'shadow.opacity'
        );
      });
      newKeyframe(
        'shadow.opacity',
        canvas.getItemById(keyframe.id),
        newtime,
        keyarr2[0].value,
        true
      );
      var keyarr2 = $.grep(keyframes, function (e) {
        return (
          e.t == keyframe.t &&
          e.id == keyframe.id &&
          e.name == 'shadow.offsetX'
        );
      });
      newKeyframe(
        'shadow.offsetX',
        canvas.getItemById(keyframe.id),
        newtime,
        keyarr2[0].value,
        true
      );
      var keyarr2 = $.grep(keyframes, function (e) {
        return (
          e.t == keyframe.t &&
          e.id == keyframe.id &&
          e.name == 'shadow.offsetY'
        );
      });

      newKeyframe(
        'shadow.offsetY',
        canvas.getItemById(keyframe.id),
        newtime,
        keyarr2[0].value,
        true
      );
      var keyarr2 = $.grep(keyframes, function (e) {
        return (
          e.t == keyframe.t &&
          e.id == keyframe.id &&
          e.name == 'shadow.blur'
        );
      });
      newKeyframe(
        'shadow.blur',
        canvas.getItemById(keyframe.id),
        newtime,
        keyarr2[0].value,
        true
      );
    }
    save();
  });
}

// Update keyframe (after dragging)
function updateKeyframe(drag, newval, offset) {
  var time = parseFloat(
    (drag.position().left * timelinetime).toFixed(1)
  );
  const keyprop = drag.attr('data-property');
  const keytime = drag.attr('data-time');
  const keyarr = $.grep(keyframes, function (e) {
    return (
      e.t == parseFloat(keytime) &&
      e.id == drag.attr('data-object') &&
      e.name == keyprop
    );
  });
  const keyobj = canvas.getItemById(keyarr[0].id);
  time =
    parseFloat(
      p_keyframes.find((x) => x.id == keyobj.get('id')).start
    ) + time;
  if (newval) {
    time = currenttime;
  }
  var keyval = keyarr[0].value;
  if (newval) {
    if (keyprop == 'shadow.color') {
      keyval = keyobj.shadow.color;
    } else if (keyprop == 'volume') {
      keyval = parseFloat($('#object-volume input').val() / 200);
    } else {
      keyval = keyobj.get(keyprop);
    }
  } else if (keyprop == 'left') {
    keyval = keyval + artboard.get('left');
  }
  keyframes = $.grep(keyframes, function (e) {
    return (
      e.t != parseFloat(keytime) ||
      e.id != drag.attr('data-object') ||
      e.name != keyprop
    );
  });
  newKeyframe(keyprop, keyobj, time, keyval, false);
  if (keyprop == 'left') {
    const keyarr2 = $.grep(keyframes, function (e) {
      return (
        e.t == parseFloat(keytime) &&
        e.id == drag.attr('data-object') &&
        e.name == 'top'
      );
    });
    var keyval2 = keyarr2[0].value + artboard.get('top');
    if (newval) {
      keyval2 = canvas.getItemById(keyarr2[0].id).get('top');
    }
    keyframes = $.grep(keyframes, function (e) {
      return (
        e.t != parseFloat(keytime) ||
        e.id != drag.attr('data-object') ||
        e.name != 'top'
      );
    });
    newKeyframe('top', keyobj, time, keyval2, false);
  } else if (keyprop == 'scaleX') {
    var keyarr2 = $.grep(keyframes, function (e) {
      return (
        e.t == parseFloat(keytime) &&
        e.id == drag.attr('data-object') &&
        e.name == 'scaleY'
      );
    });
    var keyval2 = keyarr2[0].value;
    if (newval) {
      keyval2 = canvas.getItemById(keyarr2[0].id).get('scaleY');
    }
    keyframes = $.grep(keyframes, function (e) {
      return (
        e.t != parseFloat(keytime) ||
        e.id != drag.attr('data-object') ||
        e.name != 'scaleY'
      );
    });
    newKeyframe('scaleY', keyobj, time, keyval2, false);
    var keyarr2 = $.grep(keyframes, function (e) {
      return (
        e.t == parseFloat(keytime) &&
        e.id == drag.attr('data-object') &&
        e.name == 'width'
      );
    });
    if (keyarr2.length > 0) {
      var keyval2 = keyarr2[0].value;
      if (newval) {
        keyval2 = canvas.getItemById(keyarr2[0].id).get('width');
      }
      keyframes = $.grep(keyframes, function (e) {
        return (
          e.t != parseFloat(keytime) ||
          e.id != drag.attr('data-object') ||
          e.name != 'width'
        );
      });
      newKeyframe('width', keyobj, time, keyval2, false);
    }
    var keyarr2 = $.grep(keyframes, function (e) {
      return (
        e.t == parseFloat(keytime) &&
        e.id == drag.attr('data-object') &&
        e.name == 'height'
      );
    });
    if (keyarr2.length > 0) {
      var keyval2 = keyarr2[0].value;
      if (newval) {
        keyval2 = canvas.getItemById(keyarr2[0].id).get('height');
      }
      keyframes = $.grep(keyframes, function (e) {
        return (
          e.t != parseFloat(keytime) ||
          e.id != drag.attr('data-object') ||
          e.name != 'height'
        );
      });
      newKeyframe('height', keyobj, time, keyval2, false);
    }
  } else if (keyprop == 'strokeWidth') {
    const keyarr2 = $.grep(keyframes, function (e) {
      return (
        e.t == parseFloat(keytime) &&
        e.id == drag.attr('data-object') &&
        e.name == 'stroke'
      );
    });
    var keyval2 = keyarr2[0].value;
    if (newval) {
      keyval2 = canvas.getItemById(keyarr2[0].id).get('stroke');
    }
    keyframes = $.grep(keyframes, function (e) {
      return (
        e.t != parseFloat(keytime) ||
        e.id != drag.attr('data-object') ||
        e.name != 'stroke'
      );
    });
    newKeyframe('stroke', keyobj, time, keyval2, false);
  } else if (keyprop == 'charSpacing') {
    const keyarr2 = $.grep(keyframes, function (e) {
      return (
        e.t == parseFloat(keytime) &&
        e.id == drag.attr('data-object') &&
        e.name == 'lineHeight'
      );
    });
    var keyval2 = keyarr2[0].value;
    if (newval) {
      keyval2 = canvas.getItemById(keyarr2[0].id).get('lineHeight');
    }
    keyframes = $.grep(keyframes, function (e) {
      return (
        e.t != parseFloat(keytime) ||
        e.id != drag.attr('data-object') ||
        e.name != 'lineHeight'
      );
    });
    newKeyframe('lineHeight', keyobj, time, keyval2, false);
  } else if (keyprop == 'shadow.color') {
    var keyarr2 = $.grep(keyframes, function (e) {
      return (
        e.t == parseFloat(keytime) &&
        e.id == drag.attr('data-object') &&
        e.name == 'shadow.opacity'
      );
    });
    var keyval2 = keyarr2[0].value;
    if (newval) {
      keyval2 = canvas.getItemById(keyarr2[0].id).shadow.opacity;
    }
    keyframes = $.grep(keyframes, function (e) {
      return (
        e.t != parseFloat(keytime) ||
        e.id != drag.attr('data-object') ||
        e.name != 'shadow.opacity'
      );
    });
    newKeyframe('shadow.opacity', keyobj, time, keyval2, false);
    var keyarr2 = $.grep(keyframes, function (e) {
      return (
        e.t == parseFloat(keytime) &&
        e.id == drag.attr('data-object') &&
        e.name == 'shadow.offsetX'
      );
    });
    var keyval2 = keyarr2[0].value;
    if (newval) {
      keyval2 = canvas.getItemById(keyarr2[0].id).shadow.offsetX;
    }
    keyframes = $.grep(keyframes, function (e) {
      return (
        e.t != parseFloat(keytime) ||
        e.id != drag.attr('data-object') ||
        e.name != 'shadow.offsetX'
      );
    });
    newKeyframe('shadow.offsetX', keyobj, time, keyval2, false);
    var keyarr2 = $.grep(keyframes, function (e) {
      return (
        e.t == parseFloat(keytime) &&
        e.id == drag.attr('data-object') &&
        e.name == 'shadow.offsetY'
      );
    });
    var keyval2 = keyarr2[0].value;
    if (newval) {
      keyval2 = canvas.getItemById(keyarr2[0].id).shadow.offsetY;
    }
    keyframes = $.grep(keyframes, function (e) {
      return (
        e.t != parseFloat(keytime) ||
        e.id != drag.attr('data-object') ||
        e.name != 'shadow.offsetY'
      );
    });
    newKeyframe('shadow.offsetY', keyobj, time, keyval2, false);
    var keyarr2 = $.grep(keyframes, function (e) {
      return (
        e.t == parseFloat(keytime) &&
        e.id == drag.attr('data-object') &&
        e.name == 'shadow.blur'
      );
    });
    var keyval2 = keyarr2[0].value;
    if (newval) {
      keyval2 = canvas.getItemById(keyarr2[0].id).shadow.blur;
    }
    keyframes = $.grep(keyframes, function (e) {
      return (
        e.t != parseFloat(keytime) ||
        e.id != drag.attr('data-object') ||
        e.name != 'shadow.blur'
      );
    });
    newKeyframe('shadow.blur', keyobj, time, keyval2, false);
  }
  if (offset) {
    drag.attr('data-time', time);
  } else {
    drag.attr(
      'data-time',
      time + p_keyframes.find((x) => x.id == keyarr[0].id).start
    );
  }
  keyframes.sort(function (a, b) {
    if (a.id.indexOf('Group') >= 0 && b.id.indexOf('Group') == -1) {
      return 1;
    } else if (
      b.id.indexOf('Group') >= 0 &&
      a.id.indexOf('Group') == -1
    ) {
      return -1;
    } else {
      return 0;
    }
  });
}

function keyframeSnap(drag) {
  if (shiftkeys.length == 0) {
    if (
      drag.offset().left > $('#seekbar').offset().left - 5 &&
      drag.offset().left < $('#seekbar').offset().left + 5
    ) {
      drag.offset({ left: $('#seekbar').offset().left });
      $('#line-snap').offset({
        left: $('#seekbar').offset().left,
        top: drag.parent().parent().offset().top,
      });
      $('#line-snap').css({
        height: drag.parent().parent().height(),
      });
      $('#line-snap').addClass('line-active');
    } else {
      drag
        .parent()
        .parent()
        .find('.keyframe')
        .each(function (index) {
          if (!drag.is($(this))) {
            if (
              drag.offset().left > $(this).offset().left - 5 &&
              drag.offset().left < $(this).offset().left + 5
            ) {
              drag.offset({ left: $(this).offset().left });
              $('#line-snap').offset({
                left: $(this).offset().left,
                top: drag.parent().parent().offset().top,
              });
              $('#line-snap').css({
                height: drag.parent().parent().height(),
              });
              $('#line-snap').addClass('line-active');
              return false;
            }
          }
          if (index == $('.keyframe').length - 1) {
            $('#line-snap').removeClass('line-active');
          }
        });
    }
  }
}

// Dragging a keyframe
function dragKeyframe(e) {
  if (e.which == 3) {
    return false;
  }
  e.stopPropagation();
  e.preventDefault();
  var inst = this;
  var drag = $(this);
  var pageX = e.pageX;
  var offset = $(this).offset();
  var move = false;
  if (e.shiftKey) {
    if (!$(this).hasClass('keyframe-selected')) {
      shiftkeys.push({
        keyframe: this,
        offset: $(this).offset().left,
      });
      $(this).addClass('keyframe-selected');
    } else {
      shiftkeys = $.grep(shiftkeys, function (e) {
        return e.keyframe != this;
      });
      $(this).removeClass('keyframe-selected');
    }
  }
  if (shiftkeys.length > 0) {
    shiftkeys.forEach(function (key) {
      key.offset = $(key.keyframe).offset().left;
    });
  }
  function draggingKeyframe(e) {
    move = true;
    var left = offset.left + (e.pageX - pageX);
    if (shiftkeys.length == 0) {
      if (left > $('#timearea').offset().left + offset_left) {
        drag.offset({ left: left });
      } else {
        drag.offset({
          left: $('#timearea').offset().left + offset_left,
        });
      }
      keyframeSnap(drag);
    } else {
      shiftkeys.forEach(function (key) {
        if (key.keyframe != inst) {
          $(key.keyframe).offset({
            left: key.offset + (e.pageX - pageX),
          });
          keyframeSnap($(key.keyframe));
        } else {
          drag.offset({ left: left });
          keyframeSnap(drag);
        }
      });
    }
  }
  function releasedKeyframe(e) {
    $('body')
      .off('mousemove', draggingKeyframe)
      .off('mouseup', releasedKeyframe);
    $('#line-snap').removeClass('line-active');
    if (move) {
      if (shiftkeys.length == 0) {
        // Check for 60FPS playback, 16ms "slots"
        var time = parseFloat(
          (drag.position().left * timelinetime).toFixed(1)
        );
        if (time % 16.666 != 0) {
          drag.offset({
            left:
              (Math.ceil(time / 16.666) * 16.666) / timelinetime +
              drag.parent().offset().left,
          });
          updateKeyframe(drag, false);
        } else {
          updateKeyframe(drag, false);
        }
      } else {
        shiftkeys.forEach(function (key) {
          // Check for 60FPS playback, 16ms "slots"
          var time = parseFloat(
            ($(key.keyframe).position().left * timelinetime).toFixed(
              1
            )
          );
          if (time % 16.666 != 0) {
            $(key.keyframe).offset({
              left:
                (Math.ceil(time / 16.666) * 16.666) / timelinetime +
                $(key.keyframe).parent().offset().left,
            });
            updateKeyframe($(key.keyframe), false);
          } else {
            updateKeyframe($(key.keyframe), false);
          }
        });
      }
    } else if (!e.shiftDown) {
      keyframeProperties(inst);
    }
    move = false;
    $('.line-active').removeClass('line-active');
    save();
  }
  $('body')
    .on('mouseup', releasedKeyframe)
    .on('mousemove', draggingKeyframe);
}
$(document).on('mousedown', '.keyframe', dragKeyframe);

// Render current time in the playback area
function renderTime() {
  var minutes = Math.floor(currenttime / 1000 / 60);
  var seconds = (currenttime / 1000 - minutes * 60).toFixed(2);
  $('#current-time input').val(
    ('0' + minutes).slice(-2) +
      ':' +
      ('0' + Math.floor(seconds)).slice(-2) +
      ':' +
      ('0' + Math.floor((seconds % 1) * 100)).slice(-2)
  );
}

// Update current time (and account for frame "slots")
function updateTime(drag, check) {
  if ($('#timeline').scrollLeft() > offset_left) {
    currenttime = parseFloat(
      (
        (drag.position().left +
          $('#timeline').scrollLeft() -
          offset_left) *
        timelinetime
      ).toFixed(1)
    );
  } else {
    currenttime = parseFloat(
      (
        (drag.position().left +
          $('#timeline').scrollLeft() -
          offset_left) *
        timelinetime
      ).toFixed(1)
    );
  }
  // Check for 60FPS playback, 16ms "slots"
  if (currenttime % 16.666 != 0 && !check) {
    currenttime = Math.ceil(currenttime / 16.666) * 16.666;
  }
  renderTime();
  pause();
  animate(false, currenttime);
}

// Dragging the seekbar
function dragSeekBar(e) {
  if (e.which == 3) {
    return false;
  }
  var drag = $(this);
  var pageX = e.pageX;
  var offset = $(this).offset();
  tempselection = canvas.getActiveObject();
  canvas.discardActiveObject();
  function dragging(e) {
    paused = true;
    var left = offset.left + (e.pageX - pageX);
    if (
      left > $('#timearea').offset().left + offset_left &&
      left - $('#timearea').offset().left <
        duration / timelinetime + offset_left
    ) {
      drag.offset({ left: left });
    } else if (left < $('#timearea').offset().left + offset_left) {
      drag.offset({
        left: offset_left + $('#timearea').offset().left,
      });
    }
    if ($('#timeline').scrollLeft() > offset_left) {
      currenttime = parseFloat(
        (
          (drag.position().left +
            $('#timeline').scrollLeft() -
            offset_left) *
          timelinetime
        ).toFixed(1)
      );
    } else {
      currenttime = parseFloat(
        (
          (drag.position().left +
            $('#timeline').scrollLeft() -
            offset_left) *
          timelinetime
        ).toFixed(1)
      );
    }
    animate(false, currenttime);
    seeking = true;
    renderTime();
  }
  function released(e) {
    $('body').off('mousemove', dragging).off('mouseup', released);
    updateTime(drag, false);
    seeking = false;
    if (tempselection && tempselection.type != 'activeSelection') {
      reselect(tempselection);
    }
    updatePanelValues();
  }
  $('body').on('mouseup', released).on('mousemove', dragging);
}
$(document).on('mousedown', '#seekbar', dragSeekBar);

// Dragging layer horizontally
function dragObjectProps(e) {
  if (e.which == 3) {
    return false;
  }
  var drag = $(this).parent();
  var drag2 = $(this).find('.trim-row');
  var target = e.target;
  var pageX = e.pageX;
  var offset = drag.offset();
  var offset2 = drag2.offset();
  var initwidth = drag2.width();
  var initpos = drag2.position().left;
  var opened = false;
  var trim = 'no';
  // Trim layer to hovered area
  if (e.metaKey) {
    if (e.shiftKey) {
      if (drag2.position().left + e.pageX >= 0) {
        drag2.offset({
          left:
            hovertime / timelinetime -
            p_keyframes.find((x) => x.id == drag.attr('id'))
              .trimstart /
              timelinetime +
            offset2.left,
        });
        const leftval = parseFloat(
          (drag2.position().left * timelinetime).toFixed(1)
        );
        p_keyframes.find((x) => x.id == drag.attr('id')).trimstart =
          leftval;
        drag2.css({
          width:
            (p_keyframes.find((x) => x.id == drag.attr('id'))
              .trimend -
              p_keyframes.find((x) => x.id == drag.attr('id'))
                .trimstart) /
            timelinetime,
        });
        return false;
      }
    } else {
      if (
        hovertime +
          p_keyframes.find((x) => x.id == drag.attr('id')).start <
        duration
      ) {
        drag2.css({
          width:
            hovertime / timelinetime -
            p_keyframes.find((x) => x.id == drag.attr('id'))
              .trimstart /
              timelinetime,
        });
        save();
        p_keyframes.find((x) => x.id == drag.attr('id')).end =
          hovertime;
        p_keyframes.find((x) => x.id == drag.attr('id')).trimend =
          hovertime;
      }
      return false;
    }
  }
  if (pageX - $(this).find('.trim-row').offset().left < 7) {
    trim = 'left';
  } else if (
    pageX - $(this).find('.trim-row').offset().left >
    $(this).find('.trim-row').width() - 7
  ) {
    trim = 'right';
  }
  function dragging(e) {
    if (trim == 'no') {
      var left = offset.left + (e.pageX - pageX);
      if (
        left >
        $('#timearea').offset().left +
          offset_left -
          $('#timeline').scrollLeft()
      ) {
        drag.offset({ left: left });
      } else if (
        left + $('#timeline').scrollLeft() <
        $('#timearea').offset().left + offset_left
      ) {
        drag.css({ left: offset_left });
      }
      p_keyframes.find((x) => x.id == drag.attr('id')).start =
        parseFloat(
          (
            (drag.position().left -
              offset_left +
              $('#timeline').scrollLeft()) *
            timelinetime
          ).toFixed(1)
        );
      p_keyframes.find((x) => x.id == drag.attr('id')).end =
        parseFloat(
          (
            (drag.position().left +
              drag.width() -
              offset_left +
              $('#timeline').scrollLeft()) *
            timelinetime
          ).toFixed(1)
        );
      if (
        $(".keyframe-row[data-object='" + drag.attr('id') + "']").is(
          ':hidden'
        )
      ) {
        opened = true;
        $(".layer[data-object='" + drag.attr('id') + "']")
          .find('.properties')
          .toggle();
        $(".layer[data-object='" + drag.attr('id') + "']")
          .find('.properties')
          .toggleClass('layeron');
        $(
          ".keyframe-row[data-object='" + drag.attr('id') + "']"
        ).toggle();
        setTimelineZoom(timelinetime);
      }
      drag.find('.keyframe').each(function () {
        updateKeyframe($(this), false, true);
      });
      animate(false, currenttime);
    } else if (trim == 'left') {
      if (drag2.position().left + (e.pageX - pageX) >= 0) {
        drag2.offset({
          left: offset2.left + (e.pageX - pageX),
        });
        drag2.css({
          width: initwidth - (-initpos + drag2.position().left),
        });
        const leftval = parseFloat(
          (drag2.position().left * timelinetime).toFixed(1)
        );
        p_keyframes.find((x) => x.id == drag.attr('id')).trimstart =
          leftval;
      }
    } else if (trim == 'right') {
      if (initwidth + (e.pageX - pageX) < duration / timelinetime) {
        drag2.css({
          width: initwidth + (e.pageX - pageX),
        });
      } else {
        drag2.css({
          width:
            duration / timelinetime -
            drag.position().left -
            $('#timeline').scrollLeft() +
            offset_left,
        });
      }
      const rightval = parseFloat(
        (
          (drag2.position().left + drag2.width()) *
          timelinetime
        ).toFixed(1)
      );
      p_keyframes.find((x) => x.id == drag.attr('id')).end = rightval;
      p_keyframes.find((x) => x.id == drag.attr('id')).trimend =
        rightval;
    }
  }
  function released(e) {
    $('body').off('mousemove', dragging).off('mouseup', released);
    if (opened) {
      $(".layer[data-object='" + drag.attr('id') + "']")
        .find('.properties')
        .toggle();
      $(".layer[data-object='" + drag.attr('id') + "']")
        .find('.properties')
        .toggleClass('layeron');
      $(
        ".keyframe-row[data-object='" + drag.attr('id') + "']"
      ).toggle();
      setTimelineZoom(timelinetime);
    }
    animate(false, currenttime);
    save();
  }
  $('body').on('mouseup', released).on('mousemove', dragging);
}
$(document).on('mousedown', '.main-row', dragObjectProps);

function resetHeight() {
  var top = $(window).height() - oldtimelinepos - 92;
  if ($('#upload-tool').hasClass('tool-active')) {
    $('#browser').css('top', '150px');
    $('#browser').css(
      'height',
      'calc(100% - ' + (top + 97 + 150) + 'px)'
    );
  } else {
    $('#browser').css('top', '110px');
    $('#browser').css(
      'height',
      'calc(100% - ' + (top + 97 + 100) + 'px)'
    );
  }
  $('#timearea').css('height', top);
  $('#layer-list').css('height', top);
  $('#toolbar').css('height', 'calc(100% - ' + (top + 97) + 'px)');
  $('#canvas-area').css(
    'height',
    'calc(100% - ' + (top + 97) + 'px)'
  );
  $('#properties').css('height', 'calc(100% - ' + (top + 97) + 'px)');
  $('#timeline-handle').css('bottom', top + 95);
  resizeCanvas();
}

// Dragging timeline vertically
function dragTimeline(e) {
  const disableselect = (e) => {  
    return false  
  }  
  document.onselectstart = disableselect  
  document.onmousedown = disableselect
  
  oldtimelinepos = e.pageY;
  if (e.which == 3) {
    return false;
  }
  function draggingKeyframe(e) {
    oldtimelinepos = e.pageY;
    resetHeight(e);
  }
  function releasedKeyframe(e) {
    $('body')
      .off('mousemove', draggingKeyframe)
      .off('mouseup', releasedKeyframe);
  }
  $('body')
    .on('mouseup', releasedKeyframe)
    .on('mousemove', draggingKeyframe);
}

$(document).on('mousedown', '#timeline-handle', dragTimeline);

oldtimelinepos = $(window).height() - 92 - $('#timearea').height();

// Sync scrolling (vertical)
function syncScroll(el1, el2) {
  var $el1 = $(el1);
  var $el2 = $(el2);
  var forcedScroll = false;
  $el1.scroll(function () {
    performScroll($el1, $el2);
  });
  $el2.scroll(function () {
    performScroll($el2, $el1);
  });

  function performScroll($scrolled, $toScroll) {
    if (forcedScroll) return (forcedScroll = false);
    var percent =
      ($scrolled.scrollTop() /
        ($scrolled[0].scrollHeight - $scrolled.outerHeight())) *
      100;
    setScrollTopFromPercent($toScroll, percent);
  }

  function setScrollTopFromPercent($el, percent) {
    var scrollTopPos =
      (percent / 100) * ($el[0].scrollHeight - $el.outerHeight());
    forcedScroll = true;
    $el.scrollTop(scrollTopPos);
  }
}

// Sync scrolling (horizontal)
function syncScrollHoz(el1, el2) {
  var $el1 = $(el1);
  var $el2 = $(el2);
  var forcedScroll = false;
  $el1.scroll(function () {
    performScroll($el1, $el2);
  });
  $el2.scroll(function () {
    performScroll($el2, $el1);
  });

  function performScroll($scrolled, $toScroll) {
    if (forcedScroll) return (forcedScroll = false);
    var percent =
      ($scrolled.scrollLeft() / $scrolled.outerWidth()) * 100;
    setScrollLeftFromPercent($toScroll, percent);
  }

  function setScrollLeftFromPercent($el, percent) {
    var scrollLeftPos = (percent / 100) * $el.outerWidth();
    forcedScroll = true;
    $el.scrollLeft(scrollLeftPos);
  }
}

// Show keyframe properties
function keyframeProperties(inst) {
  if (!shiftdown) {
    selectedkeyframe = $(inst);
    const popup = $('#keyframe-properties');
    var keyarr = keyframes.filter(function (e) {
      return (
        e.t == selectedkeyframe.attr('data-time') &&
        e.id == selectedkeyframe.attr('data-object') &&
        e.name == selectedkeyframe.attr('data-property')
      );
    });
    $('#easing select').val(keyarr[0].easing);
    $('#easing select').niceSelect('update');
    popup.css({
      left: $(inst).offset().left - popup.width() / 2,
      top: $(inst).offset().top - popup.height() - 20,
    });
    popup.addClass('show-properties');
    $(inst).addClass('keyframe-selected');
  }
}

// Apply easing to keyframe
function applyEasing() {
  var keyarr = keyframes.filter(function (e) {
    return (
      e.t == selectedkeyframe.attr('data-time') &&
      e.id == selectedkeyframe.attr('data-object') &&
      e.name == selectedkeyframe.attr('data-property')
    );
  });
  keyarr[0].easing = $(this).attr('data-value');
  if (selectedkeyframe.attr('data-property') == 'left') {
    var keyarr = keyframes.filter(function (e) {
      return (
        e.t == selectedkeyframe.attr('data-time') &&
        e.id == selectedkeyframe.attr('data-object') &&
        e.name == 'top'
      );
    });
    keyarr[0].easing = $('#easing select').val();
  } else if (selectedkeyframe.attr('data-property') == 'scaleX') {
    var keyarr = keyframes.filter(function (e) {
      return (
        e.t == selectedkeyframe.attr('data-time') &&
        e.id == selectedkeyframe.attr('data-object') &&
        e.name == 'scaleY'
      );
    });
    keyarr[0].easing = $('#easing select').val();
    var keyarr = keyframes.filter(function (e) {
      return (
        e.t == selectedkeyframe.attr('data-time') &&
        e.id == selectedkeyframe.attr('data-object') &&
        e.name == 'width'
      );
    });
    keyarr[0].easing = $('#easing select').val();
    var keyarr = keyframes.filter(function (e) {
      return (
        e.t == selectedkeyframe.attr('data-time') &&
        e.id == selectedkeyframe.attr('data-object') &&
        e.name == 'height'
      );
    });
    keyarr[0].easing = $('#easing select').val();
  } else if (
    selectedkeyframe.attr('data-property') == 'strokeWidth'
  ) {
    var keyarr = keyframes.filter(function (e) {
      return (
        e.t == selectedkeyframe.attr('data-time') &&
        e.id == selectedkeyframe.attr('data-object') &&
        e.name == 'stroke'
      );
    });
    keyarr[0].easing = $('#easing select').val();
  } else if (
    selectedkeyframe.attr('data-property') == 'shadow.color'
  ) {
    var keyarr = keyframes.filter(function (e) {
      return (
        e.t == selectedkeyframe.attr('data-time') &&
        e.id == selectedkeyframe.attr('data-object') &&
        e.name == 'shadow.opacity'
      );
    });
    keyarr[0].easing = $('#easing select').val();
    var keyarr = keyframes.filter(function (e) {
      return (
        e.t == selectedkeyframe.attr('data-time') &&
        e.id == selectedkeyframe.attr('data-object') &&
        e.name == 'shadow.offsetX'
      );
    });
    keyarr[0].easing = $('#easing select').val();
    var keyarr = keyframes.filter(function (e) {
      return (
        e.t == selectedkeyframe.attr('data-time') &&
        e.id == selectedkeyframe.attr('data-object') &&
        e.name == 'shadow.offsetY'
      );
    });
    keyarr[0].easing = $('#easing select').val();
    var keyarr = keyframes.filter(function (e) {
      return (
        e.t == selectedkeyframe.attr('data-time') &&
        e.id == selectedkeyframe.attr('data-object') &&
        e.name == 'shadow.blur'
      );
    });
    keyarr[0].easing = $('#easing select').val();
  } else if (
    selectedkeyframe.attr('data-property') == 'charSpacing'
  ) {
    var keyarr = keyframes.filter(function (e) {
      return (
        e.t == selectedkeyframe.attr('data-time') &&
        e.id == selectedkeyframe.attr('data-object') &&
        e.name == 'lineHeight'
      );
    });
    keyarr[0].easing = $('#easing select').val();
  }
  $('#keyframe-properties').removeClass('show-properties');
  selectedkeyframe.removeClass('keyframe-selected');
  save();
}
$(document).on('mouseup', '#easing li', applyEasing);

// Click on seek area to seek (still not working properly)
function seekTo(e) {
  if ($(e.target).hasClass('keyframe')) {
    return false;
  }
  paused = true;
  if ($('#seekarea').scrollLeft() > offset_left) {
    currenttime = parseFloat(
      (
        (e.pageX +
          $('#seekarea').scrollLeft() -
          $('#timearea').offset().left -
          offset_left) *
        timelinetime
      ).toFixed(1)
    );
  } else {
    currenttime = parseFloat(
      (
        (e.pageX +
          $('#seekarea').scrollLeft() -
          $('#timearea').offset().left -
          offset_left) *
        timelinetime
      ).toFixed(1)
    );
  }
  if (currenttime < 0) {
    currenttime = 0;
  }
  // Check for 60FPS playback, 16ms "slots"
  if (currenttime % 16.666 != 0) {
    currenttime = Math.ceil(currenttime / 16.666) * 16.666;
  }
  renderTime();
  $('#seekbar').offset({
    left:
      offset_left +
      $('#inner-timeline').offset().left +
      currenttime / timelinetime,
  });
  animate(false, currenttime);
  updatePanelValues();
}
$(document).on('click', '#seekevents', seekTo);
$(document).on('click', '#timearea', seekTo);

function hideSeekbar() {
  $('#seek-hover').css({ opacity: 0 });
}
function followCursor(e) {
  $('#seek-hover').css({ opacity: 0.3 });
  if ($('#seekarea').scrollLeft() > offset_left) {
    hovertime = parseFloat(
      (
        (e.pageX +
          $('#seekarea').scrollLeft() -
          $('#timearea').offset().left -
          offset_left) *
        timelinetime
      ).toFixed(1)
    );
  } else {
    hovertime = parseFloat(
      (
        (e.pageX +
          $('#seekarea').scrollLeft() -
          $('#timearea').offset().left -
          offset_left) *
        timelinetime
      ).toFixed(1)
    );
  }
  if (e.pageX >= offset_left + $('#inner-timeline').offset().left) {
    $('#seek-hover').offset({ left: e.pageX });
  }
}
$(document).on('mousemove', '#timearea', followCursor);
$(document).on('mousemove', '#seekevents', followCursor);
$(document).on('mousemove', '#toolbar', hideSeekbar);
$(document).on('mousemove', '#canvas-area', hideSeekbar);
$(document).on('mousemove', '#browser', hideSeekbar);
$(document).on('mousemove', '#properties', hideSeekbar);
$(document).on('mousemove', '#controls', hideSeekbar);

function orderLayers() {
  $('.layer').each(function (index) {
    const object = canvas.getItemById($(this).attr('data-object'));
    canvas.sendToBack(object);
    canvas.renderAll();
    objects.splice(
      $('.layer').length - index - 1,
      0,
      objects.splice(
        objects.findIndex((x) => x.id == object.get('id')),
        1
      )[0]
    );
  });
  save();
}

function handTool() {
  if ($(this).hasClass('hand-active')) {
    $(this).removeClass('hand-active');
    $(this).find('img').attr('src', 'assets/hand-tool.svg');
    handtool = false;
    canvas.defaultCursor = 'default';
    canvas.renderAll();
  } else {
    $(this).addClass('hand-active');
    $(this).find('img').attr('src', 'assets/hand-tool-active.svg');
    handtool = true;
    canvas.defaultCursor = 'grab';
    canvas.renderAll();
  }
}
$(document).on('click', '#hand-tool', handTool);
// Set defaults
setDuration(10000);
checkDB();