From 858919e82616464bdb55ac9a62b75f0055528b9a Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Fri, 15 Mar 2024 16:01:58 +0530 Subject: [PATCH 01/16] feat: Import / Export standard formats - Added Custom Field for Print Designer Template Location - Added Hook to define folder location for Print Designer Templates ( location fallbacks to default_templates if not defined in hooks for export ) - when print designer is installed, loop over all apps and import standard templates. - when any new app is installed, check if it has print designer templates and install them. - it also checks print_designer app for new templates and installs them. - Modified export functionality for print formats that are made using print_designer. - it will export print format in custom directory if defined in hooks else in default_templates folder. - user / developer can define app where print format should be exported in print_designer_template_app field. --- print_designer/custom_fields.py | 8 +++ print_designer/default_formats.py | 72 +++++++++++++++++++ print_designer/hooks.py | 9 ++- print_designer/install.py | 7 ++ print_designer/patches.txt | 1 + .../patches/create_custom_fields.py | 7 ++ .../client_scripts/print_format.js | 10 +++ .../print_designer/overrides/print_format.py | 54 ++++++++++++++ 8 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 print_designer/default_formats.py create mode 100644 print_designer/patches/create_custom_fields.py create mode 100644 print_designer/print_designer/overrides/print_format.py diff --git a/print_designer/custom_fields.py b/print_designer/custom_fields.py index 981b556..b511c30 100644 --- a/print_designer/custom_fields.py +++ b/print_designer/custom_fields.py @@ -44,5 +44,13 @@ "fieldtype": "JSON", "label": "Print Designer Settings", }, + { + "depends_on": "eval:doc.print_designer && doc.standard == 'Yes'", + "fieldname": "print_designer_template_app", + "fieldtype": "Select", + "label": "Print Designer Template Location", + "default": "print_designer", + "insert_after": "standard", + }, ] } diff --git a/print_designer/default_formats.py b/print_designer/default_formats.py new file mode 100644 index 0000000..6393a04 --- /dev/null +++ b/print_designer/default_formats.py @@ -0,0 +1,72 @@ +import os +from pathlib import Path + +import frappe +from frappe.modules.import_file import import_file_by_path + +""" +features: + - Print Designer App can have default formats for all installed apps. + - Any Custom/Standard App can have default formats for any installed apps + ( This will only install formats if print_designer is installed ). + - This will be useful when we have standalone formats that can be used without print designer app. + +when print_designer app is installed + - get hooks from all installed apps including pd and load default formats from defined folders. + +when any new app is installed + - if exists in print_designer/default_templates, load default formats for newly installed app. + - get hooks from new app and load default formats for all installed apps from app's format dir. +""" + +# TODO: handle override of default formats from different apps or even Custom Formats with same name. + +# add default formats for all installed apps. +def on_print_designer_install(): + for app in frappe.get_installed_apps(): + install_default_formats(app=app, load_pd_formats=False) + + +# called after install of any new app. +def install_default_formats(app, filter_by="", load_pd_formats=True): + if load_pd_formats: + # load formats from print_designer app if some new app is installed and have default formats + install_default_formats(app="print_designer", filter_by=app, load_pd_formats=False) + + # get dir path and load formats from installed app + pd_folder = frappe.get_hooks("pd_standard_format_folder", app_name=app) + if len(pd_folder) == 0: + return + + print_formats = get_filtered_formats_by_app( + app=app, templates_folder=pd_folder[0], filter_by=filter_by + ) + + for json_file_path in print_formats: + import_file_by_path(json_file_path) + frappe.db.commit() + + +def get_filtered_formats_by_app(app, templates_folder, filter_by=""): + app_path = frappe.get_app_path(app) + if filter_by == "": + folders = Path(os.path.join(app_path, templates_folder)) + return get_formats_from_folders(folders=folders) + else: + folder = Path(os.path.join(app_path, templates_folder, filter_by)) + return get_json_files(folder) + + +def get_formats_from_folders(folders): + formats = set() + for folder in folders.iterdir(): + if folder.is_dir() and folder.name in frappe.get_installed_apps(): + formats.update(get_json_files(folder)) + return formats + + +def get_json_files(folder): + formats = set() + for json_file in folder.glob("*.json"): + formats.add(json_file) + return formats diff --git a/print_designer/hooks.py b/print_designer/hooks.py index 68e9d5e..4b1079b 100644 --- a/print_designer/hooks.py +++ b/print_designer/hooks.py @@ -72,6 +72,7 @@ before_install = "print_designer.install.before_install" after_install = "print_designer.install.after_install" +after_app_install = "print_designer.install.after_app_install" # Uninstallation # ------------ @@ -110,10 +111,12 @@ # --------------- # Override standard doctype classes -# override_doctype_class = { -# "ToDo": "custom_app.overrides.CustomToDo" -# } +override_doctype_class = { + "Print Format": "print_designer.print_designer.overrides.print_format.PDPrintFormat", +} +# Path Relative to the app folder where default templates should be stored +pd_standard_format_folder = "default_templates" # Document Events # --------------- # Hook on document methods and events diff --git a/print_designer/install.py b/print_designer/install.py index af9200c..22ebc80 100644 --- a/print_designer/install.py +++ b/print_designer/install.py @@ -3,6 +3,7 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from print_designer.custom_fields import CUSTOM_FIELDS +from print_designer.default_formats import install_default_formats, on_print_designer_install def check_frappe_version(): @@ -27,3 +28,9 @@ def before_install(): def after_install(): create_custom_fields(CUSTOM_FIELDS, ignore_validate=True) + on_print_designer_install() + + +def after_app_install(app): + if app != "print_designer": + install_default_formats(app) diff --git a/print_designer/patches.txt b/print_designer/patches.txt index 1c006ff..6f8c620 100644 --- a/print_designer/patches.txt +++ b/print_designer/patches.txt @@ -9,3 +9,4 @@ 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 +execute:from print_designer.patches.create_custom_fields import custom_field_patch; custom_field_patch() diff --git a/print_designer/patches/create_custom_fields.py b/print_designer/patches/create_custom_fields.py new file mode 100644 index 0000000..304b916 --- /dev/null +++ b/print_designer/patches/create_custom_fields.py @@ -0,0 +1,7 @@ +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + +from print_designer.custom_fields import CUSTOM_FIELDS + + +def custom_field_patch(): + create_custom_fields(CUSTOM_FIELDS, ignore_validate=True) diff --git a/print_designer/print_designer/client_scripts/print_format.js b/print_designer/print_designer/client_scripts/print_format.js index 6e88365..c4bacc9 100644 --- a/print_designer/print_designer/client_scripts/print_format.js +++ b/print_designer/print_designer/client_scripts/print_format.js @@ -1,6 +1,16 @@ +const set_template_app_options = (frm) => { + frappe.xcall("frappe.core.doctype.module_def.module_def.get_installed_apps").then((r) => { + frm.set_df_property("print_designer_template_app", "options", JSON.parse(r)); + if (!frm.doc.print_designer_template_app) { + frm.set_value("print_designer_template_app", "print_designer"); + } + }); +}; + frappe.ui.form.on("Print Format", { refresh: function (frm) { frm.trigger("render_buttons"); + set_template_app_options(frm); }, render_buttons: function (frm) { frm.page.clear_inner_toolbar(); diff --git a/print_designer/print_designer/overrides/print_format.py b/print_designer/print_designer/overrides/print_format.py new file mode 100644 index 0000000..c9fa03f --- /dev/null +++ b/print_designer/print_designer/overrides/print_format.py @@ -0,0 +1,54 @@ +import os + +import frappe +from frappe.modules.utils import scrub +from frappe.printing.doctype.print_format.print_format import PrintFormat + + +class PDPrintFormat(PrintFormat): + def export_doc(self): + if ( + not self.standard + or not frappe.conf.developer_mode + or frappe.flags.in_patch + or frappe.flags.in_install + or frappe.flags.in_migrate + or frappe.flags.in_import + or frappe.flags.in_setup_wizard + ): + return + + if not self.print_designer: + return super().export_doc() + + self.write_document_file() + + def write_document_file(self): + doc = self + doc_export = doc.as_dict(no_nulls=True) + doc.run_method("before_export", doc_export) + + # create folder + folder = self.create_folder(doc.doc_type, doc.name) + + fname = scrub(doc.name) + + # write the data file + path = os.path.join(folder, f"{fname}.json") + with open(path, "w+") as jsonfile: + jsonfile.write(frappe.as_json(doc_export)) + print(f"Wrote document file for {doc.doctype} {doc.name} at {path}") + + def create_folder(self, dt, dn): + app = scrub(frappe.get_doctype_app(dt)) + dn = scrub(dn) + pd_folder = frappe.get_hooks( + "pd_standard_format_folder", app_name=self.print_designer_template_app + ) + if len(pd_folder) == 0: + pd_folder = ["default_templates"] + folder = os.path.join( + frappe.get_app_path(self.print_designer_template_app), os.path.join(pd_folder[0], app) + ) + frappe.create_folder(folder) + return folder From 5a716fcf77aa2a56f4af050c086dbb1510bed452 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Sun, 17 Mar 2024 21:51:27 +0530 Subject: [PATCH 02/16] feat: generate and store format pdf preview. added new custom field "print_designer_preview_img" to store the format preview image. added html2canvas on save - make copy of main-container dom and convert to canvas using html2canvas. - create file instance and pass blob to it from canvas.toBlob() - send file to /api/method/upload_file , client side function is just modified version of FileUploader. - remove old format preview image after new one is uploaded successfully. This will be used to display the format preview in the format Grid View. --- package.json | 3 +- print_designer/custom_fields.py | 6 ++ .../js/print_designer/store/ElementStore.js | 81 +++++++++++++++++++ yarn.lock | 34 ++++++++ 4 files changed, 123 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index bd1cd93..e7e7c5f 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,11 @@ "author": "Frappe Technologies Pvt. Ltd.", "license": "AGPL-3.0-or-later", "dependencies": { - "@interactjs/interact": "^1.10.17", "@interactjs/actions": "^1.10.17", "@interactjs/auto-start": "^1.10.17", + "@interactjs/interact": "^1.10.17", "@interactjs/modifiers": "^1.10.17", + "html2canvas": "^1.4.1", "pdfjs-dist": "v3.4.120" } } diff --git a/print_designer/custom_fields.py b/print_designer/custom_fields.py index b511c30..c0c1045 100644 --- a/print_designer/custom_fields.py +++ b/print_designer/custom_fields.py @@ -44,6 +44,12 @@ "fieldtype": "JSON", "label": "Print Designer Settings", }, + { + "fieldname": "print_designer_preview_img", + "hidden": 1, + "fieldtype": "Attach Image", + "label": "Print Designer Preview Image", + }, { "depends_on": "eval:doc.print_designer && doc.standard == 'Yes'", "fieldname": "print_designer_template_app", diff --git a/print_designer/public/js/print_designer/store/ElementStore.js b/print_designer/public/js/print_designer/store/ElementStore.js index 40b8b51..b358fc7 100644 --- a/print_designer/public/js/print_designer/store/ElementStore.js +++ b/print_designer/public/js/print_designer/store/ElementStore.js @@ -9,6 +9,9 @@ import { createBarcode, } from "../defaultObjects"; import { handlePrintFonts, setCurrentElement } from "../utils"; + +import html2canvas from "html2canvas"; + export const useElementStore = defineStore("ElementStore", { state: () => ({ Elements: new Array(), @@ -34,6 +37,83 @@ export const useElementStore = defineStore("ElementStore", { } return newElement; }, + // This is mofiied version of upload function used in frappe/FileUploader.vue + async upload_file(file) { + const MainStore = useMainStore(); + const filter = { + attached_to_doctype: "Print Format", + attached_to_name: MainStore.printDesignName, + attached_to_field: "print_designer_preview_img", + }; + // get filename before uploading new file + let old_filename = await frappe.db.get_value("File", filter, "name"); + old_filename = old_filename.message.name; + + return new Promise((resolve, reject) => { + let xhr = new XMLHttpRequest(); + xhr.onreadystatechange = () => { + if (xhr.readyState == XMLHttpRequest.DONE) { + if (xhr.status === 200) { + // delete old preview image when new image is successfully uploaded + old_filename && frappe.db.delete_doc("File", old_filename); + } + } + }; + xhr.open("POST", "/api/method/upload_file", true); + xhr.setRequestHeader("Accept", "application/json"); + xhr.setRequestHeader("X-Frappe-CSRF-Token", frappe.csrf_token); + + let form_data = new FormData(); + if (file.file_obj) { + form_data.append("file", file.file_obj, file.name); + } + form_data.append("is_private", 1); + + form_data.append("doctype", "Print Format"); + form_data.append("docname", MainStore.printDesignName); + + form_data.append("fieldname", "print_designer_preview_img"); + + if (file.optimize) { + form_data.append("optimize", true); + } + xhr.send(form_data); + }); + }, + async generatePreview() { + const MainStore = useMainStore(); + const options = { + backgroundColor: "#ffffff", + height: MainStore.page.height / 2, + width: MainStore.page.width, + }; + const print_stylesheet = document.createElement("style"); + print_stylesheet.rel = "stylesheet"; + let st = `.main-container::after { + display: none; + }`; + document.getElementsByClassName("main-container")[0].appendChild(print_stylesheet); + print_stylesheet.sheet.insertRule(st, 0); + const preview_canvas = await html2canvas( + document.getElementsByClassName("main-container")[0], + options + ); + document.getElementsByClassName("main-container")[0].removeChild(print_stylesheet); + preview_canvas.toBlob((blob) => { + const file = new File( + [blob], + `${MainStore.printDesignName}_${MainStore.currentDoc}.jpg`, + { type: "image/jpeg" } + ); + const file_data = { + file_obj: file, + optimize: 1, + name: file.name, + private: true, + }; + this.upload_file(file_data); + }); + }, async saveElements() { const MainStore = useMainStore(); if (this.checkIfAnyTableIsEmpty()) return; @@ -133,6 +213,7 @@ export const useElementStore = defineStore("ElementStore", { 5 ); } + this.generatePreview(); }, checkIfAnyTableIsEmpty() { const emptyTable = this.Elements.find((el) => el.type == "table" && el.table == null); diff --git a/yarn.lock b/yarn.lock index 8f1cafd..8893f97 100644 --- a/yarn.lock +++ b/yarn.lock @@ -106,6 +106,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base64-arraybuffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc" + integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -143,6 +148,13 @@ console-control-strings@^1.0.0, console-control-strings@^1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== +css-line-break@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-2.1.0.tgz#bfef660dfa6f5397ea54116bb3cb4873edbc4fa0" + integrity sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w== + dependencies: + utrie "^1.0.2" + debug@4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -216,6 +228,14 @@ has-unicode@^2.0.1: resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== +html2canvas@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/html2canvas/-/html2canvas-1.4.1.tgz#7cef1888311b5011d507794a066041b14669a543" + integrity sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA== + dependencies: + css-line-break "^2.1.0" + text-segmentation "^1.0.3" + https-proxy-agent@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" @@ -451,6 +471,13 @@ tar@^6.1.11: mkdirp "^1.0.3" yallist "^4.0.0" +text-segmentation@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/text-segmentation/-/text-segmentation-1.0.3.tgz#52a388159efffe746b24a63ba311b6ac9f2d7943" + integrity sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw== + dependencies: + utrie "^1.0.2" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -461,6 +488,13 @@ util-deprecate@^1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +utrie@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/utrie/-/utrie-1.0.2.tgz#d42fe44de9bc0119c25de7f564a6ed1b2c87a645" + integrity sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw== + dependencies: + base64-arraybuffer "^1.0.2" + web-streams-polyfill@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" From 5da772833efe11aa65b72b975c63c178a6cc0292 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Sun, 17 Mar 2024 23:11:49 +0530 Subject: [PATCH 03/16] fix: don't save value of dynamic text. dynamic text value is fetched from db so there is no need to save it as part of format. --- .../js/print_designer/store/ElementStore.js | 56 ++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/print_designer/public/js/print_designer/store/ElementStore.js b/print_designer/public/js/print_designer/store/ElementStore.js index b358fc7..b748e76 100644 --- a/print_designer/public/js/print_designer/store/ElementStore.js +++ b/print_designer/public/js/print_designer/store/ElementStore.js @@ -117,6 +117,7 @@ export const useElementStore = defineStore("ElementStore", { async saveElements() { const MainStore = useMainStore(); if (this.checkIfAnyTableIsEmpty()) return; + if (MainStore.mode == "preview") return; // Update the header and footer height with margin MainStore.page.headerHeightWithMargin = @@ -458,6 +459,7 @@ export const useElementStore = defineStore("ElementStore", { delete saveEl.snapPoints; delete saveEl.snapEdges; delete saveEl.parent; + this.cleanUpDynamicContent(saveEl); if (printFonts && ["text", "table"].indexOf(saveEl.type) != -1) { handlePrintFonts(saveEl, printFonts); } @@ -472,6 +474,48 @@ export const useElementStore = defineStore("ElementStore", { return saveEl; }, + cleanUpDynamicContent(element) { + 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) => { + const newEl = { ...el }; + if (!el.is_static) { + newEl.value = ""; + } + return newEl; + }), + ]; + element.selectedDynamicText = null; + } else if (element.type === "table") { + element.columns = [ + ...element.columns.map((el) => { + const newEl = { ...el }; + delete newEl.DOMRef; + return newEl; + }), + ]; + element.columns.forEach((col) => { + if (!col.dynamicContent) return; + col.dynamicContent = [ + ...col.dynamicContent.map((el) => { + const newEl = { ...el }; + if (!el.is_static) { + newEl.value = ""; + } + return newEl; + }), + ]; + col.selectedDynamicText = null; + }); + } else { + element.image = { ...element.image }; + } + } + }, getPrintFormatData({ header, body, footer }) { const headerElements = this.createWrapperElement( header.elements, @@ -583,7 +627,11 @@ export const useElementStore = defineStore("ElementStore", { if (["text", "barcode"].indexOf(element.type) != -1) { element.dynamicContent = [ ...element.dynamicContent.map((el) => { - return { ...el }; + const newEl = { ...el }; + if (!el.is_static) { + newEl.value = ""; + } + return newEl; }), ]; element.selectedDynamicText = null; @@ -598,7 +646,11 @@ export const useElementStore = defineStore("ElementStore", { if (!col.dynamicContent) return; col.dynamicContent = [ ...col.dynamicContent.map((el) => { - return { ...el }; + const newEl = { ...el }; + if (!el.is_static) { + newEl.value = ""; + } + return newEl; }), ]; col.selectedDynamicText = null; From e98ea4ddf5fc9cb3656430284a8b0c601c6ec200 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Mon, 18 Mar 2024 02:53:54 +0530 Subject: [PATCH 04/16] fix: don't save child table's metaFields. we have to consider and load metaFields from db so remove it from Print Format JSON. --- .../public/js/print_designer/store/ElementStore.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/print_designer/public/js/print_designer/store/ElementStore.js b/print_designer/public/js/print_designer/store/ElementStore.js index b748e76..0091490 100644 --- a/print_designer/public/js/print_designer/store/ElementStore.js +++ b/print_designer/public/js/print_designer/store/ElementStore.js @@ -460,6 +460,10 @@ export const useElementStore = defineStore("ElementStore", { delete saveEl.snapEdges; delete saveEl.parent; this.cleanUpDynamicContent(saveEl); + if (saveEl.type == "table") { + delete saveEl.table.childfields; + delete saveEl.table.default_layout; + } if (printFonts && ["text", "table"].indexOf(saveEl.type) != -1) { handlePrintFonts(saveEl, printFonts); } @@ -637,6 +641,13 @@ export const useElementStore = defineStore("ElementStore", { element.selectedDynamicText = null; MainStore.dynamicData.push(...element.dynamicContent); } else if (element.type === "table") { + const mf = MainStore.metaFields.find( + (field) => field.fieldname == element.table.fieldname + ); + if (mf) { + element.table = mf; + } + element.columns = [ ...element.columns.map((el) => { return { ...el }; From 07ad4baa4898094c95a95c8dc94d2e90f6787cec Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Mon, 18 Mar 2024 04:18:04 +0530 Subject: [PATCH 05/16] fix: don't save file_values in format - if standard is set to true, remove static file urls as it can't be used across site. - only try to load file if url value exists for the file. - This doesn't check if image file exists or not. --- .../print_designer/jinja/macros/image.html | 18 +++++++++++++++--- .../print_designer/jinja/old_print_format.html | 17 ++++++++++++++++- .../js/print_designer/store/ElementStore.js | 17 ++++++++++++++--- 3 files changed, 45 insertions(+), 7 deletions(-) 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 index 7662b9f..0515361 100644 --- a/print_designer/print_designer/page/print_designer/jinja/macros/image.html +++ b/print_designer/print_designer/page/print_designer/jinja/macros/image.html @@ -1,14 +1,26 @@ {% macro image(element) -%} +{%- if element.image.file_url -%} + {%- set value = element.image.file_url -%} +{%- elif element.image.fieldname -%} + {%- if element.image.parent == doc.doctype -%} + {%- set value = doc.get(element.image.fieldname) -%} + {%- else -%} + {%- set value = frappe.db.get_value(element.image.doctype, doc[element.image.parentField], element.image.fieldname) -%} + {%- endif -%} +{%- else -%} + {%- set value = "" -%} +{%- endif -%} + +{%- if value -%}
+{%- endif -%} {%- endmacro %} \ No newline at end of file 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 c1fb537..901e99d 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 @@ -40,6 +40,20 @@ {%- endmacro %} {% macro render_image(element) -%} + +{%- if element.image.file_url -%} + {%- set value = element.image.file_url -%} +{%- elif element.image.fieldname -%} + {%- if element.image.parent == doc.doctype -%} + {%- set value = doc.get(element.image.fieldname) -%} + {%- else -%} + {%- set value = frappe.db.get_value(element.image.doctype, doc[element.image.parentField], element.image.fieldname) -%} + {%- endif -%} +{%- else -%} + {%- set value = "" -%} +{%- endif -%} + +{%- if value -%}
+{%- endif -%} {%- endmacro %} {% macro render_barcode(element, send_to_jinja) -%} {%- set field = element.dynamicContent[0] -%} diff --git a/print_designer/public/js/print_designer/store/ElementStore.js b/print_designer/public/js/print_designer/store/ElementStore.js index 0091490..1a38edb 100644 --- a/print_designer/public/js/print_designer/store/ElementStore.js +++ b/print_designer/public/js/print_designer/store/ElementStore.js @@ -118,7 +118,12 @@ export const useElementStore = defineStore("ElementStore", { const MainStore = useMainStore(); if (this.checkIfAnyTableIsEmpty()) return; if (MainStore.mode == "preview") return; - + let is_standard = await frappe.db.get_value( + "Print Format", + MainStore.printDesignName, + "standard" + ); + is_standard = is_standard.message.standard; // Update the header and footer height with margin MainStore.page.headerHeightWithMargin = MainStore.page.headerHeight + MainStore.page.marginTop; @@ -480,8 +485,8 @@ export const useElementStore = defineStore("ElementStore", { }, cleanUpDynamicContent(element) { if ( - element.type == "table" || - (["text", "image", "barcode"].indexOf(element.type) != -1 && element.isDynamic) + ["table", "image"].includes(element.type) || + (["text", "barcode"].includes(element.type) && element.isDynamic) ) { if (["text", "barcode"].indexOf(element.type) != -1) { element.dynamicContent = [ @@ -517,6 +522,12 @@ export const useElementStore = defineStore("ElementStore", { }); } else { element.image = { ...element.image }; + if (is_standard) { + // remove file_url and file_name if format is standard + ["value", "name", "file_name", "file_url", "modified"].forEach((key) => { + element.image[key] = ""; + }); + } } } }, From dd4e48d3c10a6635f2b36a5fb32e369f6c31d2eb Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Mon, 18 Mar 2024 07:50:55 +0530 Subject: [PATCH 06/16] fix: typo and set file url in print format As standard field returns Yes or No, update the logic to compare string instead of boolean. update preview image url in print_designer_preview_img field --- .../js/print_designer/store/ElementStore.js | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/print_designer/public/js/print_designer/store/ElementStore.js b/print_designer/public/js/print_designer/store/ElementStore.js index 1a38edb..7ede3e2 100644 --- a/print_designer/public/js/print_designer/store/ElementStore.js +++ b/print_designer/public/js/print_designer/store/ElementStore.js @@ -56,6 +56,34 @@ export const useElementStore = defineStore("ElementStore", { if (xhr.status === 200) { // delete old preview image when new image is successfully uploaded old_filename && frappe.db.delete_doc("File", old_filename); + try { + r = JSON.parse(xhr.responseText); + if (r.message.doctype === "File") { + file_doc = r.message; + frappe.db.set_value( + "Print Format", + MainStore.printDesignName, + "print_designer_preview_img", + file_doc.file_url + ); + } + } catch (e) { + r = xhr.responseText; + } + try { + r = JSON.parse(xhr.responseText); + if (r.message.doctype === "File") { + file_doc = r.message; + frappe.db.set_value( + "Print Format", + MainStore.printDesignName, + "print_designer_preview_img", + file_doc.file_url + ); + } + } catch (e) { + r = xhr.responseText; + } } } }; @@ -123,7 +151,7 @@ export const useElementStore = defineStore("ElementStore", { MainStore.printDesignName, "standard" ); - is_standard = is_standard.message.standard; + is_standard = is_standard.message.standard == "Yes"; // Update the header and footer height with margin MainStore.page.headerHeightWithMargin = MainStore.page.headerHeight + MainStore.page.marginTop; From 14746f49f3f5ba480668fb129d30ad80178ce376 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Mon, 18 Mar 2024 07:55:00 +0530 Subject: [PATCH 07/16] chore: load childfield meta when table is present First check if table exists before trying to load childfield meta --- .../public/js/print_designer/store/ElementStore.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/print_designer/public/js/print_designer/store/ElementStore.js b/print_designer/public/js/print_designer/store/ElementStore.js index 7ede3e2..802c281 100644 --- a/print_designer/public/js/print_designer/store/ElementStore.js +++ b/print_designer/public/js/print_designer/store/ElementStore.js @@ -680,11 +680,13 @@ export const useElementStore = defineStore("ElementStore", { element.selectedDynamicText = null; MainStore.dynamicData.push(...element.dynamicContent); } else if (element.type === "table") { - const mf = MainStore.metaFields.find( - (field) => field.fieldname == element.table.fieldname - ); - if (mf) { - element.table = mf; + if (element.table) { + const mf = MainStore.metaFields.find( + (field) => field.fieldname == element.table.fieldname + ); + if (mf) { + element.table = mf; + } } element.columns = [ From d8ecc52df31aa6a650f52e321e3c4ce150a3770d Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Mon, 18 Mar 2024 10:23:32 +0530 Subject: [PATCH 08/16] chore: copy image to export directory Copy the image to the export directory so that it can be used for preview while installing the format. --- .../print_designer/overrides/print_format.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/print_designer/print_designer/overrides/print_format.py b/print_designer/print_designer/overrides/print_format.py index c9fa03f..16b3948 100644 --- a/print_designer/print_designer/overrides/print_format.py +++ b/print_designer/print_designer/overrides/print_format.py @@ -1,4 +1,5 @@ import os +import shutil import frappe from frappe.modules.utils import scrub @@ -38,6 +39,7 @@ def write_document_file(self): with open(path, "w+") as jsonfile: jsonfile.write(frappe.as_json(doc_export)) print(f"Wrote document file for {doc.doctype} {doc.name} at {path}") + self.export_preview(folder=folder, fname=fname) def create_folder(self, dt, dn): app = scrub(frappe.get_doctype_app(dt)) @@ -52,3 +54,11 @@ def create_folder(self, dt, dn): ) frappe.create_folder(folder) return folder + + def export_preview(self, folder, fname): + if self.print_designer_preview_img: + file = frappe.get_doc("File", {"file_url": self.print_designer_preview_img}) + org_path = file.get_full_path() + target_path = os.path.join(folder, f"{fname}-preview.jpg") + shutil.copy2(org_path, target_path) + print(f"Wrote preview file for {self.doctype} {self.name} at {target_path}") From 9883c6d180e8c96e65650fc99b7041c9848817bc Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Mon, 1 Apr 2024 13:33:10 +0530 Subject: [PATCH 09/16] fix: export import preview images when print_designer format is saved in developer mode and format is standard. preview images and " File " document is exported as per the location defined in the selected app's hook. after install once the formats are imported, copy the file to private/files folder and update the file path in the format. --- print_designer/default_formats.py | 45 ++++++++++++++++++- .../print_designer/overrides/print_format.py | 30 ++++++++++--- .../js/print_designer/store/ElementStore.js | 13 ++++-- 3 files changed, 79 insertions(+), 9 deletions(-) diff --git a/print_designer/default_formats.py b/print_designer/default_formats.py index 6393a04..f6ed704 100644 --- a/print_designer/default_formats.py +++ b/print_designer/default_formats.py @@ -1,8 +1,10 @@ import os +import shutil from pathlib import Path import frappe from frappe.modules.import_file import import_file_by_path +from frappe.utils import get_files_path """ features: @@ -27,6 +29,27 @@ def on_print_designer_install(): install_default_formats(app=app, load_pd_formats=False) +def get_preview_image_folder_path(print_format): + app = frappe.scrub(frappe.get_doctype_app(print_format.doc_type)) + pd_folder = frappe.get_hooks( + "pd_standard_format_folder", app_name=print_format.print_designer_template_app + ) + if len(pd_folder) == 0: + pd_folder = ["default_templates"] + return os.path.join( + frappe.get_app_path(print_format.print_designer_template_app), os.path.join(pd_folder[0], app) + ) + + +def update_preview_img(file): + print_format = frappe.get_doc(file.attached_to_doctype, file.attached_to_name) + folder = get_preview_image_folder_path(print_format) + file_name = print_format.print_designer_preview_img.split("/")[-1] + org_path = os.path.join(folder, file_name) + target_path = get_files_path(file_name, is_private=1) + shutil.copy2(org_path, target_path) + + # called after install of any new app. def install_default_formats(app, filter_by="", load_pd_formats=True): if load_pd_formats: @@ -42,10 +65,30 @@ def install_default_formats(app, filter_by="", load_pd_formats=True): app=app, templates_folder=pd_folder[0], filter_by=filter_by ) + preview_files = [f for f in print_formats if f.name.endswith("-preview.json")] + print_formats = [f for f in print_formats if not f.name.endswith("-preview.json")] + for json_file_path in print_formats: - import_file_by_path(json_file_path) + import_file_by_path(path=json_file_path) frappe.db.commit() + for json_file_path in preview_files: + import_file_by_path(path=json_file_path, pre_process=update_preview_img) + frappe.db.commit() + + for pf in frappe.db.get_all("Print Format", filters={"standard": "Yes", "print_designer": 1}): + updated_url = frappe.db.get_value( + "File", + { + "attached_to_doctype": "Print Format", + "attached_to_name": pf.name, + "attached_to_field": "print_designer_preview_img", + }, + "file_url", + ) + if updated_url: + frappe.set_value("Print Format", pf.name, "print_designer_preview_img", updated_url) + def get_filtered_formats_by_app(app, templates_folder, filter_by=""): app_path = frappe.get_app_path(app) diff --git a/print_designer/print_designer/overrides/print_format.py b/print_designer/print_designer/overrides/print_format.py index 16b3948..6b3ca46 100644 --- a/print_designer/print_designer/overrides/print_format.py +++ b/print_designer/print_designer/overrides/print_format.py @@ -9,7 +9,7 @@ class PDPrintFormat(PrintFormat): def export_doc(self): if ( - not self.standard + not self.standard == "Yes" or not frappe.conf.developer_mode or frappe.flags.in_patch or frappe.flags.in_install @@ -36,8 +36,8 @@ def write_document_file(self): # write the data file path = os.path.join(folder, f"{fname}.json") - with open(path, "w+") as jsonfile: - jsonfile.write(frappe.as_json(doc_export)) + with open(path, "w+") as json_file: + json_file.write(frappe.as_json(doc_export)) print(f"Wrote document file for {doc.doctype} {doc.name} at {path}") self.export_preview(folder=folder, fname=fname) @@ -57,8 +57,28 @@ def create_folder(self, dt, dn): def export_preview(self, folder, fname): if self.print_designer_preview_img: - file = frappe.get_doc("File", {"file_url": self.print_designer_preview_img}) + try: + file = frappe.get_doc( + "File", + { + "file_url": self.print_designer_preview_img, + "attached_to_doctype": self.doctype, + "attached_to_name": self.name, + "attached_to_field": "print_designer_preview_img", + }, + ) + except frappe.DoesNotExistError: + file = None + if not file: + return + file_export = file.as_dict(no_nulls=True) + file.run_method("before_export", file_export) org_path = file.get_full_path() - target_path = os.path.join(folder, f"{fname}-preview.jpg") + target_path = os.path.join(folder, org_path.split("/")[-1]) shutil.copy2(org_path, target_path) print(f"Wrote preview file for {self.doctype} {self.name} at {target_path}") + # write the data file + path = os.path.join(folder, f"print_designer-{fname}-preview.json") + with open(path, "w+") as json_file: + json_file.write(frappe.as_json(file_export)) + print(f"Wrote document file for {file.doctype} {file.name} at {path}") diff --git a/print_designer/public/js/print_designer/store/ElementStore.js b/print_designer/public/js/print_designer/store/ElementStore.js index 802c281..f11845d 100644 --- a/print_designer/public/js/print_designer/store/ElementStore.js +++ b/print_designer/public/js/print_designer/store/ElementStore.js @@ -48,14 +48,21 @@ export const useElementStore = defineStore("ElementStore", { // get filename before uploading new file let old_filename = await frappe.db.get_value("File", filter, "name"); old_filename = old_filename.message.name; + if (old_filename) { + frappe.db.delete_doc("File", old_filename); + frappe.db.set_value( + "Print Format", + MainStore.printDesignName, + "print_designer_preview_img", + null + ); + } return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(); xhr.onreadystatechange = () => { if (xhr.readyState == XMLHttpRequest.DONE) { if (xhr.status === 200) { - // delete old preview image when new image is successfully uploaded - old_filename && frappe.db.delete_doc("File", old_filename); try { r = JSON.parse(xhr.responseText); if (r.message.doctype === "File") { @@ -130,7 +137,7 @@ export const useElementStore = defineStore("ElementStore", { preview_canvas.toBlob((blob) => { const file = new File( [blob], - `${MainStore.printDesignName}_${MainStore.currentDoc}.jpg`, + `print_designer-${frappe.scrub(MainStore.printDesignName)}-preview.jpg`, { type: "image/jpeg" } ); const file_data = { From bbe07f9d8856d914fead33f6469ca16c3c301017 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Tue, 2 Apr 2024 01:27:45 +0530 Subject: [PATCH 10/16] fix: merge conflict and file_url After merging relative containers when this in this PR conflicts were manually resolved. There were some incorrect conflict resolutions, which are fixed in this commit. Also, removed multiple updates for print format document caused by updating file_url. instead get latest file_url from the file document else set to null. --- .../js/print_designer/store/ElementStore.js | 80 ++++++++----------- 1 file changed, 34 insertions(+), 46 deletions(-) diff --git a/print_designer/public/js/print_designer/store/ElementStore.js b/print_designer/public/js/print_designer/store/ElementStore.js index f11845d..298a926 100644 --- a/print_designer/public/js/print_designer/store/ElementStore.js +++ b/print_designer/public/js/print_designer/store/ElementStore.js @@ -37,26 +37,9 @@ export const useElementStore = defineStore("ElementStore", { } return newElement; }, - // This is mofiied version of upload function used in frappe/FileUploader.vue + // This is modified version of upload function used in frappe/FileUploader.vue async upload_file(file) { const MainStore = useMainStore(); - const filter = { - attached_to_doctype: "Print Format", - attached_to_name: MainStore.printDesignName, - attached_to_field: "print_designer_preview_img", - }; - // get filename before uploading new file - let old_filename = await frappe.db.get_value("File", filter, "name"); - old_filename = old_filename.message.name; - if (old_filename) { - frappe.db.delete_doc("File", old_filename); - frappe.db.set_value( - "Print Format", - MainStore.printDesignName, - "print_designer_preview_img", - null - ); - } return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(); @@ -65,29 +48,6 @@ export const useElementStore = defineStore("ElementStore", { if (xhr.status === 200) { try { r = JSON.parse(xhr.responseText); - if (r.message.doctype === "File") { - file_doc = r.message; - frappe.db.set_value( - "Print Format", - MainStore.printDesignName, - "print_designer_preview_img", - file_doc.file_url - ); - } - } catch (e) { - r = xhr.responseText; - } - try { - r = JSON.parse(xhr.responseText); - if (r.message.doctype === "File") { - file_doc = r.message; - frappe.db.set_value( - "Print Format", - MainStore.printDesignName, - "print_designer_preview_img", - file_doc.file_url - ); - } } catch (e) { r = xhr.responseText; } @@ -117,6 +77,19 @@ export const useElementStore = defineStore("ElementStore", { }, async generatePreview() { const MainStore = useMainStore(); + // first delete old preview image + const filter = { + attached_to_doctype: "Print Format", + attached_to_name: MainStore.printDesignName, + attached_to_field: "print_designer_preview_img", + }; + // get filename before uploading new file + let old_filename = await frappe.db.get_value("File", filter, "name"); + old_filename = old_filename.message.name; + if (old_filename) { + frappe.db.delete_doc("File", old_filename); + } + const options = { backgroundColor: "#ffffff", height: MainStore.page.height / 2, @@ -134,7 +107,7 @@ export const useElementStore = defineStore("ElementStore", { options ); document.getElementsByClassName("main-container")[0].removeChild(print_stylesheet); - preview_canvas.toBlob((blob) => { + preview_canvas.toBlob(async (blob) => { const file = new File( [blob], `print_designer-${frappe.scrub(MainStore.printDesignName)}-preview.jpg`, @@ -146,7 +119,7 @@ export const useElementStore = defineStore("ElementStore", { name: file.name, private: true, }; - this.upload_file(file_data); + await this.upload_file(file_data); }); }, async saveElements() { @@ -158,7 +131,7 @@ export const useElementStore = defineStore("ElementStore", { MainStore.printDesignName, "standard" ); - is_standard = is_standard.message.standard == "Yes"; + MainStore.is_standard = is_standard.message.standard == "Yes"; // Update the header and footer height with margin MainStore.page.headerHeightWithMargin = MainStore.page.headerHeight + MainStore.page.marginTop; @@ -241,7 +214,22 @@ export const useElementStore = defineStore("ElementStore", { }, }); + await this.generatePreview(); + objectToSave.print_designer_print_format = PrintFormatData; + const filter = { + attached_to_doctype: "Print Format", + attached_to_name: MainStore.printDesignName, + attached_to_field: "print_designer_preview_img", + }; + // update filename from file + let print_designer_preview_img = await frappe.db.get_value("File", filter, "file_url"); + if (print_designer_preview_img.message.file_url) { + objectToSave.print_designer_preview_img = + print_designer_preview_img.message.file_url; + } else { + objectToSave.print_designer_preview_img = null; + } if (MainStore.isOlderSchema("1.1.0")) { await this.printFormatCopyOnOlderSchema(objectToSave); } else { @@ -254,7 +242,6 @@ export const useElementStore = defineStore("ElementStore", { 5 ); } - this.generatePreview(); }, checkIfAnyTableIsEmpty() { const emptyTable = this.Elements.find((el) => el.type == "table" && el.table == null); @@ -519,6 +506,7 @@ export const useElementStore = defineStore("ElementStore", { return saveEl; }, cleanUpDynamicContent(element) { + const MainStore = useMainStore(); if ( ["table", "image"].includes(element.type) || (["text", "barcode"].includes(element.type) && element.isDynamic) @@ -557,7 +545,7 @@ export const useElementStore = defineStore("ElementStore", { }); } else { element.image = { ...element.image }; - if (is_standard) { + if (MainStore.is_standard) { // remove file_url and file_name if format is standard ["value", "name", "file_name", "file_url", "modified"].forEach((key) => { element.image[key] = ""; From 4d0f3084563a4d3ae8bbb38f7950f20c1824f332 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Tue, 2 Apr 2024 02:27:04 +0530 Subject: [PATCH 11/16] fix: handle folder not found error in import --- print_designer/default_formats.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/print_designer/default_formats.py b/print_designer/default_formats.py index f6ed704..f0fbc6a 100644 --- a/print_designer/default_formats.py +++ b/print_designer/default_formats.py @@ -102,6 +102,8 @@ def get_filtered_formats_by_app(app, templates_folder, filter_by=""): def get_formats_from_folders(folders): formats = set() + if not folders.exists(): + return formats for folder in folders.iterdir(): if folder.is_dir() and folder.name in frappe.get_installed_apps(): formats.update(get_json_files(folder)) From fbd488ea9adb139205bd83946495bd7b997d3d9e Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Tue, 2 Apr 2024 05:23:31 +0530 Subject: [PATCH 12/16] fix: typo printFonts is object while refactoring the code, by mistake it was changed to an array. This commit fixes it. --- .../public/js/print_designer/store/ElementStore.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/print_designer/public/js/print_designer/store/ElementStore.js b/print_designer/public/js/print_designer/store/ElementStore.js index 298a926..3fc020f 100644 --- a/print_designer/public/js/print_designer/store/ElementStore.js +++ b/print_designer/public/js/print_designer/store/ElementStore.js @@ -156,6 +156,9 @@ export const useElementStore = defineStore("ElementStore", { const [cleanedBodyElements, bodyFonts] = body; const [cleanedHeaderElements, headerFonts] = header || [[], null]; const [cleanedFooterElements, footerFonts] = footer || [[], null]; + MainStore.printHeaderFonts = headerFonts; + MainStore.printBodyFonts = bodyFonts; + MainStore.printFooterFonts = footerFonts; MainStore.currentFonts.length = 0; MainStore.currentFonts.push( @@ -165,6 +168,7 @@ export const useElementStore = defineStore("ElementStore", { ...(footerFonts || {}), }) ); + debugger; const updatedPage = { ...MainStore.page }; const settingsForSave = { page: updatedPage, @@ -431,12 +435,12 @@ export const useElementStore = defineStore("ElementStore", { }, cleanUpElementsForSave(elements, type) { if (this.checkIfPrintFormatIsEmpty(elements, type)) return; - const fontsArray = []; + const fontsObject = {}; const cleanedElements = []; elements.forEach((container) => { const cleanedContainer = []; container.forEach((element) => { - let newElement = this.childrensSave(element.element, fontsArray); + let newElement = this.childrensSave(element.element, fontsObject); newElement.classes = newElement.classes.filter( (name) => ["inHeaderFooter", "overlappingHeaderFooter"].indexOf(name) == -1 ); @@ -451,7 +455,7 @@ export const useElementStore = defineStore("ElementStore", { }); cleanedElements.push(cleanedContainer); }); - return [cleanedElements, fontsArray]; + return [cleanedElements, fontsObject]; }, checkIfPrintFormatIsEmpty(elements, type) { const MainStore = useMainStore(); From 6e8469ac06cd61e1ab724830125550c676f99157 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Tue, 2 Apr 2024 05:24:37 +0530 Subject: [PATCH 13/16] fix(minor): don't remove cssRules unintended cssRules were removed by mistake so until that can be fixed removing deleteRule code. --- .../components/layout/AppCanvas.vue | 21 ------------------- 1 file changed, 21 deletions(-) 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 4828bc3..c5065ce 100644 --- a/print_designer/public/js/print_designer/components/layout/AppCanvas.vue +++ b/print_designer/public/js/print_designer/components/layout/AppCanvas.vue @@ -465,13 +465,6 @@ onMounted(() => { watchEffect(() => { if (MainStore.screenStyleSheet) { - if (MainStore.screenStyleSheet.CssRuleIndex != null) { - try { - MainStore.screenStyleSheet.deleteRule(MainStore.screenStyleSheet.CssRuleIndex); - } catch (error) { - console.warn("Error Deleting Rule", error); - } - } MainStore.screenStyleSheet.CssRuleIndex = MainStore.addStylesheetRules([ [ ":root, ::after, ::before", @@ -530,13 +523,6 @@ onMounted(() => { }); 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, @@ -568,13 +554,6 @@ 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", From 7c31f7da57acf57aca452f694bd06909e3aff8a1 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Tue, 2 Apr 2024 10:37:01 +0530 Subject: [PATCH 14/16] fix: handle race condition in preview image image upload sometime happen before the format is saved and sometimes after. handled both cases based on isFormatSaving flag. --- .../js/print_designer/store/ElementStore.js | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/print_designer/public/js/print_designer/store/ElementStore.js b/print_designer/public/js/print_designer/store/ElementStore.js index 3fc020f..1ca42f2 100644 --- a/print_designer/public/js/print_designer/store/ElementStore.js +++ b/print_designer/public/js/print_designer/store/ElementStore.js @@ -40,7 +40,7 @@ export const useElementStore = defineStore("ElementStore", { // This is modified version of upload function used in frappe/FileUploader.vue async upload_file(file) { const MainStore = useMainStore(); - + MainStore.print_designer_preview_img = null; return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(); xhr.onreadystatechange = () => { @@ -48,6 +48,21 @@ export const useElementStore = defineStore("ElementStore", { if (xhr.status === 200) { try { r = JSON.parse(xhr.responseText); + if (r.message.doctype === "File") { + file_url = r.message.file_url; + // if format is not saved then update the preview image value with it + if (MainStore.isFormatSaving) { + MainStore.print_designer_preview_img = file_url; + } else { + // if format is already saved then update the print_designer_preview_img field + frappe.db.set_value( + "Print Format", + MainStore.printDesignName, + "print_designer_preview_img", + file_url + ); + } + } } catch (e) { r = xhr.responseText; } @@ -92,7 +107,7 @@ export const useElementStore = defineStore("ElementStore", { const options = { backgroundColor: "#ffffff", - height: MainStore.page.height / 2, + height: MainStore.page.height, width: MainStore.page.width, }; const print_stylesheet = document.createElement("style"); @@ -168,7 +183,6 @@ export const useElementStore = defineStore("ElementStore", { ...(footerFonts || {}), }) ); - debugger; const updatedPage = { ...MainStore.page }; const settingsForSave = { page: updatedPage, @@ -217,27 +231,17 @@ export const useElementStore = defineStore("ElementStore", { dimensions: footerDimensions, }, }); + MainStore.isFormatSaving = true; await this.generatePreview(); objectToSave.print_designer_print_format = PrintFormatData; - const filter = { - attached_to_doctype: "Print Format", - attached_to_name: MainStore.printDesignName, - attached_to_field: "print_designer_preview_img", - }; - // update filename from file - let print_designer_preview_img = await frappe.db.get_value("File", filter, "file_url"); - if (print_designer_preview_img.message.file_url) { - objectToSave.print_designer_preview_img = - print_designer_preview_img.message.file_url; - } else { - objectToSave.print_designer_preview_img = null; - } + objectToSave.print_designer_preview_img = MainStore.print_designer_preview_img; if (MainStore.isOlderSchema("1.1.0")) { await this.printFormatCopyOnOlderSchema(objectToSave); } else { await frappe.db.set_value("Print Format", MainStore.printDesignName, objectToSave); + MainStore.isFormatSaving = false; frappe.show_alert( { message: `Print Format Saved Successfully`, From a1e112a6da4ef27f4bc0056f79ac83382bca2285 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Tue, 2 Apr 2024 23:52:28 +0530 Subject: [PATCH 15/16] chore: defer image import until fw pr is merged revert this after https://github.com/frappe/frappe/pull/25779 is released in v15 --- print_designer/default_formats.py | 36 +++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/print_designer/default_formats.py b/print_designer/default_formats.py index f0fbc6a..2e0e9f9 100644 --- a/print_designer/default_formats.py +++ b/print_designer/default_formats.py @@ -65,29 +65,29 @@ def install_default_formats(app, filter_by="", load_pd_formats=True): app=app, templates_folder=pd_folder[0], filter_by=filter_by ) - preview_files = [f for f in print_formats if f.name.endswith("-preview.json")] + # preview_files = [f for f in print_formats if f.name.endswith("-preview.json")] print_formats = [f for f in print_formats if not f.name.endswith("-preview.json")] for json_file_path in print_formats: import_file_by_path(path=json_file_path) frappe.db.commit() - - for json_file_path in preview_files: - import_file_by_path(path=json_file_path, pre_process=update_preview_img) - frappe.db.commit() - - for pf in frappe.db.get_all("Print Format", filters={"standard": "Yes", "print_designer": 1}): - updated_url = frappe.db.get_value( - "File", - { - "attached_to_doctype": "Print Format", - "attached_to_name": pf.name, - "attached_to_field": "print_designer_preview_img", - }, - "file_url", - ) - if updated_url: - frappe.set_value("Print Format", pf.name, "print_designer_preview_img", updated_url) + # TODO: enable this after this is released in v15 https://github.com/frappe/frappe/pull/25779 + # for json_file_path in preview_files: + # import_file_by_path(path=json_file_path, pre_process=update_preview_img) + # frappe.db.commit() + + # for pf in frappe.db.get_all("Print Format", filters={"standard": "Yes", "print_designer": 1}): + # updated_url = frappe.db.get_value( + # "File", + # { + # "attached_to_doctype": "Print Format", + # "attached_to_name": pf.name, + # "attached_to_field": "print_designer_preview_img", + # }, + # "file_url", + # ) + # if updated_url: + # frappe.set_value("Print Format", pf.name, "print_designer_preview_img", updated_url) def get_filtered_formats_by_app(app, templates_folder, filter_by=""): From 6afc817441d5f2258833b908458e7dc6e6fd1405 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Wed, 3 Apr 2024 11:00:13 +0530 Subject: [PATCH 16/16] fix: allow user to choose the name on copy when we create copy when user updates the formats with old schema. it was implicitly setting the name with copy + number suffix. now we set that name as default and prompt user if they want to change the name. --- .../js/print_designer/store/ElementStore.js | 106 +++++++++++++----- 1 file changed, 75 insertions(+), 31 deletions(-) diff --git a/print_designer/public/js/print_designer/store/ElementStore.js b/print_designer/public/js/print_designer/store/ElementStore.js index 1ca42f2..eede374 100644 --- a/print_designer/public/js/print_designer/store/ElementStore.js +++ b/print_designer/public/js/print_designer/store/ElementStore.js @@ -614,56 +614,100 @@ export const useElementStore = defineStore("ElementStore", { 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 (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" + "It is not compatible with current version so instead make copy of this format using new version" ); message += "
"; - message += __(`Do you want to save it as ${newName} ?`); + message += __(`Do you want to save copy of it ?`); 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); + this.promptUserForNewFormatName(objectToSave); }, async () => { + frappe.show_alert( + { + message: `Print Format not saved`, + indicator: "red", + }, + 5 + ); + // intentionally throwing error to stop the saving the format throw new Error(__("Print Format not saved")); } ); }, + async promptUserForNewFormatName(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 (pf_exists) continue; + nextFormatCopyNumber = i; + break; + } + // This is just default value for the new print format name + const print_format_name = + MainStore.printDesignName + + " ( Copy " + + (nextFormatCopyNumber ? nextFormatCopyNumber : "") + + " )"; + let d = new frappe.ui.Dialog({ + title: "New Print Format", + fields: [ + { + label: "Name", + fieldname: "print_format_name", + fieldtype: "Data", + reqd: 1, + default: print_format_name, + }, + ], + size: "small", + primary_action_label: "Save", + static: true, + async primary_action(values) { + try { + await frappe.db.insert({ + doctype: "Print Format", + name: values.print_format_name, + 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, + }); + d.hide(); + frappe.set_route("print-designer", values.print_format_name); + } catch (error) { + console.error(error); + } + }, + }); + d.get_close_btn().on("click", () => { + frappe.show_alert( + { + message: `Print Format not saved`, + indicator: "red", + }, + 5 + ); + }); + d.show(); + }, handleDynamicContent(element) { const MainStore = useMainStore(); if (