Skip to content

Commit

Permalink
feat!: dynamic row containers 🎉 (#205)
Browse files Browse the repository at this point in the history
  • Loading branch information
maharshivpatel authored Apr 1, 2024
2 parents 921cc72 + 2edcc5a commit 25f883a
Show file tree
Hide file tree
Showing 31 changed files with 1,153 additions and 421 deletions.
7 changes: 7 additions & 0 deletions print_designer/custom_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
5 changes: 4 additions & 1 deletion print_designer/patches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
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
18 changes: 18 additions & 0 deletions print_designer/patches/introduce_dynamic_containers.py
Original file line number Diff line number Diff line change
@@ -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)
19 changes: 19 additions & 0 deletions print_designer/patches/introduce_dynamic_height.py
Original file line number Diff line number Diff line change
@@ -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"],
)
23 changes: 23 additions & 0 deletions print_designer/patches/remove_unused_rectangle_gs_properties.py
Original file line number Diff line number Diff line change
@@ -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))
44 changes: 40 additions & 4 deletions print_designer/pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
"<!-- user_generated_jinja_code -->", args["settings"].get("userProvidedJinja", "")
Expand All @@ -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]
Original file line number Diff line number Diff line change
@@ -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 -%}

<div
style="position: absolute; top:{{ element.startY }}px; left:{{ element.startX }}px;width:{{ element.width }}px;height:{{ element.height }}px;
{{convert_css(element.style)}}"
class="{{ element.classes | join(' ') }}"
>
<div
style="width:100%;height:100%; {{convert_css(element.style)}}"
class="barcode {{ element.classes | join(' ') }}"
>
{% 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 %}
</div>
</div>
{%- endmacro %}
Original file line number Diff line number Diff line change
@@ -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) -%}
<div style="position:{%- if is_parent_dynamic_height -%}relative{% else %}absolute{%- endif -%}; {%- if not is_parent_dynamic_height -%}top: {%- else -%}margin-top: {%- endif -%}{{ element.startY }}px; left:{{ element.startX }}px;{% if element.isFixedSize %}width:{{ element.width }}px; {%- if not is_parent_dynamic_height -%}height:{{ element.height }}px; {%- endif -%} {% else %} width:fit-content; height:fit-content; white-space:nowrap; max-width: {{ (settings.page.width - settings.page.marginLeft - settings.page.marginRight - element.startX) + 2 }}px;{%endif%}" class="
{{ element.classes | join(' ') }}">
<div style="{% if element.isFixedSize %}width:{{ element.width }}px; {%- if not is_parent_dynamic_height -%}height:{{ element.height }}px; {%- endif -%}{% else %}width:fit-content; height:fit-content; white-space:nowrap; max-width: {{ (settings.page.width - settings.page.marginLeft - settings.page.marginRight - element.startX) + 2 }}px;{%endif%} {{convert_css(element.style)}}"
class="dynamicText {{ element.classes | join(' ') }}">
{% for field in element.dynamicContent %}
<!-- third Arg is row which is not sent outside table -->
{{ span_tag(field, element, {}, send_to_jinja)}}
{% endfor %}
</div>
</div>
{%- endmacro %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{% macro image(element) -%}
<div
style="position: absolute; top:{{ element.startY }}px; left:{{ element.startX }}px;width:{{ element.width }}px;height:{{ element.height }}px;
{{convert_css(element.style)}}"
class="image {{ element.classes | join(' ') }}"
>
<div
style="width:100%;height:100%;
background-image: url('{{frappe.get_url()}}{%if element.isDynamic %}{{element.image.value}}{% elif not element.isDynamic%}{{element.image.file_url}}{% endif %}');
"
class="image {{ element.classes | join(' ') }}"
></div>
</div>
{%- endmacro %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{% macro rectangle(element, render_element, send_to_jinja) -%}
<div id="{{ element.id }}" style="position:absolute; top:{{ element.startY }}px; left:{{ element.startX }}px; width:{{ element.width }}px; height:{{ element.height }}px; {{convert_css(element.style)}}"
class="rectangle {{ element.classes | join(' ') }}">
{% if element.childrens %}
{% for object in element.childrens %}
{{ render_element(object, send_to_jinja) }}
{% endfor %}
{% endif %}
</div>
{%- endmacro %}
Original file line number Diff line number Diff line change
@@ -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) -%}
<div style="position:relative; left:{{ element.startX }}px; {%- if not element.isDynamicHeight -%} height:{{ element.height }}px; {%- endif -%} {{convert_css(element.style)}} border: none !important;"
class="rectangle {{ element.classes | join(' ') }}">
{% if element.childrens %}
{% for object in element.childrens %}
{{ render_element(object, send_to_jinja, element.get("isDynamicHeight", false)) }}
{% endfor %}
{% endif %}
</div>
{%- endmacro %}
Original file line number Diff line number Diff line change
@@ -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 %}
Original file line number Diff line number Diff line change
@@ -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 %}
Original file line number Diff line number Diff line change
@@ -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) %}
<link rel="preconnect" href="https://fonts.gstatic.com" />
{% if settings.printHeaderFonts %}
<link
href="https://fonts.googleapis.com/css2?family={{getFontStyles(settings.printHeaderFonts)}}"
rel="stylesheet"
id="headerFontsLinkTag"
/>
{%endif%}
{% if settings.printBodyFonts %}
<link
href="https://fonts.googleapis.com/css2?family={{getFontStyles(settings.printBodyFonts)}}"
rel="stylesheet"
id="bodyFontsLinkTag"
/>
{%endif%}
{% if settings.printFooterFonts %}
<link
href="https://fonts.googleapis.com/css2?family={{getFontStyles(settings.printFooterFonts)}}"
rel="stylesheet"
id="footerFontsLinkTag"
/>
{%endif%}
{% endmacro %}
Original file line number Diff line number Diff line change
@@ -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 -%}

