diff --git a/print_designer/custom_fields.py b/print_designer/custom_fields.py index c016259..981b556 100644 --- a/print_designer/custom_fields.py +++ b/print_designer/custom_fields.py @@ -7,6 +7,13 @@ "hidden": 1, "label": "Print Designer", }, + { + "fieldname": "print_designer_print_format", + "fieldtype": "JSON", + "hidden": 1, + "label": "Print Designer Print Format", + "description": "This has json object that is used by main.html jinja template to render the print format.", + }, { "fieldname": "print_designer_header", "fieldtype": "JSON", diff --git a/print_designer/patches.txt b/print_designer/patches.txt index ffb6301..1c006ff 100644 --- a/print_designer/patches.txt +++ b/print_designer/patches.txt @@ -5,4 +5,7 @@ print_designer.patches.introduce_schema_versioning print_designer.patches.rerun_introduce_jinja print_designer.patches.introduce_table_alt_row_styles print_designer.patches.introduce_column_style -print_designer.patches.introduce_suffix_dynamic_content \ No newline at end of file +print_designer.patches.introduce_suffix_dynamic_content +print_designer.patches.introduce_dynamic_containers +print_designer.patches.introduce_dynamic_height +print_designer.patches.remove_unused_rectangle_gs_properties diff --git a/print_designer/patches/introduce_dynamic_containers.py b/print_designer/patches/introduce_dynamic_containers.py new file mode 100644 index 0000000..4a2581b --- /dev/null +++ b/print_designer/patches/introduce_dynamic_containers.py @@ -0,0 +1,18 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + + +def execute(): + """Add print_designer_print_format field for Print Format.""" + CUSTOM_FIELDS = { + "Print Format": [ + { + "fieldname": "print_designer_print_format", + "fieldtype": "JSON", + "hidden": 1, + "label": "Print Designer Print Format", + "description": "This has json object that is used by jinja template to render the print format.", + } + ] + } + create_custom_fields(CUSTOM_FIELDS, ignore_validate=True) diff --git a/print_designer/patches/introduce_dynamic_height.py b/print_designer/patches/introduce_dynamic_height.py new file mode 100644 index 0000000..e8378f1 --- /dev/null +++ b/print_designer/patches/introduce_dynamic_height.py @@ -0,0 +1,19 @@ +import frappe + +from print_designer.patches.patch_utils import patch_formats + + +def execute(): + """Updating Table and Dynamic Text Elements to have property isDynamicHeight with default value as True""" + + def element_callback(el): + if el.get("type") == "text" and not el.get("isDynamic"): + return + + if not "isDynamicHeight" in el: + el["isDynamicHeight"] = False + + patch_formats( + {"element": element_callback}, + types=["text", "table"], + ) diff --git a/print_designer/patches/remove_unused_rectangle_gs_properties.py b/print_designer/patches/remove_unused_rectangle_gs_properties.py new file mode 100644 index 0000000..71fa82f --- /dev/null +++ b/print_designer/patches/remove_unused_rectangle_gs_properties.py @@ -0,0 +1,23 @@ +import frappe + + +def execute(): + """Remove unused style properties in globalStyles for rectangle of print formats that uses print designer""" + print_formats = frappe.get_all( + "Print Format", + filters={"print_designer": 1}, + fields=["name", "print_designer_settings"], + as_list=1, + ) + for pf in print_formats: + settings = frappe.parse_json(pf[1]) + if settings: + # If globalStyles is not present, skip + if not (gs := settings.get("globalStyles")): + continue + + for key in ["display", "justifyContent", "alignItems", "alignContent", "flex"]: + if gs["rectangle"]["style"].get(key, False): + del gs["rectangle"]["style"][key] + + frappe.db.set_value("Print Format", pf[0], "print_designer_settings", frappe.as_json(settings)) diff --git a/print_designer/pdf.py b/print_designer/pdf.py index f10684f..fd32773 100644 --- a/print_designer/pdf.py +++ b/print_designer/pdf.py @@ -50,16 +50,24 @@ def pdf_body_html(print_format, jenv, args, template): add_data_to_monitor(print_designer=print_format_name, print_designer_action="download_pdf") # DEPRECATED: remove this in few months added for backward compatibility incase user didn't update frappe framework. if not frappe.get_hooks("get_print_format_template"): - template = jenv.loader.get_source(jenv, "print_designer/page/print_designer/jinja/main.html")[0] + template = get_print_format_template(jenv, print_format) + + settings = json.loads(print_format.print_designer_settings) + args.update( { "headerElement": json.loads(print_format.print_designer_header), "bodyElement": json.loads(print_format.print_designer_body), - "afterTableElement": json.loads(print_format.print_designer_after_table), "footerElement": json.loads(print_format.print_designer_footer), - "settings": json.loads(print_format.print_designer_settings), + "settings": settings, } ) + + if not is_older_schema(settings=settings, current_version="1.1.0"): + args.update({"pd_format": json.loads(print_format.print_designer_print_format)}) + else: + args.update({"afterTableElement": json.loads(print_format.print_designer_after_table or "[]")}) + # replace placeholder comment with user provided jinja code template_source = template.replace( "", args["settings"].get("userProvidedJinja", "") @@ -78,7 +86,35 @@ def pdf_body_html(print_format, jenv, args, template): return template.render(args, filters={"len": len}) +def is_older_schema(settings, current_version): + format_version = settings.get("schema_version") + format_version = format_version.split(".") + current_version = current_version.split(".") + if int(format_version[0]) < int(current_version[0]): + return True + elif int(format_version[0]) == int(current_version[0]) and int(format_version[1]) < int( + current_version[1] + ): + return True + elif ( + int(format_version[0]) == int(current_version[0]) + and int(format_version[1]) == int(current_version[1]) + and int(format_version[2]) < int(current_version[2]) + ): + return True + else: + return False + + def get_print_format_template(jenv, print_format): # if print format is created using print designer, then use print designer template if print_format and print_format.print_designer and print_format.print_designer_body: - return jenv.loader.get_source(jenv, "print_designer/page/print_designer/jinja/main.html")[0] + settings = json.loads(print_format.print_designer_settings) + if is_older_schema(settings, "1.1.0"): + return jenv.loader.get_source( + jenv, "print_designer/page/print_designer/jinja/old_print_format.html" + )[0] + else: + return jenv.loader.get_source( + jenv, "print_designer/page/print_designer/jinja/print_format.html" + )[0] diff --git a/print_designer/print_designer/page/print_designer/jinja/macros/barcode.html b/print_designer/print_designer/page/print_designer/jinja/macros/barcode.html new file mode 100644 index 0000000..2044a32 --- /dev/null +++ b/print_designer/print_designer/page/print_designer/jinja/macros/barcode.html @@ -0,0 +1,32 @@ +{% macro barcode(element, send_to_jinja) -%} + {%- set field = element.dynamicContent[0] -%} + {%- if field.is_static -%} + {% if field.parseJinja %} + {%- set value = render_user_text(field.value, doc, {}, send_to_jinja).get("message", "") -%} + {% else %} + {%- set value = _(field.value) -%} + {% endif %} + {%- elif field.doctype -%} + {%- set value = frappe.db.get_value(field.doctype, doc[field.parentField], field.fieldname) -%} + {%- else -%} + {%- set value = doc.get_formatted(field.fieldname) -%} + {%- endif -%} + +
+
+ {% if value %}{{get_barcode(element.barcodeFormat, value|string, { + "module_color": element.barcodeColor or "#000000", + "foreground": element.barcodeColor or "#ffffff", + "background": element.barcodeBackgroundColor or "#ffffff", + "quiet_zone": 1, + }).value}}{% endif %} +
+
+{%- endmacro %} \ No newline at end of file diff --git a/print_designer/print_designer/page/print_designer/jinja/macros/dynamictext.html b/print_designer/print_designer/page/print_designer/jinja/macros/dynamictext.html new file mode 100644 index 0000000..2b2c054 --- /dev/null +++ b/print_designer/print_designer/page/print_designer/jinja/macros/dynamictext.html @@ -0,0 +1,14 @@ +{% from 'print_designer/page/print_designer/jinja/macros/spantag.html' import span_tag with context %} + +{% macro dynamictext(element, send_to_jinja, is_parent_dynamic_height) -%} +
+
+ {% for field in element.dynamicContent %} + + {{ span_tag(field, element, {}, send_to_jinja)}} + {% endfor %} +
+
+{%- endmacro %} \ No newline at end of file diff --git a/print_designer/print_designer/page/print_designer/jinja/macros/image.html b/print_designer/print_designer/page/print_designer/jinja/macros/image.html new file mode 100644 index 0000000..7662b9f --- /dev/null +++ b/print_designer/print_designer/page/print_designer/jinja/macros/image.html @@ -0,0 +1,14 @@ +{% macro image(element) -%} +
+
+
+{%- endmacro %} \ No newline at end of file diff --git a/print_designer/print_designer/page/print_designer/jinja/macros/rectangle.html b/print_designer/print_designer/page/print_designer/jinja/macros/rectangle.html new file mode 100644 index 0000000..6f15065 --- /dev/null +++ b/print_designer/print_designer/page/print_designer/jinja/macros/rectangle.html @@ -0,0 +1,10 @@ +{% macro rectangle(element, render_element, send_to_jinja) -%} +
+ {% if element.childrens %} + {% for object in element.childrens %} + {{ render_element(object, send_to_jinja) }} + {% endfor %} + {% endif %} +
+{%- endmacro %} \ No newline at end of file diff --git a/print_designer/print_designer/page/print_designer/jinja/macros/relative_containers.html b/print_designer/print_designer/page/print_designer/jinja/macros/relative_containers.html new file mode 100644 index 0000000..6327524 --- /dev/null +++ b/print_designer/print_designer/page/print_designer/jinja/macros/relative_containers.html @@ -0,0 +1,12 @@ +{% from 'print_designer/page/print_designer/jinja/macros/render_element.html' import render_element with context %} + +{% macro relative_containers(element, send_to_jinja) -%} +
+ {% if element.childrens %} + {% for object in element.childrens %} + {{ render_element(object, send_to_jinja, element.get("isDynamicHeight", false)) }} + {% endfor %} + {% endif %} +
+{%- endmacro %} \ No newline at end of file diff --git a/print_designer/print_designer/page/print_designer/jinja/macros/render.html b/print_designer/print_designer/page/print_designer/jinja/macros/render.html new file mode 100644 index 0000000..df73527 --- /dev/null +++ b/print_designer/print_designer/page/print_designer/jinja/macros/render.html @@ -0,0 +1,9 @@ +{% from 'print_designer/page/print_designer/jinja/macros/relative_containers.html' import relative_containers with context %} + +{% macro render(elements, send_to_jinja) -%} + {% if element is iterable and (element is not string and element is not mapping) %} + {% for object in elements %} + {{ relative_containers(object, send_to_jinja) }} + {% endfor %} + {% endif %} +{%- endmacro %} \ No newline at end of file diff --git a/print_designer/print_designer/page/print_designer/jinja/macros/render_element.html b/print_designer/print_designer/page/print_designer/jinja/macros/render_element.html new file mode 100644 index 0000000..71b9f14 --- /dev/null +++ b/print_designer/print_designer/page/print_designer/jinja/macros/render_element.html @@ -0,0 +1,26 @@ +{% from 'print_designer/page/print_designer/jinja/macros/statictext.html' import statictext with context %} +{% from 'print_designer/page/print_designer/jinja/macros/dynamictext.html' import dynamictext with context %} +{% from 'print_designer/page/print_designer/jinja/macros/spantag.html' import span_tag with context %} +{% from 'print_designer/page/print_designer/jinja/macros/image.html' import image with context %} +{% from 'print_designer/page/print_designer/jinja/macros/barcode.html' import barcode with context %} +{% from 'print_designer/page/print_designer/jinja/macros/rectangle.html' import rectangle with context %} +{% from 'print_designer/page/print_designer/jinja/macros/table.html' import table with context %} + + +{% macro render_element(element, send_to_jinja, is_parent_dynamic_height = false) -%} + {% if element.type == "rectangle" %} + {{ rectangle(element, render_element, send_to_jinja) }} + {% elif element.type == "image" %} + {{image(element)}} + {% elif element.type == "table" %} + {{table(element, send_to_jinja, is_parent_dynamic_height)}} + {% elif element.type == "text" %} + {% if element.isDynamic %} + {{dynamictext(element, send_to_jinja, is_parent_dynamic_height)}} + {% else%} + {{statictext(element, send_to_jinja, is_parent_dynamic_height)}} + {% endif %} + {% elif element.type == "barcode" %} + {{barcode(element, send_to_jinja)}} + {% endif %} +{%- endmacro %} \ No newline at end of file diff --git a/print_designer/print_designer/page/print_designer/jinja/macros/render_google_fonts.html b/print_designer/print_designer/page/print_designer/jinja/macros/render_google_fonts.html new file mode 100644 index 0000000..6b11721 --- /dev/null +++ b/print_designer/print_designer/page/print_designer/jinja/macros/render_google_fonts.html @@ -0,0 +1,26 @@ +{% macro getFontStyles(fonts) -%}{%for key, value in fonts.items()%}{{key ~ ':ital,wght@'}}{%for index, size in enumerate(value.weight)%}{%if index > 0%};{%endif%}0,{{size}}{%endfor%}{%for index, size in enumerate(value.italic)%}{%if index > 0%};{%endif%}1,{{size}}{%endfor%}{% if not loop.last %}{{'&display=swap&family='}}{%endif%}{%endfor%}{%- endmacro %} + +{% macro render_google_fonts(settings) %} + + {% if settings.printHeaderFonts %} + + {%endif%} + {% if settings.printBodyFonts %} + + {%endif%} + {% if settings.printFooterFonts %} + + {%endif%} +{% endmacro %} \ No newline at end of file diff --git a/print_designer/print_designer/page/print_designer/jinja/macros/spantag.html b/print_designer/print_designer/page/print_designer/jinja/macros/spantag.html new file mode 100644 index 0000000..04297f5 --- /dev/null +++ b/print_designer/print_designer/page/print_designer/jinja/macros/spantag.html @@ -0,0 +1,41 @@ +{% macro page_class(field) %} + {% if field.fieldname in ['page', 'topage', 'time', 'date'] %} + page_info_{{ field.fieldname }} + {% endif %} +{% endmacro %} + +{%- macro spanvalue(field, element, row, send_to_jinja) -%} + {%- if field.is_static -%} + {% if field.parseJinja %} + {{ render_user_text(field.value, doc, row, send_to_jinja).get("message", "") }} + {% else %} + {{ _(field.value) }} + {% endif %} + {%- elif field.doctype -%} + {%- set value = _(frappe.db.get_value(field.doctype, doc[field.parentField], field.fieldname)) -%} + {{ frappe.format(value, {'fieldtype': field.fieldtype, 'options': field.options}) }} + {%- elif row -%} + {{row.get_formatted(field.fieldname)}} + {%- else -%} + {{doc.get_formatted(field.fieldname)}} + {%- endif -%} +{%- endmacro -%} + + +{% macro span_tag(field, element, row = {}, send_to_jinja = {}) -%} + {% set span_value = spanvalue(field, element, row, send_to_jinja) %} + + {% if not field.is_static and field.is_labelled and span_value %} + + {{ _(field.label) }} + + {% endif %} + + {{ span_value }} + + {% if field.nextLine %} +
+ {% endif %} +
+{%- endmacro %} \ No newline at end of file diff --git a/print_designer/print_designer/page/print_designer/jinja/macros/statictext.html b/print_designer/print_designer/page/print_designer/jinja/macros/statictext.html new file mode 100644 index 0000000..3620ba4 --- /dev/null +++ b/print_designer/print_designer/page/print_designer/jinja/macros/statictext.html @@ -0,0 +1,14 @@ + +{% macro statictext(element, send_to_jinja, dynamic_containers) -%} +
+

+ {% if element.parseJinja %} + {{ render_user_text(element.content, doc, {}, send_to_jinja).get("message", "") }} + {% else %} + {{_(element.content)}} + {% endif %} +

+
+{%- endmacro %} \ No newline at end of file diff --git a/print_designer/print_designer/page/print_designer/jinja/macros/styles.html b/print_designer/print_designer/page/print_designer/jinja/macros/styles.html new file mode 100644 index 0000000..0dcb4e8 --- /dev/null +++ b/print_designer/print_designer/page/print_designer/jinja/macros/styles.html @@ -0,0 +1,78 @@ +{% macro render_styles(settings) %} + +{% endmacro %} \ No newline at end of file diff --git a/print_designer/print_designer/page/print_designer/jinja/macros/table.html b/print_designer/print_designer/page/print_designer/jinja/macros/table.html new file mode 100644 index 0000000..b864d49 --- /dev/null +++ b/print_designer/print_designer/page/print_designer/jinja/macros/table.html @@ -0,0 +1,33 @@ +{% macro table(element, send_to_jinja, is_parent_dynamic_height) -%} + + + {% if element.columns %} + + {% for column in element.columns%} + + {% endfor %} + + {% endif %} + + + {% if element.columns %} + {% for row in doc.get(element.table.fieldname)%} + + {% set isLastRow = loop.last %} + {% for column in element.columns%} + + {% endfor %} + + {% endfor %} + {% endif %} + +
+ {{ _(column.label) }} +
+ {% if column is mapping %} + {% for field in column.dynamicContent%} + {{ span_tag(field, element, row, send_to_jinja) }} + {% endfor %} + {% endif %} +
+{%- endmacro %} \ No newline at end of file diff --git a/print_designer/print_designer/page/print_designer/jinja/main.html b/print_designer/print_designer/page/print_designer/jinja/old_print_format.html similarity index 100% rename from print_designer/print_designer/page/print_designer/jinja/main.html rename to print_designer/print_designer/page/print_designer/jinja/old_print_format.html 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 new file mode 100644 index 0000000..485cb37 --- /dev/null +++ b/print_designer/print_designer/page/print_designer/jinja/print_format.html @@ -0,0 +1,25 @@ +{% from 'print_designer/page/print_designer/jinja/macros/render.html' import render with context %} +{% from 'print_designer/page/print_designer/jinja/macros/render_google_fonts.html' import render_google_fonts with context %} +{% from 'print_designer/page/print_designer/jinja/macros/styles.html' import render_styles with context %} + +{{ render_google_fonts(settings) }} + + + + + +
+
+
+ {% if headerElement %}{{ render(pd_format.header, send_to_jinja) }}{%endif%} +
+
+ {% if bodyElement %}{{ render(pd_format.body, send_to_jinja) }}{%endif%} + +
+ +{{ render_styles(settings) }} \ No newline at end of file diff --git a/print_designer/public/js/print_designer/PropertiesPanelState.js b/print_designer/public/js/print_designer/PropertiesPanelState.js index 4624b9c..31ad16b 100644 --- a/print_designer/public/js/print_designer/PropertiesPanelState.js +++ b/print_designer/public/js/print_designer/PropertiesPanelState.js @@ -10,6 +10,7 @@ import { } from "./utils"; export const createPropertiesPanel = () => { const MainStore = useMainStore(); + const ElementStore = useElementStore(); const iconControl = ({ name, size, @@ -433,6 +434,53 @@ export const createPropertiesPanel = () => { transformInput("H", "transformHeight", "height"), transformInput("W", "transformWidth", "width"), ], + [ + { + label: "Height", + name: "isDynamicHeight", + isLabelled: true, + labelDirection: "column", + condtional: () => { + const currentEl = MainStore.getCurrentElementsValues[0]; + if ( + (currentEl.parent === ElementStore.Elements && + currentEl?.type === "table") || + (currentEl.type === "text" && currentEl.isDynamic) + ) { + return !currentEl.isElementOverlapping; + } + return false; + }, + frappeControl: (ref, name) => { + const MainStore = useMainStore(); + makeFeild({ + name, + ref, + fieldtype: "Select", + requiredData: [MainStore.getCurrentElementsValues[0]], + reactiveObject: () => MainStore.getCurrentElementsValues[0], + propertyName: "isDynamicHeight", + isStyle: false, + options: () => [ + { label: "Auto", value: "auto" }, + { label: "Fixed", value: "fixed" }, + ], + formatValue: (object, property, isStyle) => { + if (!object) return; + return object[property] ? "auto" : "fixed"; + }, + onChangeCallback: (value = null) => { + if (value && MainStore.getCurrentElementsValues[0]) { + MainStore.getCurrentElementsValues[0]["isDynamicHeight"] = + value === "auto"; + MainStore.frappeControls[name].$input.blur(); + } + }, + }); + }, + flex: 1, + }, + ], ], }); MainStore.propertiesPanel.push({ @@ -663,7 +711,10 @@ export const createPropertiesPanel = () => { } } } - if (MainStore.getCurrentElementsValues[0]?.selectedColumn && value != "main"){ + if ( + MainStore.getCurrentElementsValues[0]?.selectedColumn && + value != "main" + ) { MainStore.getCurrentElementsValues[0].selectedColumn = null; } }, 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 3ba71f9..3737a13 100644 --- a/print_designer/public/js/print_designer/components/base/BaseDynamicText.vue +++ b/print_designer/public/js/print_designer/components/base/BaseDynamicText.vue @@ -158,6 +158,43 @@ watch( } ); +const showWarning = frappe.utils.debounce((pageInfoInBody) => { + frappe.show_alert({ + message: "Please move " + pageInfoInBody.join(", ") + " to header / footer", + indicator: "orange", + }); +}, 500); +// Remove If Becomes Unnecessary Overhead. This will be fine once temparary state is introduced. +watch( + () => [startY.value], + () => { + if ( + startY.value + height.value < MainStore.page.headerHeight + MainStore.page.marginTop || + startY.value > + MainStore.page.height - + MainStore.page.footerHeight - + MainStore.page.marginTop - + MainStore.page.marginBottom + ) { + classes.value.splice(classes.value.indexOf("error"), 1); + return; + } + let pageInfoInBody = []; + dynamicContent.value + .filter((el) => ["page", "topage", "date", "time"].indexOf(el.fieldname) != -1) + .forEach((field) => { + pageInfoInBody.push(field.fieldname); + }); + if (pageInfoInBody.length) { + if (classes.value.indexOf("error") == -1) { + classes.value.push("error"); + } + showWarning(pageInfoInBody); + } + }, + { flush: "post" } +); + const handleMouseDown = (e, element) => { lockAxis(element, e.shiftKey); if (MainStore.openModal) return; @@ -215,6 +252,12 @@ p:empty:before { [contenteditable]:empty:focus:before { content: ""; } + +.error { + color: var(--red-500) !important; + border: 1px solid var(--red-500) !important; +} + .text-hover:hover { box-sizing: border-box !important; border-bottom: 1px solid var(--primary-color) !important; 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 9768b87..4828bc3 100644 --- a/print_designer/public/js/print_designer/components/layout/AppCanvas.vue +++ b/print_designer/public/js/print_designer/components/layout/AppCanvas.vue @@ -73,7 +73,12 @@ import { useMainStore } from "../../store/MainStore"; import { useElementStore } from "../../store/ElementStore"; import { useMarqueeSelection } from "../../composables/MarqueeSelectionTool"; import { useDraw } from "../../composables/Draw"; -import { updateElementParameters, setCurrentElement, recursiveChildrens } from "../../utils"; +import { + updateElementParameters, + setCurrentElement, + recursiveChildrens, + checkUpdateElementOverlapping, +} from "../../utils"; import { useChangeValueUnit } from "../../composables/ChangeValueUnit"; import BaseBarcode from "../base/BaseBarcode.vue"; const isComponent = Object.freeze({ @@ -457,10 +462,15 @@ onMounted(() => { } } ); + watchEffect(() => { if (MainStore.screenStyleSheet) { if (MainStore.screenStyleSheet.CssRuleIndex != null) { - MainStore.screenStyleSheet.deleteRule(MainStore.screenStyleSheet.CssRuleIndex); + try { + MainStore.screenStyleSheet.deleteRule(MainStore.screenStyleSheet.CssRuleIndex); + } catch (error) { + console.warn("Error Deleting Rule", error); + } } MainStore.screenStyleSheet.CssRuleIndex = MainStore.addStylesheetRules([ [ @@ -508,9 +518,25 @@ onMounted(() => { ]); } }); + + ElementStore.$subscribe((mutation, state) => { + if ( + (mutation.events.type === "set" && mutation.events.key == "Elements") || + (mutation.events.type === "add" && mutation.events.newValue.parent == state.Elements) + ) { + checkUpdateElementOverlapping(); + } + }); }); watchEffect(() => { if (MainStore.printStyleSheet && MainStore.page) { + for (let index = 0; index < MainStore.printStyleSheet.cssRules.length; index++) { + try { + MainStore.printStyleSheet.deleteRule(index); + } catch (error) { + console.warn("Error Deleting Rule", error); + } + } const convertToMM = (input) => { let convertedUnit = useChangeValueUnit({ inputString: input, @@ -542,6 +568,13 @@ watchEffect(() => { }); watchEffect(() => { if (MainStore.screenStyleSheet && MainStore.modalLocation) { + for (let index = 0; index < MainStore.screenStyleSheet.cssRules.length; index++) { + try { + MainStore.screenStyleSheet.deleteRule(index); + } catch (error) { + console.warn("Error Deleting Rule", error); + } + } MainStore.addStylesheetRules([ [ ":root", diff --git a/print_designer/public/js/print_designer/composables/AttachKeyBindings.js b/print_designer/public/js/print_designer/composables/AttachKeyBindings.js index 594f2fd..1449d43 100644 --- a/print_designer/public/js/print_designer/composables/AttachKeyBindings.js +++ b/print_designer/public/js/print_designer/composables/AttachKeyBindings.js @@ -1,7 +1,7 @@ import { onMounted, onUnmounted } from "vue"; import { useMainStore } from "../store/MainStore"; import { useElementStore } from "../store/ElementStore"; -import { deleteCurrentElements } from "../utils"; +import { checkUpdateElementOverlapping, deleteCurrentElements } from "../utils"; export function useAttachKeyBindings() { const MainStore = useMainStore(); @@ -28,6 +28,7 @@ export function useAttachKeyBindings() { element[`start${axis}`] += value; } }); + checkUpdateElementOverlapping(); } function updateWidthHeight(key, value) { MainStore.getCurrentElementsValues.forEach((element) => { @@ -48,6 +49,7 @@ export function useAttachKeyBindings() { element[key] += value; } }); + checkUpdateElementOverlapping(); } const handleKeyDown = async (e) => { MainStore.isAltKey = e.altKey; diff --git a/print_designer/public/js/print_designer/composables/Draggable.js b/print_designer/public/js/print_designer/composables/Draggable.js index 8398514..05fb6bc 100644 --- a/print_designer/public/js/print_designer/composables/Draggable.js +++ b/print_designer/public/js/print_designer/composables/Draggable.js @@ -4,7 +4,7 @@ import "@interactjs/auto-start"; import "@interactjs/modifiers"; import { useMainStore } from "../store/MainStore"; import { useElementStore } from "../store/ElementStore"; -import { recursiveChildrens } from "../utils"; +import { recursiveChildrens, checkUpdateElementOverlapping } from "../utils"; export function useDraggable({ element, @@ -85,6 +85,7 @@ export function useDraggable({ droppedElement[splicedElement.id] = splicedElement; MainStore.isDropped = droppedElement; } + checkUpdateElementOverlapping(element); }); return; } diff --git a/print_designer/public/js/print_designer/composables/Resizable.js b/print_designer/public/js/print_designer/composables/Resizable.js index 99802b2..91e3f01 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 } from "../utils"; +import { recursiveChildrens, checkUpdateElementOverlapping } from "../utils"; export function useResizable({ element, @@ -44,6 +44,7 @@ export function useResizable({ if (element.DOMRef.className == "modal-dialog modal-sm") { return; } + checkUpdateElementOverlapping(element); if (element.parent == e.target.piniaElementRef.parent) return; if ( !e.dropzone && diff --git a/print_designer/public/js/print_designer/defaultObjects.js b/print_designer/public/js/print_designer/defaultObjects.js index 2b64547..1121d0b 100644 --- a/print_designer/public/js/print_designer/defaultObjects.js +++ b/print_designer/public/js/print_designer/defaultObjects.js @@ -166,6 +166,7 @@ export const createTable = (cordinates, parent = null) => { labelStyle: {}, headerStyle: {}, altStyle: {}, + isDynamicHeight: true, classes: [], }; @@ -258,6 +259,7 @@ export const createDynamicText = (cordinates, parent = null) => { labelDisplayStyle: "standard", style: {}, labelStyle: {}, + isDynamicHeight: true, classes: [], }; parent !== ElementStore.Elements diff --git a/print_designer/public/js/print_designer/globalStyles.js b/print_designer/public/js/print_designer/globalStyles.js index b753b54..8d8c4b9 100644 --- a/print_designer/public/js/print_designer/globalStyles.js +++ b/print_designer/public/js/print_designer/globalStyles.js @@ -120,11 +120,6 @@ export const globalStyles = { isDynamic: false, mainRuleSelector: ".rectangle", style: { - display: "inline-flex", - justifyContent: "flex-start", - alignItems: "flex-start", - alignContent: "flex-start", - flex: 1, paddingTop: "0px", paddingBottom: "0px", paddingLeft: "0px", diff --git a/print_designer/public/js/print_designer/store/ElementStore.js b/print_designer/public/js/print_designer/store/ElementStore.js index c5061c0..40b8b51 100644 --- a/print_designer/public/js/print_designer/store/ElementStore.js +++ b/print_designer/public/js/print_designer/store/ElementStore.js @@ -8,7 +8,7 @@ import { createTable, createBarcode, } from "../defaultObjects"; -import { handlePrintFonts } from "../utils"; +import { handlePrintFonts, setCurrentElement } from "../utils"; export const useElementStore = defineStore("ElementStore", { state: () => ({ Elements: new Array(), @@ -36,37 +36,109 @@ export const useElementStore = defineStore("ElementStore", { }, async saveElements() { const MainStore = useMainStore(); - if (MainStore.mode == "preview") return; - let mainPrintFonts = {}; - let headerPrintFonts = {}; - let footerprintFonts = {}; - const childrensSave = (element, printFonts) => { - let saveEl = { ...element }; - delete saveEl.DOMRef; - delete saveEl.index; - delete saveEl.snapPoints; - delete saveEl.snapEdges; - delete saveEl.parent; - ["text", "table"].indexOf(saveEl.type) != -1 && - handlePrintFonts(saveEl, printFonts); - if (saveEl.type == "rectangle") { - const childrensArray = saveEl.childrens; - saveEl.childrens = []; - childrensArray.forEach((el) => { - const child = childrensSave(el, printFonts); - child && saveEl.childrens.push(child); - }); - } + if (this.checkIfAnyTableIsEmpty()) return; + + // Update the header and footer height with margin + MainStore.page.headerHeightWithMargin = + MainStore.page.headerHeight + MainStore.page.marginTop; + MainStore.page.footerHeightWithMargin = + MainStore.page.footerHeight + MainStore.page.marginBottom; + + const [headerElements, bodyElements, footerElements] = await this.computeLayout(); + if (!this.handleHeaderFooterOverlapping(headerElements.flat())) return; + if (!this.handleHeaderFooterOverlapping(bodyElements.flat())) return; + if (!this.handleHeaderFooterOverlapping(footerElements.flat())) return; + + const headerDimensions = this.computeElementDimensions(headerElements, "header"); + const bodyDimensions = this.computeElementDimensions(bodyElements, "body"); + const footerDimensions = this.computeElementDimensions(footerElements, "footer"); + + const header = this.cleanUpElementsForSave(headerElements, "header"); + const body = this.cleanUpElementsForSave(bodyElements, "body"); + const footer = this.cleanUpElementsForSave(footerElements, "footer"); + + if (!body) return; + + const [cleanedBodyElements, bodyFonts] = body; + const [cleanedHeaderElements, headerFonts] = header || [[], null]; + const [cleanedFooterElements, footerFonts] = footer || [[], null]; + + MainStore.currentFonts.length = 0; + MainStore.currentFonts.push( + ...Object.keys({ + ...(headerFonts || {}), + ...(bodyFonts || {}), + ...(footerFonts || {}), + }) + ); + const updatedPage = { ...MainStore.page }; + const settingsForSave = { + page: updatedPage, + pdfPrintDPI: MainStore.pdfPrintDPI, + globalStyles: MainStore.globalStyles, + currentPageSize: MainStore.currentPageSize, + isHeaderFooterAuto: MainStore.isHeaderFooterAuto, + currentDoc: MainStore.currentDoc, + textControlType: MainStore.textControlType, + currentFonts: MainStore.currentFonts, + printHeaderFonts: MainStore.printHeaderFonts, + printFooterFonts: MainStore.printFooterFonts, + printBodyFonts: MainStore.printBodyFonts, + userProvidedJinja: MainStore.userProvidedJinja, + schema_version: MainStore.schema_version, + }; + const convertCsstoString = (stylesheet) => { + let cssRule = Array.from(stylesheet.cssRules) + .map((rule) => rule.cssText || "") + .join(" "); + return stylesheet.cssRules ? cssRule : ""; + }; + const css = + convertCsstoString(MainStore.screenStyleSheet) + + convertCsstoString(MainStore.printStyleSheet); - return saveEl; + const objectToSave = { + print_designer_header: JSON.stringify(cleanedHeaderElements[0]), + print_designer_body: JSON.stringify(cleanedBodyElements.flat()), + print_designer_after_table: null, + print_designer_footer: JSON.stringify(cleanedFooterElements[0]), + print_designer_settings: JSON.stringify(settingsForSave), + css: css, }; - const headerElements = []; - const mainElements = []; - const afterTableElements = []; - const footerElements = []; - let tableElement = this.Elements.filter((el) => el.type == "table"); - if (tableElement.some((el) => el.table == null)) { + const PrintFormatData = this.getPrintFormatData({ + header: { + elements: cleanedHeaderElements, + dimensions: headerDimensions, + }, + body: { + elements: cleanedBodyElements, + dimensions: bodyDimensions, + }, + footer: { + elements: cleanedFooterElements, + dimensions: footerDimensions, + }, + }); + + objectToSave.print_designer_print_format = PrintFormatData; + if (MainStore.isOlderSchema("1.1.0")) { + await this.printFormatCopyOnOlderSchema(objectToSave); + } else { + await frappe.db.set_value("Print Format", MainStore.printDesignName, objectToSave); + frappe.show_alert( + { + message: `Print Format Saved Successfully`, + indicator: "green", + }, + 5 + ); + } + }, + checkIfAnyTableIsEmpty() { + const emptyTable = this.Elements.find((el) => el.type == "table" && el.table == null); + if (emptyTable) { let message = __("You have Empty Table. Please add table fields or remove table."); + setCurrentElement({}, emptyTable); frappe.show_alert( { message: message, @@ -74,270 +146,434 @@ export const useElementStore = defineStore("ElementStore", { }, 5 ); - return; + return true; } + return false; + }, + async computeLayout(element = null) { + const MainStore = useMainStore(); + const elements = [...this.Elements].map((el, index) => { + return { + index, + startY: parseInt(el.startY), + endY: parseInt(el.startY + el.height), + element: el, + }; + }); + elements.sort((a, b) => { + return a.startY < b.startY ? -1 : 1; + }); + const fullWidthElements = elements.filter( + (currentEl) => !currentEl.element.isElementOverlapping + ); + const headerContainer = []; + const bodyContainer = []; + const footerContainer = []; + const tempElementsArray = []; + elements.forEach((currentEl) => { + if (MainStore.page.headerHeight && currentEl.endY <= MainStore.page.headerHeight) { + headerContainer.push(currentEl); + } else if ( + MainStore.page.footerHeight && + currentEl.startY >= + MainStore.page.height - + MainStore.page.footerHeightWithMargin - + MainStore.page.marginTop + ) { + footerContainer.push(currentEl); + } else if ( + fullWidthElements.includes(currentEl) && + currentEl.element.isDynamicHeight + ) { + if (tempElementsArray.length) { + bodyContainer.push([...tempElementsArray]); + } + bodyContainer.push([currentEl]); + tempElementsArray.length = 0; + } else { + tempElementsArray.push(currentEl); + } + }); + if (tempElementsArray.length) { + bodyContainer.push(tempElementsArray); + } + return [[headerContainer], bodyContainer, [footerContainer]]; + }, + handleHeaderFooterOverlapping(elements) { + const MainStore = useMainStore(); + const tableElement = this.Elements.filter((el) => el.type == "table"); let isOverlapping = false; + if (tableElement.length == 1 && MainStore.isHeaderFooterAuto) { - this.Elements.forEach((element) => { + isOverlapping = !this.autoCalculateHeaderFooter(tableElement[0]); + } else { + isOverlapping = elements.some((element) => { + element = element.element; if ( - (element.startY < tableElement[0].startY + MainStore.page.marginTop && - element.startY + element.height > - tableElement[0].startY + MainStore.page.marginTop) || + (element.startY < MainStore.page.headerHeight && + element.startY + element.height > MainStore.page.headerHeight) || (element.startY < MainStore.page.height - - (MainStore.page.height - - (tableElement[0].startY + tableElement[0].height)) - + MainStore.page.footerHeight - MainStore.page.marginTop - MainStore.page.marginBottom && element.startY + element.height > MainStore.page.height - - (MainStore.page.height - - (tableElement[0].startY + tableElement[0].height)) - + MainStore.page.footerHeight - MainStore.page.marginTop - MainStore.page.marginBottom) ) { - isOverlapping = true; + return true; } + return false; }); - if (isOverlapping) { - MainStore.mode = "editing"; - } else if (!isOverlapping) { - MainStore.page.headerHeight = tableElement[0].startY; - MainStore.page.footerHeight = - MainStore.page.height - - (tableElement[0].startY + - tableElement[0].height + - MainStore.page.marginTop + - MainStore.page.marginBottom); - } - } else { - let isHeaderOverlapping = false; - let isFooterOverlapping = false; - this.Elements.forEach((element) => { - if ( - element.startY < MainStore.page.headerHeight && - element.startY + element.height > MainStore.page.headerHeight - ) { - isHeaderOverlapping = true; - } - if ( - element.startY < - MainStore.page.height - - MainStore.page.footerHeight - - MainStore.page.marginTop - - MainStore.page.marginBottom && - element.startY + element.height > - MainStore.page.height - - MainStore.page.footerHeight - - MainStore.page.marginTop - - MainStore.page.marginBottom - ) { - isFooterOverlapping = true; - } - }); - if (isHeaderOverlapping || isFooterOverlapping) { - MainStore.mode = "pdfSetup"; - frappe.show_alert( - { - message: `Please resolve overlapping ${ - isHeaderOverlapping ? "header" : "" - } ${isHeaderOverlapping && isFooterOverlapping ? " and " : ""} ${ - isFooterOverlapping ? "footer" : "" - }`, - indicator: "red", - }, - 5 + } + if (!isOverlapping) return true; + MainStore.mode = "pdfSetup"; + frappe.show_alert( + { + message: "Please resolve overlapping header/footer elements", + indicator: "red", + }, + 5 + ); + }, + autoCalculateHeaderFooter(tableEl) { + const MainStore = useMainStore(); + + if (this.isElementOverlapping(tableEl)) return false; + + MainStore.page.headerHeight = tableEl.startY; + MainStore.page.footerHeight = + MainStore.page.height - + (tableEl.startY + + tableEl.height + + MainStore.page.marginTop + + MainStore.page.marginBottom); + + return true; + }, + computeElementDimensions(elements, containerType = "body") { + const dimensions = []; + elements.reduce( + (prevDimensions, container, index) => { + const calculatedDimensions = this.calculateWrapperElementDimensions( + prevDimensions, + container, + containerType, + index ); - return; + dimensions.push(calculatedDimensions); + return calculatedDimensions; + }, + { top: 0, bottom: 0 } + ); + return dimensions; + }, + calculateWrapperElementDimensions(prevDimensions, children, containerType, index) { + // basically returns lowest left - top highest right - bottom from all of the children elements + const MainStore = useMainStore(); + const parentRect = { + top: 0, + left: 0, + width: + MainStore.page.width - MainStore.page.marginLeft - MainStore.page.marginRight, + height: + MainStore.page.height - MainStore.page.marginTop - MainStore.page.marginBottom, + }; + let offsetRect = children.reduce( + (offset, currentElement) => { + currentElement = currentElement.element; + let currentElementRect = { + top: currentElement.startY, + left: currentElement.startX, + right: currentElement.startX + currentElement.width, + bottom: currentElement.startY + currentElement.height, + }; + currentElementRect.left < offset.left && + (offset.left = currentElementRect.left); + currentElementRect.top < offset.top && (offset.top = currentElementRect.top); + currentElementRect.right > offset.right && + (offset.right = currentElementRect.right); + currentElementRect.bottom > offset.bottom && + (offset.bottom = currentElementRect.bottom); + return offset; + }, + { left: 9999, top: 9999, right: 0, bottom: 0 } + ); + (offsetRect.top -= parentRect.top), (offsetRect.left -= parentRect.left); + (offsetRect.right -= parentRect.left), (offsetRect.bottom -= parentRect.top); + + if (containerType == "header") { + offsetRect.top = 0; + offsetRect.bottom = MainStore.page.headerHeight; + } + // if its the first element then update top to header height + // also checking if element is below header ( just safe guard ) + if (containerType == "body") { + if (index == 0 && offsetRect.top >= MainStore.page.headerHeight) { + offsetRect.top = MainStore.page.headerHeight; + } + if (index != 0) { + offsetRect.top = prevDimensions.bottom; } } - let isHeaderEmpty = true; - let isBodyEmpty = true; - let isFooterEmpty = true; - let pageInfoInBody = []; - if (tableElement.length == 1) { - tableElement[0].isPrimaryTable = true; - } else if (tableElement.length > 1) { - let primaryTableEl = tableElement.filter((el) => el.isPrimaryTable); - if (primaryTableEl.length == 1) { - tableElement = primaryTableEl; - } else { - const message = __( - "As You have multiple tables, you have to select Primary Table.

1. Go to Table Element that you wish to set as Primary.

2. Select it and from properties panel select Set as Primary Table as Yes " - ); - frappe.msgprint( - { - title: __("Multiple Tables."), - message: message, - indicator: "red", - }, - 5 + return offsetRect; + }, + cleanUpElementsForSave(elements, type) { + if (this.checkIfPrintFormatIsEmpty(elements, type)) return; + const fontsArray = []; + const cleanedElements = []; + elements.forEach((container) => { + const cleanedContainer = []; + container.forEach((element) => { + let newElement = this.childrensSave(element.element, fontsArray); + newElement.classes = newElement.classes.filter( + (name) => ["inHeaderFooter", "overlappingHeaderFooter"].indexOf(name) == -1 ); - return; + if (element.type == "rectangle" && element.childrens.length) { + let childrensArray = element.childrens; + newElement.childrens = []; + childrensArray.forEach((el) => { + newElement.childrens.push(this.childrensSave(el, printFonts)); + }); + } + cleanedContainer.push(newElement); + }); + cleanedElements.push(cleanedContainer); + }); + return [cleanedElements, fontsArray]; + }, + checkIfPrintFormatIsEmpty(elements, type) { + const MainStore = useMainStore(); + if (elements.length == 0) { + switch (type) { + case "header": + MainStore.printHeaderFonts = null; + break; + case "body": + MainStore.printBodyFonts = null; + frappe.show_alert( + { + message: "Atleast 1 element is required inside body", + indicator: "red", + }, + 5 + ); + // This is intentionally using throw to stop the execution + throw new Error(__("Atleast 1 element is required inside body")); + case "footer": + MainStore.printFooterFonts = null; + break; } + return true; + } + return false; + }, + childrensSave(element, printFonts) { + let saveEl = { ...element }; + delete saveEl.DOMRef; + delete saveEl.index; + delete saveEl.snapPoints; + delete saveEl.snapEdges; + delete saveEl.parent; + if (printFonts && ["text", "table"].indexOf(saveEl.type) != -1) { + handlePrintFonts(saveEl, printFonts); } - this.Elements.forEach((element) => { - let is_header = false; - let is_footer = false; + if (saveEl.type == "rectangle") { + const childrensArray = saveEl.childrens; + saveEl.childrens = []; + childrensArray.forEach((el) => { + const child = this.childrensSave(el, printFonts); + child && saveEl.childrens.push(child); + }); + } + + return saveEl; + }, + getPrintFormatData({ header, body, footer }) { + const headerElements = this.createWrapperElement( + header.elements, + header.dimensions, + "header" + ); + const bodyElements = this.createWrapperElement(body.elements, body.dimensions, "body"); + const footerElements = this.createWrapperElement( + footer.elements, + footer.dimensions, + "footer" + ); + return JSON.stringify({ + header: headerElements, + body: bodyElements, + footer: footerElements, + }); + }, + createWrapperElement(containers, dimensions, containerType = "body") { + const MainStore = useMainStore(); + const wrapperContainers = { childrens: [] }; + containers.forEach((container, index) => { + const calculatedDimensions = dimensions[index]; + const cordinates = { + startY: calculatedDimensions.top, + pageY: calculatedDimensions.top, + startX: 0, + pageX: 0, + }; + const wrapperRectangleEl = createRectangle(cordinates, wrapperContainers); + wrapperRectangleEl.height = calculatedDimensions.bottom - calculatedDimensions.top; + wrapperRectangleEl.width = + MainStore.page.width - MainStore.page.marginLeft - MainStore.page.marginRight; + wrapperRectangleEl.childrens = container; if ( - element.startY + element.height < - MainStore.page.headerHeight + MainStore.page.marginTop + containerType == "body" && + wrapperRectangleEl.childrens.length == 1 && + wrapperRectangleEl.childrens[0].isDynamicHeight == true ) { - is_header = true; - } else if ( - element.startY > - MainStore.page.height - - MainStore.page.footerHeight - - MainStore.page.marginTop - - MainStore.page.marginBottom - ) { - is_footer = true; - } else { - if (element.type == "text" && element.isDynamic) { - element.dynamicContent - .filter( - (el) => - ["page", "topage", "date", "time"].indexOf(el.fieldname) != -1 - ) - .forEach((field) => { - pageInfoInBody.push(field.fieldname); - }); - } + wrapperRectangleEl.isDynamicHeight = true; } - let printFonts = is_header - ? headerPrintFonts - : is_footer - ? footerprintFonts - : mainPrintFonts; - let newElement = childrensSave(element, printFonts); - newElement.classes = newElement.classes.filter( - (name) => ["inHeaderFooter", "overlappingHeaderFooter"].indexOf(name) == -1 + wrapperRectangleEl.childrens.forEach((el) => { + el.startY -= cordinates.startY; + if (containerType == "header") { + el.startY += MainStore.page.marginTop; + } + }); + wrapperRectangleEl.style.backgroundColor = ""; + }); + return wrapperContainers.childrens.map((el) => this.childrensSave(el)); + }, + async printFormatCopyOnOlderSchema(objectToSave) { + const MainStore = useMainStore(); + let nextFormatCopyNumber = 0; + for (let i = 0; i < 100; i++) { + const pf_exists = await frappe.db.exists( + "Print Format", + MainStore.printDesignName + " ( Copy " + (i ? i : "") + " )" ); - if (element.type == "rectangle" && element.childrens.length) { - let childrensArray = element.childrens; - newElement.childrens = []; - childrensArray.forEach((el) => { - newElement.childrens.push(childrensSave(el, printFonts)); + if (pf_exists) continue; + nextFormatCopyNumber = i; + break; + } + const newName = + MainStore.printDesignName + + " ( Copy " + + (nextFormatCopyNumber ? nextFormatCopyNumber : "") + + " )"; + // TODO: have better message. + let message = __( + "This Print Format was created from older version of Print Designer." + ); + message += "
"; + message += __( + "It is not compatible with current version so instead we will make copy of this format for you using new version" + ); + message += "
"; + message += __(`Do you want to save it as ${newName} ?`); + + frappe.confirm( + message, + async () => { + await frappe.db.insert({ + doctype: "Print Format", + name: newName, + doc_type: MainStore.doctype, + print_designer: 1, + print_designer_header: objectToSave.print_designer_header, + print_designer_body: objectToSave.print_designer_body, + print_designer_after_table: null, + print_designer_footer: objectToSave.print_designer_footer, + print_designer_print_format: objectToSave.print_designer_print_format, + print_designer_settings: objectToSave.print_designer_settings, }); + frappe.set_route("print-designer", newName); + }, + async () => { + throw new Error(__("Print Format not saved")); } - if (is_header) { - newElement.printY = newElement.startY + MainStore.page.marginTop; - MainStore.printHeaderFonts = printFonts; - headerElements.push(newElement); - isHeaderEmpty = false; - } else if (is_footer) { - newElement.printY = - newElement.startY - - (MainStore.page.height - - MainStore.page.footerHeight - - MainStore.page.marginBottom - - MainStore.page.marginTop); - MainStore.printFooterFonts = printFonts; - footerElements.push(newElement); - isFooterEmpty = false; - } else { - newElement.printY = newElement.startY - MainStore.page.headerHeight; - MainStore.printBodyFonts = printFonts; - if ( - tableElement.length == 1 && - tableElement[0].startY + tableElement[0].height <= newElement.startY + 2 - ) { - newElement.printY = - newElement.startY - (tableElement[0].startY + tableElement[0].height); - newElement.printX = newElement.startX - tableElement[0].startX; + ); + }, - afterTableElements.push(newElement); - } else { - mainElements.push(newElement); - } - isBodyEmpty = false; - } - }); - if (isHeaderEmpty) { - MainStore.printHeaderFonts = null; - } - if (isBodyEmpty) { - if (!isHeaderEmpty || !isFooterEmpty) { - MainStore.mode = "pdfSetup"; - frappe.show_alert( - { - message: "Atleast 1 element is required inside body", - indicator: "red", - }, - 5 - ); - return; + handleDynamicContent(element) { + const MainStore = useMainStore(); + if ( + element.type == "table" || + (["text", "image", "barcode"].indexOf(element.type) != -1 && element.isDynamic) + ) { + if (["text", "barcode"].indexOf(element.type) != -1) { + element.dynamicContent = [ + ...element.dynamicContent.map((el) => { + return { ...el }; + }), + ]; + element.selectedDynamicText = null; + MainStore.dynamicData.push(...element.dynamicContent); + } else if (element.type === "table") { + element.columns = [ + ...element.columns.map((el) => { + return { ...el }; + }), + ]; + element.columns.forEach((col) => { + if (!col.dynamicContent) return; + col.dynamicContent = [ + ...col.dynamicContent.map((el) => { + return { ...el }; + }), + ]; + col.selectedDynamicText = null; + MainStore.dynamicData.push(...col.dynamicContent); + }); + } else { + element.image = { ...element.image }; + MainStore.dynamicData.push(element.image); } - MainStore.printBodyFonts = null; } - if (pageInfoInBody.length) { - frappe.show_alert({ - message: - "Please move " + pageInfoInBody.join(", ") + " to header / footer", - indicator: "orange", + }, + childrensLoad(element, parent) { + element.parent = parent; + element.DOMRef = null; + delete element.printY; + element.isDraggable = true; + element.isResizable = true; + this.handleDynamicContent(element); + if (element.type == "rectangle") { + element.isDropZone = true; + const childrensArray = element.childrens; + element.childrens = []; + childrensArray.forEach((el) => { + const child = this.childrensLoad(el, element); + child && element.childrens.push(child); }); - return; + } else if (element.type == "text" && !element.isDynamic) { + element.contenteditable = false; } - if (isFooterEmpty) { - MainStore.printFooterFonts = null; - } - MainStore.currentFonts.length = 0; - MainStore.currentFonts.push( - ...Object.keys({ - ...(headerPrintFonts || {}), - ...(mainPrintFonts || {}), - ...(footerprintFonts || {}), - }) - ); - const updatedPage = { ...MainStore.page }; - updatedPage.headerHeightWithMargin = - MainStore.page.headerHeight + MainStore.page.marginTop; - updatedPage.footerHeightWithMargin = - MainStore.page.footerHeight + MainStore.page.marginBottom; - const settingsForSave = { - page: updatedPage, - pdfPrintDPI: MainStore.pdfPrintDPI, - globalStyles: MainStore.globalStyles, - currentPageSize: MainStore.currentPageSize, - isHeaderFooterAuto: MainStore.isHeaderFooterAuto, - currentDoc: MainStore.currentDoc, - textControlType: MainStore.textControlType, - currentFonts: MainStore.currentFonts, - printHeaderFonts: MainStore.printHeaderFonts, - printFooterFonts: MainStore.printFooterFonts, - printBodyFonts: MainStore.printBodyFonts, - userProvidedJinja: MainStore.userProvidedJinja, - schema_version: MainStore.schema_version, - }; - await frappe.dom.freeze(); - const convertCsstoString = (stylesheet) => { - let cssRule = Array.from(stylesheet.cssRules) - .map((rule) => rule.cssText || "") - .join(" "); - return stylesheet.cssRules ? cssRule : ""; - }; - const css = - convertCsstoString(MainStore.screenStyleSheet) + - convertCsstoString(MainStore.printStyleSheet); - await frappe.db.set_value("Print Format", MainStore.printDesignName, { - print_designer_header: JSON.stringify(headerElements), - print_designer_body: JSON.stringify(mainElements), - print_designer_after_table: JSON.stringify(afterTableElements), - print_designer_footer: JSON.stringify(footerElements), - print_designer_settings: JSON.stringify(settingsForSave), - css, + + return element; + }, + loadSettings(settings) { + const MainStore = useMainStore(); + if (!settings) return; + Object.keys(settings).forEach((key) => { + switch (key) { + case "schema_version": + MainStore.old_schema_version = settings["schema_version"]; + case "currentDoc": + frappe.db + .exists(MainStore.doctype, settings["currentDoc"]) + .then((exists) => { + if (exists) { + MainStore.currentDoc = settings["currentDoc"]; + } + }); + break; + default: + MainStore[key] = settings[key]; + break; + } }); - await frappe.dom.unfreeze(); - frappe.show_alert( - { - message: `Print Format Saved Successfully`, - indicator: "green", - }, - 5 - ); + return; }, async loadElements(printDesignName) { - const MainStore = useMainStore(); frappe.dom.freeze(__("Loading Print Format")); const printFormat = await frappe.db.get_value("Print Format", printDesignName, [ "print_designer_header", @@ -351,168 +587,27 @@ export const useElementStore = defineStore("ElementStore", { 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); - settings && - Object.keys(settings).forEach(async (key) => { - if ( - ["currentDoc", "schema_version"].indexOf(key) == -1 || - (await frappe.db.exists(MainStore.doctype, settings[key])) - ) { - MainStore[key] = settings[key]; - } - if (key == "schema_version" && settings[key] != MainStore.schema_version) { - MainStore.old_schema_version = settings[key]; - } - }); - const handleDynamicContent = (element) => { - const MainStore = useMainStore(); - if ( - element.type == "table" || - (["text", "image", "barcode"].indexOf(element.type) != -1 && element.isDynamic) - ) { - if (["text", "barcode"].indexOf(element.type) != -1) { - element.dynamicContent = [ - ...element.dynamicContent.map((el) => { - return { ...el }; - }), - ]; - element.selectedDynamicText = null; - MainStore.dynamicData.push(...element.dynamicContent); - } else if (element.type === "table") { - element.columns = [ - ...element.columns.map((el) => { - return { ...el }; - }), - ]; - element.columns.forEach((col) => { - if (!col.dynamicContent) return; - col.dynamicContent = [ - ...col.dynamicContent.map((el) => { - return { ...el }; - }), - ]; - col.selectedDynamicText = null; - MainStore.dynamicData.push(...col.dynamicContent); - }); - } else { - element.image = { ...element.image }; - MainStore.dynamicData.push(element.image); - } - } - }; - const childrensLoad = (element, parent) => { - element.parent = parent; - element.DOMRef = null; - delete element.printY; - element.isDraggable = true; - element.isResizable = true; - handleDynamicContent(element); - if (element.type == "rectangle") { - element.isDropZone = true; - const childrensArray = element.childrens; - element.childrens = []; - childrensArray.forEach((el) => { - const child = childrensLoad(el, element); - child && element.childrens.push(child); - }); - } else if (element.type == "text" && !element.isDynamic) { - element.contenteditable = false; - } - - return element; - }; + this.loadSettings(settings); this.Elements = [ ...(ElementsHeader || []), ...(ElementsBody || []), - ...(ElementsFooter || []), ...(ElementsAfterTable || []), + ...(ElementsFooter || []), ]; - if (this.Elements.length === 0 && !!MainStore.getTableMetaFields.length) { - const newTable = { - id: frappe.utils.get_random(10), - type: "table", - DOMRef: null, - parent: this.Elements, - isDraggable: true, - isResizable: true, - isDropZone: false, - table: null, - columns: [ - { - id: 0, - label: "", - style: {}, - applyStyleToHeader: false, - }, - { - id: 1, - label: "", - style: {}, - applyStyleToHeader: false, - }, - { - id: 2, - label: "", - style: {}, - applyStyleToHeader: false, - }, - { - id: 3, - label: "", - style: {}, - applyStyleToHeader: false, - }, - { - id: 4, - label: "", - style: {}, - applyStyleToHeader: false, - }, - { - id: 5, - label: "", - style: {}, - applyStyleToHeader: false, - }, - { - id: 6, - label: "", - style: {}, - applyStyleToHeader: false, - }, - ], - PreviewRowNo: 1, - selectedColumn: null, - selectedDynamicText: null, - startX: 11.338582677, - startY: 393.826771658, - pageX: 228, - pageY: 435, - width: 771.0236220564, - height: 442.20472441469997, - styleEditMode: "main", - labelDisplayStyle: "standard", - style: {}, - labelStyle: {}, - headerStyle: {}, - altStyle: {}, - classes: [], - }; - this.Elements.push(newTable); - } this.Elements.map((element) => { element.DOMRef = null; element.parent = this.Elements; delete element.printY; element.isDraggable = true; element.isResizable = true; - handleDynamicContent(element); + this.handleDynamicContent(element); if (element.type == "rectangle") { element.isDropZone = true; if (element.childrens.length) { let childrensArray = element.childrens; element.childrens = []; childrensArray.forEach((el) => { - element.childrens.push(childrensLoad(el, element)); + element.childrens.push(this.childrensLoad(el, element)); }); } } else if (element.type == "text" && !element.isDynamic) { @@ -532,5 +627,31 @@ export const useElementStore = defineStore("ElementStore", { t.isPrimaryTable = t == tableEl; }); }, + // This is called to check if the element is overlapping with any other element + isElementOverlapping(currentEl, elements = this.Elements) { + const currentElIndex = + currentEl.index || this.Elements.findIndex((el) => el === currentEl); + const currentStartY = parseInt(currentEl.startY); + const currentEndY = currentEl.endY || parseInt(currentEl.startY + currentEl.height); + + return ( + elements.findIndex((el, index) => { + if (index == currentElIndex) return false; + const elStartY = parseInt(el.startY); + const elEndY = el.endY || parseInt(el.startY + el.height); + if (currentStartY <= elStartY && elStartY <= currentEndY) { + return true; + } else if (currentStartY <= elEndY && elEndY <= currentEndY) { + return true; + } else if (elStartY <= currentStartY && currentStartY <= elEndY) { + return true; + } else if (elStartY <= currentEndY && currentEndY <= elEndY) { + return true; + } else { + return false; + } + }) != -1 + ); + }, }, }); diff --git a/print_designer/public/js/print_designer/store/MainStore.js b/print_designer/public/js/print_designer/store/MainStore.js index 1e00a86..af50f21 100644 --- a/print_designer/public/js/print_designer/store/MainStore.js +++ b/print_designer/public/js/print_designer/store/MainStore.js @@ -17,7 +17,7 @@ export const useMainStore = defineStore("MainStore", { /** * @type {'editing'|'pdfSetup'|'preview'} mode */ - schema_version: "1.0.1", + schema_version: "1.1.0", mode: "editing", cursor: "url('/assets/print_designer/images/mouse-pointer.svg'), default !important", isMarqueeActive: false, @@ -87,6 +87,8 @@ export const useMainStore = defineStore("MainStore", { marginRight: 0, headerHeight: 0, footerHeight: 0, + headerHeightWithMargin: 0, + footerHeightWithMargin: 0, UOM: "mm", }, controls: { @@ -446,6 +448,28 @@ export const useMainStore = defineStore("MainStore", { } } }, + isOlderSchema: (state) => (currentVersion) => { + if (!state.old_schema_version) return false; + let formatVersion = state.old_schema_version.split("."); + if (currentVersion == formatVersion) return false; + currentVersion = currentVersion.split("."); + if (parseInt(formatVersion[0]) < parseInt(currentVersion[0])) { + return true; + } else if ( + parseInt(formatVersion[0]) === parseInt(currentVersion[0]) && + parseInt(formatVersion[1]) < parseInt(currentVersion[1]) + ) { + return true; + } else if ( + parseInt(formatVersion[0]) === parseInt(currentVersion[0]) && + parseInt(formatVersion[1]) === parseInt(currentVersion[1]) && + parseInt(formatVersion[2]) < parseInt(currentVersion[2]) + ) { + return true; + } else { + return false; + } + }, }, actions: { /** diff --git a/print_designer/public/js/print_designer/utils.js b/print_designer/public/js/print_designer/utils.js index 920e180..1b5ba26 100644 --- a/print_designer/public/js/print_designer/utils.js +++ b/print_designer/public/js/print_designer/utils.js @@ -37,7 +37,7 @@ import { useElementStore } from "./store/ElementStore"; import { useDraggable } from "./composables/Draggable"; import { useResizable } from "./composables/Resizable"; import { useDropZone } from "./composables/DropZone"; -import { isRef } from "vue"; +import { isRef, nextTick } from "vue"; export const changeDraggable = (element) => { if ( @@ -346,6 +346,7 @@ export const deleteCurrentElements = () => { MainStore.getCurrentElementsId.forEach((element) => { delete MainStore.currentElements[element]; }); + checkUpdateElementOverlapping(); }; export const cloneElement = () => { @@ -806,3 +807,21 @@ export const selectElementContents = (el) => { sel.removeAllRanges(); sel.addRange(range); }; + +export const checkUpdateElementOverlapping = (element = null) => { + const MainStore = useMainStore(); + const ElementStore = useElementStore(); + nextTick(() => { + if (element && element.parent != ElementStore.Elements) return; + isOlderSchema = MainStore.isOlderSchema("1.1.0"); + ElementStore.Elements.forEach((el) => { + const isElementOverlapping = ElementStore.isElementOverlapping(el); + if (el.isElementOverlapping != isElementOverlapping) { + el.isElementOverlapping = isElementOverlapping; + } + if (isOlderSchema && el.type == "table" && !isElementOverlapping) { + el.isDynamicHeight = true; + } + }); + }, {}); +};