feat: New Print Format Builder
- Print Format Builder Beta page - Add margin fields in Print Format - Using vuedraggable for drag and drop
This commit is contained in:
parent
c3c4057866
commit
b8fbed0f66
17 changed files with 815 additions and 12 deletions
|
|
@ -19,6 +19,10 @@
|
|||
"html",
|
||||
"raw_commands",
|
||||
"section_break_9",
|
||||
"margin_top",
|
||||
"margin_bottom",
|
||||
"margin_left",
|
||||
"margin_right",
|
||||
"align_labels_right",
|
||||
"show_section_headings",
|
||||
"line_breaks",
|
||||
|
|
@ -31,7 +35,8 @@
|
|||
"section_break_13",
|
||||
"print_format_help",
|
||||
"format_data",
|
||||
"print_format_builder"
|
||||
"print_format_builder",
|
||||
"print_format_builder_beta"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -205,13 +210,43 @@
|
|||
"fieldname": "absolute_value",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Absolute Values"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "print_format_builder_beta",
|
||||
"fieldtype": "Check",
|
||||
"label": "Print Format Builder Beta"
|
||||
},
|
||||
{
|
||||
"default": "15",
|
||||
"fieldname": "margin_top",
|
||||
"fieldtype": "Float",
|
||||
"label": "Margin Top"
|
||||
},
|
||||
{
|
||||
"default": "15",
|
||||
"fieldname": "margin_bottom",
|
||||
"fieldtype": "Float",
|
||||
"label": "Margin Bottom"
|
||||
},
|
||||
{
|
||||
"default": "15",
|
||||
"fieldname": "margin_left",
|
||||
"fieldtype": "Float",
|
||||
"label": "Margin Left"
|
||||
},
|
||||
{
|
||||
"default": "15",
|
||||
"fieldname": "margin_right",
|
||||
"fieldtype": "Float",
|
||||
"label": "Margin Right"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-print",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-01 15:25:46.578863",
|
||||
"modified": "2021-07-11 11:53:52.028982",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Printing",
|
||||
"name": "Print Format",
|
||||
|
|
|
|||
|
|
@ -38,6 +38,10 @@ class PrintFormat(Document):
|
|||
|
||||
def extract_images(self):
|
||||
from frappe.core.doctype.file.file import extract_images_from_html
|
||||
|
||||
if self.print_format_builder_beta:
|
||||
return
|
||||
|
||||
if self.format_data:
|
||||
data = json.loads(self.format_data)
|
||||
for df in data:
|
||||
|
|
|
|||
|
|
@ -258,6 +258,11 @@ frappe.ui.form.PrintView = class {
|
|||
fieldtype: 'Read Only',
|
||||
default: print_format.name || 'Standard',
|
||||
},
|
||||
{
|
||||
label: __('Use the new Print Format Builder Beta'),
|
||||
fieldname: 'beta',
|
||||
fieldtype: 'Check'
|
||||
},
|
||||
],
|
||||
(data) => {
|
||||
frappe.route_options = {
|
||||
|
|
@ -265,6 +270,7 @@ frappe.ui.form.PrintView = class {
|
|||
doctype: this.frm.doctype,
|
||||
name: data.print_format_name,
|
||||
based_on: data.based_on,
|
||||
beta: data.beta
|
||||
};
|
||||
frappe.set_route('print-format-builder');
|
||||
this.print_sel.val(data.print_format_name);
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ frappe.pages['print-format-builder'].on_page_show = function(wrapper) {
|
|||
});
|
||||
} else if(frappe.route_options) {
|
||||
if(frappe.route_options.make_new) {
|
||||
let { doctype, name, based_on } = frappe.route_options;
|
||||
let { doctype, name, based_on, beta } = frappe.route_options;
|
||||
frappe.route_options = null;
|
||||
frappe.print_format_builder.setup_new_print_format(doctype, name, based_on);
|
||||
frappe.print_format_builder.setup_new_print_format(doctype, name, based_on, beta);
|
||||
} else {
|
||||
frappe.print_format_builder.print_format = frappe.route_options.doc;
|
||||
frappe.route_options = null;
|
||||
|
|
@ -126,18 +126,22 @@ frappe.PrintFormatBuilder = class PrintFormatBuilder {
|
|||
|
||||
});
|
||||
}
|
||||
setup_new_print_format(doctype, name, based_on) {
|
||||
setup_new_print_format(doctype, name, based_on, beta) {
|
||||
frappe.call({
|
||||
method: 'frappe.printing.page.print_format_builder.print_format_builder.create_custom_format',
|
||||
args: {
|
||||
doctype: doctype,
|
||||
name: name,
|
||||
based_on: based_on
|
||||
based_on: based_on,
|
||||
beta: Boolean(beta)
|
||||
},
|
||||
callback: (r) => {
|
||||
if(!r.exc) {
|
||||
if(r.message) {
|
||||
this.print_format = r.message;
|
||||
if(r.message) {
|
||||
let print_format = r.message;
|
||||
if (print_format.print_format_builder_beta) {
|
||||
frappe.set_route('print-format-builder-beta', print_format.name);
|
||||
} else {
|
||||
this.print_format = print_format;
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
import frappe
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_custom_format(doctype, name, based_on='Standard'):
|
||||
def create_custom_format(doctype, name, based_on='Standard', beta=False):
|
||||
doc = frappe.new_doc('Print Format')
|
||||
doc.doc_type = doctype
|
||||
doc.name = name
|
||||
doc.print_format_builder = 1
|
||||
beta = frappe.parse_json(beta)
|
||||
|
||||
if beta:
|
||||
doc.print_format_builder_beta = 1
|
||||
else:
|
||||
doc.print_format_builder = 1
|
||||
doc.format_data = frappe.db.get_value('Print Format', based_on, 'format_data') \
|
||||
if based_on != 'Standard' else None
|
||||
doc.insert()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
.layout-main-section-wrapper {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
frappe.pages["print-format-builder-beta"].on_page_load = function(wrapper) {
|
||||
var page = frappe.ui.make_app_page({
|
||||
parent: wrapper,
|
||||
title: __("Print Format Builder"),
|
||||
single_column: true
|
||||
});
|
||||
|
||||
function load_print_format_builder_beta() {
|
||||
let route = frappe.get_route();
|
||||
let $parent = $(wrapper).find(".layout-main-section");
|
||||
$parent.empty();
|
||||
|
||||
if (route.length > 1) {
|
||||
frappe.require("print_format_builder.bundle.js").then(() => {
|
||||
frappe.print_format_builder = new frappe.ui.PrintFormatBuilder({
|
||||
wrapper: $parent,
|
||||
page,
|
||||
print_format: route[1]
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
load_print_format_builder_beta();
|
||||
|
||||
// hot reload in development
|
||||
if (frappe.boot.developer_mode) {
|
||||
frappe.hot_update = frappe.hot_update || [];
|
||||
frappe.hot_update.push(load_print_format_builder_beta);
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"content": null,
|
||||
"creation": "2021-07-10 12:22:16.138485",
|
||||
"docstatus": 0,
|
||||
"doctype": "Page",
|
||||
"idx": 0,
|
||||
"modified": "2021-07-10 12:22:16.138485",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Printing",
|
||||
"name": "print-format-builder-beta",
|
||||
"owner": "Administrator",
|
||||
"page_name": "Print Format Builder Beta",
|
||||
"roles": [
|
||||
{
|
||||
"role": "System Manager"
|
||||
}
|
||||
],
|
||||
"script": null,
|
||||
"standard": "Yes",
|
||||
"style": null,
|
||||
"system_page": 0
|
||||
}
|
||||
73
frappe/public/js/print_format_builder/PrintFormat.vue
Normal file
73
frappe/public/js/print_format_builder/PrintFormat.vue
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
<template>
|
||||
<div class="print-format-main" :style="rootStyles">
|
||||
<draggable
|
||||
v-model="layout.sections"
|
||||
group="sections"
|
||||
filter=".section-columns, .column, .field"
|
||||
:animation="200"
|
||||
>
|
||||
<PrintFormatSection
|
||||
v-for="(section, i) in layout.sections"
|
||||
:key="i"
|
||||
:section="section"
|
||||
@add_section_above="add_section_above(section)"
|
||||
/>
|
||||
</draggable>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from "vuedraggable";
|
||||
import PrintFormatSection from "./PrintFormatSection.vue";
|
||||
|
||||
export default {
|
||||
name: "PrintFormat",
|
||||
props: ["print_format", "meta", "layout"],
|
||||
components: {
|
||||
draggable,
|
||||
PrintFormatSection,
|
||||
},
|
||||
computed: {
|
||||
rootStyles() {
|
||||
let {
|
||||
margin_top = 0,
|
||||
margin_bottom = 0,
|
||||
margin_left = 0,
|
||||
margin_right = 0,
|
||||
} = this.print_format;
|
||||
return {
|
||||
padding: `${margin_top}mm ${margin_right}mm ${margin_bottom}mm ${margin_left}mm`,
|
||||
width: "210mm",
|
||||
minHeight: "297mm",
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
add_section_above(section) {
|
||||
let sections = [];
|
||||
for (let _section of this.layout.sections) {
|
||||
if (_section === section) {
|
||||
sections.push({
|
||||
label: "",
|
||||
columns: [
|
||||
{ label: "", fields: [] },
|
||||
{ label: "", fields: [] },
|
||||
],
|
||||
});
|
||||
}
|
||||
sections.push(_section);
|
||||
}
|
||||
this.$set(this.layout, "sections", sections);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.print-format-main {
|
||||
margin-left: auto;
|
||||
background-color: white;
|
||||
box-shadow: var(--shadow-lg);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
</style>
|
||||
110
frappe/public/js/print_format_builder/PrintFormatBuilder.vue
Normal file
110
frappe/public/js/print_format_builder/PrintFormatBuilder.vue
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
<template>
|
||||
<div class="layout-main-section row" v-if="print_format && meta && layout">
|
||||
<div class="col-3">
|
||||
<PrintFormatControls
|
||||
:print_format="print_format"
|
||||
:meta="meta"
|
||||
@update="update($event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="print-format-container col-9">
|
||||
<PrintFormat :print_format="print_format" :meta="meta" :layout="layout" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PrintFormat from "./PrintFormat.vue";
|
||||
import PrintFormatControls from "./PrintFormatControls.vue";
|
||||
import { create_default_layout } from "./utils";
|
||||
|
||||
export default {
|
||||
name: "PrintFormatBuilder",
|
||||
props: ["print_format_name"],
|
||||
components: {
|
||||
PrintFormat,
|
||||
PrintFormatControls,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
print_format: null,
|
||||
doctype: null,
|
||||
meta: null,
|
||||
layout: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.fetch();
|
||||
},
|
||||
methods: {
|
||||
fetch() {
|
||||
frappe.dom.freeze(__("Loading..."));
|
||||
frappe.model.clear_doc("Print Format", this.print_format_name);
|
||||
frappe.model.with_doc("Print Format", this.print_format_name, () => {
|
||||
this.print_format = frappe.get_doc(
|
||||
"Print Format",
|
||||
this.print_format_name
|
||||
);
|
||||
frappe.model.with_doctype(this.print_format.doc_type, () => {
|
||||
this.meta = frappe.get_meta(this.print_format.doc_type);
|
||||
this.layout = this.get_layout();
|
||||
frappe.dom.unfreeze();
|
||||
});
|
||||
});
|
||||
},
|
||||
update({ fieldname, value }) {
|
||||
this.$set(this.print_format, fieldname, value);
|
||||
},
|
||||
save_changes() {
|
||||
frappe.dom.freeze();
|
||||
|
||||
this.layout.sections = this.layout.sections
|
||||
.map((section) => {
|
||||
section.columns = section.columns.map((column) => {
|
||||
column.fields = column.fields.filter((df) => !df.remove);
|
||||
return column;
|
||||
});
|
||||
return section.remove ? null : section;
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
this.print_format.format_data = JSON.stringify(this.layout);
|
||||
|
||||
frappe
|
||||
.call("frappe.client.save", {
|
||||
doc: this.print_format,
|
||||
})
|
||||
.then(() => {
|
||||
this.fetch();
|
||||
})
|
||||
.always(() => {
|
||||
frappe.dom.unfreeze();
|
||||
});
|
||||
},
|
||||
reset_changes() {
|
||||
this.fetch();
|
||||
},
|
||||
get_layout() {
|
||||
if (this.print_format) {
|
||||
if (!this.print_format.format_data) {
|
||||
return create_default_layout(this.meta);
|
||||
}
|
||||
if (typeof this.print_format.format_data == "string") {
|
||||
return JSON.parse(this.print_format.format_data);
|
||||
}
|
||||
return this.print_format.format_data;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.print-format-container {
|
||||
height: calc(100vh - 140px);
|
||||
overflow-y: auto;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 4rem;
|
||||
}
|
||||
</style>
|
||||
163
frappe/public/js/print_format_builder/PrintFormatControls.vue
Normal file
163
frappe/public/js/print_format_builder/PrintFormatControls.vue
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
<template>
|
||||
<div class="layout-side-section">
|
||||
<div class="form-sidebar">
|
||||
<div class="sidebar-menu">
|
||||
<div class="sidebar-label">{{ __("Page Margins") }}</div>
|
||||
<div class="margin-controls">
|
||||
<div class="form-group" v-for="df in margins" :key="df.fieldname">
|
||||
<div class="clearfix">
|
||||
<label class="control-label"> {{ df.label }} </label>
|
||||
</div>
|
||||
<div class="control-input-wrapper">
|
||||
<div class="control-input">
|
||||
<input
|
||||
type="number"
|
||||
class="form-control form-control-sm"
|
||||
:value="print_format[df.fieldname]"
|
||||
@change="(e) => update_margin(df.fieldname, e.target.value)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-menu">
|
||||
<div class="sidebar-label">{{ __("Fields") }}</div>
|
||||
<input
|
||||
class="form-control form-control-sm mb-2"
|
||||
type="text"
|
||||
:placeholder="__('Search fields')"
|
||||
v-model="search_text"
|
||||
/>
|
||||
<draggable
|
||||
class="fields-container"
|
||||
:list="fields"
|
||||
:group="{ name: 'fields', pull: 'clone', put: false }"
|
||||
:sort="false"
|
||||
>
|
||||
<div class="field" v-for="df in fields" :key="df.fieldname">
|
||||
{{ df.label }}
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from "vuedraggable";
|
||||
import { get_table_columns } from "./utils";
|
||||
|
||||
export default {
|
||||
name: "PrintFormatControls",
|
||||
props: ["print_format", "meta"],
|
||||
data() {
|
||||
return {
|
||||
search_text: "",
|
||||
};
|
||||
},
|
||||
components: {
|
||||
draggable,
|
||||
},
|
||||
methods: {
|
||||
update_margin(fieldname, value) {
|
||||
value = parseFloat(value);
|
||||
if (value < 0) {
|
||||
value = 0;
|
||||
}
|
||||
this.$emit("update", { fieldname, value });
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
margins() {
|
||||
return [
|
||||
{ label: __("Top"), fieldname: "margin_top" },
|
||||
{ label: __("Bottom"), fieldname: "margin_bottom" },
|
||||
{ label: __("Left"), fieldname: "margin_left" },
|
||||
{ label: __("Right"), fieldname: "margin_right" },
|
||||
];
|
||||
},
|
||||
fields() {
|
||||
let fields = this.meta.fields
|
||||
.filter((df) => {
|
||||
if (["Section Break", "Column Break"].includes(df.fieldtype)) {
|
||||
return false;
|
||||
}
|
||||
if (this.search_text) {
|
||||
if (df.fieldname.includes(this.search_text)) {
|
||||
return true;
|
||||
}
|
||||
if (df.label && df.label.includes(this.search_text)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.map((df) => {
|
||||
let out = {
|
||||
label: df.label,
|
||||
fieldname: df.fieldname,
|
||||
options: df.options,
|
||||
reqd: df.reqd,
|
||||
};
|
||||
if (df.fieldtype == "Table") {
|
||||
out.table_columns = get_table_columns(df);
|
||||
}
|
||||
return out;
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
label: "Custom HTML",
|
||||
fieldname: "custom_html",
|
||||
fieldtype: "HTML",
|
||||
html: "",
|
||||
},
|
||||
...fields,
|
||||
];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.margin-controls {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.margin-controls .form-control {
|
||||
background: white;
|
||||
}
|
||||
|
||||
.margin-controls > .form-group + .form-group {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.fields-container {
|
||||
max-height: calc(100vh - 22rem);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.field {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
background-color: var(--bg-light-gray);
|
||||
border-radius: var(--border-radius);
|
||||
border: 1px dashed var(--gray-400);
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: var(--text-sm);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.field:not(:first-child) {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.sidebar-menu:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
203
frappe/public/js/print_format_builder/PrintFormatSection.vue
Normal file
203
frappe/public/js/print_format_builder/PrintFormatSection.vue
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
<template>
|
||||
<div class="print-format-section" v-if="!section.remove">
|
||||
<div class="section-header">
|
||||
<input
|
||||
class="input-section-label w-50"
|
||||
type="text"
|
||||
:placeholder="__('Section Title')"
|
||||
v-model="section.label"
|
||||
/>
|
||||
|
||||
<div class="dropdown">
|
||||
<button
|
||||
class="btn btn-xs btn-section dropdown-button"
|
||||
data-toggle="dropdown"
|
||||
>
|
||||
<svg class="icon icon-sm">
|
||||
<use xlink:href="#icon-dot-horizontal"></use>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right" role="menu">
|
||||
<button
|
||||
class="dropdown-item"
|
||||
@click="add_column"
|
||||
v-if="section.columns.length < 4"
|
||||
>
|
||||
{{ __("Add column") }}
|
||||
</button>
|
||||
<button
|
||||
class="dropdown-item"
|
||||
@click="remove_column"
|
||||
v-if="section.columns.length > 1"
|
||||
>
|
||||
{{ __("Remove column") }}
|
||||
</button>
|
||||
<button class="dropdown-item" @click="$emit('add_section_above')">
|
||||
{{ __("Add section above") }}
|
||||
</button>
|
||||
<button class="dropdown-item" @click="$set(section, 'remove', true)">
|
||||
{{ __("Remove section") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row section-columns">
|
||||
<div class="column col" v-for="(column, i) in section.columns" :key="i">
|
||||
<draggable
|
||||
class="drag-container"
|
||||
v-model="column.fields"
|
||||
group="fields"
|
||||
:animation="150"
|
||||
>
|
||||
<button
|
||||
class="field"
|
||||
v-for="df in get_fields(column)"
|
||||
:key="df.fieldname"
|
||||
>
|
||||
<div>
|
||||
{{ df.label }}
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-xs btn-remove-field"
|
||||
@click="$set(df, 'remove', true)"
|
||||
>
|
||||
<svg class="icon icon-sm">
|
||||
<use xlink:href="#icon-close"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</button>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from "vuedraggable";
|
||||
|
||||
export default {
|
||||
name: "PrintFormatSection",
|
||||
props: ["section"],
|
||||
components: {
|
||||
draggable,
|
||||
},
|
||||
methods: {
|
||||
add_column() {
|
||||
if (this.section.columns.length < 4) {
|
||||
this.section.columns.push({
|
||||
label: "",
|
||||
fields: [],
|
||||
});
|
||||
}
|
||||
},
|
||||
remove_column() {
|
||||
if (this.section.columns.length <= 1) return;
|
||||
|
||||
let columns = this.section.columns.slice();
|
||||
let last_column_fields = columns.slice(-1)[0].fields.slice();
|
||||
let index = columns.length - 1;
|
||||
columns = columns.slice(0, index);
|
||||
let last_column = columns[index - 1];
|
||||
last_column.fields = [...last_column.fields, ...last_column_fields];
|
||||
|
||||
this.$set(this.section, "columns", columns);
|
||||
},
|
||||
get_fields(column) {
|
||||
return column.fields.filter((df) => !df.remove);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.print-format-section {
|
||||
background-color: white;
|
||||
border: 1px solid var(--dark-border-color);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.input-section-label {
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--border-radius);
|
||||
font-size: var(--text-md);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.input-section-label:focus {
|
||||
border-color: var(--border-color);
|
||||
outline: none;
|
||||
background-color: var(--control-bg);
|
||||
}
|
||||
|
||||
.input-section-label::placeholder {
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.btn-section {
|
||||
padding: var(--padding-xs);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.btn-section:hover {
|
||||
background-color: var(--bg-light-gray);
|
||||
}
|
||||
|
||||
.print-format-section:not(:first-child) {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.section-columns {
|
||||
margin-left: -8px;
|
||||
margin-right: -8px;
|
||||
}
|
||||
|
||||
.column {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.field {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
background-color: var(--bg-light-gray);
|
||||
border-radius: var(--border-radius);
|
||||
border: 1px dashed var(--gray-400);
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.field:not(:first-child) {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-remove-field {
|
||||
opacity: 0;
|
||||
padding: 2px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.btn-remove-field:hover {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.field:hover .btn-remove-field {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.drag-container {
|
||||
height: 100%;
|
||||
min-height: 2rem;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import PrintFormatBuilderComponent from "./PrintFormatBuilder.vue";
|
||||
|
||||
class PrintFormatBuilder {
|
||||
constructor({ wrapper, page, print_format }) {
|
||||
this.$wrapper = $(wrapper);
|
||||
this.page = page;
|
||||
this.print_format = print_format;
|
||||
this.page.set_title(__("Editing {0}", [this.print_format]));
|
||||
this.page.set_primary_action(__("Save changes"), () => {
|
||||
this.$component.save_changes();
|
||||
});
|
||||
this.page.set_secondary_action(__("Reset changes"), () => {
|
||||
this.$component.reset_changes();
|
||||
});
|
||||
|
||||
let $vm = new Vue({
|
||||
el: this.$wrapper.get(0),
|
||||
render: h =>
|
||||
h(PrintFormatBuilderComponent, {
|
||||
props: {
|
||||
print_format_name: print_format
|
||||
}
|
||||
})
|
||||
});
|
||||
this.$component = $vm.$children[0];
|
||||
}
|
||||
}
|
||||
|
||||
frappe.provide("frappe.ui");
|
||||
frappe.ui.PrintFormatBuilder = PrintFormatBuilder;
|
||||
export default PrintFormatBuilder;
|
||||
100
frappe/public/js/print_format_builder/utils.js
Normal file
100
frappe/public/js/print_format_builder/utils.js
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
export function create_default_layout(meta) {
|
||||
let layout = {
|
||||
sections: []
|
||||
};
|
||||
|
||||
let section = null,
|
||||
column = null;
|
||||
|
||||
function set_column(df) {
|
||||
if (!section) {
|
||||
set_section();
|
||||
}
|
||||
column = get_new_column(df);
|
||||
section.columns.push(column);
|
||||
}
|
||||
|
||||
function set_section(df) {
|
||||
section = get_new_section(df);
|
||||
column = null;
|
||||
layout.sections.push(section);
|
||||
}
|
||||
|
||||
function get_new_section(df) {
|
||||
if (!df) {
|
||||
df = { label: "" };
|
||||
}
|
||||
return {
|
||||
label: df.label || "",
|
||||
columns: []
|
||||
};
|
||||
}
|
||||
|
||||
function get_new_column(df) {
|
||||
if (!df) {
|
||||
df = { label: "" };
|
||||
}
|
||||
return {
|
||||
label: df.label || "",
|
||||
fields: []
|
||||
};
|
||||
}
|
||||
|
||||
for (let df of meta.fields) {
|
||||
if (df.fieldname) {
|
||||
// make a copy to avoid mutation bugs
|
||||
df = JSON.parse(JSON.stringify(df));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (df.fieldtype === "Section Break") {
|
||||
set_section(df);
|
||||
} else if (df.fieldtype === "Column Break") {
|
||||
set_column(df);
|
||||
} else if (df.label) {
|
||||
if (!column) set_column();
|
||||
|
||||
if (!df.print_hide) {
|
||||
let field = {
|
||||
label: df.label,
|
||||
fieldname: df.fieldname,
|
||||
options: df.options
|
||||
};
|
||||
|
||||
if (df.fieldtype === "Table") {
|
||||
field.table_columns = get_table_columns(df);
|
||||
}
|
||||
|
||||
column.fields.push(field);
|
||||
section.has_fields = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove empty sections
|
||||
layout.sections = layout.sections.filter(section => section.has_fields);
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
export function get_table_columns(df) {
|
||||
let table_columns = [];
|
||||
let table_fields = frappe.get_meta(df.options).fields;
|
||||
|
||||
for (let tf of table_fields) {
|
||||
if (
|
||||
!in_list(["Section Break", "Column Break"], tf.fieldtype) &&
|
||||
!tf.print_hide &&
|
||||
df.label
|
||||
) {
|
||||
table_columns.push({
|
||||
label: tf.label,
|
||||
fieldname: tf.fieldname,
|
||||
options: tf.options,
|
||||
width: tf.width || 0
|
||||
});
|
||||
}
|
||||
}
|
||||
return table_columns;
|
||||
}
|
||||
|
|
@ -61,7 +61,8 @@
|
|||
"superagent": "^3.8.2",
|
||||
"touch": "^3.1.0",
|
||||
"vue": "2.6.12",
|
||||
"vue-router": "^2.0.0"
|
||||
"vue-router": "^2.0.0",
|
||||
"vuedraggable": "^2.24.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chalk": "^2.3.2",
|
||||
|
|
|
|||
12
yarn.lock
12
yarn.lock
|
|
@ -6911,6 +6911,11 @@ socket.io@^2.4.0:
|
|||
socket.io-client "2.4.0"
|
||||
socket.io-parser "~3.4.0"
|
||||
|
||||
sortablejs@1.10.2:
|
||||
version "1.10.2"
|
||||
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.10.2.tgz#6e40364d913f98b85a14f6678f92b5c1221f5290"
|
||||
integrity sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A==
|
||||
|
||||
sortablejs@^1.7.0:
|
||||
version "1.8.3"
|
||||
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.8.3.tgz#5ae908ef96300966e95440a143340f5dd565a0df"
|
||||
|
|
@ -7790,6 +7795,13 @@ vue@2.6.12:
|
|||
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.12.tgz#f5ebd4fa6bd2869403e29a896aed4904456c9123"
|
||||
integrity sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg==
|
||||
|
||||
vuedraggable@^2.24.3:
|
||||
version "2.24.3"
|
||||
resolved "https://registry.yarnpkg.com/vuedraggable/-/vuedraggable-2.24.3.tgz#43c93849b746a24ce503e123d5b259c701ba0d19"
|
||||
integrity sha512-6/HDXi92GzB+Hcs9fC6PAAozK1RLt1ewPTLjK0anTYguXLAeySDmcnqE8IC0xa7shvSzRjQXq3/+dsZ7ETGF3g==
|
||||
dependencies:
|
||||
sortablejs "1.10.2"
|
||||
|
||||
wcwidth@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue