KoctrX

EditorBase.js

Aug 22nd, 2024
22
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import utils from './utils';
  2. import _ from 'lodash';
  3. import store from '../store';
  4. import History from './History';
  5. import CreatePDF from './pdf/CreatePDF';
  6. import textbox from './data/textbox';
  7. import tablePlugin from './tablePlugin';
  8. window.tablePlugin = tablePlugin;
  9. import lineArrow from './create-editor/lineArrow';
  10. // ed.fillWatermarkOnPage(ed.pages[0]);
  11. window.CreatePDF = CreatePDF;
  12. import idb from './idb';
  13. import brushes from './brushes';
  14. import ScrollPlugin from './scrollPlugin';
  15. import ImportPDF from './importPDF';
  16. import pdf_previewer from './pdf_previewer';
  17. import provider from '../provider';
  18.  
  19. export default class {
  20.     constructor(documentElement = false, isConfirmMode = false) {
  21.         this.updatedObjects = [];
  22.         this.isConfirmMode = isConfirmMode;
  23.         this.stackUpdateThumbail = [];
  24.         this.pending_pages = [];
  25.         this.isRenderCache = false;
  26.         this.queueRender = [];
  27.         this.disposed_pages_content = {};
  28.  
  29.         this.settings = {
  30.             fieldBackgroundColor: 'rgb(255 241 214)', //'rgba(255, 183, 0, 1)',
  31.             errorBackgroundField: 'rgb(255 241 241)', //'rgba(200, 0, 0, 1)'
  32.         };
  33.  
  34.         this.importPDF = new ImportPDF(this);
  35.         this.scrollPlugin = new ScrollPlugin();
  36.         this.lockUpdateThumbail = false;
  37.         this.has_grid = false;
  38.         this.grid = 15;
  39.  
  40.         this.payloadPage = {};
  41.         this.loadedTemplate = false;
  42.         this.filled_template = {
  43.             id: 0,
  44.             hash: ''
  45.         };
  46.  
  47.         this.history = new History(this);
  48.         window.ed = this;
  49.  
  50.         this.defOptions = {
  51.             padding: 8
  52.         };
  53.  
  54.         this.defaultFields = [
  55.             'left', 'top', 'width', 'height', 'styles', 'text',
  56.             'subtype', 'payload', 'scaleX', 'padding', 'fill', 'id',
  57.             'scaleY', 'angle', 'stroke', 'strokeWidth', 'rx', 'ry',
  58.             'unicode_string', 'positions',
  59.             'from_pdf', 'linesCharSpacing', 'customLineHeights', 'dynamicConfig'
  60.         ];
  61.  
  62.         this.globalStyles = ['textAlign', 'lineHeight', 'angle', 'scaleY', 'scaleX'];
  63.         this.events = [];
  64.         this.options = { padding: 25 };
  65.         this.documentElement = documentElement;
  66.         this.pages = [];
  67.  
  68.         this.initPasteboard();
  69.  
  70.         this.pagesInViewport = _.debounce(this.handlePagesInViewport, 100);
  71.         this.updateThumbailDebounce = _.debounce(this.updateThumbail, 500);
  72.     }
  73.  
  74.     changeDrawingOptions(key, value) {
  75.         store.state.drawingMode[key] = value;
  76.  
  77.         this.pages.forEach(page => {
  78.             page.canvas.freeDrawingBrush[key] = value;
  79.         });
  80.     }
  81.  
  82.     removeAllWatermarks() {
  83.         this.pages.forEach(page => {
  84.             this.history.lockHistory = true;
  85.  
  86.             try {
  87.                 const marks = page.canvas.getObjects().filter(o => o && o.payload && o.payload.byWatermark);
  88.                 marks.forEach(m => page.canvas.remove(m));
  89.             } catch (err) {
  90.                 console.error(err);
  91.             }
  92.  
  93.             this.history.lockHistory = false;
  94.         });
  95.     }
  96.  
  97.     async appendWatermarkJSON(page, objects = []) {
  98.         objects = objects.filter(o => !(o && o.payload && o.payload.byWatermark));
  99.         const waters = store.state.user && store.state.user.plan == 'premium' ? [] : await this.createWatermark(page, true);
  100.         return [...objects, ...waters.map(w => this.setupDefaultOptions(w))];
  101.     }
  102.  
  103.     createWatermark(page, isJSON = false) {
  104.         const opts = {
  105.             fontFamily: 'Arial',
  106.             fill: '#1155c7',
  107.             fontSize: 10,
  108.             evented: false,
  109.             selectable: false
  110.         };
  111.  
  112.         const url = 'deftpdf.com';
  113.         const link = new fabric.Text(url, {
  114.             ...opts, styles: [new Array(url.length).fill(false).map(q => (_.cloneDeep(opts)))],
  115.             payload: {
  116.                 byWatermark: true, type: 'link',
  117.                 url: 'https://deftpdf.com'
  118.             }
  119.         });
  120.  
  121.         const created = new fabric.Text('created by', {
  122.             ...opts, fill: '#000', payload: { type: 'text', byWatermark: true }
  123.         });
  124.  
  125.         const padding = 10;
  126.         let canvasWidth = page.canvas.getWidth() / page.canvas.getZoom();
  127.         let canvasHeight = page.canvas.getHeight() / page.canvas.getZoom();
  128.  
  129.         const top = (canvasHeight - link.height / 2 - padding);
  130.         link.set({ left: (canvasWidth - link.width / 2 - padding), top });
  131.         created.set({ left: (link.left - created.width) - 10, top });
  132.  
  133.         if (isJSON) return [link.toJSON(this.defaultFields), created.toJSON(this.defaultFields)];
  134.  
  135.         return { created, link };
  136.     }
  137.  
  138.     async appendWatermarks(renderOnPage = false) {
  139.         return this.removeAllWatermarks();
  140.         if (store.state.user && store.state.user.plan == 'premium') {
  141.             return this.removeAllWatermarks();
  142.         }
  143.  
  144.         for (const page of this.pages.filter(page => !page.waitingRender)) {
  145.             if (renderOnPage && renderOnPage.id != page.id) continue;
  146.             this.history.lockHistory = true;
  147.             page.canvas.getObjects().forEach(obj => {
  148.                 if (obj && obj.payload && obj.payload.byWatermark) {
  149.                     page.canvas.remove(obj);
  150.                 }
  151.             });
  152.  
  153.             const { created, link } = await this.createWatermark(page);
  154.             page.canvas.add(created);
  155.             page.canvas.add(link);
  156.             this.history.lockHistory = false;
  157.         }
  158.     }
  159.  
  160.     changeEditorMode(mode) {
  161.         switch (mode) {
  162.             case 'drawing':
  163.             case 'drawing-spray': {
  164.                 const drawingMode = store.state.drawingMode;
  165.  
  166.                 this.pages.forEach(page => {
  167.                     page.canvas.upperCanvasEl.style['pointer-events'] = 'all';
  168.                     page.canvas.isDrawingMode = true;
  169.  
  170.                     if (mode == 'drawing') {
  171.                         page.canvas.freeDrawingBrush = new fabric.PencilBrush(page.canvas);
  172.                         if (store.state.editorMode != mode) {
  173.                             store.state.drawingMode.width = 5;
  174.                         }
  175.  
  176.                         if (store.state.drawingMode.color.length > 7) {
  177.                             store.state.drawingMode.color = store.state.drawingMode.color.substr(0, 7);
  178.                         }
  179.                     }
  180.  
  181.                     if (mode == 'drawing-spray') {
  182.                         page.canvas.freeDrawingBrush = new fabric.PencilBrush(page.canvas);
  183.                         if (store.state.editorMode != mode) {
  184.                             store.state.drawingMode.width = 10;
  185.                         }
  186.  
  187.                         if (store.state.drawingMode.color.length <= 7) {
  188.                             store.state.drawingMode.color += '88';
  189.                         }
  190.                     }
  191.                     // page.canvas.freeDrawingBrush = new fabric.SprayBrush(page.canvas);
  192.  
  193.                     page.canvas.freeDrawingBrush.color = drawingMode.color;
  194.                     page.canvas.freeDrawingBrush.width = drawingMode.width;
  195.                 });
  196.  
  197.                 store.state.editorMode = mode;
  198.                 break;
  199.             }
  200.  
  201.             case 'erase': {
  202.                 store.state.editorMode = mode;
  203.                 this.pages.forEach(page => {
  204.                     page.canvas.isDrawingMode = false;
  205.                     page.canvas.upperCanvasEl.style['pointer-events'] = 'none';
  206.                 });
  207.  
  208.                 break;
  209.             }
  210.  
  211.             default:
  212.                 this.pages.forEach(page => {
  213.                     page.canvas.isDrawingMode = false;
  214.                     page.canvas.upperCanvasEl.style['pointer-events'] = 'all';
  215.                 });
  216.  
  217.                 store.state.editorMode = '';
  218.         }
  219.     }
  220.  
  221.     async createImageAppend(padding = 10, width = 200, height = 120) {
  222.         const img = await new Promise(resolve => fabric.Image.fromURL('/pdf-templates/img/image.svg', resolve));
  223.         const text = new fabric.Text('Select image', {
  224.             fontSize: 16, fill: '#8C49F7',
  225.             left: 30, fontFamily: 'Arial'
  226.         });
  227.  
  228.         const rect = new fabric.Rect({
  229.             left: 0, top: 0, fill: '#f9f9f9',
  230.             width, height
  231.         });
  232.  
  233.  
  234.         img.top = rect.height / 2 - img.height / 2;
  235.         img.left = (rect.width - (img.width + text.width + padding)) / 2;
  236.         text.set({
  237.             top: rect.height / 2 - text.height / 2,
  238.             left: img.left + img.width + padding
  239.         });
  240.  
  241.  
  242.         const group = new fabric.Group([rect, img, text], { left: 100, top: 100 });
  243.         return group;
  244.     }
  245.  
  246.     initPasteboard() {
  247.         document.onpaste = async event => {
  248.             if (this._type != 'create' || !this.isConfirmMode) return;
  249.             const activeElement = document.activeElement;
  250.  
  251.             if (!['input', 'textarea'].includes((activeElement.tagName || '').toLowerCase())) {
  252.                 const cd = (event.clipboardData || event.originalEvent.clipboardData);
  253.                 const items = cd.items;
  254.  
  255.                 const file = Array.from(items).find(item => item.kind == 'file');
  256.                 if (file) {
  257.                     const blob = file.getAsFile();
  258.                     var reader = new FileReader();
  259.  
  260.                     reader.onload = event => this.addElement('image', { src: event.target.result });
  261.                     reader.readAsDataURL(blob);
  262.                 } else {
  263.                     const text = cd.getData('text') || Array.from(items).find(item => item.kind == 'string');
  264.                     const object = await this.addElement('text', { text, options: { fontSize: 12, width: 400 } });
  265.  
  266.                     const pasteElement = $(cd.getData('text/html'))[0];
  267.                     if (pasteElement) {
  268.                         try {
  269.                             const parsed = utils.parserPaste.parseHTMLStyles(pasteElement);
  270.                             // const backgroundColor = pasteElement.style.backgroundColor;
  271.  
  272.                             // if (backgroundColor) object.set({ backgroundColor: backgroundColor });
  273.                             parsed.styles.forEach((style, index) => {
  274.                                 object.setSelectionStyles(style, index, index + 1);
  275.                             });
  276.  
  277.                             object.canvas.renderAll();
  278.                         } catch (err) {
  279.                             console.error(err);
  280.                         }
  281.                     }
  282.                 }
  283.             }
  284.         }
  285.     }
  286.  
  287.     async generatePreviewFirstPage() {
  288.         const page = this.pages[0];
  289.         const multiplier = (page.canvas.width / page.canvas.getZoom()) / page.canvas.width
  290.         const base64 = page.canvas.toDataURL({ multiplier });
  291.  
  292.         return base64;
  293.     }
  294.  
  295.     replaceObjectById(objectId, dataJSON = {}, scrollToObject = false) {
  296.         const object = this.pages.reduce((result, page) => {
  297.             const o = page.canvas.getObjects().find(ob => ob.id == objectId);
  298.             if (o) return o;
  299.  
  300.             return result;
  301.         }, false);
  302.         if (!object) return console.error('Object not found');
  303.  
  304.         object.set(dataJSON);
  305.         object.canvas.requestRenderAll();
  306.  
  307.         if (scrollToObject) this.scrollToObjectById(objectId);
  308.     }
  309.  
  310.     scrollToObjectById(objectId) {
  311.         const object = this.pages.reduce((result, page) => {
  312.             const o = page.canvas.getObjects().find(ob => ob.id == objectId);
  313.             if (o) return o;
  314.  
  315.             return result;
  316.         }, false);
  317.         if (!object) return console.error('Object not found');
  318.  
  319.         const calculatePosition = (obj, tooltip, parent) => {
  320.             const zoom = obj.canvas.getZoom();
  321.             const p = 20;// * zoom;
  322.  
  323.             const width = parent.clientWidth - p;
  324.             const height = parent.clientHeight - p;
  325.  
  326.             const w = (obj.width * obj.scaleX) * zoom;
  327.             const h = (obj.height * obj.scaleY) * zoom;
  328.  
  329.             let left = (obj.left * zoom - w / 2 - 10);
  330.             let top = (obj.top * zoom + h / 2 + p);
  331.  
  332.             if (left + (tooltip.clientWidth) > width) {
  333.                 left = width - tooltip.clientWidth;
  334.             }
  335.  
  336.             if (top + (tooltip.clientHeight) > height) {
  337.                 top = top - (tooltip.clientHeight) - h - (p * 2);
  338.             }
  339.  
  340.             if (left < p) left = p;
  341.             if (top < p) top = p;
  342.  
  343.             const styles = {
  344.                 position: 'absolute',
  345.                 left: `${left}px`,
  346.                 top: `${top}px`,
  347.                 opacity: 0,
  348.                 'pointer-events': 'none',
  349.                 'user-select': 'none'
  350.             };
  351.  
  352.             tooltip.style = utils.objectStyleToString(styles);
  353.         };
  354.  
  355.         object.canvas.setActiveObject(object).renderAll();
  356.  
  357.         const tooltip = document.createElement('div');
  358.         tooltip.setAttribute('class', 'tooltip_document');
  359.         tooltip.setAttribute('data-attach', `${object.id}`);
  360.  
  361.         const parent = object.canvas.upperCanvasEl.closest('.document--new');
  362.         parent.appendChild(tooltip);
  363.  
  364.         calculatePosition(object, tooltip, parent);
  365.         tooltip.scrollIntoView({ block: 'center' });
  366.     }
  367.  
  368.     async exportCurrentState() {
  369.         // const fr = new FileReader();
  370.         // const thumb = await fetch(this.pages[0].thumb).then(r => r.blob());
  371.         //return new Promise(resolve => {
  372.         //    fr.onload = async e => {
  373.  
  374.         const previewBase64 = await this.generatePreviewFirstPage();//e.target.result
  375.         let pages = [];
  376.         for (const page of this.pages) {
  377.             page.canvas.discardActiveObject().requestRenderAll();
  378.             let json = false;
  379.             if (page.waitingRender && !page.viewer) {
  380.                 json = _.cloneDeep(page.json);
  381.             } else if (page && page.viewer && !page.viewer.isRendered) {
  382.                 const data = await viewer.loadPdfPage(page.viewer.pageNumber);
  383.                 const importedData = await this.importPDF.parsePageToJSON(data);
  384.  
  385.                 json = {
  386.                     objects: importedData.json.objects.map(object => {
  387.                         object = this.setupDefaultOptions(object);
  388.                         if (this.initialParseObjects) {
  389.                             object = this.initialParseObjects(object);
  390.                         }
  391.  
  392.                         return object;
  393.                     }),
  394.  
  395.                     width: importedData.data.options.width,
  396.                     height: importedData.data.options.height
  397.                 }
  398.  
  399.                 page.json = _.cloneDeep(json);
  400.                 page.waitingRender = true;
  401.                 delete page.viewer;
  402.             } else {
  403.                 json = page.canvas.toJSON(this.defaultFields);
  404.             }
  405.  
  406.             pages.push({
  407.                 id: page.id,
  408.                 isLockRotate: !!page.isLockRotate,
  409.                 rotate: page.rotate || 0,
  410.                 json, data: page.data
  411.             });
  412.         }
  413.  
  414.         const data = {
  415.             id: store.state.activeTemplate.id,
  416.             preview: previewBase64 || require('@/assets/img/preview_page.png'),
  417.             title: store.state.activeTemplate.title,
  418.             categories: store.state.activeTemplate.categories,
  419.             created_at: store.state.activeTemplate.created_at || new Date().toISOString(),
  420.             data: { fonts: store.state.activeTemplate.data.fonts || {}, pages }
  421.         };
  422.  
  423.         return data;
  424.         //     resolve(data);
  425.         // };
  426.  
  427.         // fr.readAsDataURL(thumb);
  428.         // });
  429.     }
  430.  
  431.     setZoom(value = 100) {
  432.         const minZoom = 10;
  433.         const maxZoom = 400;
  434.  
  435.         if (value < minZoom) value = minZoom;
  436.         if (value > maxZoom) value = maxZoom;
  437.  
  438.         for (const page of this.pages) {
  439.             const w = (value / 100) * page.data.options.width;
  440.             const h = (value / 100) * page.data.options.height;
  441.  
  442.             page.canvas.setZoom(value / 100);
  443.             page.canvas.setWidth(w);
  444.             page.canvas.setHeight(h);
  445.  
  446.             $(page.canvas.upperCanvasEl.closest('.document--new')).css({
  447.                 width: `${w}px`,
  448.                 height: `${h}px`
  449.             });
  450.  
  451.             page.canvas.getObjects().forEach(o => {
  452.                 if (o && o.payload && ['radio_button', 'checkbox', 'dropdown'].includes(o.payload.type)) {
  453.                     o.fire('sync:position');
  454.                 }
  455.             });
  456.         }
  457.  
  458.         store.state.zoom = value;
  459.         const active = this.getActiveObject();
  460.         if (active && active.fire) active.fire('next:tooltip');
  461.     }
  462.  
  463.     togglePreviewMode() {
  464.         const body = document.body;
  465.         const classString = 'workspase--preview';
  466.         const isPreview = body.classList.contains(classString);
  467.         body.classList[isPreview ? 'remove' : 'add'](classString);
  468.         store.state.previewMode = !isPreview;
  469.         this.pages.forEach(page => page.canvas.discardActiveObject().renderAll());
  470.     }
  471.  
  472.     enlivenObjects(objs = []) {
  473.         return new Promise(resolve => {
  474.             fabric.util.enlivenObjects(objs, resolve);
  475.         });
  476.     }
  477.  
  478.     async addElement(type, data = {}, forcePage = false, addCallback = false) {
  479.         const page = this.getActivePage(forcePage);
  480.         const defaultSettings = this.defaultSettings;
  481.         if (!page) return false;
  482.         const payload = { type, settings: _.cloneDeep(defaultSettings) };
  483.         const options = { ...(data.options || {}), payload };
  484.         const addElementToPage = addCallback || (data => this.addElementToPage(data, true, forcePage));
  485.  
  486.         switch (type) {
  487.             case 'text': {
  488.                 return addElementToPage(
  489.                     new fabric.Textbox(data.text || 'Body text', {
  490.                         fontSize: 22, fontFamily: 'Arial', //'sans-serif',
  491.                         //left: 200, top: 200,
  492.                         width: 200,
  493.                         ...options,
  494.                     }), true, forcePage
  495.                 );
  496.             }
  497.  
  498.             case 'table': {
  499.                 let cols = 3;
  500.                 let rows = 4;
  501.  
  502.                 const json = data.jsonData || tablePlugin.createTableJSON(cols, rows);
  503.                 const table = await tablePlugin.loadTableJSON(json);
  504.                 table.set({ ...options, payload: { ...options.payload, ...table.payload } });
  505.  
  506.                 return addElementToPage(table, true, forcePage);
  507.             }
  508.  
  509.             case 'heading': {
  510.                 payload.heading = 'h1';
  511.                 return addElementToPage(
  512.                     new fabric.Textbox(data.text || 'Heading', {
  513.                         width: 200, fontSize: 32,
  514.                         fontFamily: 'Arial', fontWeight: 'bold',
  515.                         ...options,
  516.                     }), true, forcePage
  517.                 );
  518.             }
  519.  
  520.             case 'image': {
  521.                 options.payload.settings.isEditable = true;
  522.                 options.left = 0;
  523.                 options.top = 0;
  524.  
  525.                 let image = false;
  526.                 if (data.src) {
  527.                     image = await new Promise((resolve, reject) => {
  528.                         fabric.Image.fromURL(data.src, (img, err) => {
  529.                             if (err) reject(err);
  530.                             resolve(img);
  531.                         }, options);
  532.                     });
  533.  
  534.                     const minSize = .3 * Math.min(page.canvas.width, page.canvas.height);
  535.                     let scale = Math.min(minSize / image.width, minSize / image.height);
  536.                     if (scale > 1) scale = 1;
  537.                     image.set({ scaleX: scale, scaleY: scale });
  538.                 } else {
  539.                     image = await this.createImageAppend();
  540.                     options.payload.groupImagePreview = true;
  541.                     image.set(options);
  542.                 }
  543.  
  544.                 /* if (image.left <= 0 && image.top <= 0) {
  545.                     image.set({
  546.                         left: (image.width * image.scaleX / 2),
  547.                         top: (image.height * image.scaleY / 2),
  548.                     });
  549.                 } */
  550.  
  551.                 return addElementToPage(image, true, forcePage);
  552.             }
  553.  
  554.             case 'square': {
  555.                 return addElementToPage(new fabric.Rect({
  556.                     width: 100, height: 100, fill: 'rgba(0, 0, 0, 0)',
  557.                     stroke: '#bda6fc', strokeWidth: 1, strokeUniform: true,
  558.                     rx: 10, ry: 10, ...options
  559.                 }), true, forcePage);
  560.             }
  561.  
  562.             case 'elipse': {
  563.                 return addElementToPage(new fabric.Rect({
  564.                     width: 100, height: 100, fill: 'rgba(0, 0, 0, 0)',
  565.                     stroke: '#bda6fc', strokeWidth: 1, strokeUniform: true,
  566.                     rx: 100, ry: 100, ...options
  567.                 }), true, forcePage);
  568.             }
  569.  
  570.             case 'line': {
  571.                 return addElementToPage(new fabric.Line([0, 0, 200, 0], {
  572.                     stroke: '#bda6fc', strokeWidth: 3,
  573.                     strokeUniform: true, ...options
  574.                 }), true, forcePage);
  575.             }
  576.  
  577.             // case 'sign': {
  578.             //     const image = await new Promise((resolve, reject) => {
  579.             //         fabric.Image.fromURL(require('@/assets/img/la_signature.svg'), (img, err) => {
  580.             //             if (err) reject(err);
  581.             //             resolve(img);
  582.             //         }, options);
  583.             //     });
  584.  
  585.             //     options.payload.settings.isEditable = true;
  586.             //     options.payload.settings.isResizable = true;
  587.             //     image.set({ scaleX: 5, scaleY: 5, backgroundColor: 'rgba(200, 200, 200, .5)' });
  588.             //     return this.addElementToPage(image, true, forcePage);
  589.             // }
  590.  
  591.             case 'full_name':
  592.             case 'email':
  593.             case 'address':
  594.             case 'phone':
  595.             case 'website':
  596.             case 'custom_field':
  597.             case 'sign':
  598.             case 'number':
  599.             case 'checkbox':
  600.             case 'radio_button':
  601.             case 'dropdown':
  602.             case 'initials':
  603.             case 'currency':
  604.             case 'date': {
  605.                 let fieldText = {
  606.                     full_name: 'Full Name',
  607.                     email: 'email@mail.com',
  608.                     address: '4088 Woodbridge Dr Lansing, Michigan(MI), 48911',
  609.                     phone: '(517) 887-7086',
  610.                     website: location.origin,
  611.                     sign: 'Signature',
  612.                     custom_field: 'Custom Field',
  613.                     date: (() => {
  614.                         const date = new Date();
  615.                         return `${date.getDate()}.${date.getMonth() + 1}.${date.getFullYear()}`
  616.                     })(),//'01.01.2001',
  617.                     number: `${utils.getRandomNumber(1000, 9999)}`,
  618.                     initials: 'initials', //'L. Bury.',
  619.                     currency: '36.2$',
  620.                 };
  621.  
  622.                 let element = false;
  623.                 options.payload.settings.isEditable = true;
  624.                 if (['sign', 'initials'].includes(type)) {
  625.                     const rect = new fabric.Rect({
  626.                         left: 0, top: 0, width: 250, height: 80,
  627.                         strokeWidth: 0, fill: '#e3e3e3'
  628.                     });
  629.  
  630.                     options.payload.type = 'sign';
  631.                     if (type == 'initials') {
  632.                         options.payload.subtype = 'initials';
  633.                     }
  634.  
  635.                     const text = new fabric.Text(fieldText[type], { fontSize: 22, fontFamily: 'Arial' });
  636.                     //text.set({ left: (rect.width - text.width) / 2, top: (rect.height - text.height) / 2 });
  637.  
  638.                     element = new fabric.Group([rect, text], options);
  639.                 } else if (['checkbox', 'radio_button', 'dropdown'].includes(type)) {
  640.                     const id = utils.uuidv4();
  641.                     options.payload.settings.name = `input_element#${id}`;
  642.  
  643.                     if (['checkbox', 'radio_button'].includes(type)) {
  644.                         options.payload.settings.checked = false;
  645.                     } else {
  646.                         options.payload.settings.active = '';
  647.                         options.payload.settings.options = [
  648.                             'Option 1', 'Option 2', 'Option 3'
  649.                         ];
  650.                     }
  651.  
  652.                     element = new fabric.Rect({
  653.                         id, width: type == 'dropdown' ? 180 : 35,
  654.                         fill: '#ffffff', strokeWidth: 0,
  655.                         height: type == 'dropdown' ? 40 : 35,
  656.                         stroke: '#000', ...options,
  657.                     });
  658.                 } else {
  659.                     element = new fabric.Textbox(fieldText[type], {
  660.                         fontSize: 12, fontFamily: 'Arial',//,'sans-serif',
  661.                         //left: 200, top: 200,
  662.                         width: 200,
  663.                         backgroundColor: this.settings.fieldBackgroundColor,//'rgba(255, 183, 0, 0.2)',
  664.                         ...options,
  665.                     });
  666.  
  667.                     if (type == 'currency') {
  668.                         element.payload.settings.currency = '$';
  669.                     }
  670.  
  671.                     if (type == 'date') {
  672.                         element.payload.date = Date.now();
  673.                         element.payload.format = 'dd.mm.yyyy';
  674.                         element.editable = false;
  675.                     }
  676.                 }
  677.  
  678.                 return addElementToPage(element, true, forcePage);
  679.             }
  680.  
  681.             case 'arrow': {
  682.                 const arrow = lineArrow.drawArrow(0, 0, 100, 0, options);
  683.                 return addElementToPage(arrow, true, forcePage);
  684.             }
  685.         }
  686.  
  687.         return addElementToPage(false);
  688.     }
  689.     //TODO import pages here
  690.     async generateImportedDocument(importData = {}, filename = '', isAppendPages = false) {
  691.         const { data, fonts } = await this.importPDF.parseImportData(importData);
  692.         // console.time('t');
  693.         // const previews = importData.file ? await pdf_previewer.parseDocumentThumbails(URL.createObjectURL(importData.file)) : [];
  694.         // console.timeEnd('t');
  695.         const prerenderThumbs = array => {
  696.             return false;
  697.             // PREVIEW OFF
  698.             if (importData.file) {
  699.                 pdf_previewer.parseDocumentThumbails(URL.createObjectURL(importData.file), array, this);
  700.             }
  701.         };
  702.  
  703.         const previewProcessArray = data.reduce((result, page, index) => ([...result, { id: page.id, index, pageNumber: page.viewer.pageNumber }]), []);
  704.         if (isAppendPages) {
  705.             const skip = _.cloneDeep(this.pages.map(q => q.id));
  706.  
  707.             this.pages.push(...data.map(page => ({ ...page, thumb: false })));
  708.             store.state.activeTemplate.data.fonts = _.assign(store.state.activeTemplate.data.fonts || {}, fonts);
  709.             // store.state.activeTemplate.data.pages = this.pages;
  710.             await this.renderPages(skip);
  711.  
  712.             const firstIncludePage = this.pages.find(page => data[0] && data[0].id && page.id == data[0].id);
  713.             if (firstIncludePage) this.scrollToPage(firstIncludePage);
  714.             this.setZoom(store.state.zoom);
  715.             prerenderThumbs(previewProcessArray);
  716.  
  717.             this.appendWatermarks();
  718.         } else {
  719.             await this.saveCurrentTemplate();
  720.             const payloadData = { pages: data, fonts };
  721.             const newTemplate = _.assign(
  722.                 _.cloneDeep(store.state.emptyTemplate),
  723.                 { data: payloadData, title: filename }
  724.             );
  725.  
  726.             store.state.lockLoaded = true;
  727.             store.state.lastSavedTemplate = newTemplate;
  728.             store.state.activeTemplate = newTemplate;
  729.  
  730.             const result = await this.openTemplate(newTemplate);
  731.             //store.state.$router.push('/create-template').catch(err => { });
  732.             prerenderThumbs(previewProcessArray);
  733.  
  734.             return result;
  735.         }
  736.     }
  737.  
  738.     async saveCurrentTemplate() {
  739.         const isSave = () => {
  740.             return new Promise(resolve => {
  741.                 store.state.swal({
  742.                     title: 'Save document', icon: 'warning',
  743.                     text: `Do you want to save document "${store.state.activeTemplate.title}"?`,
  744.                     showCancelButton: true,
  745.                     // confirmButtonColor: '#3085d6',
  746.                     cancelButtonColor: '#d33',
  747.                     confirmButtonText: 'Save'
  748.                 }).then(result => resolve(result.isConfirmed));
  749.             });
  750.         };
  751.  
  752.         const isNew = !store.state.activeTemplate.hash;
  753.         const isPages = this.pages.length > 1;
  754.         store.state.loading = true;
  755.  
  756.         if (isNew) {
  757.             if (this.pages.length <= 1 && !this.pages[0].canvas.getObjects().length) {
  758.                 store.state.loading = false;
  759.                 return false;
  760.             }
  761.  
  762.             if (await isSave()) {
  763.                 const json = await this.exportCurrentState();
  764.                 await idb.saveTemplate({
  765.                     ...json, ..._.pick(_.cloneDeep(store.state.activeTemplate), ['id', 'hash'])
  766.                 }, false);
  767.             }
  768.         } else {
  769.             const json = await this.exportCurrentState();
  770.             await idb.saveTemplate({
  771.                 ...json, ..._.pick(_.cloneDeep(store.state.activeTemplate), ['id', 'hash'])
  772.             }, false);
  773.         }
  774.  
  775.         store.state.loading = false;
  776.     }
  777.  
  778.     fixTextNull(objects) {
  779.         return objects.reduce((res, obj) => {
  780.             if (obj.objects && obj.objects.length) {
  781.                 obj.objects = this.fixTextNull(obj.objects);
  782.             }
  783.  
  784.             // eslint-disable-next-line no-prototype-builtins
  785.             if ((obj || {}).hasOwnProperty('text')) {
  786.                 obj.text = obj.text || ' ';
  787.             }
  788.  
  789.             res.push(obj);
  790.             return res;
  791.         }, []);
  792.     }
  793.  
  794.     async openTemplate(template, bodyElement = false) {
  795.         console.timeEnd('openTemplate');
  796.         console.time('RENDER PDF');
  797.         if (template && template.data && template.data.pages && template.data.pages.length) {
  798.             template.data.pages = template.data.pages.reduce((res, page) => {
  799.                 if (page.json && page.json.objects && page.json.objects.length) {
  800.                     page.json.objects = this.fixTextNull(page.json.objects);
  801.                 }
  802.  
  803.                 res.push(page);
  804.                 return res;
  805.             }, []);
  806.  
  807.         }
  808.  
  809.         this.lockUpdateThumbail = true;
  810.         try {
  811.             this.loadedTemplate = false;
  812.  
  813.             if (template) {
  814.                 store.state.sharedUsers = _.cloneDeep(template.users || []);
  815.                 if (this.pages && this.pages.length) {
  816.                     this.pages.forEach(page => page.canvas.dispose());
  817.                 }
  818.  
  819.                 this.loadedTemplate = _.cloneDeep(template);
  820.                 await store.dispatch('addRecent', template);
  821.                 try {
  822.                     if (template.data.fonts && Object.keys(template.data.fonts).length) {
  823.                         Object.values(_.cloneDeep(template.data.fonts))
  824.                             .forEach(font => viewer.appendCustomFontOnPage(font, true));
  825.  
  826.                         console.time('Load Fonts');
  827.                         await new Promise(resolve => {
  828.                             WebFont.load({
  829.                                 custom: {
  830.                                     families: Object.keys(template.data.fonts)
  831.                                 },
  832.  
  833.                                 // fontloading(fn) {
  834.                                 //     console.log('Loaded custom font: ', fn);
  835.                                 // },
  836.                                 active: resolve,
  837.                             });
  838.                         });
  839.  
  840.                         console.timeEnd('Load Fonts');
  841.                     }
  842.                 } catch (err) {
  843.                     console.error(err);
  844.                 }
  845.  
  846.                 const skipRenderPages = [];
  847.                 this.pages = template.data.pages.map((page, i) => {
  848.                     const result = { ...page, thumb: page.thumb || false };
  849.                     if (i > 2 && !page.viewer) {
  850.                         result.waitingRender = true;
  851.                         skipRenderPages.push(page.id);
  852.                     }
  853.  
  854.                     return result;
  855.                 });
  856.  
  857.                 await this.renderPages(skipRenderPages, true);
  858.  
  859.                 await this.fitToScreenPages(bodyElement);
  860.                 // if (!bodyElement) bodyElement = document.querySelector('.workspase__body');
  861.                 // if (!bodyElement || !this.pages.length) {
  862.                 //     await this.setZoom(store.state.zoom);
  863.                 // } else {
  864.                 //     const maxZoomSize = bodyElement.getBoundingClientRect().width - 100;
  865.                 //     const maxPageSize = Math.max(...this.pages.map(p => p.data.options.width));
  866.  
  867.                 //     let zoom = maxZoomSize / maxPageSize;
  868.                 //     // TODO:zoom = 1;
  869.                 //     if (zoom > 4) zoom = 4;
  870.                 //     // zoom = 1;
  871.                 //     await this.setZoom(zoom * 100);
  872.                 // }
  873.  
  874.                 // store.state.sharedUsers = [];
  875.                 this.history.clearHistory(false);
  876.                 this.syncEditableObjects();
  877.  
  878.                 // if (this._type == 'edit' && store.state.user.plan != 'premium') {
  879.                 //     await this.appendWatermarks();
  880.                 // }
  881.  
  882.                 await this.appendWatermarks();
  883.             }
  884.         } catch (err) {
  885.             console.error(err);
  886.             return this;
  887.         }
  888.  
  889.         this.lockUpdateThumbail = false;
  890.         console.timeEnd('RENDER PDF');
  891.         return this;
  892.     }
  893.  
  894.     async fitToScreenPages(bodyElement = false) {
  895.         if (!bodyElement) bodyElement = document.querySelector('.workspase__body');
  896.         if (!bodyElement || !this.pages.length) {
  897.             await this.setZoom(store.state.zoom);
  898.         } else {
  899.             const maxZoomSize = bodyElement.getBoundingClientRect().width - 100;
  900.             const maxPageSize = Math.max(...this.pages.map(p => p.data.options.width));
  901.  
  902.             let zoom = maxZoomSize / maxPageSize;
  903.             // TODO:zoom = 1;
  904.             if (zoom > 4) zoom = 4;
  905.             // zoom = 1;
  906.             await this.setZoom(zoom * 100);
  907.         }
  908.     }
  909.  
  910.     syncEditableObjects() { }
  911.  
  912.     deletePage(page) {
  913.         if (this.pages.length == 1) return;// alert('DELETE LAST PAGE?');
  914.  
  915.         const clonePage = _.cloneDeep(_.omit(page, ['canvas']));
  916.         const canvas = page.canvas;
  917.         const pageIndex = this.getPageIndex(page);
  918.         const element = canvas.upperCanvasEl.closest('.document--new');
  919.         const json = canvas.toJSON(this.defaultFields);
  920.         canvas.dispose();
  921.         element.remove();
  922.  
  923.         this.pages = this.pages.filter(p => p.id != page.id);
  924.         if (page.id == store.state.createTemplate.activePage) {
  925.             this.selectActivePage(this.pages[pageIndex] || this.pages[pageIndex - 1]);
  926.         }
  927.  
  928.         this.history.add({ page: { payload: clonePage, json, isAdded: false, index: pageIndex } });
  929.     }
  930.  
  931.     selectActivePage(page) {
  932.         store.dispatch('selectActiveObject', { target: false, page: page.id });
  933.         setTimeout(() => this.scrollToPage(page), 10);
  934.     }
  935.  
  936.     async addPage(payloadPage = false, options = {}) {
  937.         const page = payloadPage || {
  938.             id: utils.uuidv4(),
  939.             thumb: false,
  940.             data: {
  941.                 options: {
  942.                     backgroundColor: '#ffffff',
  943.                     width: 612, height: 792,
  944.                     controlsAboveOverlay: true
  945.                 }
  946.             }
  947.         };
  948.  
  949.         const clonePage = _.cloneDeep(page);
  950.         if (options && (options.indexAppend || options.indexAppend == 0)) {
  951.             this.pages.splice(options.indexAppend, 0, page);
  952.         } else {
  953.             this.pages.push(page);
  954.         }
  955.  
  956.         this.renderPage(page, options || {});
  957.         this.selectActivePage(page);
  958.         this.history.add({ page: { payload: clonePage, isAdded: true, json: false, index: this.getPageIndex(page) } });
  959.         this.appendWatermarks();
  960.         this.fitToScreenPages();
  961.     }
  962.  
  963.     getPageIndex(page) {
  964.         return this.pages.findIndex(p => p.id == page.id);
  965.         return Array.from(page.canvas.upperCanvasEl.closest('.document.document--new'))
  966.             .findIndex(el => el.getAttribute('data-page') == clonePage.id);
  967.     }
  968.  
  969.     scrollToPage(page) {
  970.         const scrollElement = page.canvas.upperCanvasEl.closest('.document--new');
  971.         scrollElement.scrollIntoView({ block: 'start', behavior: 'smooth' });
  972.     }
  973.  
  974.     tick() { }
  975.  
  976.     _setupControls(object, update, def = false) {
  977.         const controls = {
  978.             tl: def, mt: def, tr: def, ml: def,
  979.             mr: def, bl: def, mb: def, br: def,
  980.             mtr: def, ...update
  981.         };
  982.  
  983.         for (let key in controls) {
  984.             object.setControlVisible(key, controls[key]);
  985.         }
  986.     }
  987.  
  988.     fillWatermarkOnPage(page = false) {
  989.         if (!page || !page.canvas) return false;
  990.  
  991.         const canvas = page.canvas;
  992.         fabric.Image.fromURL('/img/logo.png', img => {
  993.             let watermarks = [];
  994.             img.set({ angle: 45, opacity: .2, left: 0, top: 0 });
  995.             const size = utils.getOriginSize(img);
  996.  
  997.             img.set({
  998.                 left: (size.width / 2) - size.width * 1.5,
  999.                 top: (size.height / 2) - size.height * 1.5
  1000.             });
  1001.  
  1002.             while (true) {
  1003.                 const last = watermarks[watermarks.length - 1] || img;
  1004.                 const size = utils.getOriginSize(last);
  1005.                 const right = size.width + size.x;
  1006.                 const bottom = size.height + size.y;
  1007.  
  1008.                 if (watermarks.length > 50) break;
  1009.  
  1010.                 if (right > canvas.getWidth()) {
  1011.                     if (bottom > canvas.getHeight() + size.height) {
  1012.                         break;
  1013.                     } else {
  1014.                         const clone = fabric.util.object.clone(img);
  1015.                         clone.set({ left: 0/* size.width / 2 */, top: last.top + size.height * 2 });
  1016.  
  1017.                         watermarks.push(clone);
  1018.                     }
  1019.                 } else {
  1020.                     const clone = fabric.util.object.clone(img);
  1021.                     clone.set({ left: last.left + size.width * 2, top: last.top });
  1022.                     watermarks.push(clone);
  1023.                 }
  1024.             }
  1025.  
  1026.             const group = new fabric.Group(watermarks);
  1027.             group.set({
  1028.                 left: group.width / 2,
  1029.                 top: group.height / 2,
  1030.                 payload: { type: 'watermark' },
  1031.                 evented: false,
  1032.                 selectable: false
  1033.             });
  1034.  
  1035.             canvas.add(group);
  1036.         });
  1037.     }
  1038.  
  1039.     addObjectHistory(obj, page = false) {
  1040.         if (obj && obj.payload && tablePlugin.appends.includes(obj.payload.type)) return;
  1041.  
  1042.         if (obj.type === 'activeSelection') {
  1043.             if (!page) page = this.getPageByTarget(obj.getObjects()[0]);
  1044.             const objs = obj.getObjects().map(o => {
  1045.                 const json = o.toJSON(this.defaultFields);
  1046.                 json.left += obj.left;
  1047.                 json.top += obj.top;
  1048.  
  1049.                 return json;
  1050.             });
  1051.  
  1052.             this.history.add({ objs, pageId: page ? page.id : false });
  1053.         } else {
  1054.             if (!page) page = this.getPageByTarget(obj);
  1055.  
  1056.             const JSON = obj.toJSON(this.defaultFields);
  1057.             this.history.add({ objs: [JSON], pageId: page ? page.id : false });
  1058.         }
  1059.     }
  1060.  
  1061.     appendElement(obj) {
  1062.         let element = false;
  1063.         if (obj.payload.type == 'checkbox') {
  1064.             const label = document.createElement('label');
  1065.             const input = document.createElement('input');
  1066.             const icon = document.createElement('span');
  1067.  
  1068.             label.setAttribute('class', 'custom-checkbox');
  1069.             input.setAttribute('type', 'checkbox');
  1070.             input.setAttribute('name', obj.payload.settings.name);
  1071.             icon.setAttribute('class', 'custom-checkbox__icon');
  1072.             label.append(input);
  1073.             label.append(icon);
  1074.  
  1075.             element = label;
  1076.             if (obj.payload.settings.checked)
  1077.                 input.setAttribute('checked', obj.payload.settings.checked);
  1078.  
  1079.             input.addEventListener('input', e => {
  1080.                 obj.payload.settings.checked = e.target.checked;
  1081.                 this.addObjectHistory(obj);
  1082.             });
  1083.  
  1084.             obj.on('sync:options', e => input.name = obj.payload.settings.name);
  1085.             this._setupControls(obj, { tl: true, tr: true, bl: true, br: true });
  1086.         }
  1087.  
  1088.         if (obj.payload.type == 'radio_button') {
  1089.             // <label class="custom-radio mb-12">
  1090.             //     <input type="radio" name="radio-1">
  1091.             //     <span class="custom-radio__icon"></span>
  1092.             // </label>
  1093.             const label = document.createElement('label');
  1094.             const input = document.createElement('input');
  1095.             const icon = document.createElement('span');
  1096.  
  1097.             label.setAttribute('class', 'custom-radio');
  1098.             input.setAttribute('type', 'radio');
  1099.             icon.setAttribute('class', 'custom-radio__icon');
  1100.             input.setAttribute('name', obj.payload.settings.name);
  1101.  
  1102.             label.append(input);
  1103.             label.append(icon);
  1104.             element = label;
  1105.  
  1106.             if (obj.payload.settings.checked)
  1107.                 input.setAttribute('checked', obj.payload.settings.checked);
  1108.  
  1109.             input.addEventListener('input', e => {
  1110.                 const canvas = obj.canvas;
  1111.                 let objsHistory = [];
  1112.  
  1113.                 canvas.getObjects().forEach(o => {
  1114.                     if (o.payload && o.payload.settings && o.payload.settings.name == obj.payload.settings.name) {
  1115.                         if (o.id != obj.id) {
  1116.                             o.payload.settings.checked = false;
  1117.                             objsHistory.push(o);
  1118.                         }
  1119.                     }
  1120.                 });
  1121.  
  1122.                 obj.payload.settings.checked = e.target.checked;
  1123.                 const page = this.getPageByTarget(obj);
  1124.                 const JSON = [obj, ...objsHistory].map(obj => obj.toJSON(this.defaultFields));
  1125.                 this.history.add({ objs: JSON, pageId: page ? page.id : false });
  1126.             });
  1127.  
  1128.             obj.on('sync:options', e => input.name = obj.payload.settings.name);
  1129.             this._setupControls(obj, { tl: true, tr: true, bl: true, br: true });
  1130.         }
  1131.  
  1132.         if (obj.payload.type == 'dropdown') {
  1133.             this._setupControls(obj, { /*ml: true, mr: true,*/ br: true });
  1134.             const outer = document.createElement('div');
  1135.             outer.setAttribute('class', 'zindex-3');
  1136.             const select = document.createElement('select');
  1137.             select.setAttribute('class', 'select-default');
  1138.             outer.append(select);
  1139.             element = outer;
  1140.         }
  1141.  
  1142.         if (element) {
  1143.             // element.setAttribute('name', obj.payload.settings.name);
  1144.             element.setAttribute('class', 'data-element-editor ' + element.classList.toString());
  1145.             element.setAttribute('id', `element_${obj.id}`);
  1146.  
  1147.             const updatePosition = () => {
  1148.                 const zoom = obj.canvas.getZoom();
  1149.                 const w = (obj.width * obj.scaleX);// * zoom;
  1150.                 const h = (obj.height * obj.scaleY);// * zoom;
  1151.                 let bounding_rect = obj.getBoundingRect();
  1152.                 //console.log("obj.scaleX", obj.scaleX, obj.scaleY);
  1153.  
  1154.                 let styles = {
  1155.                     position: 'absolute',
  1156.                     left: `${obj.left * zoom - w * zoom / 2}px`,
  1157.                     top: `${obj.top * zoom - h * zoom / 2}px`,
  1158.                     'pointer-events': 'none',
  1159.                     //                    width: `${w}px`,
  1160.                     //                    height: `${h}px`,
  1161.  
  1162.                     width: `${bounding_rect.width - (obj.padding * 2)}px`,
  1163.                     height: `${bounding_rect.height - (obj.padding * 2)}px`,
  1164.  
  1165.                     //transform: `scale(${zoom})`,
  1166.                     'transform-origin': 'top left',
  1167.                     'font-size': `${obj.fontSize || 14}px`
  1168.                 };
  1169.  
  1170.                 //                if(obj.payload.type=='dropdown'){
  1171.                 //                 
  1172.                 //                  styles['transform'] = `scale(${obj.scaleX}, ${obj.scaleY})`;
  1173.                 //                }
  1174.  
  1175.                 element.setAttribute('style', utils.objectStyleToString(styles));
  1176.             };
  1177.  
  1178.             updatePosition();
  1179.  
  1180.             // if (obj.payload.type == 'dropdown') {
  1181.             //     element.addEventListener('click', e => updatePosition());
  1182.             // }
  1183.  
  1184.             obj.on('sync:position', e => updatePosition());
  1185.             ['moving', 'scaling', 'resizing', 'rotating', 'skewing'].forEach(ev => {
  1186.                 obj.on(ev, e => updatePosition());
  1187.             });
  1188.  
  1189.             if (obj.payload.type != 'dropdown') {
  1190.                 const clickToElement = () => {
  1191.                     element.style['pointer-events'] = 'all';
  1192.                     element.click();
  1193.                     updatePosition();
  1194.                 };
  1195.  
  1196.                 if (this._type == 'create' && !this.isConfirmMode) {
  1197.                     obj.on('mousedblclick', e => clickToElement());
  1198.                 }
  1199.  
  1200.                 if (obj.payload.settings.isEditable && this._type == 'edit') {
  1201.                     obj.on('mouseup', e => {
  1202.                         if (this.isClickByMouseUpFabric(e)) clickToElement();
  1203.                     });
  1204.                 }
  1205.             }
  1206.  
  1207.             const canvas = obj.canvas;
  1208.             const upperCanvasEl = canvas.upperCanvasEl;
  1209.             const parent = upperCanvasEl.closest('.document--new');
  1210.             parent.appendChild(element);
  1211.  
  1212.             if (obj.payload.type == 'dropdown') {
  1213.                 try {
  1214.                     element.children[0].addEventListener('change', e => {
  1215.                         const value = e.target.value;
  1216.                         obj.payload.settings.active = value;
  1217.  
  1218.                         this.addObjectHistory(obj);
  1219.                     });
  1220.  
  1221.                     const choice = new Choices(element.children[0], {
  1222.                         searchChoices: false, searchEnabled: false,
  1223.                         allowHTML: true, position: 'top',
  1224.                         shouldSort: false, classNames: {
  1225.                             containerOuter: 'choices select-default',
  1226.                             selectedState: 'is-selected',
  1227.                         }
  1228.                     });
  1229.  
  1230.                     if (this._type == 'create' && !this.isConfirmMode) {
  1231.                         obj.on('mousedblclick', e => {
  1232.                             choice.dropdown.show();
  1233.                             Array.from(document.querySelectorAll('.data-element-editor.zindex-9')).forEach(dee => {
  1234.                                 dee.classList.remove('zindex-9');
  1235.                             });
  1236.  
  1237.                             element.classList.add('zindex-9');
  1238.  
  1239.                             updatePosition();
  1240.                         });
  1241.                     }
  1242.  
  1243.                     if (obj.payload.settings.isEditable && this._type == 'edit') {
  1244.                         obj.on('mouseup', e => {
  1245.                             if (this.isClickByMouseUpFabric(e)) {
  1246.                                 setTimeout(() => {
  1247.                                     choice.dropdown.show();
  1248.                                     updatePosition();
  1249.                                 }, 5);
  1250.                             }
  1251.                         });
  1252.                     }
  1253.  
  1254.                     const renderOptions = () => {
  1255.                         if (obj.payload.settings.options && obj.payload.settings.options.length) {
  1256.                             const list = obj.payload.settings.options.map(q => ({ label: q, value: q }));
  1257.                             choice.setChoices(list, undefined, undefined, true);
  1258.                         }
  1259.  
  1260.                         choice.setChoiceByValue(obj.payload.settings.active);
  1261.                     };
  1262.  
  1263.                     renderOptions();
  1264.                     obj.on('sync:options', e => renderOptions());
  1265.                 } catch (err) {
  1266.                     console.error(err);
  1267.                 }
  1268.             }
  1269.         }
  1270.     }
  1271.  
  1272.     appendErrorMessage(obj = false, isError = false, message) {
  1273.         if (!obj) return false;
  1274.  
  1275.         const isRequired = obj && obj.payload && obj.payload.settings ? !!obj.payload.settings.isRequeired : true;
  1276.         if (((this._type == 'create' && !this.isConfirmMode) || !isRequired) && !obj.text) {
  1277.             isError = false;
  1278.             // fix todo:
  1279.         }
  1280.  
  1281.         obj.set({
  1282.             backgroundColor: !isError ? this.settings.fieldBackgroundColor : this.settings.errorBackgroundField,
  1283.             payload: {
  1284.                 ...obj.payload,
  1285.                 errorMessage: !isError ? '' : message
  1286.             }
  1287.         });
  1288.  
  1289.         try {
  1290.             store.state.editTemplate.editableObjects = store.state.editTemplate.editableObjects.map(eo => {
  1291.                 if (eo.id == obj.id) eo.payload = obj.payload;
  1292.  
  1293.                 return eo;
  1294.             });
  1295.         } catch (err) {
  1296.             console.error(err);
  1297.         }
  1298.  
  1299.         if (isError) store.state.$toast({ message: message, type: 'error' });
  1300.         obj.canvas.renderAll();
  1301.     }
  1302.  
  1303.     isEditableObject(obj) {
  1304.         return obj && obj.payload && obj.payload.settings && obj.payload.settings.isEditable;
  1305.     }
  1306.  
  1307.     isRequiredEdit(obj) {
  1308.         return this.isEditableObject(obj) && obj.payload.settings.isRequeired;
  1309.     }
  1310.  
  1311.     setupDefaultOptions(obj) {
  1312.         const isFabricObject = !!obj.on;
  1313.  
  1314.         if (obj && obj.payload && obj.payload.type == 'image' && isFabricObject && !this.isConfirmMode) {
  1315.             const openReplace = () => {
  1316.                 store.state.activeChangedImage = true;
  1317.                 store.state.sidebar = 'image';
  1318.                 store.state.sidebarTabs.image = 'images';
  1319.             };
  1320.  
  1321.             obj.on('mousedblclick', e => {
  1322.                 utils.openUploadFile('image/png, image/jpg, image/jpeg').then(async files => {
  1323.                     if (files && files.length) {
  1324.                         await idb.uploadImage(files[0]);
  1325.                         await this.replaceSelectedImage(files[0]);
  1326.  
  1327.                         if (this._type != 'edit') setTimeout(() => {
  1328.                             store.state.createTemplate.ticker += 1;
  1329.                         }, 100);
  1330.                     }
  1331.                 });
  1332.             });
  1333.         }
  1334.  
  1335.         if (isFabricObject) {
  1336.             obj.on('modified', e => {
  1337.                 let findedErrorMessages = [];
  1338.                 for (const page of this.pages) {
  1339.                     const errorObjects = page.canvas.getObjects().filter(o => o && o.payload && o.payload.errorMessage);
  1340.                     if (errorObjects.length) {
  1341.                         findedErrorMessages.push({ page: page.id, objects: errorObjects });
  1342.                     }
  1343.                 }
  1344.  
  1345.                 store.state.beforeSave.errors = findedErrorMessages;
  1346.             });
  1347.         }
  1348.  
  1349.         if (this._type == 'edit' && isFabricObject) {
  1350.             if (obj && obj.payload && obj.payload.type && tablePlugin.appends.includes(obj.payload.type)) {
  1351.  
  1352.             } else {
  1353.                 this.setupFieldsSwitching(obj);
  1354.                 if (!this.isEditableObject(obj)) {
  1355.                     obj.backgroundColor = 'rgba(0, 0, 0, 0)';
  1356.                 } else {
  1357.                     obj.backgroundColor = this.settings.fieldBackgroundColor;//'rgba(255, 183, 0, 0.2)';
  1358.                 }
  1359.  
  1360.                 if (this.isRequiredEdit(obj)) {
  1361.                     obj.backgroundColor = '#ffeddf';
  1362.                     store.state.beforeSave.requiredObjects.push(obj.id);
  1363.  
  1364.                     obj.on('modified', e => {
  1365.                         obj.backgroundColor = this.settings.fieldBackgroundColor;//'rgba(255, 183, 0, 0.2)';
  1366.                         store.state.beforeSave.requiredObjects = store.state.beforeSave.requiredObjects.filter(r => r != obj.id);
  1367.                         obj.canvas.renderAll();
  1368.                     });
  1369.                 }
  1370.             }
  1371.         }
  1372.  
  1373.         if (!obj.payload) obj.payload = { type: 'unnamed' };
  1374.         if (isFabricObject) {
  1375.             const isTextElement = ['i-text', 'textbox', 'line'].includes(obj.type);
  1376.  
  1377.             obj.setControlVisible('mtr', false);
  1378.             if (isTextElement) {
  1379.                 obj.setControlVisible('mb', false);
  1380.                 obj.setControlVisible('tl', false);
  1381.                 obj.setControlVisible('tr', false);
  1382.                 obj.setControlVisible('mt', false);
  1383.                 obj.setControlVisible('bl', false);
  1384.                 obj.setControlVisible('br', false);
  1385.  
  1386.                 if (obj.type == 'i-text' || (obj && obj.payload && obj.payload.fromPDF)) {
  1387.                     obj.setControlVisible('ml', false);
  1388.                     obj.setControlVisible('mr', false);
  1389.  
  1390.                     obj.on('mousedown', e => {
  1391.                         if (obj.objectCaching)
  1392.                             obj.objectCaching = false;
  1393.                     })
  1394.                 }
  1395.             }
  1396.  
  1397.             const formList = ['date', 'email', 'website', 'phone', 'number', 'currency'];
  1398.             if (obj.payload && ['checkbox', 'radio_button', 'dropdown'].includes(obj.payload.type)) {
  1399.                 this.appendElement(obj);
  1400.             } else if (obj.payload && formList.includes(obj.payload.type)) {
  1401.                 if ((obj.payload.settings || obj.payload.settings.isEditable) || (this._type == 'create' || !this.isConfirmMode)) {
  1402.                     switch (obj.payload.type) {
  1403.                         case 'date':
  1404.                             if (this._type == 'edit') {
  1405.                                 obj.on('mouseup', e => store.state.widget = 'date');
  1406.                             } else {
  1407.                                 obj.on('mousedblclick', e => store.state.widget = 'date');
  1408.                             }
  1409.  
  1410.                             break;
  1411.  
  1412.                         case 'number':
  1413.                             obj.on('editing:exited', e => {
  1414.                                 const numberValue = parseInt(obj.text);
  1415.                                 const isError = isNaN(numberValue);
  1416.  
  1417.                                 if (obj.text != numberValue && !isError) {
  1418.                                     obj.text = String(numberValue);
  1419.                                 }
  1420.  
  1421.                                 this.appendErrorMessage(obj, isError, 'Invalid number value!');
  1422.                             });
  1423.  
  1424.                             break;
  1425.  
  1426.                         case 'currency':
  1427.                             obj.on('editing:exited', e => {
  1428.                                 const numberValue = parseFloat(obj.text);
  1429.                                 const isError = isNaN(numberValue);
  1430.  
  1431.                                 if (!isError) {
  1432.                                     obj.text = String(numberValue) + `${obj.payload.settings.currency || '$'}`;
  1433.                                 }
  1434.  
  1435.                                 this.appendErrorMessage(obj, isError, 'Invalid currency value!');
  1436.                             });
  1437.  
  1438.                             break;
  1439.  
  1440.                         case 'email':
  1441.                             obj.on('editing:exited', e => {
  1442.                                 const isEmail = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g.test(obj.text);
  1443.                                 this.appendErrorMessage(obj, !isEmail, 'Incorrect email address entered!');
  1444.                             });
  1445.  
  1446.                             break;
  1447.  
  1448.                         case 'website':
  1449.                             obj.on('editing:exited', e => {
  1450.                                 const isUrl = /[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)?/gi.test(obj.text);
  1451.                                 obj.set({
  1452.                                     backgroundColor: isUrl ? this.settings.fieldBackgroundColor : this.settings.errorBackgroundField,
  1453.                                     payload: {
  1454.                                         ...obj.payload,
  1455.                                         errorMessage: isUrl
  1456.                                             ? '' : 'Incorrect URL adress!'
  1457.                                     }
  1458.                                 });
  1459.  
  1460.                                 obj.canvas.renderAll();
  1461.                             });
  1462.  
  1463.                             break;
  1464.                     }
  1465.                 }
  1466.             }
  1467.  
  1468.             if (obj.payload && ['sign'].includes(obj.payload.type)) {
  1469.                 if (obj.type == 'textbox') {
  1470.                     obj.setControlVisible('mb', false);
  1471.                     obj.setControlVisible('tl', false);
  1472.                     obj.setControlVisible('tr', false);
  1473.                     obj.setControlVisible('mt', false);
  1474.                     obj.setControlVisible('bl', false);
  1475.                     obj.setControlVisible('br', false);
  1476.                 } else {
  1477.                     obj.setControlVisible('mb', false);
  1478.                     obj.setControlVisible('mr', false);
  1479.                     obj.setControlVisible('mt', false);
  1480.                     obj.setControlVisible('ml', false);
  1481.                 }
  1482.             }
  1483.  
  1484.             if (obj.payload && obj.payload.isElement) {
  1485.                 // this.fixElementData(obj);
  1486.                 obj.setControlVisible('mb', false);
  1487.                 obj.setControlVisible('tl', false);
  1488.                 obj.setControlVisible('tr', false);
  1489.                 obj.setControlVisible('mt', false);
  1490.                 obj.setControlVisible('bl', false);
  1491.                 obj.setControlVisible('br', false);
  1492.                 obj.setControlVisible('ml', false);
  1493.                 obj.setControlVisible('mr', false);
  1494.             }
  1495.  
  1496.             if (obj.payload && ['arrow', 'line'].includes(obj.payload.type)) {
  1497.                 obj.setControlVisible('mb', false);
  1498.                 obj.setControlVisible('tl', false);
  1499.                 obj.setControlVisible('tr', false);
  1500.                 obj.setControlVisible('mt', false);
  1501.                 obj.setControlVisible('bl', false);
  1502.                 obj.setControlVisible('br', false);
  1503.                 obj.setControlVisible('mtr', true);
  1504.  
  1505.                 lineArrow.initArrowScaling(obj);
  1506.             }
  1507.         }
  1508.  
  1509.         if (!tablePlugin.appends.includes(obj.payload.type)) {
  1510.             const dataFields = {
  1511.                 id: obj.id || utils.uuidv4(),
  1512.                 transparentCorners: false, borderColor: '#7C4DFF',
  1513.                 centeredRotation: true, borderDashArray: [4, 2], cornerColor: '#7C4DFF',
  1514.                 cornerSize: 8, strokeUniform: true,
  1515.                 originX: 'center', originY: 'center'
  1516.             };
  1517.  
  1518.             if (isFabricObject) {
  1519.                 obj.set(dataFields);
  1520.             } else {
  1521.                 for (const key in dataFields) {
  1522.                     obj[key] = dataFields[key];
  1523.                 }
  1524.             }
  1525.  
  1526.             if (obj.payload.type == 'table' && isFabricObject) {
  1527.                 obj.set({ originX: 'left', originY: 'top' });
  1528.  
  1529.                 tablePlugin.initTableFromJSON(obj, true);
  1530.                 setTimeout(() => tablePlugin.calculateGroupPrint(obj), 500);
  1531.             }
  1532.         }
  1533.  
  1534.         if (obj.setCoords) obj.setCoords();
  1535.         return obj;
  1536.     }
  1537.  
  1538.     setupEraseCanvas(canvas) {
  1539.         const lowerCanvasEl = canvas.lowerCanvasEl;
  1540.  
  1541.         let objectToDrawing = false;
  1542.         let origX = false, origY = false;
  1543.         lowerCanvasEl.addEventListener('mousedown', e => {
  1544.             const layerX = e.layerX / canvas.getZoom();
  1545.             const layerY = e.layerY / canvas.getZoom();
  1546.  
  1547.             if (store.state.editorMode == 'erase') {
  1548.                 origX = layerX;
  1549.                 origY = layerY;
  1550.  
  1551.                 objectToDrawing = new fabric.Rect({
  1552.                     left: layerX,
  1553.                     top: layerY,
  1554.                     width: 1, height: 1,
  1555.                     strokeWidth: 0,
  1556.                     fill: '#ffffff',
  1557.                     payload: { type: 'erase' }
  1558.                 });
  1559.  
  1560.                 canvas.add(objectToDrawing);
  1561.             }
  1562.         });
  1563.  
  1564.         lowerCanvasEl.addEventListener('mousemove', e => {
  1565.             const layerX = e.layerX / canvas.getZoom();
  1566.             const layerY = e.layerY / canvas.getZoom();
  1567.  
  1568.             if (objectToDrawing) {
  1569.                 if (origX > layerX)
  1570.                     objectToDrawing.set({ left: Math.abs(layerX) });
  1571.  
  1572.                 if (origY > layerY)
  1573.                     objectToDrawing.set({ top: Math.abs(layerY) });
  1574.  
  1575.                 objectToDrawing.set({
  1576.                     height: Math.abs(origY - layerY),
  1577.                     width: Math.abs(origX - layerX),
  1578.                     originX: 'left', originY: 'top'
  1579.                 });
  1580.  
  1581.                 if (objectToDrawing.canvas)
  1582.                     objectToDrawing.canvas.renderAll();
  1583.             }
  1584.         });
  1585.  
  1586.         const stopErase = () => {
  1587.             if (objectToDrawing) {
  1588.                 objectToDrawing.set({
  1589.                     left: objectToDrawing.left + objectToDrawing.width / 2,
  1590.                     top: objectToDrawing.top + objectToDrawing.height / 2,
  1591.                     originX: 'center', originY: 'center'
  1592.                 });
  1593.  
  1594.                 objectToDrawing.setCoords();
  1595.                 if (objectToDrawing.canvas)
  1596.                     objectToDrawing.canvas.renderAll();
  1597.                 //objectToDrawing.canvas.remove(objectToDrawing);
  1598.  
  1599.                 objectToDrawing = false;
  1600.             }
  1601.         };
  1602.  
  1603.         lowerCanvasEl.addEventListener('mouseup', e => stopErase());
  1604.     }
  1605.  
  1606.     createCanvas(page, pageElement) {
  1607.         page.canvas = new fabric.Canvas(pageElement, {
  1608.             ...this.payloadPage,
  1609.             ...page.data.options,
  1610.             uniformScaling: false
  1611.         });
  1612.  
  1613.         //!this.isConfirmMode
  1614.  
  1615.         this.setupEraseCanvas(page.canvas);
  1616.         store.state.canvas = page.canvas;
  1617.         this.setupEvents(page);
  1618.         if (this._type == 'edit') {
  1619.             page.canvas.on('mouse:up', e => {
  1620.                 if (!e.target) return;
  1621.                 if (this.isClickByMouseUpFabric(e)) {
  1622.                     const obj = e.target;
  1623.  
  1624.                     if (obj && obj.payload && obj.payload.settings && obj.payload.settings.isEditable) {
  1625.                         if (obj.payload.type == 'sign') {
  1626.                             if (obj.type != 'textbox')
  1627.                                 return store.state.popup = 'signature';
  1628.                         }
  1629.  
  1630.                         if (obj.type == 'textbox') {
  1631.                             obj.enterEditing();
  1632.                             obj.selectAll();
  1633.  
  1634.                             return;
  1635.                         }
  1636.                     }
  1637.                 }
  1638.             });
  1639.         }
  1640.  
  1641.         page.canvas.on('path:created', e => {
  1642.             const path = e.path;
  1643.  
  1644.             path.payload = { type: 'image', settings: _.cloneDeep(this.defaultSettings || {}) };
  1645.             path.set({
  1646.                 left: (path.left + path.width / 2) + path.strokeWidth / 2,
  1647.                 top: (path.top + path.height / 2) + path.strokeWidth / 2
  1648.             });
  1649.  
  1650.             path.setCoords();
  1651.             page.canvas.renderAll();
  1652.         });
  1653.  
  1654.         return page;
  1655.     }
  1656.  
  1657.     isClickByMouseUpFabric(e) {
  1658.         try {
  1659.             const isTransform = e.transform && e.transform.original;
  1660.             if (isTransform) {
  1661.                 const original = e.transform.original;
  1662.                 const keys = ['left', 'top', 'scaleX', 'scaleY'];
  1663.  
  1664.                 const isClick = keys.reduce((res, key) => {
  1665.                     if (!res) return false;
  1666.                     if (original[key] != e.target[key]) return false;
  1667.  
  1668.                     return res;
  1669.                 }, true);
  1670.  
  1671.                 return isClick;
  1672.             }
  1673.  
  1674.             return false;
  1675.         } catch (err) {
  1676.             console.error(err);
  1677.         }
  1678.  
  1679.         return false;
  1680.     }
  1681.  
  1682.     setupEvents(page) { }
  1683.  
  1684.     showGrid(canvas) {
  1685.         let grid = this.grid;
  1686.         if (this.has_grid) {
  1687.             return;
  1688.         }
  1689.  
  1690.         this.has_grid = true;
  1691.         for (var i = 0; i < (canvas.width / grid); i++) {
  1692.             canvas.add(new fabric.Line([i * grid, 0, i * grid, canvas.height], { type: 'line', stroke: '#ccc', selectable: false, grid_element: true }));
  1693.             canvas.add(new fabric.Line([0, i * grid, canvas.width, i * grid], { type: 'line', stroke: '#ccc', selectable: false, grid_element: true }));
  1694.         }
  1695.         //          let l2 = new fabric.Line([0, i * grid, 600, i * grid], {
  1696.         //              stroke: '#ccc',
  1697.         //              selectable: false
  1698.         //          });
  1699.         //          l2.grid_element = true;
  1700.         //          canvas.add(l2);
  1701.  
  1702.     }
  1703.  
  1704.     hideGrid(canvas) {
  1705.         if (!canvas) {
  1706.             return;
  1707.         }
  1708.  
  1709.         canvas.getObjects().forEach(function (el) {
  1710.             if (el.grid_element) {
  1711.                 canvas.remove(el);
  1712.             }
  1713.         });
  1714.  
  1715.         this.has_grid = false;
  1716.     }
  1717.  
  1718.     snapToGrid(options) {
  1719.         let grid = this.grid;
  1720.         const target = options.target;
  1721.  
  1722.         let incrementOriginX = 0;
  1723.         let incrementOriginY = 0;
  1724.  
  1725.         if (target.originX == 'center') incrementOriginX = (target.width / 2);
  1726.         if (target.originY == 'center') incrementOriginY = (target.height / 2);
  1727.  
  1728.         if (target.padding > 0) {
  1729.             incrementOriginX += target.padding / 2;
  1730.             incrementOriginY += target.padding / 2;
  1731.         }
  1732.  
  1733.         let left = target.left - incrementOriginX;
  1734.         let top = target.top - incrementOriginY;
  1735.  
  1736.         if (Math.round(left / grid * 4) % 4 == 0 && Math.round(top / grid * 4) % 4 == 0) {
  1737.             options.target.set({
  1738.                 left: (Math.round(left / grid) * grid) + incrementOriginX,
  1739.                 top: (Math.round(top / grid) * grid) + incrementOriginY
  1740.             }).setCoords();
  1741.         }
  1742.     }
  1743.  
  1744.     async renderPage(page, options = {}, renderEmptyPage = false) {
  1745.         const doc = document.createElement('div');
  1746.         doc.setAttribute('data-page', page.id);
  1747.         doc.setAttribute('class', 'document document--new');
  1748.  
  1749.         const pageElement = document.createElement('canvas');
  1750.         pageElement.setAttribute('class', 'document-container');
  1751.         doc.appendChild(pageElement);
  1752.         if (this.documentElement.children.length > options.indexAppend) {
  1753.             const element = this.documentElement.children[options.indexAppend];
  1754.             this.documentElement.insertBefore(doc, element);
  1755.         } else {
  1756.             this.documentElement.appendChild(doc);
  1757.         }
  1758.  
  1759.         this.createCanvas(page, pageElement);
  1760.         doc.setAttribute('style', `width:${page.canvas.width}px;height:${page.canvas.height}px;max-width:fit-content;`);
  1761.  
  1762.         const isDroppable = e => {
  1763.             if (store.state.dragging) {
  1764.                 return ['element_sidebar', 'image_sidebar'].includes(store.state.dragging.type);
  1765.             } else {
  1766.                 // console.log('else....');
  1767.             }
  1768.         };
  1769.  
  1770.         doc.addEventListener('dragover', e => {
  1771.             if (isDroppable(e)) doc.style['box-shadow'] = '0 0 5px 0px #7C4DFF';
  1772.         });
  1773.  
  1774.         doc.addEventListener('dragleave', e => {
  1775.             if (isDroppable(e)) doc.style['box-shadow'] = 'none';
  1776.         });
  1777.  
  1778.         doc.addEventListener('drop', e => {
  1779.             doc.style['box-shadow'] = 'none';
  1780.             if (isDroppable(e)) {
  1781.                 const left = e.layerX / page.canvas.getZoom();
  1782.                 const top = e.layerY / page.canvas.getZoom();
  1783.  
  1784.                 const opts = { options: { left, top } };
  1785.                 if (store.state.dragging.type == 'image_sidebar') {
  1786.                     this.openImage(store.state.dragging.data.file, opts, page.id);
  1787.                 } else {
  1788.                     const payload = store.state.dragging.data.payload || {};
  1789.                     if (payload.options && typeof payload.options === 'object') {
  1790.                         payload.options = {
  1791.                             ...payload.options,
  1792.                             ...opts.options
  1793.                         };
  1794.                     } else {
  1795.                         payload.options = opts.options;
  1796.                     }
  1797.  
  1798.                     this.addElement(store.state.dragging.data.key, payload, page.id);
  1799.                 }
  1800.             }
  1801.  
  1802.             store.state.dragging = false;
  1803.         });
  1804.  
  1805.         if (!renderEmptyPage) await this.loadPageDataFromJson(page);
  1806.         return page;
  1807.     }
  1808.  
  1809.     async loadPageDataFromJson(page) {
  1810.         console.time('RENDER PAGE: ');
  1811.         if (!page || !page.json) return false;
  1812.         if (page.json && page.json.objects) {
  1813.             const parseTexts = objects => {
  1814.                 return objects.map(o => {
  1815.                     if (['textbox', 'i-text'].includes(o.type)) {
  1816.                         // if (!o.text) o.text = ' ';
  1817.                         if (o.text == ' ') o.text = '';
  1818.                     }
  1819.  
  1820.                     if (o.type == 'group') {
  1821.                         o.objects = parseTexts(o.objects);
  1822.                     }
  1823.  
  1824.                     return o;
  1825.                 });
  1826.             };
  1827.  
  1828.             page.json.objects = parseTexts(page.json.objects);
  1829.         }
  1830.  
  1831.         page = tablePlugin.parseTableFillData(page);
  1832.  
  1833.         const json = JSON.parse(JSON.stringify(page.json));
  1834.         await new Promise(resolve => {
  1835.             page.canvas.loadFromJSON(json, () => {
  1836.  
  1837.                 page.canvas.renderAll.bind(page.canvas);
  1838.                 if (this.initialParseObjects) {
  1839.                     page.canvas.getObjects().forEach(obj => this.initialParseObjects(obj));
  1840.                 }
  1841.  
  1842.                 resolve();
  1843.             });
  1844.         });
  1845.  
  1846.         setTimeout(() => this.updateThumbail(page, true), 900);
  1847.  
  1848.         console.timeEnd('RENDER PAGE: ');
  1849.         return page;
  1850.     }
  1851.  
  1852.     get activeObject() {
  1853.         const create = store.state.createTemplate.editor;
  1854.         const editor = store.state.editTemplate.editor;
  1855.  
  1856.         if (create) return store.state.createTemplate.activeObject;
  1857.         if (editor) return store.state.editTemplate.activeObject;
  1858.  
  1859.         return false;
  1860.     }
  1861.  
  1862.     updateThumbail(page, force = false) {
  1863.         if (page && page.viewer && !page.viewer.isRendered) return;
  1864.         if (this.lockUpdateThumbail && !force) return false;
  1865.         const canvas = page.canvas;
  1866.         if (!page) return;
  1867.  
  1868.         const sc = Math.min(135 / (canvas.width * canvas.getZoom()), 185 / (canvas.height * canvas.getZoom()));
  1869.         const base64 = canvas.toDataURL({ multiplier: sc * 3 });
  1870.         fetch(base64).then(async res => {
  1871.             page.thumb = URL.createObjectURL(await res.blob());
  1872.         });
  1873.  
  1874.         return base64;
  1875.     }
  1876.  
  1877.     async loadFileToBase64(file) {
  1878.         return new Promise((resolve, reject) => {
  1879.             const fileReader = new FileReader();
  1880.             fileReader.onload = e => resolve(e.target.result);
  1881.             fileReader.onerror = e => reject(e);
  1882.             fileReader.readAsDataURL(file);
  1883.         });
  1884.     }
  1885.  
  1886.     cloneActiveElement() {
  1887.         const active = this.getActiveObject();
  1888.         if (active.type != 'activeSelection') {
  1889.             active.clone(clone => {
  1890.                 clone.left += 10;
  1891.                 clone.top += 10;
  1892.                 clone.id = utils.uuidv4();
  1893.  
  1894.                 // if(clone.payload.type == 'table') {
  1895.                 //     tablePlugin.initTableFromJSON(clone, true);
  1896.                 // }
  1897.  
  1898.                 active.canvas.add(clone).setActiveObject(clone);
  1899.             }, this.defaultFields);
  1900.         } else {
  1901.             // const clone = fabric.util.object.clone(active);
  1902.         }
  1903.     }
  1904.  
  1905.     getActiveObject() {
  1906.         const activePage = this.getActivePage();
  1907.         if (!activePage) return false;
  1908.  
  1909.         const active = activePage.canvas.getActiveObject();
  1910.         if (!active) return false;
  1911.  
  1912.         return active;
  1913.     }
  1914.  
  1915.     async replaceSelectedImage(file, options = {}, pageId = false) {
  1916.         const base64 = typeof file == 'string' ? file : await this.loadFileToBase64(file);
  1917.         const activeObject = this.activeObject;
  1918.  
  1919.         const w = activeObject.width * activeObject.scaleX;
  1920.         const h = activeObject.height * activeObject.scaleY;
  1921.         if (activeObject.type == 'image') {
  1922.             activeObject.setSrc(base64, target => {
  1923.                 const scale = Math.min(w / target.width, h / target.height);
  1924.                 target.set({ scaleX: scale, scaleY: scale });
  1925.  
  1926.                 this.addObjectHistory(target);
  1927.                 if (this.initialParseObjects) this.initialParseObjects(target);
  1928.                 target.canvas.requestRenderAll();
  1929.             });
  1930.         } else {
  1931.             const image = await new Promise(resolve => fabric.Image.fromURL(base64, object => {
  1932.                 const payload = _.cloneDeep(activeObject.payload);
  1933.                 const scale = Math.min(w / object.width, h / object.height);
  1934.  
  1935.                 object.set({
  1936.                     scaleX: scale,
  1937.                     scaleY: scale,
  1938.                     left: activeObject.left,
  1939.                     top: activeObject.top,
  1940.  
  1941.                     id: activeObject.id,
  1942.                     payload
  1943.                 });
  1944.  
  1945.                 resolve(object);
  1946.             }));
  1947.  
  1948.             if (activeObject.canvas) {
  1949.                 const canvas = activeObject.canvas;
  1950.                 canvas.remove(activeObject);
  1951.                 canvas.add(image).setActiveObject(image).requestRenderAll();
  1952.                 if (this.initialParseObjects) this.initialParseObjects(image);
  1953.             }
  1954.         }
  1955.  
  1956.         return activeObject;
  1957.     }
  1958.  
  1959.     addElementToPage(object, toActive = true, forcePage = false, lockPositions = false) {
  1960.         if (!object) return false;
  1961.         const page = this.getActivePage(forcePage);
  1962.         if (!page) return false;
  1963.  
  1964.         // page rotate
  1965.         if (!this.history.lockHistory) {
  1966.             this.pages = this.pages.map(p => {
  1967.                 if (p.id == page.id) p.isLockRotate = true;
  1968.  
  1969.                 return p;
  1970.             });
  1971.         }
  1972.         // page rotate
  1973.  
  1974.         let left = object.left;
  1975.         let top = object.top;
  1976.         const canvasWidth = page.canvas.getWidth() / page.canvas.getZoom();
  1977.         const canvasHeight = page.canvas.getHeight() / page.canvas.getZoom();
  1978.  
  1979.         if (left <= 0 && top <= 0) {
  1980.             left = (canvasWidth - object.width / object.scaleX) / 2;
  1981.             top = (canvasHeight - object.height / object.scaleY) / 2;
  1982.  
  1983.             if (object.payload.type == 'table') {
  1984.                 left = (canvasWidth - 402) / 2;
  1985.                 top = (canvasHeight - 116) / 2
  1986.             }
  1987.         }
  1988.  
  1989.         if (object.dynamicConfig && object.dynamicConfig.type == 'table') {
  1990.             object.set({ left: 0, top: 0, padding: this.defOptions });
  1991.         } else if (!lockPositions) {
  1992.             object.set({
  1993.                 left: object.width * object.scaleX / 2 + left,
  1994.                 top: object.height * object.scaleY / 2 + top,
  1995.                 padding: this.defOptions.padding
  1996.             });
  1997.         }
  1998.  
  1999.         page.canvas.add(object);
  2000.         if (toActive) page.canvas.setActiveObject(object).renderAll();
  2001.  
  2002.         return object;
  2003.     }
  2004.  
  2005.     async downloadCurrentDocument(isWindowOpen = false) {
  2006.         store.state.loading = true;
  2007.  
  2008.         setTimeout(async () => {
  2009.             try {
  2010.                 const zoomBefore = store.state.zoom;
  2011.                 await this.setZoom(100);
  2012.                 const blob = await new CreatePDF(_.cloneDeep(this.pages)).createDoc(this.pages, this);
  2013.                 this.setZoom(zoomBefore);
  2014.                 utils.downloadFile(URL.createObjectURL(blob), `${store.state.activeTemplate.title}.pdf`, isWindowOpen);
  2015.             } catch (err) {
  2016.                 console.error(err);
  2017.                 store.state.$toast({ message: 'Something went wrong', type: 'error' });
  2018.             }
  2019.  
  2020.             store.state.loading = false;
  2021.         }, 500);
  2022.     }
  2023.  
  2024.     async exportPDF(isDownload = false, toPreview = false) {
  2025.         const zoomBefore = store.state.zoom;
  2026.         await this.setZoom(100);
  2027.         const blob = await new CreatePDF(_.cloneDeep(this.pages)).createDoc(this.pages, this);
  2028.         let upload_url = provider.routers.save_filled.replace('%d', this.loadedTemplate.id);
  2029.         if (this.filled_template.id) { }
  2030.  
  2031.         const json = await this.exportCurrentState();
  2032.         let resp = await utils.uploadFile(upload_url, utils.blobToFile(blob), this.filled_template, json);
  2033.         if (resp.success) {
  2034.             this.filled_template.id = resp.id;
  2035.             this.filled_template.hash = resp.hash;
  2036.         } else {
  2037.             utils.showError("Error", resp.message ? resp.message : "Unknown error");
  2038.             return false;
  2039.         }
  2040.  
  2041.         //if (toPreview) return store.state.$router.push(`/preview-template/${resp.hash}?access_key=${resp.access_key}`);
  2042.         if (!isDownload) return blob;
  2043.         this.setZoom(zoomBefore);
  2044.         utils.downloadFile(URL.createObjectURL(blob), `${store.state.activeTemplate.title}.pdf`);
  2045.         return blob;
  2046.     }
  2047.  
  2048.     modifyObject(obj) {
  2049.         if (!obj) return false;
  2050.  
  2051.         store.state.editTemplate.editableObjects = store.state.editTemplate.editableObjects.map(eo => {
  2052.             if (eo && eo.payload && eo.payload.type) {
  2053.                 switch (eo.payload.type) {
  2054.                     case 'sign': {
  2055.                         const object = this.pages.reduce((obj, page) => {
  2056.                             const o = page.canvas.getObjects().find(ob => ob.id == eo.id);
  2057.                             if (o) return o;
  2058.  
  2059.                             return obj;
  2060.                         }, false);
  2061.  
  2062.                         if (object.type == 'group' && object.getObjects) {
  2063.                             if (object.getObjects().find(q => q.type != 'path')) return eo;
  2064.                         }
  2065.  
  2066.                         break;
  2067.                     }
  2068.                 }
  2069.             }
  2070.  
  2071.             if (eo.id == obj.id) eo.isModified = true;
  2072.             return eo;
  2073.         });
  2074.     }
  2075.  
  2076.     async addSignature(sign = false, json = false) {
  2077.         // debugger;
  2078.         let isNew = false;
  2079.  
  2080.         if (!sign) {
  2081.             isNew = true;
  2082.             sign = await new Promise(resolve => this.addElement('sign', {}, false, resolve));
  2083.         }
  2084.  
  2085.         if (!sign || !json) return false;
  2086.         let object = (await this.enlivenObjects([json]))[0];
  2087.         const sw = sign.width * sign.scaleX;
  2088.         const sh = sign.height * sign.scaleY;
  2089.  
  2090.         const rect = new fabric.Rect({
  2091.             left: 0, top: 0,
  2092.             width: sw, height: sh,
  2093.             fill: 'rgba(0, 0, 0, 0)',
  2094.             stroke: 'rgba(0, 0, 0, 0)',
  2095.             strokeWidth: 0
  2096.         });
  2097.  
  2098.         let scale = Math.min(sw / object.width, sh / object.height);
  2099.         if (object.type == 'textbox' && scale > 1) scale = 1;
  2100.  
  2101.         object.set({
  2102.             scaleX: scale, scaleY: scale,
  2103.             left: (rect.width - object.width * scale) / 2,
  2104.             top: (rect.height - object.height * scale) / 2
  2105.         });
  2106.  
  2107.         let gr = new fabric.Group([rect, object], {
  2108.             left: sign.left,
  2109.             top: sign.top,
  2110.             id: sign.id,
  2111.             payload: _.cloneDeep({ ...sign.payload, isUpdated: true }),
  2112.             editable: true,
  2113.             backgroundColor: sign.backgroundColor,
  2114.             originX: 'center', originY: 'center'
  2115.         });
  2116.  
  2117.         object = gr;
  2118.         // const scale = /* object.type == 'textbox' ? 1 :  */Math.min(
  2119.         //     // (sign.width * sign.scaleX) / object.width,
  2120.         //     (sign.height * sign.scaleY) / object.height
  2121.         // );
  2122.  
  2123.         // const ow = object.width;// * scale;
  2124.         // const oh = object.height;// * scale;
  2125.  
  2126.         // const sw = sign.width * sign.scaleX;
  2127.         // const sh = sign.height * sign.scaleY;
  2128.  
  2129.         // console.log('????sign.left', sign.left, sw, ow);
  2130.         // object.set({
  2131.         //     id: sign.id,
  2132.         //     scaleX: scale, scaleY: scale,
  2133.         //     left: sign.left - (sw / 2) + ow / 2,
  2134.         //     top: sign.top - (sh / 2) + oh / 2,
  2135.         //     // top: sign.top - sh / 2 + (object.height * scale) / 2,
  2136.         //     payload: _.cloneDeep({ ...sign.payload, isUpdated: true }),
  2137.         //     editable: true,
  2138.         //     backgroundColor: sign.backgroundColor
  2139.         // });
  2140.  
  2141.         if (sign.canvas) sign.canvas.remove(sign);
  2142.         // if (isNew) object.set({ left: object.width / 2 + 100, top: object.height / 2 + 100 });
  2143.         const obj = await this.addElementToPage(object, true, false, !isNew);
  2144.  
  2145.         this.modifyObject(obj);
  2146.         if (this.initialParseObjects) this.initialParseObjects(obj);
  2147.         return obj;
  2148.     }
  2149.  
  2150.     async openImage(file, options = {}, pageId = false) {
  2151.         const base64 = await this.loadFileToBase64(file);
  2152.         return this.addElement('image', { src: base64, ...options }, pageId);
  2153.     }
  2154.  
  2155.     setListStyle(textObject, type, canvas, allStyles) {
  2156.         var styles = ['\u25CF', '\u25C8', '\u25D8', '\u25BA', '\u25CB', '\u25A0', '\u27F6', '-'];
  2157.  
  2158.         var text = textObject.text;
  2159.         var textArray = text.split('\n')
  2160.         var tempStr = [];
  2161.         textArray.forEach((text, i) => {
  2162.             if (styles.includes(text.substr(0, 1))) {
  2163.                 tempStr.push(text.replace(text.substr(0, 1), allStyles[type]));
  2164.             } else {
  2165.                 tempStr.push(allStyles[type] + '' + text);
  2166.             }
  2167.         })
  2168.         textObject['text'] = tempStr.join('\n');
  2169.         canvas.renderAll();
  2170.     }
  2171.  
  2172.     setSelectionStyles(styles = {}, isUpdate = false) {
  2173.         const target = this.activeObject;
  2174.         if (!target) return false;
  2175.         let payload = {};
  2176.  
  2177.         const isFields = [
  2178.             'full_name', 'email',
  2179.             'address', 'date', 'phone',
  2180.             'website', 'custom_field', 'sign',
  2181.             'number', 'initials', 'currency'
  2182.         ].includes(target.payload ? (target.payload.type || '') : '');
  2183.  
  2184.         const getValueByKey = (key, value) => {
  2185.             switch (key) {
  2186.                 case 'color':
  2187.                     return { fill: value };
  2188.  
  2189.                 case 'font':
  2190.                     return { fontFamily: value };
  2191.  
  2192.                 case 'bold':
  2193.                     return { fontWeight: value ? 'bold' : 'normal' };
  2194.  
  2195.                 case 'italic':
  2196.                     return { fontStyle: value ? 'italic' : 'normal' };
  2197.  
  2198.                 case 'header': {
  2199.                     target.payload.heading = styles[key];
  2200.                     const stylesHeading = {
  2201.                         h1: { fontSize: 32, fontWeight: 'bold' },
  2202.                         h2: { fontSize: 24, fontWeight: 'bold' },
  2203.                         h3: { fontSize: 18, fontWeight: 'bold' },
  2204.                         h4: { fontSize: 16, fontWeight: 'bold' },
  2205.                         h5: { fontSize: 14, fontWeight: 'bold' },
  2206.                         h6: { fontSize: 10, fontWeight: 'bold' },
  2207.                     };
  2208.  
  2209.                     if (stylesHeading[value]) {
  2210.                         return { ...payload, ...stylesHeading[styles[key]] };
  2211.                     } else {
  2212.                         return {};
  2213.                     }
  2214.                 }
  2215.  
  2216.                 default: return { [key]: value };
  2217.             }
  2218.         };
  2219.  
  2220.         for (const key in styles) {
  2221.             if (this.globalStyles.find(gs => gs == key) || isFields) {
  2222.                 target.set(getValueByKey(key, styles[key]));
  2223.                 target.canvas.requestRenderAll();
  2224.             } else {
  2225.                 payload = { ...payload, ...getValueByKey(key, styles[key]) };
  2226.             }
  2227.         }
  2228.  
  2229.         if (Object.keys(payload).length) {
  2230.             if (target.isEditing) {
  2231.                 let s = target.selectionStart, e = target.selectionEnd;
  2232.                 if (target.selectionStart == target.selectionEnd) {
  2233.                     s = target.selectionStart - 1;
  2234.                     if (s < 0) s = 0, e = 1;
  2235.                 }
  2236.  
  2237.                 target.setSelectionStyles(payload, s, e);
  2238.             } else {
  2239.                 target.set(payload);
  2240.             }
  2241.  
  2242.             target.canvas.requestRenderAll();
  2243.         }
  2244.  
  2245.         this.tick();
  2246.         if (isUpdate) this.addObjectHistory(target);
  2247.     }
  2248.  
  2249.     async renderPages(skipRenderPages = [], renderEmpty = false) {
  2250.         if (!skipRenderPages.length || renderEmpty) this.documentElement.innerHTML = '';
  2251.  
  2252.         for (const page of this.pages) {
  2253.             if (skipRenderPages.find(sp => sp == page.id)) {
  2254.                 if (!renderEmpty) continue;
  2255.  
  2256.                 await this.renderPage(page, {}, true);
  2257.             } else {
  2258.                 await this.renderPage(page);
  2259.             }
  2260.         }
  2261.     }
  2262.  
  2263.     async url2file(dataurl, filename) {
  2264.         let arr = dataurl.split(','),
  2265.             mime = arr[0].match(/:(.*?);/)[1],
  2266.             bstr = atob(arr[1]),
  2267.             n = bstr.length,
  2268.             u8arr = new Uint8Array(n);
  2269.  
  2270.         while (n--) {
  2271.             u8arr[n] = bstr.charCodeAt(n);
  2272.         }
  2273.  
  2274.         return new File([u8arr], filename, { type: mime });
  2275.     }
  2276.  
  2277.     isPageReadyRendered(page) {
  2278.         if (!page) return false;
  2279.         if (page.viewer && page.viewer && !page.viewer.isRendered) return true;
  2280.         if (page.waitingRender) return true;
  2281.  
  2282.         return false;
  2283.     }
  2284.  
  2285.     handlePagesInViewport(ev) {
  2286.         const viewportElement = this.scrollPlugin.determineVisibleRows();
  2287.         if (viewportElement) this.renderCachePage(viewportElement);
  2288.         // document--new
  2289.         // const viewport = this.scrollPlugin.determineVisibleRows();
  2290.         // setAttribute('style', `width:${page.canvas.width}px;height:${page.canvas.height}px;max-width:fit-content;`);
  2291.     }
  2292.  
  2293.     async removeUnnecessaryPages(pageElement) {
  2294.         const pageIndex = this.pages.findIndex(page => page.id == pageElement.getAttribute('data-page'));
  2295.         if (pageIndex == -1) return false;
  2296.  
  2297.         let viewed_pages = [pageIndex - 1, pageIndex, pageIndex + 1, pageIndex + 2];
  2298.         for (let i = 0; i < this.pages.length; i++) {
  2299.             const page = this.pages[i];
  2300.             if (!viewed_pages.includes(i) && !page.disposed) {
  2301.                 // Remove page and save json
  2302.                 page.disposed = true;
  2303.                 page.cacheJSON = {
  2304.                     ...page.canvas.toJSON(this.defaultFields),
  2305.                     zoom: page.canvas.getZoom()
  2306.                 };
  2307.  
  2308.                 // page.canvas.dispose();
  2309.                 page.canvas.clear();
  2310.             }
  2311.         }
  2312.     }
  2313.  
  2314.     async renderCachePage(pageElement) {
  2315.         const zoom = store.state.zoom / 100;
  2316.         const page = this.pages.find(page => page.id == pageElement.getAttribute('data-page'));
  2317.         if ((this.isRenderCache && !page.viewer) || !page) return false;
  2318.  
  2319.         if (page.waitingRender && !page.viewer) {
  2320.             this.isRenderCache = true;
  2321.             this.history.lockHistory = true;
  2322.             page.waitingRender = false;
  2323.             try {
  2324.                 await this.loadPageDataFromJson(page);
  2325.                 const width = page.data.options.width * zoom;
  2326.                 const height = page.data.options.height * zoom;
  2327.  
  2328.                 page.canvas.setWidth(width);
  2329.                 page.canvas.setHeight(height);
  2330.                 pageElement.style.width = `${width}px`;
  2331.                 pageElement.style.height = `${height}px`;
  2332.                 const pageNumber = this.pages.findIndex(p => p.id == page.id);
  2333.                 $(document).trigger("after_page_render", [[pageNumber], page]);
  2334.             } catch (err) {
  2335.                 page.waitingRender = true;
  2336.                 console.error(err);
  2337.             }
  2338.  
  2339.             this.history.lockHistory = false;
  2340.             this.isRenderCache = false;
  2341.             this.appendWatermarks(page);
  2342.             return;
  2343.         }
  2344.  
  2345.         if (page && page.viewer && page.viewer.document && !page.viewer.isRendered) {
  2346.             if (this.queueRender.find(qr => qr.document == page.viewer.document && qr.pageNumber == page.viewer.pageNumber))
  2347.                 return false;
  2348.  
  2349.             if (viewer.now_render) return false;
  2350.  
  2351.             this.history.lockHistory = true;
  2352.             this.queueRender.push({
  2353.                 document: page.viewer.document,
  2354.                 pageNumber: page.viewer.pageNumber
  2355.             });
  2356.  
  2357.             // if(page.cacheJSON && page.disposed) {
  2358.             //     console.log('????');
  2359.             //     await this.loadPageDataFromJson(page.cacheJSON);
  2360.             //     page.disposed = false;
  2361.             // } else {
  2362.             try {
  2363.                 const loaded = await viewer.loadPDFByDocumentId(page.viewer.document);
  2364.  
  2365.                 if (loaded) {
  2366.                     const data = await viewer.loadPdfPage(page.viewer.pageNumber);
  2367.                     const importedData = await this.importPDF.parsePageToJSON(data);
  2368.  
  2369.                     page.json = importedData.json;
  2370.                     page.data.options = importedData.data.options;
  2371.  
  2372.                     const width = importedData.data.options.width * zoom;
  2373.                     const height = importedData.data.options.height * zoom;
  2374.  
  2375.                     page.canvas.setWidth(width);
  2376.                     page.canvas.setHeight(height);
  2377.                     pageElement.style.width = `${width}px`;
  2378.                     pageElement.style.height = `${height}px`;
  2379.  
  2380.                     await this.loadPageDataFromJson(page);
  2381.                     //this.removeUnnecessaryPages(pageElement);
  2382.                     this.appendWatermarks(page);
  2383.                     page.viewer.isRendered = true;
  2384.                 }
  2385.             } catch (err) {
  2386.                 console.error(err);
  2387.             }
  2388.             //}
  2389.  
  2390.             this.history.lockHistory = false;
  2391.         }
  2392.     }
  2393. }
  2394.  
Add Comment
Please, Sign In to add comment