seitime-frappe/frappe/public/js/print_format_builder/Field.vue
2021-10-19 19:13:50 +05:30

360 lines
6.7 KiB
Vue

<template>
<div class="field" :title="df.fieldname" @click="editing = true">
<div class="field-controls">
<div>
<div
class="custom-html"
v-if="df.fieldtype == 'HTML' && df.html"
v-html="df.html"
></div>
<div
class="custom-html"
v-if="df.fieldtype == 'Field Template'"
>
{{ df.label }}
</div>
<input
v-else-if="editing && df.fieldtype != 'HTML'"
ref="label-input"
class="label-input"
type="text"
:placeholder="__('Label')"
v-model="df.label"
@keydown.enter="editing = false"
@blur="editing = false"
/>
<span v-else-if="df.label">{{ df.label }}</span>
<i class="text-muted" v-else>
{{ __("No Label") }} ({{ df.fieldname }})
</i>
</div>
<div class="field-actions">
<button
v-if="df.fieldtype == 'HTML'"
class="btn btn-xs btn-icon"
@click="edit_html"
>
<svg class="icon icon-sm">
<use xlink:href="#icon-edit"></use>
</svg>
</button>
<button
v-if="df.fieldtype == 'Table'"
class="btn btn-xs btn-default"
@click="configure_columns"
>
Configure columns
</button>
<button
class="btn btn-xs btn-icon"
@click="$set(df, 'remove', true)"
>
<svg class="icon icon-sm">
<use xlink:href="#icon-close"></use>
</svg>
</button>
</div>
</div>
<div
v-if="df.fieldtype == 'Table'"
class="table-controls row no-gutters"
:style="{ opacity: 1 }"
>
<div
class="table-column"
:style="{ width: tf.width + '%' }"
v-for="(tf, i) in df.table_columns"
:key="tf.fieldname"
>
<div class="table-field">
{{ tf.label }}
</div>
</div>
</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();
}
}
},
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", "");
}
}
}
}
],
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)
);
},
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];
}
return {
...frappe.meta.get_docfield(this.df.options, fieldname),
width: 10
};
},
validate_table_columns() {
if (this.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;
}
}
}
}
};
</script>
<style>
.field {
text-align: left;
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-controls {
display: flex;
justify-content: space-between;
align-items: center;
}
.field:not(:first-child) {
margin-top: 0.5rem;
}
.custom-html {
padding-right: var(--padding-xs);
word-break: break-all;
}
.label-input {
background-color: transparent;
border: none;
padding: 0;
}
.label-input:focus {
outline: none;
}
.field:focus-within {
border-style: solid;
border-color: var(--gray-600);
}
.field-actions {
flex: none;
}
.field-actions .btn {
opacity: 0;
}
.field-actions .btn-icon {
box-shadow: none;
}
.btn-icon {
padding: 2px;
}
.btn-icon:hover {
background-color: white;
}
.field:hover .btn {
opacity: 1;
}
.table-controls {
display: flex;
margin-top: 1rem;
}
.table-column {
position: relative;
}
.table-field {
text-align: left;
width: 100%;
background-color: white;
border-radius: var(--border-radius);
border: 1px dashed var(--gray-400);
padding: 0.5rem 0.75rem;
font-size: var(--text-sm);
user-select: none;
white-space: nowrap;
overflow: hidden;
}
.column-resize {
position: absolute;
right: 0;
top: 0;
width: 6px;
border-radius: 2px;
height: 80%;
background-color: var(--gray-600);
transform: translate(50%, 10%);
z-index: 999;
cursor: col-resize;
}
.column-resize-actions {
position: absolute;
top: 0;
right: 0;
height: 100%;
display: flex;
align-items: center;
padding-right: 0.25rem;
}
.column-resize-actions .btn-icon {
background: white;
}
.column-resize-actions .btn-icon:hover {
background: var(--bg-light-gray);
}
.columns-input {
padding: var(--padding-sm);
}
</style>