diff --git a/README.md b/README.md index 5a0086a..ae24f37 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,10 @@ There are many ways you can contribute even if you don't code: - [HomeBrew](https://brew.sh/) and `brew install pkg-config cairo pango libpng jpeg giflib librsvg pixman` - If you have **xcode 10.0 or higher** installed, in order to build from source you need **NPM 6.4.1 or higher** `npm install -g npm@latest`. +3. Linux ARM CPU Installation Error + - If error has `node-pre-gyp WARN Pre-built binaries not installable for canvas@x.x.x and node@x.x.x` it means that there aren't any pre-built binaries for your system so it will try to compile them + - In order to do that you need `sudo apt-get update && sudo apt-get install build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev` + ## License [GNU Affero General Public License v3.0](license.txt) diff --git a/print_designer/hooks.py b/print_designer/hooks.py index 166f781..68e9d5e 100644 --- a/print_designer/hooks.py +++ b/print_designer/hooks.py @@ -11,7 +11,7 @@ # ------------------ # include js, css files in header of desk.html -app_include_js = "pdf.worker.bundle.js" +# app_include_js = "" # include js, css files in header of web template @@ -26,7 +26,10 @@ # webform_include_css = {"doctype": "public/css/doctype.css"} # include js in page -page_js = {"print": "print_designer/client_scripts/print.js"} +page_js = { + "print": "print_designer/client_scripts/print.js", + "point-of-sale": "print_designer/client_scripts/point_of_sale.js", +} # include js in doctype views doctype_js = {"Print Format": "print_designer/client_scripts/print_format.js"} @@ -84,6 +87,7 @@ pdf_body_html = "print_designer.pdf.pdf_body_html" pdf_footer_html = "print_designer.pdf.pdf_header_footer_html" +get_print_format_template = "print_designer.pdf.get_print_format_template" # Desk Notifications # ------------------ # See frappe.core.notifications.get_notification_config diff --git a/print_designer/patches.txt b/print_designer/patches.txt index 1869987..ffb6301 100644 --- a/print_designer/patches.txt +++ b/print_designer/patches.txt @@ -2,4 +2,7 @@ print_designer.patches.update_white_space_property print_designer.patches.introduce_barcode print_designer.patches.introduce_jinja print_designer.patches.introduce_schema_versioning -print_designer.patches.rerun_introduce_jinja \ No newline at end of file +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 diff --git a/print_designer/patches/introduce_column_style.py b/print_designer/patches/introduce_column_style.py new file mode 100644 index 0000000..3d2b551 --- /dev/null +++ b/print_designer/patches/introduce_column_style.py @@ -0,0 +1,18 @@ +import frappe + +from print_designer.patches.patch_utils import patch_formats + + +def execute(): + """Modify Formats to work with New Column Style Feature""" + + def element_callback(el): + el["selectedColumn"] = None + for col in el["columns"]: + col["style"] = {} + col["applyStyleToHeader"] = False + + patch_formats( + {"element": element_callback}, + types=["table"], + ) diff --git a/print_designer/patches/introduce_suffix_dynamic_content.py b/print_designer/patches/introduce_suffix_dynamic_content.py new file mode 100644 index 0000000..07640f3 --- /dev/null +++ b/print_designer/patches/introduce_suffix_dynamic_content.py @@ -0,0 +1,17 @@ +import frappe + +from print_designer.patches.patch_utils import patch_formats + + +def execute(): + """Introduce suffix to dynamic content elements""" + + def dynamic_content_callback(el): + if not el.get("is_static", True): + if not "suffix" in el: + el["suffix"] = None + + patch_formats( + {"dynamic_content": dynamic_content_callback}, + types=["text", "table"], + ) diff --git a/print_designer/patches/introduce_table_alt_row_styles.py b/print_designer/patches/introduce_table_alt_row_styles.py new file mode 100644 index 0000000..2c22f78 --- /dev/null +++ b/print_designer/patches/introduce_table_alt_row_styles.py @@ -0,0 +1,30 @@ +import frappe + +from print_designer.patches.patch_utils import patch_formats + + +def execute(): + """Add altStyle object for alternate rows in table elements and in globalStyles 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 + + gs["table"]["altStyle"] = {} + frappe.db.set_value("Print Format", pf[0], "print_designer_settings", frappe.as_json(settings)) + + def element_callback(el): + el["altStyle"] = {} + + patch_formats( + {"element": element_callback}, + types=["table"], + ) diff --git a/print_designer/pdf.py b/print_designer/pdf.py index 7b58a7d..f10684f 100644 --- a/print_designer/pdf.py +++ b/print_designer/pdf.py @@ -1,25 +1,36 @@ +import hashlib +import html import json import frappe +from frappe.monitor import add_data_to_monitor +from frappe.utils.error import log_error from frappe.utils.jinja_globals import is_rtl def pdf_header_footer_html(soup, head, content, styles, html_id, css): if soup.find(id="__print_designer"): - return frappe.render_template( - "print_designer/page/print_designer/jinja/header_footer.html", - { - "head": head, - "content": content, - "styles": styles, - "html_id": html_id, - "css": css, - "headerFonts": soup.find(id="headerFontsLinkTag"), - "footerFonts": soup.find(id="footerFontsLinkTag"), - "lang": frappe.local.lang, - "layout_direction": "rtl" if is_rtl() else "ltr", - }, - ) + try: + return frappe.render_template( + "print_designer/page/print_designer/jinja/header_footer.html", + { + "head": head, + "content": content, + "styles": styles, + "html_id": html_id, + "css": css, + "headerFonts": soup.find(id="headerFontsLinkTag"), + "footerFonts": soup.find(id="footerFontsLinkTag"), + "lang": frappe.local.lang, + "layout_direction": "rtl" if is_rtl() else "ltr", + }, + ) + except Exception as e: + error = log_error(title=e, reference_doctype="Print Format") + frappe.throw( + msg=f"Something went wrong ( Error ) : If you don't know what just happened, and wish to file a ticket or issue on github, please copy the error from Error Log {error.name} or ask Administrator.", + exc=e, + ) else: from frappe.utils.pdf import pdf_footer_html, pdf_header_html @@ -35,7 +46,11 @@ def pdf_header_footer_html(soup, head, content, styles, html_id, css): def pdf_body_html(print_format, jenv, args, template): if print_format and print_format.print_designer and print_format.print_designer_body: - template = jenv.loader.get_source(jenv, "print_designer/page/print_designer/jinja/main.html")[0] + print_format_name = hashlib.md5(print_format.name.encode(), usedforsecurity=False).hexdigest() + 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] args.update( { "headerElement": json.loads(print_format.print_designer_header), @@ -49,5 +64,21 @@ def pdf_body_html(print_format, jenv, args, template): template_source = template.replace( "", args["settings"].get("userProvidedJinja", "") ) - template = jenv.from_string(template_source) + try: + template = jenv.from_string(template_source) + return template.render(args, filters={"len": len}) + + except Exception as e: + error = log_error(title=e, reference_doctype="Print Format", reference_name=print_format.name) + if frappe.conf.developer_mode: + return f"