<!-- third Arg is row which is not sent outside table -->
{% macro span_tag(field, element, row = {}, send_to_jinja = {}) -%}
{% set span_value = spanvalue(field, element, row, send_to_jinja) %}
<span class="{% if not field.is_static and field.is_labelled %}baseSpanTag{% endif %}">
{% if not field.is_static and field.is_labelled and span_value %}
<span class="{% if row %}printTable{% else %}dynamicText{% endif %} label-text labelSpanTag" style="user-select:auto; {%if element.labelStyle %}{{convert_css(element.labelStyle)}}{%endif%}{%if field.labelStyle %}{{convert_css(field.labelStyle)}}{%endif%} white-space:nowrap; ">
{{ _(field.label) }}
</span>
{% endif %}
<span class="dynamic-span {% if not field.is_static and field.is_labelled %}valueSpanTag{%endif%} {{page_class(field)}} }}"
style="{%- if element.style.get('color') -%}{{ convert_css({'color': element.style.get('color')})}}{%- endif -%} {{convert_css(field.style)}} user-select:auto;">
{{ span_value }}
</span>
{% if field.nextLine %}
<br/>
{% endif %}
</span>
{%- endmacro %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!-- third Arg in render_user_text is row which is not sent outside table -->
{% macro statictext(element, send_to_jinja, dynamic_containers) -%}
<div style="position:{%- if dynamic_containers -%}relative{% else %}absolute{%- endif -%}; top:{{ element.startY }}px; left:{{ element.startX }}px;{% if element.isFixedSize %}width:{{ element.width }}px;{%- if not is_parent_dynamic_height -%}height:{{ element.height }}px; {%- endif -%}{% else %}width:fit-content; height:fit-content; max-width: {{ (settings.page.width - settings.page.marginLeft - settings.page.marginRight - element.startX) + 2 }}px; white-space:nowrap; {%endif%}" class="
{{ element.classes | join(' ') }}">
<p style="{% if element.isFixedSize %}width:{{ element.width }}px; {%- if not is_parent_dynamic_height -%}height:{{ element.height }}px; {%- endif -%}{% else %}width:fit-content; height:fit-content; max-width: {{ (settings.page.width - settings.page.marginLeft - settings.page.marginRight - element.startX ) + 2 }}px; white-space:nowrap;{%endif%} {{convert_css(element.style)}}"
class="staticText {{ element.classes | join(' ') }}">
{% if element.parseJinja %}
{{ render_user_text(element.content, doc, {}, send_to_jinja).get("message", "") }}
{% else %}
{{_(element.content)}}
{% endif %}
</p>
</div>
{%- endmacro %}
Loading

0 comments on commit 25f883a

Please sign in to comment.