From 353f1d0337cedf098c318d86f71422c44712e2cb Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Thu, 6 Jun 2024 15:14:03 +0530 Subject: [PATCH] feat: Multi Page and Multi Header/Footer - added patch to convert all json to match new format with pages. - updated jinja formats to loop over pages and render them. - modified code to design multiple headers and footers. - added option to add new pages. - updated drag, resize, drop to work with multiple pages. - updated marquee tool. - added options to select headers / footers for first / last / odd /even pages - Changed Canvas to render multiple pages. - Header / footer now needs to designed separately. --- print_designer/patches.txt | 1 + .../move_header_footers_to_new_schema.py | 76 +++ .../print_designer/jinja/header_footer.html | 65 +- .../jinja/old_print_format.html | 6 +- .../print_designer/jinja/print_format.html | 18 +- .../page/print_designer/print_designer.js | 31 + .../js/print_designer/PropertiesPanelState.js | 147 ++++- .../components/base/BaseDynamicText.vue | 6 +- .../components/base/BaseRectangle.vue | 60 +- .../components/layout/AppCanvas.vue | 603 +++++++----------- .../components/layout/AppPages.vue | 447 +++++++++++++ .../components/layout/AppPdfSetup.vue | 225 ------- .../components/layout/AppPropertiesPanel.vue | 23 +- .../layout/AppPropertiesPanelSection.vue | 19 +- .../composables/AttachKeyBindings.js | 22 +- .../print_designer/composables/Draggable.js | 29 - .../js/print_designer/composables/DropZone.js | 15 +- .../js/print_designer/composables/Element.js | 70 +- .../composables/MarqueeSelectionTool.js | 57 +- .../print_designer/composables/Resizable.js | 25 +- .../js/print_designer/defaultObjects.js | 25 +- .../js/print_designer/icons/IconsUse.vue | 2 +- .../js/print_designer/store/ElementStore.js | 233 ++++--- .../js/print_designer/store/MainStore.js | 33 +- .../print_designer/store/fetchMetaAndData.js | 100 +-- .../public/js/print_designer/utils.js | 180 ++++-- 26 files changed, 1588 insertions(+), 930 deletions(-) create mode 100644 print_designer/patches/move_header_footers_to_new_schema.py create mode 100644 print_designer/public/js/print_designer/components/layout/AppPages.vue delete mode 100644 print_designer/public/js/print_designer/components/layout/AppPdfSetup.vue diff --git a/print_designer/patches.txt b/print_designer/patches.txt index 5cd0b74..3de7206 100644 --- a/print_designer/patches.txt +++ b/print_designer/patches.txt @@ -15,3 +15,4 @@ print_designer.patches.introduce_dynamic_height print_designer.patches.remove_unused_rectangle_gs_properties print_designer.patches.change_dynamic_height_variable print_designer.patches.introduce_z_index +print_designer.patches.move_header_footers_to_new_schema diff --git a/print_designer/patches/move_header_footers_to_new_schema.py b/print_designer/patches/move_header_footers_to_new_schema.py new file mode 100644 index 0000000..0eb9e67 --- /dev/null +++ b/print_designer/patches/move_header_footers_to_new_schema.py @@ -0,0 +1,76 @@ +import frappe + + +def patch_format(): + print_formats = frappe.get_all( + "Print Format", + filters={"print_designer": 1}, + fields=[ + "name", + "print_designer_header", + "print_designer_body", + "print_designer_after_table", + "print_designer_footer", + "print_designer_print_format", + "print_designer_settings", + ], + ) + for pf in print_formats: + settings = frappe.json.loads(pf.print_designer_settings or "{}") + + header_childrens = frappe.json.loads(pf.print_designer_header or "[]") + header_data = [ + { + "type": "page", + "childrens": header_childrens, + "firstPage": True, + "oddPage": True, + "evenPage": True, + "lastPage": True, + } + ] + + footer_childrens = frappe.json.loads(pf.print_designer_footer or "[]") + footer_data = [ + { + "type": "page", + "childrens": footer_childrens, + "firstPage": True, + "oddPage": True, + "evenPage": True, + "lastPage": True, + } + ] + for child in footer_childrens: + child["startY"] -= ( + settings["page"].get("height", 0) + - settings["page"].get("marginTop", 0) + - settings["page"].get("footerHeight", 0) + ) + + childrens = frappe.json.loads(pf.print_designer_body or "[]") + bodyPage = [ + { + "index": 0, + "type": "page", + "childrens": childrens, + "isDropZone": True, + } + ] + + frappe.set_value( + "Print Format", + pf.name, + { + "print_designer_header": frappe.json.dumps(header_data), + "print_designer_body": frappe.json.dumps(bodyPage), + "print_designer_footer": frappe.json.dumps(footer_data), + "print_designer_settings": frappe.json.dumps(settings), + }, + ) + return print_formats + + +def execute(): + """Updating Table and Dynamic Text Elements to have property isDynamicHeight with default value as True""" + patch_format() diff --git a/print_designer/print_designer/page/print_designer/jinja/header_footer.html b/print_designer/print_designer/page/print_designer/jinja/header_footer.html index e7f024d..552e490 100644 --- a/print_designer/print_designer/page/print_designer/jinja/header_footer.html +++ b/print_designer/print_designer/page/print_designer/jinja/header_footer.html @@ -51,7 +51,70 @@ y[j].textContent = vars[x[i]]; } } - } + + const headers = { + firstPage : document.getElementById("firstPageHeader"), + oddPage : document.getElementById("oddPageHeader"), + evenPage : document.getElementById("evenPageHeader"), + lastPage : document.getElementById("lastPageHeader"), + } + const footers = { + firstPage : document.getElementById("firstPageFooter"), + oddPage : document.getElementById("oddPageFooter"), + evenPage : document.getElementById("evenPageFooter"), + lastPage : document.getElementById("lastPageFooter"), + } + function displayHeaderFooter(elements, selectedElements) { + for (var key in elements) { + if (elements[key]) { + if (elements[key] === selectedElements) { + elements[key].style.display = "block"; + } else { + elements[key].style.display = "none"; + } + } + } + } + var page_no = parseInt(vars["page"]); + var total_page_no = parseInt(vars["topage"]); + if (page_no == 1) { + if (headers.firstPage) { + displayHeaderFooter(headers, headers.firstPage); + } else { + displayHeaderFooter(headers, headers.oddPage); + } + if (footers.firstPage) { + displayHeaderFooter(footers, footers.firstPage); + } else { + displayHeaderFooter(footers, footers.oddPage); + } + } else if (page_no == total_page_no) { + if (headers.lastPage) { + displayHeaderFooter(headers, headers.lastPage); + } else { + if (total_page_no % 2 == 0) { + displayHeaderFooter(headers, headers.evenPage); + } else { + displayHeaderFooter(headers, headers.oddPage); + } + } + if (footers.lastPage) { + displayHeaderFooter(footers, footers.lastPage); + } else { + if (total_page_no % 2 == 0) { + displayHeaderFooter(footers, footers.evenPage); + } else { + displayHeaderFooter(footers, footers.oddPage); + } + } + } else if (page_no % 2 == 0) { + displayHeaderFooter(headers, headers.evenPage); + displayHeaderFooter(footers, footers.evenPage); + } else { + displayHeaderFooter(headers, headers.oddPage); + displayHeaderFooter(footers, footers.oddPage); + } + } {% for tag in styles -%} diff --git a/print_designer/print_designer/page/print_designer/jinja/old_print_format.html b/print_designer/print_designer/page/print_designer/jinja/old_print_format.html index 7e0e1cf..85f3f0c 100644 --- a/print_designer/print_designer/page/print_designer/jinja/old_print_format.html +++ b/print_designer/print_designer/page/print_designer/jinja/old_print_format.html @@ -205,9 +205,9 @@ - {% set renderHeader = render_element(headerElement, send_to_jinja) %} - {% set renderBody = render_element(bodyElement, send_to_jinja) %} - {% set renderFooter = render_element(footerElement, send_to_jinja) %} + {% set renderHeader = render_element(headerElement[0].childrens, send_to_jinja) %} + {% set renderBody = render_element(bodyElement[0].childrens, send_to_jinja) %} + {% set renderFooter = render_element(footerElement[0].childrens, send_to_jinja) %} {% if settings.printHeaderFonts %} {% set printHeaderFonts = getFontStyles(settings.printHeaderFonts) %} diff --git a/print_designer/print_designer/page/print_designer/jinja/print_format.html b/print_designer/print_designer/page/print_designer/jinja/print_format.html index 8624edb..988cb7e 100644 --- a/print_designer/print_designer/page/print_designer/jinja/print_format.html +++ b/print_designer/print_designer/page/print_designer/jinja/print_format.html @@ -10,16 +10,26 @@
-
+
- {% if headerElement %}{{ render(pd_format.header, send_to_jinja) }}{%endif%} +
{% if pd_format.header.firstPage %}{{ render(pd_format.header.firstPage, send_to_jinja) }}{%endif%}
+ + + +
- {% if bodyElement %}{{ render(pd_format.body, send_to_jinja) }}{%endif%} + {%- for body in pd_format.body -%} + {{ render(body.childrens, send_to_jinja) }} + {%- endfor -%}
diff --git a/print_designer/print_designer/page/print_designer/print_designer.js b/print_designer/print_designer/page/print_designer/print_designer.js index e4b168c..df965d5 100644 --- a/print_designer/print_designer/page/print_designer/print_designer.js +++ b/print_designer/print_designer/page/print_designer/print_designer.js @@ -76,6 +76,37 @@ const printDesignerDialog = () => { name: print_format_name, doc_type: doctype, print_designer: 1, + print_designer_header: JSON.stringify([ + { + type: "page", + childrens: [], + firstPage: true, + oddPage: true, + evenPage: true, + lastPage: true, + DOMRef: null, + }, + ]), + print_designer_body: JSON.stringify([ + { + type: "page", + index: 0, + DOMRef: null, + isDropZone: true, + childrens: [], + }, + ]), + print_designer_footer: JSON.stringify([ + { + type: "page", + childrens: [], + firstPage: true, + oddPage: true, + evenPage: true, + lastPage: true, + DOMRef: null, + }, + ]), }) .then((doc) => { // Incase Route is Same, set_route() is needed to refresh. diff --git a/print_designer/public/js/print_designer/PropertiesPanelState.js b/print_designer/public/js/print_designer/PropertiesPanelState.js index e82fca5..429b5fa 100644 --- a/print_designer/public/js/print_designer/PropertiesPanelState.js +++ b/print_designer/public/js/print_designer/PropertiesPanelState.js @@ -389,8 +389,36 @@ export const createPropertiesPanel = () => { }, }, [ - pageInput("Height", "page_height", "height", { parentBorderTop: true }), - pageInput("Width", "page_width", "width"), + pageInput("Height", "page_height", "height", { + parentBorderTop: true, + condtional: () => MainStore.mode == "editing", + }), + pageInput("Width", "page_width", "width", { + condtional: () => MainStore.mode == "editing", + }), + ], + [ + { + label: "Delete Page", + name: "deletePage", + isLabelled: true, + flex: "auto", + condtional: () => MainStore.activePage, + reactiveObject: () => MainStore.activePage, + button: { + label: "Delete Page", + size: "sm", + style: "secondary", + margin: 15, + onClick: (e, field) => { + ElementStore.Elements.splice( + ElementStore.Elements.indexOf(MainStore.activePage), + 1 + ); + e.target.blur(); + }, + }, + }, ], ], }); @@ -410,11 +438,9 @@ export const createPropertiesPanel = () => { ], }); MainStore.propertiesPanel.push({ - title: "PDF Settings", + title: "Header / Footer", sectionCondtional: () => - MainStore.mode == "pdfSetup" && - !MainStore.getCurrentElementsId.length && - MainStore.activeControl === "mouse-pointer", + !MainStore.getCurrentElementsId.length && MainStore.activeControl === "mouse-pointer", fields: [ [ pageInput("Header", "page_header", "headerHeight"), @@ -422,6 +448,112 @@ export const createPropertiesPanel = () => { ], ], }); + + MainStore.propertiesPanel.push({ + title: "Select Pages", + sectionCondtional: () => + MainStore.mode != "editing" && + MainStore.activePage && + !MainStore.getCurrentElementsId.length && + MainStore.activeControl === "mouse-pointer", + fields: [ + [ + { + label: "First", + name: "firstPage", + isLabelled: true, + condtional: () => MainStore.activePage, + reactiveObject: () => MainStore.activePage, + button: { + label: "First", + size: "sm", + style: () => (MainStore.activePage.firstPage ? "primary" : "secondary"), + margin: 15, + onClick: (e, field) => { + MainStore.activePage.firstPage = !MainStore.activePage.firstPage; + if (MainStore.activePage.firstPage) { + ElementStore.Elements.forEach((element) => { + if (element == MainStore.activePage) return; + element.firstPage = false; + }); + } + e.target.blur(); + }, + }, + }, + { + label: "Odd", + name: "oddPages", + isLabelled: true, + condtional: () => MainStore.activePage, + reactiveObject: () => MainStore.activePage, + button: { + label: "Odd", + style: () => (MainStore.activePage.oddPage ? "primary" : "secondary"), + margin: 15, + size: "sm", + onClick: (e, field) => { + MainStore.activePage.oddPage = !MainStore.activePage.oddPage; + if (MainStore.activePage.oddPage) { + ElementStore.Elements.forEach((element) => { + if (element == MainStore.activePage) return; + element.oddPage = false; + }); + } + e.target.blur(); + }, + }, + }, + { + label: "Even", + name: "evenPages", + isLabelled: true, + condtional: () => MainStore.activePage, + reactiveObject: () => MainStore.activePage, + button: { + label: "Even", + style: () => (MainStore.activePage.evenPage ? "primary" : "secondary"), + margin: 15, + size: "sm", + onClick: (e, field) => { + MainStore.activePage.evenPage = !MainStore.activePage.evenPage; + if (MainStore.activePage.evenPage) { + ElementStore.Elements.forEach((element) => { + if (element == MainStore.activePage) return; + element.evenPage = false; + }); + } + e.target.blur(); + }, + }, + }, + { + label: "Last", + name: "lastPages", + isLabelled: true, + condtional: () => MainStore.activePage, + reactiveObject: () => MainStore.activePage, + button: { + label: "Last", + style: () => (MainStore.activePage.lastPage ? "primary" : "secondary"), + margin: 15, + size: "sm", + onClick: (e, field) => { + MainStore.activePage.lastPage = !MainStore.activePage.lastPage; + if (MainStore.activePage.lastPage) { + ElementStore.Elements.forEach((element) => { + if (element == MainStore.activePage) return; + element.lastPage = false; + }); + } + e.target.blur(); + }, + }, + }, + ], + ], + }); + MainStore.propertiesPanel.push({ title: "Transform", sectionCondtional: () => MainStore.getCurrentElementsId.length === 1, @@ -442,6 +574,9 @@ export const createPropertiesPanel = () => { labelDirection: "column", condtional: () => { const currentEl = MainStore.getCurrentElementsValues[0]; + if (currentEl.isElementOverlapping) { + return false; + } if ( currentEl?.type === "table" || (currentEl.type === "text" && diff --git a/print_designer/public/js/print_designer/components/base/BaseDynamicText.vue b/print_designer/public/js/print_designer/components/base/BaseDynamicText.vue index 0eda607..f45e486 100644 --- a/print_designer/public/js/print_designer/components/base/BaseDynamicText.vue +++ b/print_designer/public/js/print_designer/components/base/BaseDynamicText.vue @@ -69,7 +69,7 @@ - + diff --git a/print_designer/public/js/print_designer/components/layout/AppCanvas.vue b/print_designer/public/js/print_designer/components/layout/AppCanvas.vue index 8052de6..07058ad 100644 --- a/print_designer/public/js/print_designer/components/layout/AppCanvas.vue +++ b/print_designer/public/js/print_designer/components/layout/AppCanvas.vue @@ -3,34 +3,73 @@
-
- - + +
+ + +
+ + diff --git a/print_designer/public/js/print_designer/components/layout/AppPdfSetup.vue b/print_designer/public/js/print_designer/components/layout/AppPdfSetup.vue deleted file mode 100644 index f927e54..0000000 --- a/print_designer/public/js/print_designer/components/layout/AppPdfSetup.vue +++ /dev/null @@ -1,225 +0,0 @@ - - - - - diff --git a/print_designer/public/js/print_designer/components/layout/AppPropertiesPanel.vue b/print_designer/public/js/print_designer/components/layout/AppPropertiesPanel.vue index 8330282..1be0677 100644 --- a/print_designer/public/js/print_designer/components/layout/AppPropertiesPanel.vue +++ b/print_designer/public/js/print_designer/components/layout/AppPropertiesPanel.vue @@ -12,19 +12,10 @@
-
- -
{ element.startX -= finalValue.value - object[property]; @@ -351,15 +352,23 @@ const handleBlur = ({ }); if (!object.isStyle && object.UOM == MainStore.page.UOM) { if (property == "marginTop") { - ElementStore.Elements.forEach((element) => { - element.startY -= convertedValue.value - object[property]; + ElementStore.Elements.forEach((page) => { + page.childrens.forEach((element) => { + element.startY -= convertedValue.value - object[property]; + }); + page.header[0].height += object[property] - convertedValue.value; + page.footer[0].startY += object[property] - convertedValue.value; }); MainStore.page.headerHeight += object[property] - convertedValue.value; - MainStore.page.footerHeight += object[property] - convertedValue.value; } else if (property == "marginLeft") { ElementStore.Elements.forEach((element) => { element.startX -= convertedValue.value - object[property]; }); + } else if (property == "marginBottom") { + ElementStore.Elements.forEach((page) => { + page.footer[0].height += object[property] - convertedValue.value; + MainStore.page.footerHeight += object[property] - convertedValue.value; + }); } if (["width", "height"].indexOf(property)) { let propertyValue = useChangeValueUnit({ diff --git a/print_designer/public/js/print_designer/composables/AttachKeyBindings.js b/print_designer/public/js/print_designer/composables/AttachKeyBindings.js index 3ebf97b..105c88e 100644 --- a/print_designer/public/js/print_designer/composables/AttachKeyBindings.js +++ b/print_designer/public/js/print_designer/composables/AttachKeyBindings.js @@ -9,11 +9,7 @@ export function useAttachKeyBindings() { function updateStartXY(axis, value) { MainStore.getCurrentElementsValues.forEach((element) => { let restrict; - if (Array.isArray(element.parent)) { - restrict = MainStore.mainContainer.getBoundingClientRect(); - } else { - restrict = element.parent.DOMRef.getBoundingClientRect(); - } + restrict = element.parent.DOMRef.getBoundingClientRect(); if (element[`start${axis}`] + value <= -1) { element[`start${axis}`] = -1; } else if ( @@ -32,12 +28,7 @@ export function useAttachKeyBindings() { } function updateWidthHeight(key, value) { MainStore.getCurrentElementsValues.forEach((element) => { - let restrict; - if (Array.isArray(element.parent)) { - restrict = MainStore.mainContainer.getBoundingClientRect(); - } else { - restrict = element.parent.DOMRef.getBoundingClientRect(); - } + let restrict = element.parent.DOMRef.getBoundingClientRect(); if (element[key] + value <= -1) { element[key] = -1; } else if ( @@ -54,12 +45,13 @@ export function useAttachKeyBindings() { const handleKeyDown = async (e) => { MainStore.isAltKey = e.altKey; MainStore.isShiftKey = e.shiftKey; - if (e.target !== document.body || MainStore.mode != "editing" || MainStore.openModal) - return; + if (e.target !== document.body || MainStore.openModal) return; if (e.ctrlKey || e.metaKey) { if (["a", "A"].indexOf(e.key) != -1) { - ElementStore.Elements.forEach((element) => { - MainStore.currentElements[element.id] = element; + ElementStore.Elements.forEach((page) => { + page.childrens.forEach((element) => { + MainStore.currentElements[element.id] = element; + }); }); } else if (!e.repeat && ["s", "S"].indexOf(e.key) != -1) { await ElementStore.saveElements(); diff --git a/print_designer/public/js/print_designer/composables/Draggable.js b/print_designer/public/js/print_designer/composables/Draggable.js index dc91858..17b09be 100644 --- a/print_designer/public/js/print_designer/composables/Draggable.js +++ b/print_designer/public/js/print_designer/composables/Draggable.js @@ -17,7 +17,6 @@ export function useDraggable({ if (interact.isSet(element["DOMRef"]) && interact(element["DOMRef"]).draggable().enabled) return; const MainStore = useMainStore(); - const ElementStore = useElementStore(); let elementPreviousZAxis; let top, left, bottom, right; if (typeof restrict != "string") { @@ -57,34 +56,6 @@ export function useDraggable({ if (element.DOMRef.className == "modal-dialog modal-sm") { return; } - if ( - !e.dropzone && - !Array.isArray(e.target.piniaElementRef.parent) && - !MainStore.lastCloned - ) { - let splicedElement; - let currentRect = e.target.getBoundingClientRect(); - let canvasRect = MainStore.mainContainer.getBoundingClientRect(); - let currentParent = e.target.piniaElementRef.parent; - if (Array.isArray(currentParent)) { - splicedElement = currentParent.splice(e.target.piniaElementRef.index, 1)[0]; - } else { - splicedElement = currentParent.childrens.splice( - e.target.piniaElementRef.index, - 1 - )[0]; - } - splicedElement = { ...splicedElement }; - splicedElement.id = frappe.utils.get_random(10); - splicedElement.startX = currentRect.left - canvasRect.left; - splicedElement.startY = currentRect.top - canvasRect.top; - splicedElement.parent = ElementStore.Elements; - recursiveChildrens({ element: splicedElement, isClone: false }); - ElementStore.Elements.push(splicedElement); - let droppedElement = new Object(); - droppedElement[splicedElement.id] = splicedElement; - MainStore.isDropped = droppedElement; - } checkUpdateElementOverlapping(element); }); return; diff --git a/print_designer/public/js/print_designer/composables/DropZone.js b/print_designer/public/js/print_designer/composables/DropZone.js index 6846a9a..70397ab 100644 --- a/print_designer/public/js/print_designer/composables/DropZone.js +++ b/print_designer/public/js/print_designer/composables/DropZone.js @@ -2,13 +2,11 @@ import interact from "@interactjs/interact"; import "@interactjs/actions/drop"; import "@interactjs/auto-start"; import "@interactjs/modifiers"; -import { useElementStore } from "../store/ElementStore"; import { useMainStore } from "../store/MainStore"; import { recursiveChildrens } from "../utils"; export function useDropZone({ element }) { const MainStore = useMainStore(); - const ElementStore = useElementStore(); if (interact.isSet(element["DOMRef"]) && interact(element["DOMRef"]).dropzone().enabled) return; interact(element.DOMRef).dropzone({ @@ -20,22 +18,13 @@ export function useDropZone({ element }) { let currentRect = event.draggable.target.getBoundingClientRect(); let dropRect = event.dropzone.target.getBoundingClientRect(); if (currentDROP === currentRef.parent) return; - let splicedElement; - if (Array.isArray(currentRef.parent)) { - splicedElement = currentRef.parent.splice(currentRef.index, 1)[0]; - } else { - splicedElement = currentRef.parent.childrens.splice(currentRef.index, 1)[0]; - } + let splicedElement = currentRef.parent.childrens.splice(currentRef.index, 1)[0]; splicedElement = { ...splicedElement }; splicedElement.startX = currentRect.left - dropRect.left; splicedElement.startY = currentRect.top - dropRect.top; splicedElement.parent = currentDROP; recursiveChildrens({ element: splicedElement, isClone: false }); - if (Array.isArray(currentDROP)) { - currentDROP.push(splicedElement); - } else { - currentDROP.childrens.push(splicedElement); - } + currentDROP.childrens.push(splicedElement); let droppedElement = new Object(); droppedElement[splicedElement.id] = splicedElement; MainStore.isDropped = droppedElement; diff --git a/print_designer/public/js/print_designer/composables/Element.js b/print_designer/public/js/print_designer/composables/Element.js index 3ff1485..9c168f0 100644 --- a/print_designer/public/js/print_designer/composables/Element.js +++ b/print_designer/public/js/print_designer/composables/Element.js @@ -1,15 +1,19 @@ import { useMainStore } from "../store/MainStore"; -import { useElementStore } from "../store/ElementStore"; import { useDraggable } from "./Draggable"; import { useResizable } from "./Resizable"; import { useDropZone } from "./DropZone"; import { watch, markRaw } from "vue"; import interact from "@interactjs/interact"; -import { changeDraggable, changeResizable, changeDropZone, getSnapPointsAndEdges } from "../utils"; +import { + changeDraggable, + changeResizable, + changeDropZone, + getSnapPointsAndEdges, + getParentPage, +} from "../utils"; export function useElement({ draggable = true, resizable = true }) { const MainStore = useMainStore(); - const ElementStore = useElementStore(); const setReferance = (element) => (el) => { element.DOMRef = markRaw(el); el.piniaElementRef = element; @@ -18,8 +22,8 @@ export function useElement({ draggable = true, resizable = true }) { if (!element) return; element.index = index; if (element.DOMRef) return; - setReferance(element)(DOMElement); if (element && DOMElement) { + setReferance(element)(DOMElement); draggable && setDraggable(element); resizable && setResizable(element); const { @@ -32,7 +36,9 @@ export function useElement({ draggable = true, resizable = true }) { } = getSnapPointsAndEdges(element); element.snapPoints = [rowSnapPoint, columnSnapPoint]; element.snapEdges = [leftSnapEdge, rightSnapEdge, topSnapEdge, bottomSnapEdge]; - element.type == "rectangle" && setDropZone(element); + if (element.type == "rectangle" || element.type == "page") { + setDropZone(element); + } element && changeResizable(element); element && changeDraggable(element); element && changeDropZone(element); @@ -59,10 +65,12 @@ export function useElement({ draggable = true, resizable = true }) { () => { if (!element) return; if (MainStore.activeControl == "mouse-pointer") { - element.isDraggable = true; + if (element.type != "page") { + element.isDraggable = true; + } if (!MainStore.isAltKey) { element.isResizable = true; - if (element.type == "rectangle") { + if (element.type == "rectangle" || element.type == "page") { element.isDropZone = true; } } else { @@ -97,9 +105,15 @@ export function useElement({ draggable = true, resizable = true }) { } }; const setDraggable = (element) => { + if (element.relativeContainer) return; + const pageParent = getParentPage(element); + if (!pageParent) { + return; + } + const parentDOMRef = pageParent.DOMRef; useDraggable({ element, - restrict: MainStore.mainContainer, + restrict: parentDOMRef, dragMoveListener: (e) => { if (e.metaKey || e.ctrlKey) { e.interactable.options.drag.modifiers[0].disable(); @@ -127,12 +141,9 @@ export function useElement({ draggable = true, resizable = true }) { e.stopImmediatePropagation(); }, dragStartListener: (e) => { - let parentRect; - if (Array.isArray(element.parent)) { - parentRect = MainStore.mainContainer.getBoundingClientRect(); - } else { - parentRect = element.parent.DOMRef.getBoundingClientRect(); - } + const parentRect = + element.parent.DOMRef?.getBoundingClientRect() || + parentDOMRef.getBoundingClientRect(); const elementRect = element.DOMRef.getBoundingClientRect(); let offsetRect = MainStore.getCurrentElementsValues.reduce( (offset, currentElement) => { @@ -171,33 +182,42 @@ export function useElement({ draggable = true, resizable = true }) { }; elementPreviousZAxis = element.style.zIndex || 0; element.style.zIndex = 9999; - - e.interactable.options.drag.modifiers[0].options.restriction = { + const restrictionRect = { top: parentRect.top + restrictRect.top, left: parentRect.left + restrictRect.left, right: parentRect.right - restrictRect.right, bottom: parentRect.bottom - restrictRect.bottom, }; + if (MainStore.mode == "editing" && element.parent.type == "page") { + restrictionRect.top += MainStore.page.headerHeight; + restrictionRect.bottom -= MainStore.page.footerHeight; + } + e.interactable.options.drag.modifiers[0].options.restriction = restrictionRect; }, }); }; const setResizable = (element) => { + const pageParent = getParentPage(element); + if (!pageParent) { + return; + } + const parentDOMRef = pageParent.DOMRef; useResizable({ element, - restrict: MainStore.mainContainer, + restrict: parentDOMRef, resizeStartListener: (e) => { - let parentRect; - if (Array.isArray(element.parent)) { - parentRect = MainStore.mainContainer.getBoundingClientRect(); - } else { - parentRect = element.parent.DOMRef.getBoundingClientRect(); - } - e.interactable.options.resize.modifiers[0].options.outer = { + let parentRect = element.parent.DOMRef.getBoundingClientRect(); + const restrictionRect = { top: parentRect.top, left: parentRect.left, right: parentRect.right, bottom: parentRect.bottom, }; + if (MainStore.mode == "editing" && element.parent.type == "page") { + restrictionRect.top += MainStore.page.headerHeight; + restrictionRect.bottom -= MainStore.page.footerHeight; + } + e.interactable.options.resize.modifiers[0].options.outer = restrictionRect; if (!element.childrens || !element.childrens.length) return; let offsetRect = element.childrens.reduce( (offset, currentElement) => { @@ -235,7 +255,7 @@ export function useElement({ draggable = true, resizable = true }) { element.startY = (element.startY || 0) + e.deltaRect.top; element.width = (element.width || 0) - e.deltaRect.left + e.deltaRect.right; element.height = (element.height || 0) - e.deltaRect.top + e.deltaRect.bottom; - if (element.type == "rectangle") { + if (element.type == "rectangle" || element.type == "page") { element.childrens && element.childrens.forEach((childEl) => { childEl.startX -= e.deltaRect.left; diff --git a/print_designer/public/js/print_designer/composables/MarqueeSelectionTool.js b/print_designer/public/js/print_designer/composables/MarqueeSelectionTool.js index 3c65341..cbc47ac 100644 --- a/print_designer/public/js/print_designer/composables/MarqueeSelectionTool.js +++ b/print_designer/public/js/print_designer/composables/MarqueeSelectionTool.js @@ -37,6 +37,7 @@ export function useMarqueeSelection() { if (e.buttons != 1) return; if (e.target.id == "canvas" && MainStore.activeControl != "mouse-pointer") { MainStore.setActiveControl("MousePointer"); + MainStore.activePage = null; MainStore.isMarqueeActive = true; } if (!MainStore[beforeDraw]) return; @@ -95,38 +96,34 @@ export function useMarqueeSelection() { }); } - const a = { - x: parameters.startX - canvas.getBoundingClientRect().left, - y: parameters.startY - canvas.getBoundingClientRect().top, + const canvas = { + x: parameters.startX, + y: parameters.startY, width: Math.abs(parameters.width), - height: Math.abs(parameters.height) + canvas.scrollTop, + height: Math.abs(parameters.height), }; - const mainContainerRect = MainStore.mainContainer.getBoundingClientRect(); - a.x -= - mainContainerRect.x - MainStore.toolbarWidth > 0 - ? mainContainerRect.x - MainStore.toolbarWidth - : 0; - a.y -= - mainContainerRect.y - MainStore.page.marginTop <= 110 - ? MainStore.page.marginTop + 50 - : mainContainerRect.y - MainStore.page.marginTop - 110; - for (const value of ElementStore.Elements) { - const { id, startX, startY, width, height, DOMRef } = value; - const b = { - id, - x: startX, - y: startY, - width, - height, - DOMRef, - }; - - if (isInBounds(a, b)) { - inBounds.push(DOMRef); - if ((e.metaKey || e.ctrlKey) && e.shiftKey) { - delete MainStore.currentElements[id]; - } else { - MainStore.currentElements[id] = value; + for (const page of ElementStore.Elements) { + const pageRect = page.DOMRef.getBoundingClientRect(); + a = { ...canvas }; + a.x -= pageRect.x; + a.y -= pageRect.y; + for (const element of page.childrens) { + const { id, startX, startY, width, height, DOMRef } = element; + const b = { + id, + x: startX, + y: startY, + width, + height, + DOMRef, + }; + if (!element.relativeContainer && isInBounds(a, b)) { + inBounds.push(DOMRef); + if ((e.metaKey || e.ctrlKey) && e.shiftKey) { + delete MainStore.currentElements[id]; + } else { + MainStore.currentElements[id] = element; + } } } } diff --git a/print_designer/public/js/print_designer/composables/Resizable.js b/print_designer/public/js/print_designer/composables/Resizable.js index 99af2f9..61de2a1 100644 --- a/print_designer/public/js/print_designer/composables/Resizable.js +++ b/print_designer/public/js/print_designer/composables/Resizable.js @@ -4,7 +4,7 @@ import "@interactjs/auto-start"; import "@interactjs/modifiers"; import { useMainStore } from "../store/MainStore"; import { useElementStore } from "../store/ElementStore"; -import { recursiveChildrens, checkUpdateElementOverlapping } from "../utils"; +import { recursiveChildrens, checkUpdateElementOverlapping, getParentPage } from "../utils"; export function useResizable({ element, @@ -19,15 +19,18 @@ export function useResizable({ } const MainStore = useMainStore(); const ElementStore = useElementStore(); + const edges = { + bottom: ".resize-bottom", + }; + if (!element.relativeContainer) { + edges.left = ".resize-left"; + edges.right = ".resize-right"; + edges.top = ".resize-top"; + } interact(element.DOMRef) .resizable({ ignoreFrom: ".resizer", - edges: { - left: ".resize-left", - right: ".resize-right", - bottom: ".resize-bottom", - top: ".resize-top", - }, + edges: edges, modifiers: [ interact.modifiers.restrictEdges(), interact.modifiers.snapEdges({ @@ -48,14 +51,16 @@ export function useResizable({ if (element.parent == e.target.piniaElementRef.parent) return; if ( !e.dropzone && - !Array.isArray(e.target.piniaElementRef.parent) && + e.target.piniaElementRef.parent.type != "page" && !MainStore.lastCloned ) { let splicedElement; let currentRect = e.target.getBoundingClientRect(); - let canvasRect = MainStore.mainContainer.getBoundingClientRect(); + let canvasRect = getParentPage( + e.target.piniaElementRef.parent + ).DOMRef.getBoundingClientRect(); let currentParent = e.target.piniaElementRef.parent; - if (Array.isArray(currentParent)) { + if (currentParent.type == "page") { splicedElement = currentParent.splice( e.target.piniaElementRef.index, 1 diff --git a/print_designer/public/js/print_designer/defaultObjects.js b/print_designer/public/js/print_designer/defaultObjects.js index 74dafd1..ef6ad21 100644 --- a/print_designer/public/js/print_designer/defaultObjects.js +++ b/print_designer/public/js/print_designer/defaultObjects.js @@ -1,11 +1,8 @@ import { useMainStore } from "./store/MainStore"; -import { useElementStore } from "./store/ElementStore"; export const createRectangle = (cordinates, parent = null) => { - const ElementStore = useElementStore(); const MainStore = useMainStore(); - if (parent === null) parent = ElementStore.Elements; let id = frappe.utils.get_random(10); if (cordinates instanceof MouseEvent) { cordinates = { @@ -35,15 +32,13 @@ export const createRectangle = (cordinates, parent = null) => { classes: [], }; - Array.isArray(parent) ? parent.push(newRectangle) : parent.childrens.push(newRectangle); + parent.childrens?.push(newRectangle); MainStore.lastCreatedElement = newRectangle; return newRectangle; }; export const createImage = (cordinates, parent = null) => { - const ElementStore = useElementStore(); const MainStore = useMainStore(); - if (parent === null) parent = ElementStore.Elements; let id = frappe.utils.get_random(10); if (cordinates instanceof MouseEvent) { cordinates = { @@ -74,15 +69,13 @@ export const createImage = (cordinates, parent = null) => { classes: [], }; - Array.isArray(parent) ? parent.push(newImage) : parent.childrens.push(newImage); + parent.childrens?.push(newImage) || parent.childrens.push(newImage); MainStore.lastCreatedElement = newImage; return newImage; }; export const createBarcode = (cordinates, parent = null) => { - const ElementStore = useElementStore(); const MainStore = useMainStore(); - if (parent === null) parent = ElementStore.Elements; let id = frappe.utils.get_random(10); if (cordinates instanceof MouseEvent) { cordinates = { @@ -117,15 +110,13 @@ export const createBarcode = (cordinates, parent = null) => { classes: [], }; - Array.isArray(parent) ? parent.push(newBarcode) : parent.childrens.push(newBarcode); + parent.childrens?.push(newBarcode) || parent.childrens.push(newBarcode); MainStore.lastCreatedElement = newBarcode; return newBarcode; }; export const createTable = (cordinates, parent = null) => { - const ElementStore = useElementStore(); const MainStore = useMainStore(); - if (parent === null) parent = ElementStore.Elements; let id = frappe.utils.get_random(10); if (cordinates instanceof MouseEvent) { cordinates = { @@ -164,16 +155,14 @@ export const createTable = (cordinates, parent = null) => { classes: [], }; - Array.isArray(parent) ? parent.push(newTable) : parent.childrens.push(newTable); + parent.childrens?.push(newTable) || parent.childrens.push(newTable); MainStore.lastCreatedElement = newTable; return newTable; }; export const createText = (cordinates, parent = null) => { - const ElementStore = useElementStore(); const MainStore = useMainStore(); - if (parent === null) parent = ElementStore.Elements; let id = frappe.utils.get_random(10); if (cordinates instanceof MouseEvent) { cordinates = { @@ -207,15 +196,13 @@ export const createText = (cordinates, parent = null) => { style: {}, classes: [], }; - Array.isArray(parent) ? parent.push(newStaticText) : parent.childrens.push(newStaticText); + parent.childrens?.push(newStaticText) || parent.childrens.push(newStaticText); MainStore.lastCreatedElement = newStaticText; return newStaticText; }; export const createDynamicText = (cordinates, parent = null) => { - const ElementStore = useElementStore(); const MainStore = useMainStore(); - if (parent === null) parent = ElementStore.Elements; let id = frappe.utils.get_random(10); if (cordinates instanceof MouseEvent) { cordinates = { @@ -252,7 +239,7 @@ export const createDynamicText = (cordinates, parent = null) => { heightType: "auto", classes: [], }; - Array.isArray(parent) ? parent.push(newDynamicText) : parent.childrens.push(newDynamicText); + parent.childrens?.push(newDynamicText) || parent.childrens.push(newDynamicText); MainStore.lastCreatedElement = newDynamicText; return newDynamicText; }; diff --git a/print_designer/public/js/print_designer/icons/IconsUse.vue b/print_designer/public/js/print_designer/icons/IconsUse.vue index 70f3aac..d4d1c90 100644 --- a/print_designer/public/js/print_designer/icons/IconsUse.vue +++ b/print_designer/public/js/print_designer/icons/IconsUse.vue @@ -29,7 +29,7 @@ const props = defineProps({ default: 0, }, color: { - default: "#000000", + default: "var(--neutral)", }, class: { type: String, diff --git a/print_designer/public/js/print_designer/store/ElementStore.js b/print_designer/public/js/print_designer/store/ElementStore.js index db4e30e..8761b86 100644 --- a/print_designer/public/js/print_designer/store/ElementStore.js +++ b/print_designer/public/js/print_designer/store/ElementStore.js @@ -8,13 +8,20 @@ import { createTable, createBarcode, } from "../defaultObjects"; -import { handlePrintFonts, setCurrentElement } from "../utils"; +import { + handlePrintFonts, + setCurrentElement, + createHeaderFooterElement, + getParentPage, +} from "../utils"; import html2canvas from "html2canvas"; export const useElementStore = defineStore("ElementStore", { state: () => ({ Elements: new Array(), + Headers: new Array(), + Footers: new Array(), }), actions: { createNewObject(event, element) { @@ -53,22 +60,50 @@ export const useElementStore = defineStore("ElementStore", { body: [], footer: [], }; - // {childrens: []} is passed because we update parent in createRectangle function. let headerElements = []; let bodyElements = []; let footerElements = []; - // WARNING: 2 lines below are for debugging purpose only. - // this.Elements.length = 0; - // headerElements = bodyElements = footerElements = this.Elements; if (header) { - layout.header = this.computeRowLayout(header, headerElements, "header"); + const headerArray = header.map((h) => { + h.childrens = this.computeRowLayout(h.childrens, headerElements, "header"); + return h; + }); + layout.header = { + firstPage: headerArray.find((h) => h.firstPage).childrens, + oddPage: headerArray.find((h) => h.oddPage).childrens, + evenPage: headerArray.find((h) => h.evenPage).childrens, + lastPage: headerArray.find((h) => h.lastPage).childrens, + }; } // it will throw error if body is empty so no need to check here - layout.body = this.computeRowLayout(body, bodyElements, "body"); + layout.body = body.map((b) => { + b.childrens = this.computeRowLayout(b.childrens, bodyElements, "body"); + return b; + }); if (footer) { - layout.footer = this.computeRowLayout(footer, footerElements, "footer"); + const footerArray = footer.map((f) => { + f.childrens = this.computeRowLayout(f.childrens, footerElements, "footer"); + return f; + }); + layout.footer = { + firstPage: footerArray.find((h) => h.firstPage).childrens, + oddPage: footerArray.find((h) => h.oddPage).childrens, + evenPage: footerArray.find((h) => h.evenPage).childrens, + lastPage: footerArray.find((h) => h.lastPage).childrens, + }; } - + // WARNING: lines below are for debugging purpose only. + // this.Elements.length = 0; + // this.Headers.length = 0; + // this.Footers.length = 0; + // this.Headers.push(...layout.header); + // this.Elements.push(...layout.body); + // this.Footers.push(...layout.footer); + // this.Elements.forEach((page, index) => { + // page.header = [createHeaderFooterElement(this.getHeaderObject(index).childrens, "header")]; + // page.footer = [createHeaderFooterElement(this.getFooterObject(index).childrens, "footer")] + // }); + // End of debugging code objectToSave.print_designer_print_format = JSON.stringify(layout); // update fonts in store @@ -180,7 +215,6 @@ export const useElementStore = defineStore("ElementStore", { async saveElements() { const MainStore = useMainStore(); if (this.checkIfAnyTableIsEmpty()) return; - if (MainStore.mode == "preview") return; let is_standard = await frappe.db.get_value( "Print Format", MainStore.printDesignName, @@ -254,33 +288,34 @@ export const useElementStore = defineStore("ElementStore", { return false; }, computeMainLayout() { - const MainStore = useMainStore(); - elements = [...this.Elements]; - elements.sort((a, b) => { - return a.startY < b.startY ? -1 : 1; + let header = []; + let body = []; + let footer = []; + const pages = [...this.Elements]; + const headerArray = [...this.Headers]; + const footerArray = [...this.Footers]; + headerArray.forEach((h) => { + const headerCopy = { ...h }; + h.childrens = this.cleanUpElementsForSave(h.childrens, "header") || []; + header.push(headerCopy); + }); + pages.forEach((page) => { + const pageCopy = { ...page }; + delete pageCopy.DOMRef; + delete pageCopy.parent; + delete pageCopy.header; + delete pageCopy.footer; + pageCopy.childrens.sort((a, b) => { + return a.startY < b.startY ? -1 : 1; + }); + pageCopy.childrens = this.cleanUpElementsForSave(pageCopy.childrens, "body"); + body.push(pageCopy); + }); + footerArray.forEach((f) => { + const footerCopy = { ...f }; + footerCopy.childrens = this.cleanUpElementsForSave(f.childrens, "footer") || []; + footer.push(footerCopy); }); - const findLastHeaderEl = (el) => { - return el.startY >= MainStore.page.headerHeight; - }; - const findFirstFooterEl = (el) => { - return ( - el.startY >= - MainStore.page.height - - MainStore.page.footerHeight - - MainStore.page.marginTop - - MainStore.page.marginBottom - ); - }; - let headerIndex = elements.findIndex((el) => findLastHeaderEl(el)); - headerIndex == -1 && (headerIndex = elements.length); - const header = this.cleanUpElementsForSave(elements.splice(0, headerIndex), "header"); - let footerIndex = elements.findIndex((el) => findFirstFooterEl(el)); - footerIndex == -1 && (footerIndex = elements.length); - const footer = this.cleanUpElementsForSave( - elements.splice(footerIndex, elements.length - footerIndex), - "footer" - ); - const body = this.cleanUpElementsForSave(elements, "body"); return { header, body, footer }; }, // TODO: Refactor this function @@ -345,11 +380,11 @@ export const useElementStore = defineStore("ElementStore", { rowElements.push(wrapper); } rowElements.sort((a, b) => (a.startY < b.startY ? -1 : 1)); - if (type == "header") { + if (type == "header" && rowElements.length) { const lastHeaderRow = rowElements[rowElements.length - 1]; lastHeaderRow.height = MainStore.page.headerHeight - MainStore.page.marginTop - lastHeaderRow.startY; - } else if (type == "footer") { + } else if (type == "footer" && rowElements.length) { const lastHeaderRow = rowElements[rowElements.length - 1]; lastHeaderRow.height = MainStore.page.height - @@ -456,7 +491,6 @@ export const useElementStore = defineStore("ElementStore", { auto: __("in table, auto layout failed"), }); message += messageType[type]; - MainStore.mode = "pdfSetup"; frappe.show_alert( { message: message, @@ -656,19 +690,19 @@ export const useElementStore = defineStore("ElementStore", { childrensSave(element, printFonts = null) { let saveEl = { ...element }; delete saveEl.DOMRef; - delete saveEl.index; delete saveEl.snapPoints; delete saveEl.snapEdges; delete saveEl.parent; this.cleanUpDynamicContent(saveEl); if (saveEl.type == "table") { + saveEl.table = { ...saveEl.table }; delete saveEl.table.childfields; delete saveEl.table.default_layout; } if (printFonts && ["text", "table"].indexOf(saveEl.type) != -1) { handlePrintFonts(saveEl, printFonts); } - if (saveEl.type == "rectangle") { + if (saveEl.type == "rectangle" || saveEl.type == "page") { const childrensArray = saveEl.childrens; saveEl.childrens = []; childrensArray.forEach((el) => { @@ -731,7 +765,7 @@ export const useElementStore = defineStore("ElementStore", { createWrapperElement(dimensions, parent) { const MainStore = useMainStore(); const coordinates = {}; - if (Array.isArray(parent)) { + if (parent.type == "page") { coordinates["startY"] = dimensions.top; coordinates["pageY"] = dimensions.top; coordinates["startX"] = 0; @@ -778,7 +812,7 @@ export const useElementStore = defineStore("ElementStore", { return; }, updateRowChildrenDimensions(wrapper, children, parent) { - if (Array.isArray(parent)) { + if (parent.type == "page") { children.forEach((el) => { el.startY -= wrapper.startY; }); @@ -822,7 +856,7 @@ export const useElementStore = defineStore("ElementStore", { createRowWrapperElement(dimension, currentRow, parent) { const MainStore = useMainStore(); const coordinates = {}; - if (Array.isArray(parent)) { + if (parent.type == "page") { coordinates["startY"] = dimension.top; coordinates["pageY"] = dimension.top; coordinates["startX"] = 0; @@ -1054,7 +1088,7 @@ export const useElementStore = defineStore("ElementStore", { element.isDraggable = true; element.isResizable = true; this.handleDynamicContent(element); - if (element.type == "rectangle") { + if (element.type == "rectangle" || element.type == "page") { element.isDropZone = true; const childrensArray = element.childrens; element.childrens = []; @@ -1091,35 +1125,37 @@ export const useElementStore = defineStore("ElementStore", { }); return; }, - async loadElements(printDesignName) { - frappe.dom.freeze(__("Loading Print Format")); - const printFormat = await frappe.db.get_value("Print Format", printDesignName, [ - "print_designer_header", - "print_designer_body", - "print_designer_after_table", - "print_designer_footer", - "print_designer_settings", - ]); - let ElementsHeader = JSON.parse(printFormat.message.print_designer_header); - let ElementsBody = JSON.parse(printFormat.message.print_designer_body); - let ElementsAfterTable = JSON.parse(printFormat.message.print_designer_after_table); - let ElementsFooter = JSON.parse(printFormat.message.print_designer_footer); - let settings = JSON.parse(printFormat.message.print_designer_settings); - this.loadSettings(settings); - this.Elements = [ - ...(ElementsHeader || []), - ...(ElementsBody || []), - ...(ElementsAfterTable || []), - ...(ElementsFooter || []), - ]; - this.Elements.map((element) => { + getHeaderObject(index) { + if (index == 0) { + return this.Headers.find((header) => header.firstPage == true); + } else if (index == this.Elements.length - 1) { + return this.Headers.find((header) => header.lastPage == true); + } else if (index % 2 != 0) { + return this.Headers.find((header) => header.oddPage == true); + } else { + return this.Headers.find((header) => header.evenPage == true); + } + }, + getFooterObject(index) { + if (index == 0) { + return this.Footers.find((footer) => footer.firstPage == true); + } else if (index == this.Elements.length - 1) { + return this.Footers.find((footer) => footer.lastPage == true); + } else if (index % 2 != 0) { + return this.Footers.find((footer) => footer.oddPage == true); + } else { + return this.Footers.find((footer) => footer.evenPage == true); + } + }, + setElementProperties(parent) { + parent.childrens.map((element) => { element.DOMRef = null; - element.parent = this.Elements; + element.parent = parent; delete element.printY; element.isDraggable = true; element.isResizable = true; this.handleDynamicContent(element); - if (element.type == "rectangle") { + if (element.type == "rectangle" || element.type == "page") { element.isDropZone = true; if (element.childrens.length) { let childrensArray = element.childrens; @@ -1133,6 +1169,55 @@ export const useElementStore = defineStore("ElementStore", { } return element; }); + }, + createPageElement(element, type) { + return { + type: "page", + childrens: [...element], + firstPage: true, + oddPage: true, + evenPage: true, + lastPage: true, + DOMRef: null, + }; + }, + async loadElements(printDesignName) { + frappe.dom.freeze(__("Loading Print Format")); + const printFormat = await frappe.db.get_value("Print Format", printDesignName, [ + "print_designer_header", + "print_designer_body", + "print_designer_after_table", + "print_designer_footer", + "print_designer_settings", + ]); + let settings = JSON.parse(printFormat.message.print_designer_settings); + this.loadSettings(settings); + + let ElementsBody = JSON.parse(printFormat.message.print_designer_body); + let ElementsAfterTable = JSON.parse(printFormat.message.print_designer_after_table); + const headers = JSON.parse(printFormat.message.print_designer_header); + const footers = JSON.parse(printFormat.message.print_designer_footer); + headers.forEach((header) => { + this.Headers.push(header); + }); + footers.forEach((footer) => { + this.Footers.push(footer); + }); + // backwards compatibility :( + if (ElementsAfterTable && ElementsAfterTable.length) { + ElementsBody[0].childrens.push(...ElementsAfterTable); + } + this.Elements.length = 0; + this.Elements.push(...ElementsBody); + ElementsBody.forEach((page, index) => { + page.header = [ + createHeaderFooterElement(this.getHeaderObject(index).childrens, "header"), + ]; + page.footer = [ + createHeaderFooterElement(this.getFooterObject(index).childrens, "footer"), + ]; + }); + this.Elements.forEach((page) => this.setElementProperties(page)); frappe.dom.unfreeze(); }, setPrimaryTable(tableEl, value) { @@ -1147,9 +1232,13 @@ export const useElementStore = defineStore("ElementStore", { }, // This is called to check if the element is overlapping with any other element (row only) // TODO: add column calculations - isElementOverlapping(currentEl, elements = this.Elements) { - const currentElIndex = - currentEl.index || this.Elements.findIndex((el) => el === currentEl); + isElementOverlapping(currentEl, elements = null) { + const MainStore = useMainStore(); + MainStore.activePage = getParentPage(currentEl.parent); + if (!elements) { + elements = MainStore.activePage.childrens; + } + const currentElIndex = currentEl.index || elements.findIndex((el) => el === currentEl); const currentStartY = parseInt(currentEl.startY); const currentEndY = parseInt(currentEl.startY + currentEl.height); diff --git a/print_designer/public/js/print_designer/store/MainStore.js b/print_designer/public/js/print_designer/store/MainStore.js index ac36cab..780f632 100644 --- a/print_designer/public/js/print_designer/store/MainStore.js +++ b/print_designer/public/js/print_designer/store/MainStore.js @@ -15,10 +15,12 @@ export const useMainStore = defineStore("MainStore", { */ textControlType: "dynamic", /** - * @type {'editing'|'pdfSetup'|'preview'} mode + * @type {'editing'|'footer'|'header'} mode */ schema_version: "1.2.0", mode: "editing", + activePage: null, + visiblePages: [], cursor: "url('/assets/print_designer/images/mouse-pointer.svg'), default !important", isMarqueeActive: false, isDrawing: false, @@ -41,7 +43,6 @@ export const useMainStore = defineStore("MainStore", { imageDocFields: [], snapPoints: [], snapEdges: [], - mainContainer: new Object(), propertiesContainer: new Object(), openModal: false, openDynamicModal: null, @@ -172,6 +173,16 @@ export const useMainStore = defineStore("MainStore", { return parseFloat(convertedUnit.value.toFixed(3)); }; }, + getPageStyle() { + switch (this.mode) { + case "editing": + return this.getPageSettings; + case "header": + return this.getHeaderSettings; + case "footer": + return this.getFooterSettings; + } + }, getPageSettings() { return { height: @@ -184,6 +195,24 @@ export const useMainStore = defineStore("MainStore", { ) + this.page.UOM, }; }, + getHeaderSettings() { + return { + height: this.convertToPageUOM(this.page.headerHeight) + this.page.UOM, + width: + this.convertToPageUOM( + this.page.width - this.page.marginLeft - this.page.marginRight + ) + this.page.UOM, + }; + }, + getFooterSettings() { + return { + height: this.convertToPageUOM(this.page.footerHeight) + this.page.UOM, + width: + this.convertToPageUOM( + this.page.width - this.page.marginLeft - this.page.marginRight + ) + this.page.UOM, + }; + }, getCurrentElementsValues() { return Object.values(this.currentElements); }, diff --git a/print_designer/public/js/print_designer/store/fetchMetaAndData.js b/print_designer/public/js/print_designer/store/fetchMetaAndData.js index f40b8f3..28778b8 100644 --- a/print_designer/public/js/print_designer/store/fetchMetaAndData.js +++ b/print_designer/public/js/print_designer/store/fetchMetaAndData.js @@ -131,60 +131,62 @@ export const fetchDoc = async (id = null) => { }, { immediate: true } ); - + const updateDynamicData = async () => { + MainStore.dynamicData.forEach(async (el) => { + if (el.is_static) return; + let value = el.parentField + ? await getValue(el.doctype, MainStore.docData[el.parentField], el.fieldname) + : el.tableName + ? MainStore.docData[el.tableName][0] && + frappe.format( + MainStore.docData[el.tableName][0][el.fieldname], + { fieldtype: el.fieldtype, options: el.options }, + { inline: true }, + MainStore.docData + ) + : frappe.format( + MainStore.docData[el.fieldname], + { fieldtype: el.fieldtype, options: el.options }, + { inline: true }, + MainStore.docData + ); + if (typeof value == "string" && value.startsWith("`)); + value = result[1]; + } + if (!value) { + if (["Image, Attach Image"].indexOf(el.fieldtype) != -1) { + value = null; + } else { + switch (el.fieldname) { + case "page": + value = "0"; + break; + case "topage": + value = "999"; + break; + case "date": + value = frappe.datetime.now_date(); + break; + case "time": + value = frappe.datetime.now_time(); + break; + default: + value = `{{ ${el.parentField ? el.parentField + "." : ""}${ + el.fieldname + } }}`; + } + } + } + el.value = value; + }); + }; watch( () => MainStore.docData, async () => { if (!Object.keys(MainStore.docData).length) return; await frappe.dom.freeze(); - MainStore.dynamicData.forEach(async (el) => { - if (el.is_static) return; - let value = el.parentField - ? await getValue(el.doctype, MainStore.docData[el.parentField], el.fieldname) - : el.tableName - ? MainStore.docData[el.tableName][0] && - frappe.format( - MainStore.docData[el.tableName][0][el.fieldname], - { fieldtype: el.fieldtype, options: el.options }, - { inline: true }, - MainStore.docData - ) - : frappe.format( - MainStore.docData[el.fieldname], - { fieldtype: el.fieldtype, options: el.options }, - { inline: true }, - MainStore.docData - ); - if (typeof value == "string" && value.startsWith("`)); - value = result[1]; - } - if (!value) { - if (["Image, Attach Image"].indexOf(el.fieldtype) != -1) { - value = null; - } else { - switch (el.fieldname) { - case "page": - value = "0"; - break; - case "topage": - value = "999"; - break; - case "date": - value = frappe.datetime.now_date(); - break; - case "time": - value = frappe.datetime.now_time(); - break; - default: - value = `{{ ${el.parentField ? el.parentField + "." : ""}${ - el.fieldname - } }}`; - } - } - } - el.value = value; - }); + await updateDynamicData(); await frappe.dom.unfreeze(); } ); diff --git a/print_designer/public/js/print_designer/utils.js b/print_designer/public/js/print_designer/utils.js index bf62e6b..6de214e 100644 --- a/print_designer/public/js/print_designer/utils.js +++ b/print_designer/public/js/print_designer/utils.js @@ -38,8 +38,12 @@ import { useDraggable } from "./composables/Draggable"; import { useResizable } from "./composables/Resizable"; import { useDropZone } from "./composables/DropZone"; import { isRef, nextTick } from "vue"; +import { getValue } from "./store/fetchMetaAndData"; export const changeDraggable = (element) => { + if (element.relativeContainer || element.DOMRef == null) { + return; + } if ( !element.isDraggable && interact.isSet(element.DOMRef) && @@ -53,7 +57,7 @@ export const changeDraggable = (element) => { ) { interact(element.DOMRef).draggable().enabled = true; } else if (element.isDraggable && !interact.isSet(element.DOMRef)) { - useDraggable(element.id); + useDraggable({ element }); } }; @@ -88,7 +92,7 @@ export const changeDropZone = (element) => { ) { interact(element.DOMRef).dropzone().enabled = true; } else if (element.isDropZone && !interact.isSet(element.DOMRef)) { - useDropZone(element.id); + useDropZone({ element }); } }; @@ -106,7 +110,7 @@ export const changeResizable = (element) => { ) { interact(element.DOMRef).resizable().enabled = true; } else if (element.isResizable && !interact.isSet(element.DOMRef)) { - useResizable(element.id); + useResizable({ element }); } }; @@ -203,17 +207,9 @@ const childrensCleanUp = (parentElement, element, isClone, isMainElement) => { } } if (isMainElement && isClone) { - if (Array.isArray(parentElement.parent)) { - parentElement.parent.push(element); - } else { - parentElement.parent.childrens.push(element); - } + parentElement.parent.childrens.push(element); } else if (!isMainElement) { - if (Array.isArray(parentElement)) { - parentElement.push(element); - } else { - parentElement.childrens.push(element); - } + parentElement.childrens.push(element); recursiveChildrens({ element, isClone, isMainElement: false }); } }; @@ -222,7 +218,10 @@ export const recursiveChildrens = ({ element, isClone = false, isMainElement = t const childrensArray = parentElement.childrens; isMainElement && childrensCleanUp(parentElement, element, isClone, isMainElement); parentElement.childrens = []; - if (parentElement.type == "rectangle" && childrensArray.length > 0) { + if ( + parentElement.type == "rectangle" || + (element.type == "page" && childrensArray.length > 0) + ) { childrensArray.forEach((element) => { childrensCleanUp(parentElement, element, isClone, false); }); @@ -280,9 +279,45 @@ export const updateElementParameters = (e) => { } }; +export const createHeaderFooterElement = (childrens, elementType) => { + const MainStore = useMainStore(); + const ElementStore = useElementStore(); + let id = frappe.utils.get_random(10); + const pageHeader = { + type: "rectangle", + childrens: [], + DOMRef: null, + id: id, + isDraggable: false, + isResizable: false, + isDropZone: true, + relativeContainer: true, + elementType: elementType, + startX: 0, + startY: + elementType == "header" + ? 0 + : MainStore.page.height - + MainStore.page.footerHeight - + MainStore.page.marginTop - + MainStore.page.marginBottom, + pageX: 0, + pageY: 0, + width: MainStore.page.width - MainStore.page.marginLeft - MainStore.page.marginRight, + height: + elementType == "header" ? MainStore.page.headerHeight : MainStore.page.footerHeight, + styleEditMode: "main", + style: { border: "none" }, + classes: [], + }; + pageHeader.childrens = childrens.map((el) => ElementStore.childrensSave(el)); + ElementStore.setElementProperties(pageHeader); + return { ...pageHeader }; +}; + export const deleteSnapObjects = (element, recursive = false) => { const MainStore = useMainStore(); - if (!element) { + if (!element || !element.snapPoints || !element.snapEdges) { return; } element.snapPoints.forEach((point) => { @@ -291,7 +326,10 @@ export const deleteSnapObjects = (element, recursive = false) => { element.snapEdges.forEach((point) => { MainStore.snapEdges.splice(MainStore.snapEdges.indexOf(point), 1); }); - if (recursive && element.type == "rectangle" && element.childrens.length > 0) { + if ( + (recursive && element.type == "rectangle") || + (element.type == "page" && element.childrens.length > 0) + ) { element.childrens.forEach((el) => { deleteSnapObjects(el, recursive); }); @@ -312,34 +350,71 @@ const deleteDynamicReferance = (curobj) => { }); } }; - +export const updateDynamicData = async () => { + const MainStore = useMainStore(); + if (!Object.keys(MainStore.docData).length) return; + MainStore.dynamicData.forEach(async (el) => { + if (el.is_static) return; + let value = el.parentField + ? await getValue(el.doctype, MainStore.docData[el.parentField], el.fieldname) + : el.tableName + ? MainStore.docData[el.tableName][0] && + frappe.format( + MainStore.docData[el.tableName][0][el.fieldname], + { fieldtype: el.fieldtype, options: el.options }, + { inline: true }, + MainStore.docData + ) + : frappe.format( + MainStore.docData[el.fieldname], + { fieldtype: el.fieldtype, options: el.options }, + { inline: true }, + MainStore.docData + ); + if (typeof value == "string" && value.startsWith("`)); + value = result[1]; + } + if (!value) { + if (["Image, Attach Image"].indexOf(el.fieldtype) != -1) { + value = null; + } else { + switch (el.fieldname) { + case "page": + value = "0"; + break; + case "topage": + value = "999"; + break; + case "date": + value = frappe.datetime.now_date(); + break; + case "time": + value = frappe.datetime.now_time(); + break; + default: + value = `{{ ${el.parentField ? el.parentField + "." : ""}${ + el.fieldname + } }}`; + } + } + } + el.value = value; + }); +}; export const deleteCurrentElements = () => { const MainStore = useMainStore(); const ElementStore = useElementStore(); if (MainStore.getCurrentElementsValues.length === 1) { let curobj = MainStore.getCurrentElementsValues[0]; deleteDynamicReferance(curobj); - if (Array.isArray(curobj.parent)) { - deleteSnapObjects(curobj.parent.splice(curobj.index, 1)[0], true); - } else { - deleteSnapObjects(curobj.parent.childrens.splice(curobj.index, 1)[0], true); - } + deleteSnapObjects(curobj.parent.childrens.splice(curobj.index, 1)[0], true); } else { MainStore.getCurrentElementsValues.forEach((element) => { - if (Array.isArray(element.parent)) { - deleteSnapObjects( - element.parent.splice(element.parent.indexOf(element), 1)[0], - true - ); - } else { - deleteSnapObjects( - element.parent.childrens.splice( - element.parent.childrens.indexOf(element), - 1 - )[0], - true - ); - } + deleteSnapObjects( + element.parent.childrens.splice(element.parent.childrens.indexOf(element), 1)[0], + true + ); }); } MainStore.lastCreatedElement = null; @@ -448,12 +523,11 @@ export const getSnapPointsAndEdges = (element) => { export const handleAlignIconClick = (value) => { const MainStore = useMainStore(); - const ElementStore = useElementStore(); let currentElements = MainStore.getCurrentElementsValues; let parent; MainStore.getCurrentElementsValues.forEach((element) => { if (parent == null) { - if (Array.isArray(element.parent)) { + if (element.parent.type == "page") { parent = false; return; } @@ -531,7 +605,9 @@ export const handleAlignIconClick = (value) => { break; } } else if (currentElements.length > 1) { - let parentRect = MainStore.mainContainer.getBoundingClientRect(); + let parentRect = getParentPage( + MainStore.getCurrentElementsValues[0].parent + ).DOMRef.getBoundingClientRect(); if (parent) { parentRect = parent.DOMRef.getBoundingClientRect(); } @@ -617,6 +693,28 @@ export const handleBorderIconClick = (element, icon) => { } }; +export const getParentPage = (element) => { + if (!element) return; + if (element.type == "page") { + return element; + } else { + if (element.parent) { + return getParentPage(element.parent); + } + return; + } +}; + +export const isInHeaderFooter = (element) => { + if (!element) return false; + if (!element.parent) return false; + if (element.elementType == "header") { + return true; + } else { + return isInHeaderFooter(element.parent); + } +}; + const getGlobalStyleObject = (object = null, checkProperty = null) => { const MainStore = useMainStore(); let globalStyleName = MainStore.activeControl; @@ -812,9 +910,9 @@ export const checkUpdateElementOverlapping = (element = null) => { const MainStore = useMainStore(); const ElementStore = useElementStore(); nextTick(() => { - if (element && !Array.isArray(element.parent)) return; + if (!element || element.parent.type != "page") return; isOlderSchema = MainStore.isOlderSchema("1.1.0"); - element.parent.forEach((el) => { + element.parent.childrens.forEach((el) => { const isElementOverlapping = ElementStore.isElementOverlapping(el); if (el.isElementOverlapping != isElementOverlapping) { el.isElementOverlapping = isElementOverlapping;