Something went wrong while rendering the print format.
If you don't know what just happened, and wish to file a ticket or issue on Github
Please copy the error from Error Log {error.name} or ask Administrator.

Error rendering print format: {error.reference_name}

{error.method}

{html.escape(error.error)}
" + else: + return f"

Something went wrong while rendering the print format.
If you don't know what just happened, and wish to file a ticket or issue on Github
Please copy the error from Error Log {error.name} or ask Administrator.

" + return template.render(args, filters={"len": len}) + + +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] diff --git a/print_designer/print_designer/client_scripts/point_of_sale.js b/print_designer/print_designer/client_scripts/point_of_sale.js new file mode 100644 index 0000000..d519541 --- /dev/null +++ b/print_designer/print_designer/client_scripts/point_of_sale.js @@ -0,0 +1,26 @@ +// overrides the print util function that is used in the point of sale page. +// we should ideally change util function in framework to extend it. this is workaround until that. +const original_util = frappe.utils.print; +frappe.utils.print = (doctype, docname, print_format, letterhead, lang_code) => { + if (frappe.model.get_value("Print Format", print_format, "print_designer")) { + let w = window.open( + frappe.urllib.get_full_url( + "/app/print/" + + encodeURIComponent(doctype) + + "/" + + encodeURIComponent(docname) + + "?format=" + + encodeURIComponent(print_format) + + "&no_letterhead=0" + + "&trigger_print=1" + + (lang_code ? "&_lang=" + lang_code : "") + ) + ); + if (!w) { + frappe.msgprint(__("Please enable pop-ups")); + return; + } + } else { + original_util(doctype, docname, print_format, letterhead, lang_code); + } +}; diff --git a/print_designer/print_designer/client_scripts/print.js b/print_designer/print_designer/client_scripts/print.js index 25a08ec..50410fc 100644 --- a/print_designer/print_designer/client_scripts/print.js +++ b/print_designer/print_designer/client_scripts/print.js @@ -104,10 +104,13 @@ frappe.ui.form.PrintView = class PrintView extends frappe.ui.form.PrintView { } async designer_pdf(print_format) { if (typeof pdfjsLib == "undefined") { - await frappe.require("assets/print_designer/js/pdf.min.js", () => { - pdfjsLib.GlobalWorkerOptions.workerSrc = - frappe.boot.assets_json["pdf.worker.bundle.js"]; - }); + await frappe.require( + ["assets/print_designer/js/pdf.min.js", "pdf.worker.bundle.js"], + () => { + pdfjsLib.GlobalWorkerOptions.workerSrc = + frappe.boot.assets_json["pdf.worker.bundle.js"]; + } + ); } let me = this; let print_designer_settings = JSON.parse(print_format.print_designer_settings); @@ -142,16 +145,26 @@ frappe.ui.form.PrintView = class PrintView extends frappe.ui.form.PrintView { await renderPage(this.pdfDoc, pageno); } this.pdf_download_btn.prop("disabled", false); + if (frappe.route_options.trigger_print) { + this.printit(); + } this.print_btn.prop("disabled", false); } catch (err) { - console.log(err); - frappe.show_alert( - { - message: "Unable to generate PDF", - indicator: "red", + console.error(err); + frappe.msgprint({ + title: __("Unable to generate PDF"), + message: `There was error while generating PDF. Please check the error log for more details.`, + indicator: "red", + primary_action: { + label: "Open Error Log", + action(values) { + frappe.set_route("List", "Error Log", { + doctype: "Error Log", + reference_doctype: "Print Format", + }); + }, }, - 5 - ); + }); } /** * Get page info from document, resize canvas accordingly, and render page. @@ -235,6 +248,11 @@ frappe.ui.form.PrintView = class PrintView extends frappe.ui.form.PrintView { setTimeout(() => { iframe.focus(); iframe.contentWindow.print(); + if (frappe.route_options.trigger_print) { + setTimeout(function () { + window.close(); + }, 5000); + } }, 1); }; } else { diff --git a/print_designer/print_designer/page/print_designer/jinja/main.html b/print_designer/print_designer/page/print_designer/jinja/main.html index c634941..c1fb537 100644 --- a/print_designer/print_designer/page/print_designer/jinja/main.html +++ b/print_designer/print_designer/page/print_designer/jinja/main.html @@ -1,7 +1,7 @@ {% macro render_statictext(element, send_to_jinja) -%} -
-

{% if element.parseJinja %} @@ -18,9 +18,9 @@ {% endif %} {% endmacro %} {% macro render_dynamictext(element, send_to_jinja) -%} -

-
{% for field in element.dynamicContent %} @@ -90,7 +90,7 @@ {% if element.columns %} {% for column in element.columns%} - + {{ _(column.label) }} {% endfor %} @@ -103,7 +103,7 @@ {% set isLastRow = loop.last %} {% for column in element.columns%} - + {% if column is mapping %} {% for field in column.dynamicContent%} {{ render_spantag(field, element, row, send_to_jinja) }} @@ -142,14 +142,20 @@ {% set span_value = render_spanvalue(field, element, row, send_to_jinja) %} {% if not field.is_static and field.is_labelled and span_value %} - + {{ _(field.label) }} {% endif %} + style="{%- if element.style.get('color') -%}{{ convert_css({'color': element.style.get('color')})}}{%- endif -%} {{convert_css(field.style)}} user-select:auto;"> {{ span_value }} + {% if field.suffix and span_value %} + + {{ _(field.suffix) }} + + {% endif %} {% if field.nextLine %}
{% endif %} 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 d0c461e..e4b168c 100644 --- a/print_designer/print_designer/page/print_designer/print_designer.js +++ b/print_designer/print_designer/page/print_designer/print_designer.js @@ -117,8 +117,8 @@ const set_current_doc = async (format_name) => { format_name, "print_designer_settings" ); - if (!settings.message) return; - settings = JSON.parse(settings.message.print_designer_settings || "{}"); + if (!settings.message?.print_designer_settings) return; + settings = JSON.parse(settings.message.print_designer_settings); settings["currentDoc"] = currentDoc; await frappe.db.set_value( "Print Format", diff --git a/print_designer/print_designer/page/print_designer/print_designer.py b/print_designer/print_designer/page/print_designer/print_designer.py index 748f60a..e97bddf 100644 --- a/print_designer/print_designer/page/print_designer/print_designer.py +++ b/print_designer/print_designer/page/print_designer/print_designer.py @@ -15,6 +15,7 @@ def render_user_text_withdoc(string, doctype, docname=None, row=None, send_to_ji if not docname or docname == "": return render_user_text(string=string, doc={}, row=row, send_to_jinja=send_to_jinja) doc = frappe.get_cached_doc(doctype, docname) + doc.check_permission() return render_user_text(string=string, doc=doc, row=row, send_to_jinja=send_to_jinja) @@ -132,7 +133,7 @@ def convert_css(css_obj): string_css += ( "".join(["-" + i.lower() if i.isupper() else i for i in item[0]]).lstrip("-") + ":" - + str(item[1]) + + str(item[1] if item[1] != "" or item[0] != "backgroundColor" else "transparent") + "!important;" ) string_css += "user-select: all;" diff --git a/print_designer/public/js/print_designer/PropertiesPanelState.js b/print_designer/public/js/print_designer/PropertiesPanelState.js index 9b311cb..4624b9c 100644 --- a/print_designer/public/js/print_designer/PropertiesPanelState.js +++ b/print_designer/public/js/print_designer/PropertiesPanelState.js @@ -438,8 +438,9 @@ export const createPropertiesPanel = () => { MainStore.propertiesPanel.push({ title: "Table Settings", sectionCondtional: () => - MainStore.getCurrentElementsId.length === 1 && - MainStore.getCurrentElementsValues[0]?.type == "table", + (MainStore.getCurrentElementsId.length === 1 && + MainStore.getCurrentElementsValues[0]?.type == "table") || + MainStore.activeControl == "table", fields: [ [ { @@ -448,6 +449,7 @@ export const createPropertiesPanel = () => { isLabelled: true, labelDirection: "column", flex: 3, + condtional: () => MainStore.getCurrentElementsValues[0]?.type == "table", frappeControl: (ref, name) => { const MainStore = useMainStore(); makeFeild({ @@ -469,11 +471,69 @@ export const createPropertiesPanel = () => { propertyName: "table", isStyle: false, onChangeCallback: (value = null) => { - if (value && MainStore.getCurrentElementsValues[0]) { - MainStore.getCurrentElementsValues[0]["table"] = - MainStore.metaFields.find( - (field) => field.fieldname == value + const currentEL = MainStore.getCurrentElementsValues[0]; + const idx = { + fieldname: "idx", + fieldtype: "Int", + label: "No", + options: undefined, + }; + if (value && currentEL) { + currentEL["table"] = MainStore.metaFields.find( + (field) => field.fieldname == value + ); + currentEL["table"].default_layout = + frappe.get_user_settings(MainStore.doctype, "GridView")[ + currentEL["table"].options + ] || {}; + if (Object.keys(currentEL["table"].default_layout).length) { + df = { idx }; + Object.values(currentEL["table"].default_layout).forEach( + (value) => { + key = value.fieldname; + df[key] = currentEL["table"].childfields.find( + (df) => df.fieldname == key + ); + } ); + currentEL["table"].default_layout = df; + } else { + currentEL["table"].childfields.map((df) => { + currentEL["table"].default_layout["idx"] = idx; + if ( + df.fieldname && + df.in_list_view && + !df.is_virtual && + frappe.model.is_value_type(df) && + df.fieldtype !== "Read Only" && + !frappe.model.layout_fields.includes(df.fieldtype) + ) { + currentEL["table"].default_layout[df.fieldname] = + df; + } + }); + } + // fill columns with default fields if table is empty + if ( + currentEL.columns && + !currentEL.columns.some((c) => c.dynamicContent) && + currentEL["table"].default_layout + ) { + dlKeys = Object.keys(currentEL["table"].default_layout); + currentEL.columns + .slice(0, dlKeys.length) + .forEach((col, index) => { + col.dynamicContent = [ + currentEL["table"].default_layout[ + dlKeys[index] + ], + ]; + col.label = + col.dynamicContent[0].label || + col.dynamicContent[0].fieldname; + }); + } + MainStore.frappeControls[name].$input.blur(); } }, @@ -517,7 +577,7 @@ export const createPropertiesPanel = () => { ], [ { - label: "Set as Primary Table", + label: "Primary Table", name: "isPrimaryTable", isLabelled: true, labelDirection: "column", @@ -554,6 +614,107 @@ export const createPropertiesPanel = () => { }, flex: 1, }, + { + label: "Style Mode :", + isLabelled: true, + name: "tableStyleEditMode", + labelDirection: "column", + condtional: () => + [ + MainStore.getCurrentElementsValues[0]?.type, + MainStore.activeControl, + ].indexOf("table") != -1, + frappeControl: (ref, name) => { + const MainStore = useMainStore(); + makeFeild({ + name: name, + ref: ref, + fieldtype: "Select", + requiredData: [MainStore], + options: () => { + return [ + { label: "Table Header", value: "header" }, + { label: "All Rows", value: "main" }, + { label: "Alternate Rows", value: "alt" }, + { label: "Field Labels", value: "label" }, + ]; + }, + reactiveObject: () => { + return ( + MainStore.getCurrentElementsValues[0] || + MainStore.globalStyles["table"] + ); + }, + onChangeCallback: (value) => { + if (MainStore.getCurrentElementsValues[0]?.selectedDynamicText) { + if ( + MainStore.getCurrentElementsValues[0].styleEditMode == + "label" + ) { + MainStore.getCurrentElementsValues[0].selectedDynamicText.labelStyleEditing = true; + } else { + MainStore.getCurrentElementsValues[0].selectedDynamicText.labelStyleEditing = false; + if ( + MainStore.getCurrentElementsValues[0].styleEditMode == + "header" + ) { + MainStore.getCurrentElementsValues[0].selectedDynamicText = + null; + } + } + } + if (MainStore.getCurrentElementsValues[0]?.selectedColumn && value != "main"){ + MainStore.getCurrentElementsValues[0].selectedColumn = null; + } + }, + propertyName: "styleEditMode", + }); + }, + }, + ], + [ + { + label: "Apply Style to Header", + name: "applyStyleToHeader", + isLabelled: true, + labelDirection: "column", + condtional: () => + MainStore.getCurrentElementsValues[0]?.type == "table" && + MainStore.getCurrentElementsValues[0].selectedColumn, + frappeControl: (ref, name) => { + const MainStore = useMainStore(); + const ElementStore = useElementStore(); + makeFeild({ + name, + ref, + fieldtype: "Select", + requiredData: [MainStore.getCurrentElementsValues[0]], + reactiveObject: () => + MainStore.getCurrentElementsValues[0].selectedColumn, + propertyName: "applyStyleToHeader", + isStyle: false, + options: () => [ + { label: "Yes", value: "Yes" }, + { label: "No", value: "No" }, + ], + formatValue: (object, property, isStyle) => { + if (!object) return; + return object[property] ? "Yes" : "No"; + }, + onChangeCallback: (value = null) => { + if ( + value && + MainStore.getCurrentElementsValues[0].selectedColumn + ) { + MainStore.getCurrentElementsValues[0].selectedColumn.applyStyleToHeader = + value === "Yes"; + MainStore.frappeControls[name].$input.blur(); + } + }, + }); + }, + flex: 1, + }, ], ], }); @@ -652,9 +813,13 @@ export const createPropertiesPanel = () => { [ { label: "Choose Element :", - name: "styleEditMode", + name: "textStyleEditMode", labelDirection: "column", - condtional: null, + condtional: () => + [ + MainStore.getCurrentElementsValues[0]?.type, + MainStore.activeControl, + ].indexOf("table") == -1, frappeControl: (ref, name) => { const MainStore = useMainStore(); makeFeild({ @@ -663,20 +828,11 @@ export const createPropertiesPanel = () => { fieldtype: "Select", requiredData: [MainStore], options: () => { - if ( - "table" == MainStore.getCurrentElementsValues[0]?.type || - "table" == MainStore.activeControl - ) - return [ - { label: "Label Element", value: "label" }, - { label: "Main Element", value: "main" }, - { label: "Header Element", value: "header" }, - ]; if ( ("text" == MainStore.getCurrentElementsValues[0]?.type && MainStore.getCurrentElementsValues[0]?.isDynamic) || ("text" == MainStore.activeControl && - MainStore.textControlType == "static") + MainStore.textControlType == "dynamic") ) return [ { label: "Label Element", value: "label" }, @@ -685,13 +841,9 @@ export const createPropertiesPanel = () => { return [{ label: "Main Element", value: "main" }]; }, reactiveObject: () => { - let styleClass = "table"; - if (MainStore.activeControl == "text") { - if (MainStore.textControlType == "dynamic") { - styleClass = "dynamicText"; - } else { - styleClass = "staticText"; - } + let styleClass = "staticText"; + if (MainStore.textControlType == "dynamic") { + styleClass = "dynamicText"; } return ( MainStore.getCurrentElementsValues[0] || @@ -707,13 +859,6 @@ export const createPropertiesPanel = () => { MainStore.getCurrentElementsValues[0].selectedDynamicText.labelStyleEditing = true; } else { MainStore.getCurrentElementsValues[0].selectedDynamicText.labelStyleEditing = false; - if ( - MainStore.getCurrentElementsValues[0].styleEditMode == - "header" - ) { - MainStore.getCurrentElementsValues[0].selectedDynamicText = - 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 07ffe71..3ba71f9 100644 --- a/print_designer/public/js/print_designer/components/base/BaseDynamicText.vue +++ b/print_designer/public/js/print_designer/components/base/BaseDynamicText.vue @@ -23,6 +23,7 @@
+ +
diff --git a/print_designer/public/js/print_designer/components/base/BaseImage.vue b/print_designer/public/js/print_designer/components/base/BaseImage.vue index b3e3dd4..0842a19 100644 --- a/print_designer/public/js/print_designer/components/base/BaseImage.vue +++ b/print_designer/public/js/print_designer/components/base/BaseImage.vue @@ -12,6 +12,7 @@ :style="[ widthHeightStyle(width, height), style, + style.backgroundColor == '' && { backgroundColor: 'transparent' }, `background-image: url('${isDynamic ? image.value : image.file_url}');`, ]" :class="['image', classes]" diff --git a/print_designer/public/js/print_designer/components/base/BaseRectangle.vue b/print_designer/public/js/print_designer/components/base/BaseRectangle.vue index 1092101..43114ee 100644 --- a/print_designer/public/js/print_designer/components/base/BaseRectangle.vue +++ b/print_designer/public/js/print_designer/components/base/BaseRectangle.vue @@ -1,6 +1,10 @@