Merge pull request #19477 from shariquerik/form-builder-fixes-1
Fixes https://github.com/frappe/frappe/issues/19476
This commit is contained in:
commit
20ff30896a
12 changed files with 91 additions and 36 deletions
|
|
@ -546,15 +546,16 @@
|
|||
{
|
||||
"depends_on": "eval:!in_list([\"Tab Break\", \"Section Break\", \"Column Break\", \"Button\", \"HTML\"], doc.fieldtype)",
|
||||
"fieldname": "documentation_url",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Documentation URL"
|
||||
"fieldtype": "Data",
|
||||
"label": "Documentation URL",
|
||||
"options": "URL"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-11-17 14:14:39.404696",
|
||||
"modified": "2023-01-11 20:46:43.164926",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocField",
|
||||
|
|
|
|||
|
|
@ -50,11 +50,14 @@ class SystemSettings(Document):
|
|||
|
||||
social_login_enabled = frappe.db.exists("Social Login Key", {"enable_social_login": 1})
|
||||
ldap_enabled = frappe.db.get_single_value("LDAP Settings", "enabled")
|
||||
login_with_email_link_enabled = frappe.db.get_single_value(
|
||||
"System Settings", "login_with_email_link"
|
||||
)
|
||||
|
||||
if not (social_login_enabled or ldap_enabled):
|
||||
if not (social_login_enabled or ldap_enabled or login_with_email_link_enabled):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Please enable atleast one Social Login Key or LDAP before disabling username/password based login."
|
||||
"Please enable atleast one Social Login Key or LDAP or Login With Email Link before disabling username/password based login."
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -53,11 +53,11 @@ function focus_on_label() {
|
|||
@blur="editing = false"
|
||||
@click.stop
|
||||
/>
|
||||
<span v-else-if="text">{{ text }}</span>
|
||||
<span v-else-if="text" v-html="text" ></span>
|
||||
<i v-else class="text-muted">
|
||||
{{ empty_label }}
|
||||
</i>
|
||||
<span class="hidden-span" ref="hidden_text">{{ text }}</span>
|
||||
<span class="hidden-span" ref="hidden_text" v-html="text"></span>
|
||||
<span class="hidden-span" ref="hidden_placeholder">{{ placeholder }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import EditableInput from "./EditableInput.vue";
|
||||
import { ref, computed } from "vue";
|
||||
import { useStore } from "../store";
|
||||
import { move_children_to_parent } from "../utils";
|
||||
import { move_children_to_parent, clone_field } from "../utils";
|
||||
|
||||
let props = defineProps(["column", "field"]);
|
||||
let store = useStore();
|
||||
|
|
@ -28,6 +28,20 @@ function move_fields_to_column() {
|
|||
);
|
||||
move_children_to_parent(props, "column", "field", current_section);
|
||||
}
|
||||
|
||||
function duplicate_field() {
|
||||
let duplicate_field = clone_field(props.field);
|
||||
|
||||
if (duplicate_field.df.label) {
|
||||
duplicate_field.df.label = duplicate_field.df.label + " Copy";
|
||||
}
|
||||
duplicate_field.df.fieldname = "";
|
||||
|
||||
// push duplicate_field after props.field in the same column
|
||||
let index = props.column.fields.indexOf(props.field);
|
||||
props.column.fields.splice(index + 1, 0, duplicate_field);
|
||||
store.selected_field = duplicate_field.df;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -49,13 +63,16 @@ function move_fields_to_column() {
|
|||
:data-fieldtype="field.df.fieldtype"
|
||||
>
|
||||
<template #label>
|
||||
<EditableInput
|
||||
:class="{ reqd: field.df.reqd }"
|
||||
:text="field.df.label"
|
||||
:placeholder="__('Label')"
|
||||
:empty_label="`${__('No Label')} (${field.df.fieldtype})`"
|
||||
v-model="field.df.label"
|
||||
/>
|
||||
<div class="field-label">
|
||||
<EditableInput
|
||||
:text="field.df.label"
|
||||
:placeholder="__('Label')"
|
||||
:empty_label="`${__('No Label')} (${field.df.fieldtype})`"
|
||||
v-model="field.df.label"
|
||||
/>
|
||||
<div class="reqd-asterisk" v-if="field.df.reqd">*</div>
|
||||
<div class="help-icon" v-if="field.df.documentation_url" v-html="frappe.utils.icon('help', 'sm')"></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #actions>
|
||||
<div class="field-actions" :hidden="store.read_only">
|
||||
|
|
@ -76,6 +93,9 @@ function move_fields_to_column() {
|
|||
>
|
||||
<div v-html="frappe.utils.icon('move', 'sm')"></div>
|
||||
</button>
|
||||
<button class="btn btn-xs btn-icon" @click.stop="duplicate_field">
|
||||
<div v-html="frappe.utils.icon('duplicate', 'sm')"></div>
|
||||
</button>
|
||||
<button class="btn btn-xs btn-icon" @click.stop="remove_field">
|
||||
<div v-html="frappe.utils.icon('remove', 'sm')"></div>
|
||||
</button>
|
||||
|
|
@ -107,12 +127,30 @@ function move_fields_to_column() {
|
|||
}
|
||||
}
|
||||
|
||||
:deep(.form-control:read-only:focus) {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
:deep(.field-controls) {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.3rem;
|
||||
|
||||
.field-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.reqd-asterisk {
|
||||
margin-left: 3px;
|
||||
color: var(--red-400);
|
||||
}
|
||||
.help-icon {
|
||||
margin-left: 3px;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.field-actions {
|
||||
flex: none;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,15 +3,11 @@ import SearchBox from "./SearchBox.vue";
|
|||
import draggable from "vuedraggable";
|
||||
import { ref, computed } from "vue";
|
||||
import { useStore } from "../store";
|
||||
import { clone_field } from "../utils";
|
||||
|
||||
let store = useStore();
|
||||
let search_text = ref("");
|
||||
|
||||
function clone_field(field) {
|
||||
field.df.name = frappe.utils.get_random(8);
|
||||
return JSON.parse(JSON.stringify(field));
|
||||
}
|
||||
|
||||
let fields = computed(() => {
|
||||
let fields = frappe.model.all_fieldtypes
|
||||
.filter(df => {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import Sidebar from "./Sidebar.vue";
|
|||
import Tabs from "./Tabs.vue";
|
||||
import { computed, onMounted, watch, ref } from "vue";
|
||||
import { useStore } from "../store";
|
||||
import { onClickOutside } from "@vueuse/core";
|
||||
import { onClickOutside, useMagicKeys, whenever } from "@vueuse/core";
|
||||
|
||||
let store = useStore();
|
||||
|
||||
|
|
@ -14,6 +14,14 @@ let should_render = computed(() => {
|
|||
let container = ref(null);
|
||||
onClickOutside(container, () => store.selected_field = null);
|
||||
|
||||
// cmd/ctrl + s to save the form
|
||||
const { meta_s, ctrl_s } = useMagicKeys();
|
||||
whenever(() => meta_s.value || ctrl_s.value, () => {
|
||||
if (store.dirty) {
|
||||
store.save_changes();
|
||||
}
|
||||
});
|
||||
|
||||
function setup_change_doctype_dialog() {
|
||||
store.page.$title_area.on("click", () => {
|
||||
let dialog = new frappe.ui.Dialog({
|
||||
|
|
@ -109,7 +117,7 @@ onMounted(() => {
|
|||
font-size: var(--text-sm);
|
||||
cursor: pointer;
|
||||
|
||||
&:has(.drop-it-here) {
|
||||
&:not(.hovered) {
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
height: 60px;
|
||||
|
|
@ -169,10 +177,6 @@ onMounted(() => {
|
|||
}
|
||||
}
|
||||
|
||||
.reqd::after {
|
||||
content: " *";
|
||||
color: var(--red-400);
|
||||
}
|
||||
.description,
|
||||
.time-zone {
|
||||
font-size: var(--text-sm);
|
||||
|
|
@ -264,7 +268,7 @@ onMounted(() => {
|
|||
}
|
||||
}
|
||||
|
||||
.form-main:not(:has(.tab-header)) :deep(.tab-contents) {
|
||||
.form-main > :deep(div:first-child:not(.tab-header)) {
|
||||
max-height: calc(100vh - 160px);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ if (props.df.fieldtype === "Icon") {
|
|||
type="text"
|
||||
:style="{ height: df.fieldtype == 'Table MultiSelect' ? '42px' : '' }"
|
||||
:placeholder="placeholder"
|
||||
disabled
|
||||
readonly
|
||||
/>
|
||||
<input
|
||||
v-else
|
||||
|
|
@ -58,7 +58,7 @@ if (props.df.fieldtype === "Icon") {
|
|||
class="mt-2 form-control"
|
||||
type="text"
|
||||
:style="{ height: '110px' }"
|
||||
disabled
|
||||
readonly
|
||||
/>
|
||||
|
||||
<!-- description -->
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ watch(
|
|||
</div>
|
||||
|
||||
<!-- link input -->
|
||||
<input class="form-control" type="text" disabled />
|
||||
<input class="form-control" type="text" readonly />
|
||||
|
||||
<!-- description -->
|
||||
<div v-if="df.description" class="mt-2 description" v-html="df.description" />
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ watch(() => props.df.options, () => {
|
|||
|
||||
<!-- select input -->
|
||||
<div class="select-input">
|
||||
<input class="form-control" disabled />
|
||||
<input class="form-control" readonly />
|
||||
<div class="select-icon" v-html="frappe.utils.icon('select', 'sm')"></div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -8,10 +8,13 @@ let store = useStore();
|
|||
let props = defineProps(["df", "value", "modelValue"]);
|
||||
let emit = defineEmits(["update:modelValue"]);
|
||||
let slots = useSlots();
|
||||
let height = "300px";
|
||||
if (props.df.fieldtype == "Small Text") {
|
||||
height = "150px";
|
||||
}
|
||||
|
||||
let height = computed(() => {
|
||||
if (props.df.fieldtype == "Small Text") {
|
||||
return "150px";
|
||||
}
|
||||
return "300px";
|
||||
});
|
||||
|
||||
let doctype = ref("");
|
||||
let fieldname = ref("");
|
||||
|
|
@ -110,7 +113,7 @@ watch([() => doctype.value, () => fieldname.value], ([doctype_value, fieldname_v
|
|||
:style="{ height: height, maxHeight: df.max_height ?? '' }"
|
||||
class="form-control"
|
||||
type="text"
|
||||
disabled
|
||||
readonly
|
||||
/>
|
||||
<textarea
|
||||
v-else
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ onMounted(() => {
|
|||
:deep(.quill) {
|
||||
.ql-toolbar {
|
||||
pointer-events: none;
|
||||
|
||||
.ql-formats {
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
.ql-container p {
|
||||
cursor: pointer;
|
||||
|
|
|
|||
|
|
@ -318,3 +318,9 @@ export function scrub_field_names(fields) {
|
|||
|
||||
return fields;
|
||||
}
|
||||
|
||||
export function clone_field(field) {
|
||||
let cloned_field = JSON.parse(JSON.stringify(field));
|
||||
cloned_field.df.name = frappe.utils.get_random(8);
|
||||
return cloned_field;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue