refactor: Print Format Builder Beta code to vue 3
This commit is contained in:
parent
bab42ffd80
commit
45e7e92ffa
12 changed files with 1027 additions and 1079 deletions
|
|
@ -18,78 +18,74 @@
|
|||
:group="df.fieldname"
|
||||
handle=".icon-drag"
|
||||
>
|
||||
<div
|
||||
class="mt-2 row align-center column-row"
|
||||
v-for="column in df.table_columns"
|
||||
>
|
||||
<div class="col-8">
|
||||
<div class="column-label d-flex align-center">
|
||||
<div class="px-2 icon-drag ml-n2">
|
||||
<svg class="icon icon-xs">
|
||||
<use href="#icon-drag"></use>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="mt-1 ml-1">
|
||||
<input
|
||||
class="input-column-label"
|
||||
:class="{ 'text-danger': column.invalid_width }"
|
||||
type="text"
|
||||
v-model="column.label"
|
||||
/>
|
||||
<template #item="{ element }">
|
||||
<div
|
||||
class="mt-2 row align-center column-row"
|
||||
v-for="column in df.table_columns"
|
||||
>
|
||||
<div class="col-8">
|
||||
<div class="column-label d-flex align-center">
|
||||
<div class="px-2 icon-drag ml-n2">
|
||||
<svg class="icon icon-xs">
|
||||
<use href="#icon-drag"></use>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="mt-1 ml-1">
|
||||
<input
|
||||
class="input-column-label"
|
||||
:class="{ 'text-danger': column.invalid_width }"
|
||||
type="text"
|
||||
v-model="column.label"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4 d-flex align-items-center">
|
||||
<input
|
||||
type="number"
|
||||
class="text-right form-control"
|
||||
:class="{ 'text-danger is-invalid': column.invalid_width }"
|
||||
v-model.number="column.width"
|
||||
min="0"
|
||||
max="100"
|
||||
step="5"
|
||||
/>
|
||||
<button
|
||||
class="ml-2 btn btn-xs btn-icon"
|
||||
@click="remove_column(column)"
|
||||
>
|
||||
<svg class="icon icon-sm">
|
||||
<use href="#icon-close"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4 d-flex align-items-center">
|
||||
<input
|
||||
type="number"
|
||||
class="text-right form-control"
|
||||
:class="{ 'text-danger is-invalid': column.invalid_width }"
|
||||
v-model.number="column.width"
|
||||
min="0"
|
||||
max="100"
|
||||
step="5"
|
||||
/>
|
||||
<button
|
||||
class="ml-2 btn btn-xs btn-icon"
|
||||
@click="remove_column(column)"
|
||||
>
|
||||
<svg class="icon icon-sm">
|
||||
<use href="#icon-close"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
import draggable from "vuedraggable";
|
||||
export default {
|
||||
name: "ConfigureColumns",
|
||||
props: ["df"],
|
||||
components: {
|
||||
draggable
|
||||
},
|
||||
methods: {
|
||||
remove_column(column) {
|
||||
this.$set(
|
||||
this.df,
|
||||
"table_columns",
|
||||
this.df.table_columns.filter(_column => _column !== column)
|
||||
);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
help_message() {
|
||||
// prettier-ignore
|
||||
return __("Drag columns to set order. Column width is set in percentage. The total width should not be more than 100. Columns marked in red will be removed.");
|
||||
},
|
||||
total_width() {
|
||||
return this.df.table_columns.reduce((total, tf) => total + tf.width, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// props
|
||||
let props = defineProps(["df"]);
|
||||
|
||||
// methods
|
||||
function remove_column(column) {
|
||||
props.df["table_columns"] = props.df.table_columns.filter(_column => _column !== column)
|
||||
}
|
||||
// computed
|
||||
let help_message = computed(() => {
|
||||
// prettier-ignore
|
||||
return __("Drag columns to set order. Column width is set in percentage. The total width should not be more than 100. Columns marked in red will be removed.");
|
||||
});
|
||||
let total_width = computed(() => {
|
||||
return props.df.table_columns.reduce((total, tf) => total + tf.width, 0);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.icon-drag {
|
||||
cursor: grab;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="field" :title="df.fieldname" @click="editing = true">
|
||||
<div class="field" v-show="!df.remove" :title="df.fieldname" @click="editing = true">
|
||||
<div class="field-controls">
|
||||
<div>
|
||||
<div
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
</div>
|
||||
<input
|
||||
v-else-if="editing && df.fieldtype != 'HTML'"
|
||||
ref="label-input"
|
||||
ref="label_input"
|
||||
class="label-input"
|
||||
type="text"
|
||||
:placeholder="__('Label')"
|
||||
|
|
@ -47,7 +47,7 @@
|
|||
</button>
|
||||
<button
|
||||
class="btn btn-xs btn-icon"
|
||||
@click="$set(df, 'remove', true)"
|
||||
@click="df['remove'] = true"
|
||||
>
|
||||
<svg class="icon icon-sm">
|
||||
<use href="#icon-close"></use>
|
||||
|
|
@ -73,170 +73,150 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import draggable from "vuedraggable";
|
||||
import ConfigureColumnsVue from "./ConfigureColumns.vue";
|
||||
import { storeMixin } from "./store";
|
||||
|
||||
export default {
|
||||
name: "Field",
|
||||
mixins: [storeMixin],
|
||||
props: ["df"],
|
||||
components: {
|
||||
draggable
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editing: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
editing(value) {
|
||||
if (value) {
|
||||
this.$nextTick(() => this.$refs["label-input"].focus());
|
||||
}
|
||||
},
|
||||
"df.table_columns": {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.validate_table_columns();
|
||||
<script setup>
|
||||
import ConfigureColumnsVue from "./ConfigureColumns.vue";
|
||||
import { createApp, ref, nextTick, watch } from "vue";
|
||||
|
||||
// props
|
||||
let props = defineProps(["df"]);
|
||||
|
||||
// variables
|
||||
let editing = ref(false);
|
||||
let label_input = ref(null);
|
||||
|
||||
// methods
|
||||
function edit_html() {
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: __("Edit HTML"),
|
||||
fields: [
|
||||
{
|
||||
label: __("HTML"),
|
||||
fieldname: "html",
|
||||
fieldtype: "Code",
|
||||
options: "HTML"
|
||||
}
|
||||
],
|
||||
primary_action: ({ html }) => {
|
||||
html = frappe.dom.remove_script_and_style(html);
|
||||
props.df["html"] = html;
|
||||
d.hide();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
edit_html() {
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: __("Edit HTML"),
|
||||
fields: [
|
||||
{
|
||||
label: __("HTML"),
|
||||
fieldname: "html",
|
||||
fieldtype: "Code",
|
||||
options: "HTML"
|
||||
}
|
||||
],
|
||||
primary_action: ({ html }) => {
|
||||
html = frappe.dom.remove_script_and_style(html);
|
||||
this.$set(this.df, "html", html);
|
||||
d.hide();
|
||||
}
|
||||
});
|
||||
d.set_value("html", this.df.html);
|
||||
d.show();
|
||||
},
|
||||
configure_columns() {
|
||||
let dialog = new frappe.ui.Dialog({
|
||||
title: __("Configure columns for {0}", [this.df.label]),
|
||||
fields: [
|
||||
{
|
||||
fieldtype: "HTML",
|
||||
fieldname: "columns_area"
|
||||
},
|
||||
{
|
||||
label: "",
|
||||
fieldtype: "Autocomplete",
|
||||
placeholder: __("Add Column"),
|
||||
fieldname: "add_column",
|
||||
options: this.get_all_columns(),
|
||||
onchange: () => {
|
||||
let fieldname = dialog.get_value("add_column");
|
||||
if (fieldname) {
|
||||
let column = this.get_column_to_add(fieldname);
|
||||
if (column) {
|
||||
this.df.table_columns.push(column);
|
||||
this.$set(
|
||||
this.df,
|
||||
"table_columns",
|
||||
this.df.table_columns
|
||||
);
|
||||
dialog.set_value("add_column", "");
|
||||
}
|
||||
}
|
||||
});
|
||||
d.set_value("html", props.df.html);
|
||||
d.show();
|
||||
}
|
||||
function configure_columns() {
|
||||
let dialog = new frappe.ui.Dialog({
|
||||
title: __("Configure columns for {0}", [props.df.label]),
|
||||
fields: [
|
||||
{
|
||||
fieldtype: "HTML",
|
||||
fieldname: "columns_area"
|
||||
},
|
||||
{
|
||||
label: "",
|
||||
fieldtype: "Autocomplete",
|
||||
placeholder: __("Add Column"),
|
||||
fieldname: "add_column",
|
||||
options: get_all_columns(),
|
||||
onchange: () => {
|
||||
let fieldname = dialog.get_value("add_column");
|
||||
if (fieldname) {
|
||||
let column = get_column_to_add(fieldname);
|
||||
if (column) {
|
||||
props.df.table_columns.push(column);
|
||||
props.df["table_columns"] = props.df.table_columns;
|
||||
dialog.set_value("add_column", "");
|
||||
}
|
||||
}
|
||||
],
|
||||
on_page_show: () => {
|
||||
new Vue({
|
||||
el: dialog.get_field("columns_area").$wrapper.get(0),
|
||||
render: h =>
|
||||
h(ConfigureColumnsVue, {
|
||||
props: {
|
||||
df: this.df
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
on_hide: () => {
|
||||
this.$set(
|
||||
this.df,
|
||||
"table_columns",
|
||||
this.df.table_columns.filter(col => !col.invalid_width)
|
||||
);
|
||||
}
|
||||
});
|
||||
dialog.show();
|
||||
},
|
||||
get_all_columns() {
|
||||
let meta = frappe.get_meta(this.df.options);
|
||||
let more_columns = [
|
||||
{
|
||||
label: __("Sr No."),
|
||||
value: "idx"
|
||||
}
|
||||
];
|
||||
return more_columns.concat(
|
||||
meta.fields
|
||||
.map(tf => {
|
||||
if (frappe.model.no_value_type.includes(tf.fieldtype)) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
label: tf.label,
|
||||
value: tf.fieldname
|
||||
};
|
||||
})
|
||||
.filter(Boolean)
|
||||
}
|
||||
],
|
||||
on_page_show: () => {
|
||||
createApp(ConfigureColumnsVue, { df: props.df }).mount(
|
||||
dialog.get_field("columns_area").$wrapper.get(0)
|
||||
);
|
||||
},
|
||||
get_column_to_add(fieldname) {
|
||||
let standard_columns = {
|
||||
idx: {
|
||||
label: __("Sr No."),
|
||||
fieldtype: "Data",
|
||||
fieldname: "idx",
|
||||
width: 10
|
||||
on_hide: () => {
|
||||
props.df["table_columns"] = props.df.table_columns.filter(col => !col.invalid_width);
|
||||
}
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
function get_all_columns() {
|
||||
let meta = frappe.get_meta(props.df.options);
|
||||
let more_columns = [
|
||||
{
|
||||
label: __("Sr No."),
|
||||
value: "idx"
|
||||
}
|
||||
];
|
||||
return more_columns.concat(
|
||||
meta.fields
|
||||
.map(tf => {
|
||||
if (frappe.model.no_value_type.includes(tf.fieldtype)) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
return {
|
||||
label: tf.label,
|
||||
value: tf.fieldname
|
||||
};
|
||||
})
|
||||
.filter(Boolean)
|
||||
);
|
||||
}
|
||||
function get_column_to_add(fieldname) {
|
||||
let standard_columns = {
|
||||
idx: {
|
||||
label: __("Sr No."),
|
||||
fieldtype: "Data",
|
||||
fieldname: "idx",
|
||||
width: 10
|
||||
}
|
||||
};
|
||||
|
||||
if (fieldname in standard_columns) {
|
||||
return standard_columns[fieldname];
|
||||
}
|
||||
if (fieldname in standard_columns) {
|
||||
return standard_columns[fieldname];
|
||||
}
|
||||
|
||||
return {
|
||||
...frappe.meta.get_docfield(this.df.options, fieldname),
|
||||
width: 10
|
||||
};
|
||||
},
|
||||
validate_table_columns() {
|
||||
if (this.df.fieldtype != "Table") return;
|
||||
return {
|
||||
...frappe.meta.get_docfield(props.df.options, fieldname),
|
||||
width: 10
|
||||
};
|
||||
}
|
||||
function validate_table_columns() {
|
||||
if (props.df.fieldtype != "Table") return;
|
||||
|
||||
let columns = this.df.table_columns;
|
||||
let total_width = 0;
|
||||
for (let column of columns) {
|
||||
if (!column.width) {
|
||||
column.width = 10;
|
||||
}
|
||||
total_width += column.width;
|
||||
if (total_width > 100) {
|
||||
column.invalid_width = true;
|
||||
} else {
|
||||
column.invalid_width = false;
|
||||
}
|
||||
}
|
||||
let columns = props.df.table_columns;
|
||||
let total_width = 0;
|
||||
for (let column of columns) {
|
||||
if (!column.width) {
|
||||
column.width = 10;
|
||||
}
|
||||
total_width += column.width;
|
||||
if (total_width > 100) {
|
||||
column.invalid_width = true;
|
||||
} else {
|
||||
column.invalid_width = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// watch
|
||||
watch(editing, (value) => {
|
||||
if (value) {
|
||||
nextTick(() => label_input.value.focus());
|
||||
}
|
||||
});
|
||||
watch(
|
||||
() => props.df.table_columns,
|
||||
() => validate_table_columns(),
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.field {
|
||||
text-align: left;
|
||||
|
|
|
|||
|
|
@ -12,47 +12,52 @@
|
|||
<div v-show="editing" ref="editor"></div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: "HTMLEditor",
|
||||
props: ["value", "button-label"],
|
||||
data() {
|
||||
return {
|
||||
editing: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
toggle_edit() {
|
||||
if (this.editing) {
|
||||
this.$emit("change", this.get_value());
|
||||
this.editing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.editing = true;
|
||||
if (!this.control) {
|
||||
this.control = frappe.ui.form.make_control({
|
||||
parent: this.$refs.editor,
|
||||
df: {
|
||||
fieldname: "editor",
|
||||
fieldtype: "HTML Editor",
|
||||
min_lines: 10,
|
||||
max_lines: 30,
|
||||
change: () => {
|
||||
this.$emit("change", this.get_value());
|
||||
}
|
||||
},
|
||||
render_input: true
|
||||
});
|
||||
}
|
||||
this.control.set_value(this.value);
|
||||
},
|
||||
get_value() {
|
||||
return frappe.dom.remove_script_and_style(this.control.get_value());
|
||||
}
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
|
||||
// props
|
||||
let props = defineProps(["value", "button-label"]);
|
||||
|
||||
// emits
|
||||
let emit = defineEmits(["change"]);
|
||||
|
||||
// variables
|
||||
let editing = ref(false);
|
||||
let control = ref(null);
|
||||
let editor = ref(null);
|
||||
|
||||
// methods
|
||||
function toggle_edit() {
|
||||
if (editing.value) {
|
||||
emit("change", get_value());
|
||||
editing.value = false;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
editing.value = true;
|
||||
if (!control.value) {
|
||||
control.value = frappe.ui.form.make_control({
|
||||
parent: editor.value,
|
||||
df: {
|
||||
fieldname: "editor",
|
||||
fieldtype: "HTML Editor",
|
||||
min_lines: 10,
|
||||
max_lines: 30,
|
||||
change: () => {
|
||||
emit("change", get_value());
|
||||
}
|
||||
},
|
||||
render_input: true
|
||||
});
|
||||
}
|
||||
control.value.set_value(props.value);
|
||||
}
|
||||
function get_value() {
|
||||
return frappe.dom.remove_script_and_style(control.value.get_value());
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.html-editor {
|
||||
position: relative;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<div class="mb-4 d-flex justify-content-between">
|
||||
<div class="d-flex align-items-center">
|
||||
<div
|
||||
v-if="letterhead && $store.edit_letterhead"
|
||||
v-if="letterhead && store.edit_letterhead"
|
||||
class="btn-group"
|
||||
role="group"
|
||||
aria-label="Align Letterhead"
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
</div>
|
||||
<input
|
||||
class="ml-4 custom-range"
|
||||
v-if="letterhead && $store.edit_letterhead"
|
||||
v-if="letterhead && store.edit_letterhead"
|
||||
type="range"
|
||||
name="image-resize"
|
||||
min="20"
|
||||
|
|
@ -41,13 +41,13 @@
|
|||
<div>
|
||||
<button
|
||||
class="ml-2 btn btn-default btn-xs"
|
||||
v-if="letterhead && $store.edit_letterhead"
|
||||
v-if="letterhead && store.edit_letterhead"
|
||||
@click="upload_image"
|
||||
>
|
||||
{{ __("Change Image") }}
|
||||
</button>
|
||||
<button
|
||||
v-if="letterhead && $store.edit_letterhead"
|
||||
v-if="letterhead && store.edit_letterhead"
|
||||
class="ml-2 btn btn-default btn-xs btn-change-letterhead"
|
||||
@click="change_letterhead"
|
||||
>
|
||||
|
|
@ -59,7 +59,7 @@
|
|||
@click="toggle_edit_letterhead"
|
||||
>
|
||||
{{
|
||||
!$store.edit_letterhead
|
||||
!store.edit_letterhead
|
||||
? __("Edit Letter Head")
|
||||
: __("Done")
|
||||
}}
|
||||
|
|
@ -74,13 +74,13 @@
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="letterhead && !$store.edit_letterhead"
|
||||
v-if="letterhead && !store.edit_letterhead"
|
||||
v-html="letterhead.content"
|
||||
></div>
|
||||
<!-- <div v-show="letterhead && $store.edit_letterhead" ref="editor"></div> -->
|
||||
<!-- <div v-show="letterhead && store.edit_letterhead" ref="editor"></div> -->
|
||||
<div
|
||||
class="edit-letterhead"
|
||||
v-if="letterhead && $store.edit_letterhead"
|
||||
v-if="letterhead && store.edit_letterhead"
|
||||
:style="{
|
||||
justifyContent: {
|
||||
Left: 'flex-start',
|
||||
|
|
@ -112,199 +112,183 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { storeMixin } from "./store";
|
||||
|
||||
<script setup>
|
||||
import { useStore } from "./store";
|
||||
import { get_image_dimensions } from "./utils";
|
||||
export default {
|
||||
name: "LetterHeadEditor",
|
||||
mixins: [storeMixin],
|
||||
data() {
|
||||
return {
|
||||
range_input_field: null,
|
||||
aspect_ratio: null
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
letterhead: {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
handler(letterhead) {
|
||||
if (!letterhead) return;
|
||||
if (letterhead.image_width && letterhead.image_height) {
|
||||
let dimension =
|
||||
letterhead.image_width > letterhead.image_height
|
||||
? "width"
|
||||
: "height";
|
||||
let dimension_value = letterhead["image_" + dimension];
|
||||
letterhead.content = `
|
||||
<div style="text-align: ${letterhead.align.toLowerCase()};">
|
||||
<img
|
||||
src="${letterhead.image}"
|
||||
alt="${letterhead.name}"
|
||||
${dimension}="${dimension_value}"
|
||||
style="${dimension}: ${dimension_value}px;">
|
||||
</div>
|
||||
`;
|
||||
import { ref, watch, onMounted } from "vue";
|
||||
|
||||
// mixin
|
||||
let { letterhead, store } = useStore();
|
||||
|
||||
// variables
|
||||
let range_input_field = ref(null);
|
||||
let aspect_ratio = ref(null);
|
||||
let control = ref(null);
|
||||
let editor = ref(null);
|
||||
|
||||
// methods
|
||||
function toggle_edit_letterhead() {
|
||||
if (store.value.edit_letterhead) {
|
||||
store.value.edit_letterhead = false;
|
||||
return;
|
||||
}
|
||||
store.value.edit_letterhead = true;
|
||||
if (!control.value) {
|
||||
control.value = frappe.ui.form.make_control({
|
||||
parent: editor.value,
|
||||
df: {
|
||||
fieldname: "letterhead",
|
||||
fieldtype: "Comment",
|
||||
change: () => {
|
||||
letterhead.value._dirty = true;
|
||||
letterhead.value.content = control.value.get_value();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (!this.letterhead && frappe.boot.sysdefaults.letter_head) {
|
||||
this.set_letterhead(frappe.boot.sysdefaults.letter_head);
|
||||
}
|
||||
|
||||
this.$watch(
|
||||
function() {
|
||||
return this.letterhead
|
||||
? this.letterhead[this.range_input_field]
|
||||
: null;
|
||||
},
|
||||
function() {
|
||||
if (this.aspect_ratio === null) return;
|
||||
render_input: true,
|
||||
only_input: true,
|
||||
no_wrapper: true
|
||||
});
|
||||
}
|
||||
control.value.set_value(letterhead.value.content);
|
||||
};
|
||||
function change_letterhead() {
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: __("Change Letter Head"),
|
||||
fields: [
|
||||
{
|
||||
label: __("Letter Head"),
|
||||
fieldname: "letterhead",
|
||||
fieldtype: "Link",
|
||||
options: "Letter Head"
|
||||
}
|
||||
],
|
||||
primary_action: ({ letterhead }) => {
|
||||
if (letterhead) {
|
||||
set_letterhead(letterhead);
|
||||
}
|
||||
d.hide();
|
||||
}
|
||||
});
|
||||
d.show();
|
||||
};
|
||||
function upload_image() {
|
||||
new frappe.ui.FileUploader({
|
||||
folder: "Home/Attachments",
|
||||
on_success: file_doc => {
|
||||
get_image_dimensions(file_doc.file_url).then(
|
||||
({ width, height }) => {
|
||||
letterhead.value["image"] = file_doc.file_url;
|
||||
let new_width = width;
|
||||
let new_height = height;
|
||||
aspect_ratio.value = width / height;
|
||||
range_input_field.value =
|
||||
aspect_ratio.value > 1
|
||||
? "image_width"
|
||||
: "image_height";
|
||||
|
||||
let update_field =
|
||||
this.range_input_field == "image_width"
|
||||
? "image_height"
|
||||
: "image_width";
|
||||
this.letterhead[update_field] =
|
||||
update_field == "image_width"
|
||||
? this.aspect_ratio * this.letterhead.image_height
|
||||
: this.letterhead.image_width / this.aspect_ratio;
|
||||
if (width > 200) {
|
||||
new_width = 200;
|
||||
new_height = new_width / aspect_ratio.value;
|
||||
}
|
||||
if (height > 80) {
|
||||
new_height = 80;
|
||||
new_width = aspect_ratio.value * new_height;
|
||||
}
|
||||
|
||||
letterhead.value["image_height"] = new_height;
|
||||
letterhead.value["image_width"] = new_width;
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
function set_letterhead(_letterhead) {
|
||||
store.value.change_letterhead(_letterhead).then(() => {
|
||||
get_image_dimensions(letterhead.value.image).then(
|
||||
({ width, height }) => {
|
||||
aspect_ratio.value = width / height;
|
||||
range_input_field.value =
|
||||
aspect_ratio.value > 1
|
||||
? "image_width"
|
||||
: "image_height";
|
||||
}
|
||||
);
|
||||
},
|
||||
methods: {
|
||||
toggle_edit_letterhead() {
|
||||
if (this.$store.edit_letterhead) {
|
||||
this.$store.edit_letterhead = false;
|
||||
return;
|
||||
}
|
||||
this.$store.edit_letterhead = true;
|
||||
if (!this.control) {
|
||||
this.control = frappe.ui.form.make_control({
|
||||
parent: this.$refs.editor,
|
||||
df: {
|
||||
fieldname: "letterhead",
|
||||
fieldtype: "Comment",
|
||||
change: () => {
|
||||
this.letterhead._dirty = true;
|
||||
this.letterhead.content = this.control.get_value();
|
||||
}
|
||||
},
|
||||
render_input: true,
|
||||
only_input: true,
|
||||
no_wrapper: true
|
||||
});
|
||||
}
|
||||
this.control.set_value(this.letterhead.content);
|
||||
},
|
||||
change_letterhead() {
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: __("Change Letter Head"),
|
||||
fields: [
|
||||
{
|
||||
label: __("Letter Head"),
|
||||
fieldname: "letterhead",
|
||||
fieldtype: "Link",
|
||||
options: "Letter Head"
|
||||
}
|
||||
],
|
||||
primary_action: ({ letterhead }) => {
|
||||
if (letterhead) {
|
||||
this.set_letterhead(letterhead);
|
||||
}
|
||||
d.hide();
|
||||
}
|
||||
});
|
||||
d.show();
|
||||
},
|
||||
upload_image() {
|
||||
new frappe.ui.FileUploader({
|
||||
folder: "Home/Attachments",
|
||||
on_success: file_doc => {
|
||||
get_image_dimensions(file_doc.file_url).then(
|
||||
({ width, height }) => {
|
||||
this.$set(
|
||||
this.letterhead,
|
||||
"image",
|
||||
file_doc.file_url
|
||||
);
|
||||
let new_width = width;
|
||||
let new_height = height;
|
||||
this.aspect_ratio = width / height;
|
||||
this.range_input_field =
|
||||
this.aspect_ratio > 1
|
||||
? "image_width"
|
||||
: "image_height";
|
||||
|
||||
if (width > 200) {
|
||||
new_width = 200;
|
||||
new_height = new_width / aspect_ratio;
|
||||
}
|
||||
if (height > 80) {
|
||||
new_height = 80;
|
||||
new_width = aspect_ratio * new_height;
|
||||
}
|
||||
|
||||
this.$set(
|
||||
this.letterhead,
|
||||
"image_height",
|
||||
new_height
|
||||
);
|
||||
this.$set(
|
||||
this.letterhead,
|
||||
"image_width",
|
||||
new_width
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
set_letterhead(letterhead) {
|
||||
this.$store.change_letterhead(letterhead).then(() => {
|
||||
get_image_dimensions(this.letterhead.image).then(
|
||||
({ width, height }) => {
|
||||
this.aspect_ratio = width / height;
|
||||
this.range_input_field =
|
||||
this.aspect_ratio > 1
|
||||
? "image_width"
|
||||
: "image_height";
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
create_letterhead() {
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: __("Create Letter Head"),
|
||||
fields: [
|
||||
{
|
||||
label: __("Letter Head Name"),
|
||||
fieldname: "name",
|
||||
fieldtype: "Data"
|
||||
}
|
||||
],
|
||||
primary_action: ({ name }) => {
|
||||
return frappe.db
|
||||
.insert({
|
||||
doctype: "Letter Head",
|
||||
letter_head_name: name,
|
||||
source: "Image"
|
||||
})
|
||||
.then(doc => {
|
||||
d.hide();
|
||||
this.$store.change_letterhead(doc.name).then(() => {
|
||||
this.toggle_edit_letterhead();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
d.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
function create_letterhead() {
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: __("Create Letter Head"),
|
||||
fields: [
|
||||
{
|
||||
label: __("Letter Head Name"),
|
||||
fieldname: "name",
|
||||
fieldtype: "Data"
|
||||
}
|
||||
],
|
||||
primary_action: ({ name }) => {
|
||||
return frappe.db
|
||||
.insert({
|
||||
doctype: "Letter Head",
|
||||
letter_head_name: name,
|
||||
source: "Image"
|
||||
})
|
||||
.then(doc => {
|
||||
d.hide();
|
||||
store.value.change_letterhead(doc.name).then(() => {
|
||||
toggle_edit_letterhead();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
d.show();
|
||||
}
|
||||
// mounted
|
||||
onMounted(() => {
|
||||
if (!letterhead.value && frappe.boot.sysdefaults.letter_head) {
|
||||
set_letterhead(frappe.boot.sysdefaults.letter_head);
|
||||
}
|
||||
|
||||
watch(() => {
|
||||
return letterhead.value
|
||||
? letterhead.value[range_input_field.value]
|
||||
: null;
|
||||
}, () => {
|
||||
if (aspect_ratio.value === null) return;
|
||||
|
||||
let update_field =
|
||||
range_input_field.value == "image_width"
|
||||
? "image_height"
|
||||
: "image_width";
|
||||
letterhead.value[update_field] =
|
||||
update_field == "image_width"
|
||||
? aspect_ratio.value * letterhead.value.image_height
|
||||
: letterhead.value.image_width / aspect_ratio.value;
|
||||
});
|
||||
});
|
||||
|
||||
// watch
|
||||
watch(letterhead, () => {
|
||||
if (!letterhead.value) return;
|
||||
if (letterhead.value.image_width && letterhead.value.image_height) {
|
||||
let dimension =
|
||||
letterhead.value.image_width > letterhead.value.image_height
|
||||
? "width"
|
||||
: "height";
|
||||
let dimension_value = letterhead.value["image_" + dimension];
|
||||
letterhead.value.content = `
|
||||
<div style="text-align: ${letterhead.value.align.toLowerCase()};">
|
||||
<img
|
||||
src="${letterhead.value.image}"
|
||||
alt="${letterhead.value.name}"
|
||||
${dimension}="${dimension_value}"
|
||||
style="${dimension}: ${dimension_value}px;">
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}, { deep: true }, { immediate: true });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.letterhead {
|
||||
position: relative;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
<div class="h-100">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="preview-control" ref="doc-select"></div>
|
||||
<div class="preview-control" ref="doc_select_ref"></div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="preview-control" ref="preview-type"></div>
|
||||
<div class="preview-control" ref="preview_type_ref"></div>
|
||||
</div>
|
||||
<div class="col d-flex">
|
||||
<a
|
||||
|
|
@ -36,85 +36,89 @@
|
|||
></iframe>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { storeMixin } from "./store";
|
||||
export default {
|
||||
name: "Preview",
|
||||
mixins: [storeMixin],
|
||||
data() {
|
||||
return {
|
||||
type: "PDF",
|
||||
docname: null,
|
||||
preview_loaded: false
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.doc_select = frappe.ui.form.make_control({
|
||||
parent: this.$refs["doc-select"],
|
||||
df: {
|
||||
label: __("Select {0}", [__(this.doctype)]),
|
||||
fieldname: "docname",
|
||||
fieldtype: "Link",
|
||||
options: this.doctype,
|
||||
change: () => {
|
||||
this.docname = this.doc_select.get_value();
|
||||
}
|
||||
},
|
||||
render_input: true
|
||||
});
|
||||
this.preview_type = frappe.ui.form.make_control({
|
||||
parent: this.$refs["preview-type"],
|
||||
df: {
|
||||
label: __("Preview type"),
|
||||
fieldname: "docname",
|
||||
fieldtype: "Select",
|
||||
options: ["PDF", "HTML"],
|
||||
change: () => {
|
||||
this.type = this.preview_type.get_value();
|
||||
}
|
||||
},
|
||||
render_input: true
|
||||
});
|
||||
this.preview_type.set_value(this.type);
|
||||
this.get_default_docname().then(
|
||||
docname => docname && this.doc_select.set_value(docname)
|
||||
);
|
||||
this.$store.$on("after_save", () => {
|
||||
this.refresh();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
refresh() {
|
||||
this.$refs.iframe.contentWindow.location.reload();
|
||||
},
|
||||
get_default_docname() {
|
||||
return frappe.db.get_list(this.doctype, { limit: 1 }).then(doc => {
|
||||
return doc.length > 0 ? doc[0].name : null;
|
||||
});
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
doctype() {
|
||||
return this.print_format.doc_type;
|
||||
},
|
||||
url() {
|
||||
if (!this.docname) return null;
|
||||
let params = new URLSearchParams();
|
||||
params.append("doctype", this.doctype);
|
||||
params.append("name", this.docname);
|
||||
params.append("print_format", this.print_format.name);
|
||||
if (this.$store.letterhead) {
|
||||
params.append("letterhead", this.$store.letterhead.name);
|
||||
}
|
||||
let url =
|
||||
this.type == "PDF"
|
||||
? `/api/method/frappe.utils.weasyprint.download_pdf`
|
||||
: "/printpreview";
|
||||
return `${url}?${params.toString()}`;
|
||||
}
|
||||
|
||||
<script setup>
|
||||
import { useStore } from "./store";
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
|
||||
// mixin
|
||||
let { print_format, store } = useStore();
|
||||
|
||||
// variables
|
||||
let type = ref("PDF");
|
||||
let docname = ref(null);
|
||||
let preview_loaded = ref(false);
|
||||
let iframe = ref(null);
|
||||
let doc_select_ref = ref(null);
|
||||
let preview_type_ref = ref(null);
|
||||
let doc_select = ref(null);
|
||||
let preview_type = ref(null);
|
||||
|
||||
// methods
|
||||
function refresh() {
|
||||
iframe.value?.contentWindow.location.reload();
|
||||
}
|
||||
function get_default_docname() {
|
||||
return frappe.db.get_list(doctype.value, { limit: 1 }).then(doc => {
|
||||
return doc.length > 0 ? doc[0].name : null;
|
||||
});
|
||||
}
|
||||
// computed
|
||||
let doctype = computed(() => {
|
||||
return print_format.value.doc_type;
|
||||
});
|
||||
let url = computed(() => {
|
||||
if (!docname.value) return null;
|
||||
let params = new URLSearchParams();
|
||||
params.append("doctype", doctype.value);
|
||||
params.append("name", docname.value);
|
||||
params.append("print_format", print_format.value.name);
|
||||
|
||||
if (store.value.letterhead) {
|
||||
params.append("letterhead", store.value.letterhead.name);
|
||||
}
|
||||
};
|
||||
let _url =
|
||||
type.value == "PDF"
|
||||
? `/api/method/frappe.utils.weasyprint.download_pdf`
|
||||
: "/printpreview";
|
||||
return `${_url}?${params.toString()}`;
|
||||
});
|
||||
|
||||
// mounted
|
||||
onMounted(() => {
|
||||
doc_select.value = frappe.ui.form.make_control({
|
||||
parent: doc_select_ref.value,
|
||||
df: {
|
||||
label: __("Select {0}", [__(doctype.value)]),
|
||||
fieldname: "docname",
|
||||
fieldtype: "Link",
|
||||
options: doctype.value,
|
||||
change: () => {
|
||||
docname.value = doc_select.value.get_value();
|
||||
}
|
||||
},
|
||||
render_input: true
|
||||
});
|
||||
preview_type.value = frappe.ui.form.make_control({
|
||||
parent: preview_type_ref.value,
|
||||
df: {
|
||||
label: __("Preview type"),
|
||||
fieldname: "docname",
|
||||
fieldtype: "Select",
|
||||
options: ["PDF", "HTML"],
|
||||
change: () => {
|
||||
type.value = preview_type.value.get_value();
|
||||
}
|
||||
},
|
||||
render_input: true
|
||||
});
|
||||
preview_type.value.set_value(type.value);
|
||||
get_default_docname().then(doc_name => {
|
||||
doc_name && doc_select.value.set_value(doc_name);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.preview-iframe {
|
||||
width: 100%;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<LetterHeadEditor type="Header" />
|
||||
<HTMLEditor
|
||||
:value="layout.header"
|
||||
@change="$set(layout, 'header', $event)"
|
||||
@change="layout.header = $event"
|
||||
:button-label="__('Edit Header')"
|
||||
/>
|
||||
<draggable
|
||||
|
|
@ -14,17 +14,18 @@
|
|||
group="sections"
|
||||
filter=".section-columns, .column, .field"
|
||||
:animation="200"
|
||||
item-key="id"
|
||||
>
|
||||
<PrintFormatSection
|
||||
v-for="(section, i) in layout.sections"
|
||||
:key="i"
|
||||
:section="section"
|
||||
@add_section_above="add_section_above(section)"
|
||||
/>
|
||||
<template #item="{ element }">
|
||||
<PrintFormatSection
|
||||
:section="element"
|
||||
@add_section_above="add_section_above(element)"
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
<HTMLEditor
|
||||
:value="layout.footer"
|
||||
@change="$set(layout, 'footer', $event)"
|
||||
@change="layout.footer = $event"
|
||||
:button-label="__('Edit Footer')"
|
||||
/>
|
||||
<HTMLEditor
|
||||
|
|
@ -36,92 +37,87 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import draggable from "vuedraggable";
|
||||
import HTMLEditor from "./HTMLEditor.vue";
|
||||
import LetterHeadEditor from "./LetterHeadEditor.vue";
|
||||
import PrintFormatSection from "./PrintFormatSection.vue";
|
||||
import { storeMixin } from "./store";
|
||||
import { useStore } from "./store";
|
||||
import { computed, inject, watch } from "vue";
|
||||
|
||||
export default {
|
||||
name: "PrintFormat",
|
||||
mixins: [storeMixin],
|
||||
components: {
|
||||
draggable,
|
||||
PrintFormatSection,
|
||||
LetterHeadEditor,
|
||||
HTMLEditor
|
||||
},
|
||||
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"
|
||||
};
|
||||
},
|
||||
page_number_style() {
|
||||
let style = {
|
||||
position: "absolute",
|
||||
background: "white",
|
||||
padding: "4px",
|
||||
borderRadius: "var(--border-radius)",
|
||||
border: "1px solid var(--border-color)"
|
||||
};
|
||||
if (this.print_format.page_number.includes("Top")) {
|
||||
style.top = this.print_format.margin_top / 2 + "mm";
|
||||
style.transform = "translateY(-50%)";
|
||||
}
|
||||
if (this.print_format.page_number.includes("Left")) {
|
||||
style.left = this.print_format.margin_left + "mm";
|
||||
}
|
||||
if (this.print_format.page_number.includes("Right")) {
|
||||
style.right = this.print_format.margin_right + "mm";
|
||||
}
|
||||
if (this.print_format.page_number.includes("Bottom")) {
|
||||
style.bottom = this.print_format.margin_bottom / 2 + "mm";
|
||||
style.transform = "translateY(50%)";
|
||||
}
|
||||
if (this.print_format.page_number.includes("Center")) {
|
||||
style.left = "50%";
|
||||
style.transform += " translateX(-50%)";
|
||||
}
|
||||
if (this.print_format.page_number.includes("Hide")) {
|
||||
style.display = "none";
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
},
|
||||
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);
|
||||
},
|
||||
update_letterhead_footer(val) {
|
||||
this.letterhead.footer = val;
|
||||
this.letterhead._dirty = true;
|
||||
// mixins
|
||||
let { layout, letterhead, print_format } = useStore();
|
||||
let store = inject("$store");
|
||||
// methods
|
||||
function add_section_above(section) {
|
||||
let sections = [];
|
||||
for (let _section of layout.value.sections) {
|
||||
if (_section === section) {
|
||||
sections.push({
|
||||
label: "",
|
||||
columns: [
|
||||
{ label: "", fields: [] },
|
||||
{ label: "", fields: [] }
|
||||
]
|
||||
});
|
||||
}
|
||||
sections.push(_section);
|
||||
}
|
||||
};
|
||||
layout.value["sections"] = sections;
|
||||
}
|
||||
function update_letterhead_footer(val) {
|
||||
letterhead.value.footer = val;
|
||||
}
|
||||
|
||||
// computed
|
||||
let rootStyles = computed(() => {
|
||||
let {
|
||||
margin_top = 0,
|
||||
margin_bottom = 0,
|
||||
margin_left = 0,
|
||||
margin_right = 0
|
||||
} = print_format.value;
|
||||
return {
|
||||
padding: `${margin_top}mm ${margin_right}mm ${margin_bottom}mm ${margin_left}mm`,
|
||||
width: "210mm",
|
||||
minHeight: "297mm"
|
||||
};
|
||||
});
|
||||
let page_number_style = computed(() => {
|
||||
let style = {
|
||||
position: "absolute",
|
||||
background: "white",
|
||||
padding: "4px",
|
||||
borderRadius: "var(--border-radius)",
|
||||
border: "1px solid var(--border-color)"
|
||||
};
|
||||
if (print_format.value.page_number.includes("Top")) {
|
||||
style.top = print_format.value.margin_top / 2 + "mm";
|
||||
style.transform = "translateY(-50%)";
|
||||
}
|
||||
if (print_format.value.page_number.includes("Left")) {
|
||||
style.left = print_format.value.margin_left + "mm";
|
||||
}
|
||||
if (print_format.value.page_number.includes("Right")) {
|
||||
style.right = print_format.value.margin_right + "mm";
|
||||
}
|
||||
if (print_format.value.page_number.includes("Bottom")) {
|
||||
style.bottom = print_format.value.margin_bottom / 2 + "mm";
|
||||
style.transform = "translateY(50%)";
|
||||
}
|
||||
if (print_format.value.page_number.includes("Center")) {
|
||||
style.left = "50%";
|
||||
style.transform += " translateX(-50%)";
|
||||
}
|
||||
if (print_format.value.page_number.includes("Hide")) {
|
||||
style.display = "none";
|
||||
}
|
||||
|
||||
return style;
|
||||
});
|
||||
|
||||
watch(layout, () => (store.dirty.value = true), { deep: true });
|
||||
watch(print_format, () => (store.dirty.value = true), { deep: true });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -4,64 +4,59 @@
|
|||
<PrintFormatControls />
|
||||
</div>
|
||||
<div class="print-format-container col-9">
|
||||
<keep-alive>
|
||||
<Preview v-if="show_preview" />
|
||||
<PrintFormat v-else />
|
||||
</keep-alive>
|
||||
<KeepAlive>
|
||||
<component :is="Preview" v-if="show_preview" />
|
||||
<component :is="PrintFormat" v-else />
|
||||
</KeepAlive>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import PrintFormat from "./PrintFormat.vue";
|
||||
import Preview from "./Preview.vue";
|
||||
import PrintFormatControls from "./PrintFormatControls.vue";
|
||||
import { getStore } from "./store";
|
||||
import { computed, ref, onMounted, provide } from "vue";
|
||||
|
||||
export default {
|
||||
name: "PrintFormatBuilder",
|
||||
props: ["print_format_name"],
|
||||
components: {
|
||||
PrintFormat,
|
||||
PrintFormatControls,
|
||||
Preview
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show_preview: false
|
||||
};
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
$store: this.$store
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$store.fetch().then(() => {
|
||||
if (!this.$store.layout) {
|
||||
this.$store.layout = this.$store.get_default_layout();
|
||||
this.$store.save_changes();
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
toggle_preview() {
|
||||
this.show_preview = !this.show_preview;
|
||||
// props
|
||||
let props = defineProps(["print_format_name"]);
|
||||
|
||||
// variables
|
||||
let show_preview = ref(false);
|
||||
|
||||
// computed
|
||||
let $store = computed(() => {
|
||||
return getStore(props.print_format_name)
|
||||
});
|
||||
|
||||
let shouldRender = computed(() => {
|
||||
return Boolean(
|
||||
$store.value.print_format.value &&
|
||||
$store.value.meta.value &&
|
||||
$store.value.layout.value
|
||||
);
|
||||
});
|
||||
|
||||
// provide
|
||||
provide("$store", $store.value);
|
||||
|
||||
// methods
|
||||
function toggle_preview() {
|
||||
show_preview.value = !show_preview.value;
|
||||
}
|
||||
|
||||
// mounted
|
||||
onMounted(() => {
|
||||
$store.value.fetch().then(() => {
|
||||
if (!$store.value.layout.value) {
|
||||
$store.value.layout.value = $store.value.get_default_layout();
|
||||
$store.value.save_changes();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
$store() {
|
||||
return getStore(this.print_format_name);
|
||||
},
|
||||
shouldRender() {
|
||||
return Boolean(
|
||||
this.$store.print_format &&
|
||||
this.$store.meta &&
|
||||
this.$store.layout
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
defineExpose({ toggle_preview, $store });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -109,182 +109,184 @@
|
|||
:group="{ name: 'fields', pull: 'clone', put: false }"
|
||||
:sort="false"
|
||||
:clone="clone_field"
|
||||
item-key="id"
|
||||
>
|
||||
<div
|
||||
class="field"
|
||||
v-for="df in fields"
|
||||
:key="df.fieldname"
|
||||
:title="df.fieldname"
|
||||
>
|
||||
{{ df.label }}
|
||||
</div>
|
||||
<template #item="{ element }">
|
||||
<div
|
||||
class="field"
|
||||
:title="element.fieldname"
|
||||
>
|
||||
{{ element.label }}
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import draggable from "vuedraggable";
|
||||
import { get_table_columns, pluck } from "./utils";
|
||||
import { storeMixin } from "./store";
|
||||
import { useStore } from "./store";
|
||||
import { computed, onMounted, ref, watch, inject } from "vue";
|
||||
|
||||
export default {
|
||||
name: "PrintFormatControls",
|
||||
mixins: [storeMixin],
|
||||
data() {
|
||||
return {
|
||||
search_text: "",
|
||||
google_fonts: []
|
||||
};
|
||||
},
|
||||
components: {
|
||||
draggable
|
||||
},
|
||||
mounted() {
|
||||
let method =
|
||||
"frappe.printing.page.print_format_builder_beta.print_format_builder_beta.get_google_fonts";
|
||||
frappe.call(method).then(r => {
|
||||
this.google_fonts = r.message || [];
|
||||
if (!this.google_fonts.includes(this.print_format.font)) {
|
||||
this.google_fonts.push(this.print_format.font);
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
update_margin(fieldname, value) {
|
||||
value = parseFloat(value);
|
||||
if (value < 0) {
|
||||
value = 0;
|
||||
}
|
||||
this.$store.print_format[fieldname] = value;
|
||||
},
|
||||
clone_field(df) {
|
||||
let cloned = pluck(df, [
|
||||
"label",
|
||||
"fieldname",
|
||||
"fieldtype",
|
||||
"options",
|
||||
"table_columns",
|
||||
"html",
|
||||
"field_template"
|
||||
]);
|
||||
if (cloned.custom) {
|
||||
// generate unique fieldnames for custom blocks
|
||||
cloned.fieldname += "_" + frappe.utils.get_random(8);
|
||||
}
|
||||
return cloned;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
margins() {
|
||||
return [
|
||||
{ label: __("Top"), fieldname: "margin_top" },
|
||||
{ label: __("Bottom"), fieldname: "margin_bottom" },
|
||||
{ label: __("Left", null, 'alignment'), fieldname: "margin_left" },
|
||||
{ label: __("Right", null, 'alignment'), 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,
|
||||
fieldtype: df.fieldtype,
|
||||
options: df.options
|
||||
};
|
||||
if (df.fieldtype == "Table") {
|
||||
out.table_columns = get_table_columns(df);
|
||||
}
|
||||
return out;
|
||||
});
|
||||
// variables
|
||||
let search_text = ref("");
|
||||
let google_fonts = ref([]);
|
||||
|
||||
return [
|
||||
{
|
||||
label: __("Custom HTML"),
|
||||
fieldname: "custom_html",
|
||||
fieldtype: "HTML",
|
||||
html: "",
|
||||
custom: 1
|
||||
},
|
||||
{
|
||||
label: __("ID (name)"),
|
||||
fieldname: "name",
|
||||
fieldtype: "Data"
|
||||
},
|
||||
{
|
||||
label: __("Spacer"),
|
||||
fieldname: "spacer",
|
||||
fieldtype: "Spacer",
|
||||
custom: 1
|
||||
},
|
||||
{
|
||||
label: __("Divider"),
|
||||
fieldname: "divider",
|
||||
fieldtype: "Divider",
|
||||
custom: 1
|
||||
},
|
||||
...this.print_templates,
|
||||
...fields
|
||||
];
|
||||
},
|
||||
print_templates() {
|
||||
let templates = this.print_format.__onload.print_templates || {};
|
||||
let out = [];
|
||||
for (let template of templates) {
|
||||
let df;
|
||||
if (template.field) {
|
||||
df = frappe.meta.get_docfield(
|
||||
this.meta.name,
|
||||
template.field
|
||||
);
|
||||
} else {
|
||||
df = {
|
||||
label: template.name,
|
||||
fieldname: frappe.scrub(template.name)
|
||||
};
|
||||
// inject
|
||||
let store = inject("$store");
|
||||
|
||||
// mixins
|
||||
let { meta, print_format } = useStore();
|
||||
|
||||
// methods
|
||||
function update_margin(fieldname, value) {
|
||||
value = parseFloat(value);
|
||||
if (value < 0) {
|
||||
value = 0;
|
||||
}
|
||||
print_format.value[fieldname] = value;
|
||||
}
|
||||
function clone_field(df) {
|
||||
let cloned = pluck(df, [
|
||||
"label",
|
||||
"fieldname",
|
||||
"fieldtype",
|
||||
"options",
|
||||
"table_columns",
|
||||
"html",
|
||||
"field_template"
|
||||
]);
|
||||
if (cloned.custom) {
|
||||
// generate unique fieldnames for custom blocks
|
||||
cloned.fieldname += "_" + frappe.utils.get_random(8);
|
||||
}
|
||||
return cloned;
|
||||
}
|
||||
|
||||
// computed
|
||||
let margins = computed(() => {
|
||||
return [
|
||||
{ label: __("Top"), fieldname: "margin_top" },
|
||||
{ label: __("Bottom"), fieldname: "margin_bottom" },
|
||||
{ label: __("Left", null, 'alignment'), fieldname: "margin_left" },
|
||||
{ label: __("Right", null, 'alignment'), fieldname: "margin_right" }
|
||||
];
|
||||
});
|
||||
let fields = computed(() => {
|
||||
let fields = meta.value.fields
|
||||
.filter(df => {
|
||||
if (
|
||||
["Section Break", "Column Break"].includes(df.fieldtype)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (search_text.value) {
|
||||
if (df.fieldname.includes(search_text.value)) {
|
||||
return true;
|
||||
}
|
||||
out.push({
|
||||
label: `${__(df.label)} (${__("Field Template")})`,
|
||||
fieldname: df.fieldname + "_template",
|
||||
fieldtype: "Field Template",
|
||||
field_template: template.name
|
||||
});
|
||||
if (df.label && df.label.includes(search_text.value)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.map(df => {
|
||||
let out = {
|
||||
label: df.label,
|
||||
fieldname: df.fieldname,
|
||||
fieldtype: df.fieldtype,
|
||||
options: df.options
|
||||
};
|
||||
if (df.fieldtype == "Table") {
|
||||
out.table_columns = get_table_columns(df);
|
||||
}
|
||||
return out;
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
label: __("Custom HTML"),
|
||||
fieldname: "custom_html",
|
||||
fieldtype: "HTML",
|
||||
html: "",
|
||||
custom: 1
|
||||
},
|
||||
page_number_positions() {
|
||||
return [
|
||||
{ label: __("Hide"), value: "Hide" },
|
||||
{ label: __("Top Left"), value: "Top Left" },
|
||||
{ label: __("Top Center"), value: "Top Center" },
|
||||
{ label: __("Top Right"), value: "Top Right" },
|
||||
{ label: __("Bottom Left"), value: "Bottom Left" },
|
||||
{ label: __("Bottom Center"), value: "Bottom Center" },
|
||||
{ label: __("Bottom Right"), value: "Bottom Right" }
|
||||
];
|
||||
{
|
||||
label: __("ID (name)"),
|
||||
fieldname: "name",
|
||||
fieldtype: "Data"
|
||||
},
|
||||
{
|
||||
label: __("Spacer"),
|
||||
fieldname: "spacer",
|
||||
fieldtype: "Spacer",
|
||||
custom: 1
|
||||
},
|
||||
{
|
||||
label: __("Divider"),
|
||||
fieldname: "divider",
|
||||
fieldtype: "Divider",
|
||||
custom: 1
|
||||
},
|
||||
...print_templates.value,
|
||||
...fields
|
||||
];
|
||||
});
|
||||
let print_templates = computed(() => {
|
||||
let templates = print_format.value.__onload.print_templates || {};
|
||||
let out = [];
|
||||
for (let template of templates) {
|
||||
let df;
|
||||
if (template.field) {
|
||||
df = frappe.meta.get_docfield(
|
||||
meta.value.name,
|
||||
template.field
|
||||
);
|
||||
} else {
|
||||
df = {
|
||||
label: template.name,
|
||||
fieldname: frappe.scrub(template.name)
|
||||
};
|
||||
}
|
||||
out.push({
|
||||
label: `${__(df.label)} (${__("Field Template")})`,
|
||||
fieldname: df.fieldname + "_template",
|
||||
fieldtype: "Field Template",
|
||||
field_template: template.name
|
||||
});
|
||||
}
|
||||
};
|
||||
return out;
|
||||
});
|
||||
let page_number_positions = computed(() => {
|
||||
return [
|
||||
{ label: __("Hide"), value: "Hide" },
|
||||
{ label: __("Top Left"), value: "Top Left" },
|
||||
{ label: __("Top Center"), value: "Top Center" },
|
||||
{ label: __("Top Right"), value: "Top Right" },
|
||||
{ label: __("Bottom Left"), value: "Bottom Left" },
|
||||
{ label: __("Bottom Center"), value: "Bottom Center" },
|
||||
{ label: __("Bottom Right"), value: "Bottom Right" }
|
||||
];
|
||||
});
|
||||
|
||||
// mounted
|
||||
onMounted(() => {
|
||||
let method =
|
||||
"frappe.printing.page.print_format_builder_beta.print_format_builder_beta.get_google_fonts";
|
||||
frappe.call(method).then(r => {
|
||||
google_fonts.value = r.message || [];
|
||||
if (!google_fonts.value.includes(print_format.value.font)) {
|
||||
google_fonts.value.push(print_format.value.font);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
watch(print_format, () => (store.dirty.value = true), { deep: true });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -59,12 +59,11 @@
|
|||
v-model="column.fields"
|
||||
group="fields"
|
||||
:animation="150"
|
||||
item-key="id"
|
||||
>
|
||||
<Field
|
||||
v-for="df in get_fields(column)"
|
||||
:key="df.fieldname"
|
||||
:df="df"
|
||||
/>
|
||||
<template #item="{ element }">
|
||||
<Field :df="element" />
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -78,102 +77,90 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import draggable from "vuedraggable";
|
||||
import Field from "./Field.vue";
|
||||
import { storeMixin } from "./store";
|
||||
import { computed } from "vue";
|
||||
|
||||
export default {
|
||||
name: "PrintFormatSection",
|
||||
mixins: [storeMixin],
|
||||
props: ["section"],
|
||||
components: {
|
||||
draggable,
|
||||
Field
|
||||
},
|
||||
methods: {
|
||||
add_column() {
|
||||
if (this.section.columns.length < 4) {
|
||||
this.section.columns.push({
|
||||
label: "",
|
||||
fields: []
|
||||
});
|
||||
}
|
||||
},
|
||||
remove_column() {
|
||||
if (this.section.columns.length <= 1) return;
|
||||
// props
|
||||
let props = defineProps(["section"]);
|
||||
|
||||
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];
|
||||
// emits
|
||||
let emit = defineEmits(["add_section_above"]);
|
||||
|
||||
this.$set(this.section, "columns", columns);
|
||||
},
|
||||
add_page_break() {
|
||||
this.$set(this.section, "page_break", true);
|
||||
},
|
||||
remove_page_break() {
|
||||
this.$set(this.section, "page_break", false);
|
||||
},
|
||||
get_fields(column) {
|
||||
return column.fields.filter(df => !df.remove);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
section_options() {
|
||||
return [
|
||||
{
|
||||
label: __("Add section above"),
|
||||
action: () => this.$emit("add_section_above")
|
||||
},
|
||||
{
|
||||
label: __("Add column"),
|
||||
action: this.add_column,
|
||||
condition: () => this.section.columns.length < 4
|
||||
},
|
||||
{
|
||||
label: __("Remove column"),
|
||||
action: this.remove_column,
|
||||
condition: () => this.section.columns.length > 1
|
||||
},
|
||||
{
|
||||
label: __("Add page break"),
|
||||
action: this.add_page_break,
|
||||
condition: () => !this.section.page_break
|
||||
},
|
||||
{
|
||||
label: __("Remove page break"),
|
||||
action: this.remove_page_break,
|
||||
condition: () => this.section.page_break
|
||||
},
|
||||
{
|
||||
label: __("Remove section"),
|
||||
action: () => this.$set(this.section, "remove", true)
|
||||
},
|
||||
{
|
||||
label: __("Field Orientation (Left-Right)"),
|
||||
condition: () => !this.section.field_orientation,
|
||||
action: () =>
|
||||
this.$set(
|
||||
this.section,
|
||||
"field_orientation",
|
||||
"left-right"
|
||||
)
|
||||
},
|
||||
{
|
||||
label: __("Field Orientation (Top-Down)"),
|
||||
condition: () =>
|
||||
this.section.field_orientation == "left-right",
|
||||
action: () =>
|
||||
this.$set(this.section, "field_orientation", "")
|
||||
}
|
||||
].filter(option => (option.condition ? option.condition() : true));
|
||||
}
|
||||
// methods
|
||||
function add_column() {
|
||||
if (props.section.columns.length < 4) {
|
||||
props.section.columns.push({
|
||||
label: "",
|
||||
fields: []
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
function remove_column() {
|
||||
if (props.section.columns.length <= 1) return;
|
||||
|
||||
let columns = props.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];
|
||||
|
||||
props.section["columns"] = columns;
|
||||
}
|
||||
function add_page_break() {
|
||||
props.section["page_break"] = true;
|
||||
}
|
||||
function remove_page_break() {
|
||||
props.section["page_break"] = false;
|
||||
}
|
||||
|
||||
// computed
|
||||
let section_options = computed(() => {
|
||||
return [
|
||||
{
|
||||
label: __("Add section above"),
|
||||
action: () => emit("add_section_above")
|
||||
},
|
||||
{
|
||||
label: __("Add column"),
|
||||
action: add_column,
|
||||
condition: () => props.section.columns.length < 4
|
||||
},
|
||||
{
|
||||
label: __("Remove column"),
|
||||
action: remove_column,
|
||||
condition: () => props.section.columns.length > 1
|
||||
},
|
||||
{
|
||||
label: __("Add page break"),
|
||||
action: add_page_break,
|
||||
condition: () => !props.section.page_break
|
||||
},
|
||||
{
|
||||
label: __("Remove page break"),
|
||||
action: remove_page_break,
|
||||
condition: () => props.section.page_break
|
||||
},
|
||||
{
|
||||
label: __("Remove section"),
|
||||
action: () => { props.section["remove"] = true }
|
||||
},
|
||||
{
|
||||
label: __("Field Orientation (Left-Right)"),
|
||||
condition: () => !props.section.field_orientation,
|
||||
action: () => { props.section["field_orientation"] = "left-right" }
|
||||
},
|
||||
{
|
||||
label: __("Field Orientation (Top-Down)"),
|
||||
condition: () =>
|
||||
props.section.field_orientation == "left-right",
|
||||
action: () => { props.section["field_orientation"] = "" }
|
||||
}
|
||||
].filter(option => (option.condition ? option.condition() : true));
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { createApp } from "vue";
|
||||
import PrintFormatBuilderComponent from "./PrintFormatBuilder.vue";
|
||||
import { getStore } from "./store";
|
||||
|
||||
class PrintFormatBuilder {
|
||||
constructor({ wrapper, page, print_format }) {
|
||||
|
|
@ -28,28 +28,26 @@ class PrintFormatBuilder {
|
|||
frappe.set_route("print-format-builder-beta");
|
||||
});
|
||||
|
||||
let $vm = new Vue({
|
||||
el: this.$wrapper.get(0),
|
||||
render: (h) =>
|
||||
h(PrintFormatBuilderComponent, {
|
||||
props: {
|
||||
print_format_name: print_format,
|
||||
},
|
||||
}),
|
||||
});
|
||||
this.$component = $vm.$children[0];
|
||||
let store = getStore(print_format);
|
||||
store.$watch("dirty", (value) => {
|
||||
if (value) {
|
||||
this.page.set_indicator("Not Saved", "orange");
|
||||
$toggle_preview_btn.hide();
|
||||
$reset_changes_btn.show();
|
||||
} else {
|
||||
this.page.clear_indicator();
|
||||
$toggle_preview_btn.show();
|
||||
$reset_changes_btn.hide();
|
||||
}
|
||||
});
|
||||
let app = createApp(PrintFormatBuilderComponent, { print_format_name: print_format });
|
||||
SetVueGlobals(app);
|
||||
this.$component = app.mount(this.$wrapper.get(0));
|
||||
|
||||
this.$component.$watch(
|
||||
"$store.dirty",
|
||||
(dirty) => {
|
||||
if (dirty.value) {
|
||||
this.page.set_indicator("Not Saved", "orange");
|
||||
$toggle_preview_btn.hide();
|
||||
$reset_changes_btn.show();
|
||||
} else {
|
||||
this.page.clear_indicator();
|
||||
$toggle_preview_btn.show();
|
||||
$reset_changes_btn.hide();
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
this.$component.$watch("show_preview", (value) => {
|
||||
$toggle_preview_btn.text(value ? __("Hide Preview") : __("Show Preview"));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,158 +1,159 @@
|
|||
import { create_default_layout, pluck } from "./utils";
|
||||
|
||||
let stores = {};
|
||||
import { watch, ref, inject, computed, nextTick } from "vue";
|
||||
|
||||
export function getStore(print_format_name) {
|
||||
if (stores[print_format_name]) {
|
||||
return stores[print_format_name];
|
||||
}
|
||||
// variables
|
||||
let letterhead_name = ref(null);
|
||||
let print_format = ref(null);
|
||||
let letterhead = ref(null);
|
||||
let doctype = ref(null);
|
||||
let meta = ref(null);
|
||||
let layout = ref(null);
|
||||
let dirty = ref(false);
|
||||
let edit_letterhead = ref(false);
|
||||
|
||||
let options = {
|
||||
data() {
|
||||
return {
|
||||
print_format_name,
|
||||
letterhead_name: null,
|
||||
print_format: null,
|
||||
letterhead: null,
|
||||
doctype: null,
|
||||
meta: null,
|
||||
layout: null,
|
||||
dirty: false,
|
||||
edit_letterhead: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
layout: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.dirty = true;
|
||||
},
|
||||
},
|
||||
print_format: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.dirty = true;
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
fetch() {
|
||||
return new Promise((resolve) => {
|
||||
frappe.model.clear_doc("Print Format", this.print_format_name);
|
||||
frappe.model.with_doc("Print Format", this.print_format_name, () => {
|
||||
let print_format = frappe.get_doc("Print Format", this.print_format_name);
|
||||
frappe.model.with_doctype(print_format.doc_type, () => {
|
||||
this.meta = frappe.get_meta(print_format.doc_type);
|
||||
this.print_format = print_format;
|
||||
this.layout = this.get_layout();
|
||||
this.$nextTick(() => (this.dirty = false));
|
||||
this.edit_letterhead = false;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
// methods
|
||||
function fetch() {
|
||||
return new Promise((resolve) => {
|
||||
frappe.model.clear_doc("Print Format", print_format_name);
|
||||
frappe.model.with_doc("Print Format", print_format_name, () => {
|
||||
let _print_format = frappe.get_doc("Print Format", print_format_name);
|
||||
frappe.model.with_doctype(_print_format.doc_type, () => {
|
||||
meta.value = frappe.get_meta(_print_format.doc_type);
|
||||
print_format.value = _print_format;
|
||||
layout.value = get_layout();
|
||||
nextTick(() => (dirty.value = false));
|
||||
edit_letterhead.value = false;
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
update({ fieldname, value }) {
|
||||
this.$set(this.print_format, fieldname, value);
|
||||
},
|
||||
save_changes() {
|
||||
frappe.dom.freeze(__("Saving..."));
|
||||
});
|
||||
});
|
||||
}
|
||||
function update({ fieldname, value }) {
|
||||
print_format.value[fieldname] = value;
|
||||
}
|
||||
function save_changes() {
|
||||
frappe.dom.freeze(__("Saving..."));
|
||||
|
||||
this.layout.sections = this.layout.sections
|
||||
.filter((section) => !section.remove)
|
||||
.map((section) => {
|
||||
section.columns = section.columns.map((column) => {
|
||||
column.fields = column.fields
|
||||
.filter((df) => !df.remove)
|
||||
.map((df) => {
|
||||
if (df.table_columns) {
|
||||
df.table_columns = df.table_columns.map((tf) => {
|
||||
return pluck(tf, [
|
||||
"label",
|
||||
"fieldname",
|
||||
"fieldtype",
|
||||
"options",
|
||||
"width",
|
||||
"field_template",
|
||||
]);
|
||||
});
|
||||
}
|
||||
return pluck(df, [
|
||||
layout.value.sections = layout.value.sections
|
||||
.filter((section) => !section.remove)
|
||||
.map((section) => {
|
||||
section.columns = section.columns.map((column) => {
|
||||
column.fields = column.fields
|
||||
.filter((df) => !df.remove)
|
||||
.map((df) => {
|
||||
if (df.table_columns) {
|
||||
df.table_columns = df.table_columns.map((tf) => {
|
||||
return pluck(tf, [
|
||||
"label",
|
||||
"fieldname",
|
||||
"fieldtype",
|
||||
"options",
|
||||
"table_columns",
|
||||
"html",
|
||||
"width",
|
||||
"field_template",
|
||||
]);
|
||||
});
|
||||
return column;
|
||||
}
|
||||
return pluck(df, [
|
||||
"label",
|
||||
"fieldname",
|
||||
"fieldtype",
|
||||
"options",
|
||||
"table_columns",
|
||||
"html",
|
||||
"field_template",
|
||||
]);
|
||||
});
|
||||
return section;
|
||||
});
|
||||
|
||||
this.print_format.format_data = JSON.stringify(this.layout);
|
||||
|
||||
frappe
|
||||
.call("frappe.client.save", {
|
||||
doc: this.print_format,
|
||||
})
|
||||
.then(() => {
|
||||
if (this.letterhead && this.letterhead._dirty) {
|
||||
return frappe
|
||||
.call("frappe.client.save", {
|
||||
doc: this.letterhead,
|
||||
})
|
||||
.then((r) => (this.letterhead = r.message));
|
||||
}
|
||||
})
|
||||
.then(() => this.fetch())
|
||||
.always(() => {
|
||||
frappe.dom.unfreeze();
|
||||
this.$emit("after_save");
|
||||
});
|
||||
},
|
||||
reset_changes() {
|
||||
this.fetch();
|
||||
},
|
||||
get_layout() {
|
||||
if (this.print_format) {
|
||||
if (typeof this.print_format.format_data == "string") {
|
||||
return JSON.parse(this.print_format.format_data);
|
||||
}
|
||||
return this.print_format.format_data;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
get_default_layout() {
|
||||
return create_default_layout(this.meta, this.print_format);
|
||||
},
|
||||
change_letterhead(letterhead) {
|
||||
return frappe.db.get_doc("Letter Head", letterhead).then((doc) => {
|
||||
this.letterhead = doc;
|
||||
return column;
|
||||
});
|
||||
},
|
||||
},
|
||||
return section;
|
||||
});
|
||||
|
||||
print_format.value.format_data = JSON.stringify(layout.value);
|
||||
|
||||
frappe
|
||||
.call("frappe.client.save", {
|
||||
doc: print_format.value,
|
||||
})
|
||||
.then(() => {
|
||||
if (letterhead.value && letterhead.value._dirty) {
|
||||
return frappe
|
||||
.call("frappe.client.save", {
|
||||
doc: letterhead.value,
|
||||
})
|
||||
.then((r) => (letterhead.value = r.message));
|
||||
}
|
||||
})
|
||||
.then(() => fetch())
|
||||
.always(() => {
|
||||
frappe.dom.unfreeze();
|
||||
});
|
||||
}
|
||||
function reset_changes() {
|
||||
fetch();
|
||||
}
|
||||
function get_layout() {
|
||||
if (print_format.value) {
|
||||
if (typeof print_format.value.format_data == "string") {
|
||||
return JSON.parse(print_format.value.format_data);
|
||||
}
|
||||
return print_format.value.format_data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function get_default_layout() {
|
||||
return create_default_layout(meta.value, print_format.value);
|
||||
}
|
||||
function change_letterhead(_letterhead) {
|
||||
return frappe.db.get_doc("Letter Head", _letterhead).then((doc) => {
|
||||
letterhead.value = doc;
|
||||
});
|
||||
}
|
||||
|
||||
// watch
|
||||
watch(layout, () => {
|
||||
dirty.value = true;
|
||||
});
|
||||
watch(print_format, () => {
|
||||
dirty.value = true;
|
||||
});
|
||||
|
||||
return {
|
||||
letterhead_name,
|
||||
print_format,
|
||||
letterhead,
|
||||
doctype,
|
||||
meta,
|
||||
layout,
|
||||
dirty,
|
||||
edit_letterhead,
|
||||
fetch,
|
||||
update,
|
||||
save_changes,
|
||||
reset_changes,
|
||||
get_layout,
|
||||
get_default_layout,
|
||||
change_letterhead,
|
||||
};
|
||||
stores[print_format_name] = new Vue(options);
|
||||
return stores[print_format_name];
|
||||
}
|
||||
|
||||
export let storeMixin = {
|
||||
inject: ["$store"],
|
||||
computed: {
|
||||
print_format() {
|
||||
return this.$store.print_format;
|
||||
},
|
||||
layout() {
|
||||
return this.$store.layout;
|
||||
},
|
||||
letterhead() {
|
||||
return this.$store.letterhead;
|
||||
},
|
||||
meta() {
|
||||
return this.$store.meta;
|
||||
},
|
||||
},
|
||||
};
|
||||
export function useStore() {
|
||||
// inject store
|
||||
let store = ref(inject("$store"));
|
||||
|
||||
// computed
|
||||
let print_format = computed(() => {
|
||||
return store.value.print_format;
|
||||
});
|
||||
let layout = computed(() => {
|
||||
return store.value.layout;
|
||||
});
|
||||
let letterhead = computed(() => {
|
||||
return store.value.letterhead;
|
||||
});
|
||||
let meta = computed(() => {
|
||||
return store.value.meta;
|
||||
});
|
||||
|
||||
return { print_format, layout, letterhead, meta, store };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@
|
|||
"touch": "^3.1.0",
|
||||
"vue": "3.2.39",
|
||||
"vue-router": "^4.1.5",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vuex": "4.0.2",
|
||||
"@frappe/esbuild-plugin-postcss2": "^0.1.3",
|
||||
"@vue/component-compiler": "^4.2.4",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue