Merge pull request #19477 from shariquerik/form-builder-fixes-1

Fixes https://github.com/frappe/frappe/issues/19476
This commit is contained in:
Shariq Ansari 2023-01-13 14:55:18 +05:30 committed by GitHub
commit 20ff30896a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 91 additions and 36 deletions

View file

@ -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",

View file

@ -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."
)
)

View file

@ -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>

View file

@ -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;

View file

@ -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 => {

View file

@ -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);
}
}

View file

@ -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 -->

View file

@ -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" />

View file

@ -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>

View file

@ -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

View file

@ -34,6 +34,10 @@ onMounted(() => {
:deep(.quill) {
.ql-toolbar {
pointer-events: none;
.ql-formats {
margin-right: 12px;
}
}
.ql-container p {
cursor: pointer;

View file

@ -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;
}