Edit field options as sidebar (#190)

* Edit field options as sidebar

* WIP

* Finish dynamic positioning of sidebar

* Open block on addition, fix pro tag, add visual clue field open

* fix typo

* remove extra code

* remove extra code

---------

Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
formsdev 2023-09-08 16:19:13 +05:30 committed by GitHub
parent d9c71f5cee
commit d93eca7410
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 945 additions and 106 deletions

View File

@ -71,7 +71,7 @@ export default {
btnClasses() { btnClasses() {
const sizes = this.sizes const sizes = this.sizes
const colorShades = this.colorShades const colorShades = this.colorShades
return `${sizes['p-y']} ${sizes['p-x']} return `v-btn ${sizes['p-y']} ${sizes['p-x']}
${colorShades?.main} ${colorShades?.hover} ${colorShades?.ring} ${colorShades['ring-offset']} ${colorShades?.main} ${colorShades?.hover} ${colorShades?.ring} ${colorShades['ring-offset']}
${colorShades?.text} transition ease-in duration-200 text-center text-${sizes?.font} font-medium focus:outline-none focus:ring-2 ${colorShades?.text} transition ease-in duration-200 text-center text-${sizes?.font} font-medium focus:outline-none focus:ring-2
focus:ring-offset-2 rounded-lg flex items-center hover:no-underline` focus:ring-offset-2 rounded-lg flex items-center hover:no-underline`

View File

@ -1,12 +1,10 @@
<template> <template>
<div class="relative"> <div class="relative">
<div>
<slot name="trigger" <slot name="trigger"
:toggle="toggle" :toggle="toggle"
:open="open" :open="open"
:close="close" :close="close"
/> />
</div>
<transition name="fade"> <transition name="fade">
<div <div
v-if="isOpen" v-if="isOpen"
@ -31,7 +29,10 @@ export default {
}, },
props: { props: {
dropdownClass: { type: String, default: 'origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 z-20' } dropdownClass: {
type: String,
default: 'origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 z-20'
}
}, },
data() { data() {
return { return {

View File

@ -9,18 +9,18 @@
<h2 class="text-nt-blue"> <h2 class="text-nt-blue">
OpnForm PRO OpnForm PRO
</h2> </h2>
<h4 v-if="user.is_subscribed && !user.has_enterprise_subscription" class="text-center mt-5"> <h4 v-if="user &&user.is_subscribed && !user.has_enterprise_subscription" class="text-center mt-5">
We're happy to have you as a Pro customer. If you're having any issue with OpnForm, or if you have a We're happy to have you as a Pro customer. If you're having any issue with OpnForm, or if you have a
feature request, please <a href="mailto:contact@opnform.com">contact us</a>. feature request, please <a href="mailto:contact@opnform.com">contact us</a>.
<br><br> <br><br>
If you need to collaborate, or to work with multiple workspaces, or just larger file uploads, you can If you need to collaborate, or to work with multiple workspaces, or just larger file uploads, you can
also upgrade our subscription to get an Enterprise subscription. also upgrade our subscription to get an Enterprise subscription.
</h4> </h4>
<h4 v-if="user.is_subscribed && user.has_enterprise_subscription" class="text-center mt-5"> <h4 v-if="user && user.is_subscribed && user.has_enterprise_subscription" class="text-center mt-5">
We're happy to have you as an Enterprise customer. If you're having any issue with OpnForm, or if you have a We're happy to have you as an Enterprise customer. If you're having any issue with OpnForm, or if you have a
feature request, please <a href="mailto:contact@opnform.com">contact us</a>. feature request, please <a href="mailto:contact@opnform.com">contact us</a>.
</h4> </h4>
<p v-if="!user.is_subscribed" class="mt-4"> <p v-if="user && !user.is_subscribed" class="mt-4">
All the features with a<span All the features with a<span
class="bg-nt-blue text-white px-2 text-xs uppercase inline rounded-full font-semibold mx-1" class="bg-nt-blue text-white px-2 text-xs uppercase inline rounded-full font-semibold mx-1"
> >

View File

@ -69,6 +69,7 @@
<script> <script>
import FormLogicPropertyResolver from '../../../forms/FormLogicPropertyResolver.js' import FormLogicPropertyResolver from '../../../forms/FormLogicPropertyResolver.js'
import FormPendingSubmissionKey from '../../../mixins/forms/form-pending-submission-key.js' import FormPendingSubmissionKey from '../../../mixins/forms/form-pending-submission-key.js'
import {mapState} from "vuex";
export default { export default {
name: 'OpenFormField', name: 'OpenFormField',
@ -106,6 +107,10 @@ export default {
}, },
computed: { computed: {
...mapState({
selectedFieldIndex: state => state['open/working_form'].selectedFieldIndex,
showEditFieldSidebar: state => state['open/working_form'].showEditFieldSidebar
}),
fieldComponents() { fieldComponents() {
return { return {
text: 'TextInput', text: 'TextInput',
@ -160,6 +165,11 @@ export default {
isFieldDisabled () { isFieldDisabled () {
return (new FormLogicPropertyResolver(this.field, this.dataFormValue)).isDisabled() return (new FormLogicPropertyResolver(this.field, this.dataFormValue)).isDisabled()
}, },
beingEdited() {
return this.adminPreview && this.showEditFieldSidebar && this.form.properties.findIndex((item)=>{
return item.id === this.field.id
}) === this.selectedFieldIndex
},
selectionFieldsOptions () { selectionFieldsOptions () {
// For auto update hidden options // For auto update hidden options
let fieldsOptions = [] let fieldsOptions = []
@ -191,7 +201,11 @@ export default {
getFieldClasses () { getFieldClasses () {
let classes = '' let classes = ''
if (this.adminPreview) { if (this.adminPreview) {
classes += '-mx-4 px-4 -my-1 py-1 group/nffield relative' classes += '-mx-4 px-4 -my-1 py-1 group/nffield relative transition-colors'
if (this.beingEdited) {
classes += ' bg-blue-50 rounded-md'
}
} }
return classes return classes
}, },

View File

@ -49,7 +49,7 @@
</div> </div>
</div> </div>
<div class="w-full flex grow overflow-y-scroll"> <div class="w-full flex grow overflow-y-scroll relative">
<div class="relative w-full shrink-0 overflow-y-scroll border-r md:w-1/2 md:max-w-sm lg:w-2/5"> <div class="relative w-full shrink-0 overflow-y-scroll border-r md:w-1/2 md:max-w-sm lg:w-2/5">
<div class="border-b bg-blue-50 p-5 text-nt-blue-dark md:hidden"> <div class="border-b bg-blue-50 p-5 text-nt-blue-dark md:hidden">
Please create this form on a device with a larger screen. That will allow you to preview your form changes. Please create this form on a device with a larger screen. That will allow you to preview your form changes.
@ -68,6 +68,8 @@
<form-editor-preview/> <form-editor-preview/>
<form-field-edit-sidebar/>
<!-- Form Error Modal --> <!-- Form Error Modal -->
<form-error-modal <form-error-modal
:show="showFormErrorModal" :show="showFormErrorModal"
@ -83,6 +85,7 @@
<script> <script>
import {mapGetters} from 'vuex' import {mapGetters} from 'vuex'
import FormFieldEditSidebar from '../fields/FormFieldEditSidebar.vue'
import FormErrorModal from './form-components/FormErrorModal.vue' import FormErrorModal from './form-components/FormErrorModal.vue'
import FormInformation from './form-components/FormInformation.vue' import FormInformation from './form-components/FormInformation.vue'
import FormStructure from './form-components/FormStructure.vue' import FormStructure from './form-components/FormStructure.vue'
@ -100,6 +103,7 @@ import fieldsLogic from '../../../../mixins/forms/fieldsLogic.js'
export default { export default {
name: 'FormEditor', name: 'FormEditor',
components: { components: {
FormFieldEditSidebar,
FormEditorPreview, FormEditorPreview,
FormIntegrations, FormIntegrations,
FormNotifications, FormNotifications,

View File

@ -3,20 +3,6 @@
<add-form-block-modal :form-blocks="formFields" :show="showAddBlock" @block-added="blockAdded" <add-form-block-modal :form-blocks="formFields" :show="showAddBlock" @block-added="blockAdded"
@close="showAddBlock=false" @close="showAddBlock=false"
/> />
<template v-if="selectedFieldIndex !== null">
<form-field-options-modal :field="formFields[selectedFieldIndex]"
:show="!isNotAFormField(formFields[selectedFieldIndex]) && showEditFieldModal"
:form="form" @close="closeInputOptionModal"
@remove-block="removeBlock(selectedFieldIndex)"
@duplicate-block="duplicateBlock(selectedFieldIndex)"
/>
<form-block-options-modal :field="formFields[selectedFieldIndex]"
:show="isNotAFormField(formFields[selectedFieldIndex]) && showEditFieldModal"
:form="form"
@remove-block="removeBlock(selectedFieldIndex)"
@duplicate-block="duplicateBlock(selectedFieldIndex)" @close="closeInputOptionModal"
/>
</template>
<draggable v-model="formFields" <draggable v-model="formFields"
class="bg-white overflow-hidden dark:bg-notion-dark-light rounded-md w-full mx-auto border transition-colors" class="bg-white overflow-hidden dark:bg-notion-dark-light rounded-md w-full mx-auto border transition-colors"
@ -61,6 +47,15 @@
</div> </div>
</template> </template>
<template v-else> <template v-else>
<button class="hover:bg-red-50 text-gray-500 hover:text-red-600 rounded transition-colors cursor-pointer p-2 hidden md:group-hover:block"
@click="removing=field.id"
>
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M3 6H5M5 6H21M5 6V20C5 20.5304 5.21071 21.0391 5.58579 21.4142C5.96086 21.7893 6.46957 22 7 22H17C17.5304 22 18.0391 21.7893 18.4142 21.4142C18.7893 21.0391 19 20.5304 19 20V6H5ZM8 6V4C8 3.46957 8.21071 2.96086 8.58579 2.58579C8.96086 2.21071 9.46957 2 10 2H14C14.5304 2 15.0391 2.21071 15.4142 2.58579C15.7893 2.96086 16 3.46957 16 4V6M10 11V17M14 11V17"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<button class="hover:bg-nt-blue-lighter rounded transition-colors cursor-pointer p-2 hidden" <button class="hover:bg-nt-blue-lighter rounded transition-colors cursor-pointer p-2 hidden"
:class="{'text-blue-500': !field.hidden, 'text-gray-500': field.hidden, 'group-hover:md:block': !field.hidden, 'md:block':field.hidden}" :class="{'text-blue-500': !field.hidden, 'text-gray-500': field.hidden, 'group-hover:md:block': !field.hidden, 'md:block':field.hidden}"
@click="toggleHidden(field)" @click="toggleHidden(field)"
@ -144,23 +139,18 @@
<script> <script>
import draggable from 'vuedraggable' import draggable from 'vuedraggable'
import FormFieldOptionsModal from '../fields/FormFieldOptionsModal.vue'
import AddFormBlockModal from './form-components/AddFormBlockModal.vue' import AddFormBlockModal from './form-components/AddFormBlockModal.vue'
import FormBlockOptionsModal from '../fields/FormBlockOptionsModal.vue'
import ProTag from '../../../common/ProTag.vue' import ProTag from '../../../common/ProTag.vue'
import clonedeep from 'clone-deep' import clonedeep from 'clone-deep'
import EditableDiv from '../../../common/EditableDiv.vue' import EditableDiv from '../../../common/EditableDiv.vue'
import VButton from "../../../common/Button.vue"; import VButton from "../../../common/Button.vue";
import { mapState } from 'vuex'
export default { export default {
name: 'FormFieldsEditor', name: 'FormFieldsEditor',
components: { components: {
VButton, VButton,
ProTag, ProTag,
FormBlockOptionsModal,
AddFormBlockModal, AddFormBlockModal,
FormFieldOptionsModal,
draggable, draggable,
EditableDiv EditableDiv
}, },
@ -174,10 +164,6 @@ export default {
}, },
computed: { computed: {
...mapState({
selectedFieldIndex: state => state['open/working_form'].selectedFieldIndex,
showEditFieldModal: state => state['open/working_form'].showEditFieldModal
}),
form: { form: {
get() { get() {
return this.$store.state['open/working_form'].content return this.$store.state['open/working_form'].content
@ -300,29 +286,21 @@ export default {
} }
return type return type
}, },
isNotAFormField(block) {
return block && block.type.startsWith('nf')
},
editOptions(index) { editOptions(index) {
this.$store.commit('open/working_form/openSettingsForField', index) this.$store.commit('open/working_form/openSettingsForField', index)
}, },
blockAdded(block) { blockAdded(block) {
this.formFields.push(block) this.formFields.push(block)
this.$store.commit('open/working_form/openSettingsForField', this.formFields.length-1)
}, },
removeBlock(blockIndex) { removeBlock(blockIndex) {
this.closeInputOptionModal() this.closeSidebar()
const newFields = clonedeep(this.formFields) const newFields = clonedeep(this.formFields)
newFields.splice(blockIndex, 1) newFields.splice(blockIndex, 1)
this.$set(this, 'formFields', newFields) this.$set(this, 'formFields', newFields)
}, },
duplicateBlock(blockIndex) { closeSidebar() {
this.closeInputOptionModal() this.$store.commit('open/working_form/closeEditFieldSidebar')
const newField = clonedeep(this.formFields[blockIndex])
newField.id = this.generateUUID()
this.formFields.push(newField)
},
closeInputOptionModal() {
this.$store.commit('open/working_form/closeEditFieldModal')
} }
} }
} }

View File

@ -3,28 +3,7 @@
<div <div
class="bg-gray-50 dark:bg-notion-dark-light hidden md:flex flex-grow p-5 flex-col items-center overflow-y-scroll" class="bg-gray-50 dark:bg-notion-dark-light hidden md:flex flex-grow p-5 flex-col items-center overflow-y-scroll"
> >
<p class="mb-2 text-center text-gray-700"> <div class="border rounded-lg bg-white dark:bg-notion-dark w-full block transition-all max-w-5xl">
Preview Full Page
<v-switch v-model="previewEmbed" class="inline px-2" />
Preview Embed
</p>
<p class="font-semibold">
<span v-if="previewFormSubmitted && !form.re_fillable">
<a href="#" @click.prevent="$refs['form-preview'].restart()">Restart Form
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-nt-blue inline" viewBox="0 0 20 20"
fill="currentColor"
>
<path fill-rule="evenodd"
d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z"
clip-rule="evenodd"
/>
</svg>
</a>
</span>
</p>
<div class="border rounded-lg bg-white dark:bg-notion-dark w-full block transition-all"
:class="{'max-w-lg':previewEmbed,'max-w-5xl':!previewEmbed}"
>
<transition enter-active-class="linear duration-100 overflow-hidden" <transition enter-active-class="linear duration-100 overflow-hidden"
enter-class="max-h-0" enter-class="max-h-0"
enter-to-class="max-h-56" enter-to-class="max-h-56"
@ -32,7 +11,7 @@
leave-class="max-h-56" leave-class="max-h-56"
leave-to-class="max-h-0" leave-to-class="max-h-0"
> >
<div v-if="!previewEmbed && (form.logo_picture || form.cover_picture)"> <div v-if="(form.logo_picture || form.cover_picture)">
<div v-if="form.cover_picture"> <div v-if="form.cover_picture">
<div id="cover-picture" <div id="cover-picture"
class="max-h-56 rounded-t-lg w-full overflow-hidden flex items-center justify-center" class="max-h-56 rounded-t-lg w-full overflow-hidden flex items-center justify-center"
@ -58,7 +37,24 @@
@submitted="previewFormSubmitted=true" @submitted="previewFormSubmitted=true"
/> />
</div> </div>
<p v-if="creating" class=" w-full mt-2 font-normal text-center text-gray-400">Answers won't really be saved</p> <p class="text-center text-xs text-gray-400 dark:text-gray-600 mt-1">
Form Preview <span v-if="creating"
class="font-normal text-gray-400 dark:text-gray-600 text-xs"
>- Answers won't be saved</span>
<br>
<span v-if="previewFormSubmitted && !form.re_fillable">
<a href="#" @click.prevent="$refs['form-preview'].restart()">Restart Form
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-nt-blue inline" viewBox="0 0 20 20"
fill="currentColor"
>
<path fill-rule="evenodd"
d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z"
clip-rule="evenodd"
/>
</svg>
</a>
</span>
</p>
</div> </div>
</template> </template>
@ -73,7 +69,6 @@ export default {
data () { data () {
return { return {
previewFormSubmitted: false, previewFormSubmitted: false,
previewEmbed: false
} }
}, },

View File

@ -1,9 +1,11 @@
<template> <template>
<div v-if="logic" :key="resetKey" class="-mx-4 sm:-mx-6 p-5 border-b"> <collapse v-if="logic" :key="resetKey" :default-value="isCollapseOpen" @click="onClickCollapse">
<template #title>
<h3 class="font-semibold block text-lg"> <h3 class="font-semibold block text-lg">
Logic Logic
</h3> </h3>
<p class="text-gray-400 text-xs mb-5"> </template>
<p class="text-gray-400 text-xs mb-3">
Add some logic to this block. Start by adding some conditions, and then add some actions. Add some logic to this block. Start by adding some conditions, and then add some actions.
</p> </p>
<div class="relative flex"> <div class="relative flex">
@ -26,16 +28,16 @@
</div> </div>
</div> </div>
<h5 class="font-semibold mt-4"> <h5 class="font-semibold mt-3">
1. Conditions 1. Conditions
</h5> </h5>
<condition-editor ref="filter-editor" v-model="logic.conditions" class="mt-4 border-t border" :form="form"/> <condition-editor ref="filter-editor" v-model="logic.conditions" class="mt-1 border-t border rounded-md" :form="form"/>
<h5 class="font-semibold mt-4"> <h5 class="font-semibold mt-3">
2. Actions 2. Actions
</h5> </h5>
<select-input :key="resetKey" v-model="logic.actions" name="actions" <select-input :key="resetKey" v-model="logic.actions" name="actions"
:multiple="true" class="mt-4" placeholder="Actions..." :multiple="true" class="mt-1" placeholder="Actions..."
help="Action(s) triggerred when above conditions are true" help="Action(s) triggerred when above conditions are true"
:options="actionOptions" :options="actionOptions"
@input="onActionInput" @input="onActionInput"
@ -63,7 +65,7 @@
</div> </div>
</div> </div>
</modal> </modal>
</div> </collapse>
</template> </template>
<script> <script>
@ -72,10 +74,11 @@ import ConditionEditor from './ConditionEditor.vue'
import Modal from '../../../../Modal.vue' import Modal from '../../../../Modal.vue'
import SelectInput from '../../../../forms/SelectInput.vue' import SelectInput from '../../../../forms/SelectInput.vue'
import clonedeep from 'clone-deep' import clonedeep from 'clone-deep'
import Collapse from "../../../../common/Collapse.vue";
export default { export default {
name: 'FormBlockLogicEditor', name: 'FormBlockLogicEditor',
components: {SelectInput, Modal, ProTag, ConditionEditor}, components: {Collapse, SelectInput, Modal, ProTag, ConditionEditor},
props: { props: {
field: { field: {
type: Object, type: Object,
@ -89,13 +92,14 @@ export default {
data() { data() {
return { return {
isCollapseOpen: false,
resetKey: 0, resetKey: 0,
logic: this.field.logic || { logic: this.field.logic || {
conditions: null, conditions: null,
actions: [] actions: []
}, },
showCopyFormModal: false, showCopyFormModal: false,
copyFrom: null copyFrom: null,
} }
}, },
@ -199,6 +203,9 @@ export default {
} }
} }
this.showCopyFormModal = false this.showCopyFormModal = false
},
onClickCollapse (e) {
this.isCollapseOpen = e
} }
} }
} }

View File

@ -0,0 +1,157 @@
<template>
<div v-if="showSidebar"
class="absolute shadow-lg shadow-blue-800/30 top-0 h-[calc(100vh-45px)] right-0 lg:shadow-none lg:relative bg-white w-full md:w-1/2 lg:w-2/5 border-l overflow-y-scroll md:max-w-[20rem] flex-shrink-0">
<div class="p-4 border-b sticky top-0 z-10 bg-white">
<button v-if="!field" class="text-gray-500 hover:text-gray-900 cursor-pointer" @click.prevent="closeSidebar">
<svg class="h-6 w-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round"/>
</svg>
</button>
<template v-else>
<div class="flex">
<button class="text-gray-500 hover:text-gray-900 cursor-pointer" @click.prevent="closeSidebar">
<svg class="h-6 w-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round"/>
</svg>
</button>
<div class="font-semibold inline ml-2 truncate flex-grow truncate">
Configure "<span class="truncate">{{ field.name }}</span>"
</div>
</div>
<div class="flex mt-2">
<v-button color="light-gray" class="border-r-0 rounded-r-none text-xs hover:bg-red-50" size="small"
@click="removeBlock">
<svg class="h-4 w-4 text-red-600 inline mr-1 -mt-1" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M3 6H5M5 6H21M5 6V20C5 20.5304 5.21071 21.0391 5.58579 21.4142C5.96086 21.7893 6.46957 22 7 22H17C17.5304 22 18.0391 21.7893 18.4142 21.4142C18.7893 21.0391 19 20.5304 19 20V6H5ZM8 6V4C8 3.46957 8.21071 2.96086 8.58579 2.58579C8.96086 2.21071 9.46957 2 10 2H14C14.5304 2 15.0391 2.21071 15.4142 2.58579C15.7893 2.96086 16 3.46957 16 4V6M10 11V17M14 11V17"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Remove
</v-button>
<v-button size="small" class="text-xs" :class="{
'rounded-none border-r-0':!isBlockField && typeCanBeChanged,
'rounded-l-none':isBlockField || !typeCanBeChanged
}" color="light-gray" @click="duplicateBlock">
<svg class="h-4 w-4 text-blue-600 inline mr-1 -mt-1" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M5 15H4C3.46957 15 2.96086 14.7893 2.58579 14.4142C2.21071 14.0391 2 13.5304 2 13V4C2 3.46957 2.21071 2.96086 2.58579 2.58579C2.96086 2.21071 3.46957 2 4 2H13C13.5304 2 14.0391 2.21071 14.4142 2.58579C14.7893 2.96086 15 3.46957 15 4V5M11 9H20C21.1046 9 22 9.89543 22 11V20C22 21.1046 21.1046 22 20 22H11C9.89543 22 9 21.1046 9 20V11C9 9.89543 9.89543 9 11 9Z"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Duplicate
</v-button>
<change-field-type btn-classes="rounded-l-none text-xs" v-if="!isBlockField" :field="field"
@changeType="onChangeType"/>
</div>
</template>
</div>
<template v-if="field">
<field-options v-if="!isBlockField" :form="form" :field="field"/>
<block-options v-if="isBlockField" :form="form" :field="field"/>
</template>
<div v-else class="text-center p-10">
Click on field's setting icon in your form to modify it
</div>
</div>
</template>
<script>
import {mapState} from 'vuex'
import clonedeep from 'clone-deep'
import ChangeFieldType from "./components/ChangeFieldType.vue"
import FieldOptions from './components/FieldOptions.vue'
import BlockOptions from './components/BlockOptions.vue'
export default {
name: 'FormFieldEditSidebar',
components: {ChangeFieldType, FieldOptions, BlockOptions},
props: {},
data() {
return {
}
},
computed: {
...mapState({
selectedFieldIndex: state => state['open/working_form'].selectedFieldIndex,
showEditFieldSidebar: state => state['open/working_form'].showEditFieldSidebar
}),
form: {
get() {
return this.$store.state['open/working_form'].content
},
/* We add a setter */
set(value) {
this.$store.commit('open/working_form/set', value)
}
},
field() {
return (this.form && this.selectedFieldIndex !== null) ? this.form.properties[this.selectedFieldIndex] : null
},
showSidebar() {
return (this.form && this.selectedFieldIndex !== null) ? (this.form.properties[this.selectedFieldIndex] && this.showEditFieldSidebar) : false
},
isBlockField() {
return this.field && this.field.type.startsWith('nf')
},
typeCanBeChanged() {
return ['text', 'email', 'phone', 'number','select', 'multi_select'].includes(this.field.type)
}
},
watch: {},
created() {
},
mounted() {
},
methods: {
onChangeType(newType) {
if (['select', 'multi_select'].includes(this.field.type)) {
this.$set(this.field, newType, this.field[this.field.type]) // Set new options with new type
this.$delete(this.field, this.field.type) // remove old type options
}
this.$set(this.field, 'type', newType)
},
removeBlock() {
this.closeSidebar()
const newFields = clonedeep(this.form.properties)
newFields.splice(this.selectedFieldIndex, 1)
this.$set(this.form, 'properties', newFields)
},
duplicateBlock() {
this.closeSidebar()
const newFields = clonedeep(this.form.properties)
const newField = clonedeep(this.form.properties[this.selectedFieldIndex])
newField.id = this.generateUUID()
newFields.push(newField)
this.$set(this.form, 'properties', newFields)
},
closeSidebar() {
this.$store.commit('open/working_form/closeEditFieldSidebar')
},
generateUUID() {
let d = new Date().getTime()// Timestamp
let d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now() * 1000)) || 0// Time in microseconds since page-load or 0 if unsupported
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
let r = Math.random() * 16// random number between 0 and 16
if (d > 0) { // Use timestamp until depleted
r = (d + r) % 16 | 0
d = Math.floor(d / 16)
} else { // Use microseconds since page-load if supported
r = (d2 + r) % 16 | 0
d2 = Math.floor(d2 / 16)
}
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16)
})
}
}
}
</script>

View File

@ -0,0 +1,160 @@
<template>
<div v-if="field">
<!-- General -->
<div class="border-b py-2 px-4">
<h3 class="font-semibold block text-lg">
General
</h3>
<p class="text-gray-400 mb-3 text-xs">
Exclude this field or make it required.
</p>
<v-checkbox v-model="field.hidden" class="mb-3"
:name="field.id+'_hidden'"
@input="onFieldHiddenChange"
>
Hidden
</v-checkbox>
<select-input name="width" class="mt-3"
:options="[
{name:'Full',value:'full'},
{name:'1/2 (half width)',value:'1/2'},
{name:'1/3 (a third of the width)',value:'1/3'},
{name:'2/3 (two thirds of the width)',value:'2/3'},
{name:'1/4 (a quarter of the width)',value:'1/4'},
{name:'3/4 (three quarters of the width)',value:'3/4'}
]"
:form="field" label="Field Width"
/>
<select-input v-if="['nf-text','nf-image'].includes(field.type)" name="align" class="mt-3"
:options="[
{name:'Left',value:'left'},
{name:'Center',value:'center'},
{name:'Right',value:'right'},
{name:'Justify',value:'justify'}
]"
:form="field" label="Field Alignment"
/>
</div>
<div v-if="field.type == 'nf-text'" class="border-b py-2 px-4">
<rich-text-area-input name="content"
:form="field"
label="Content"
:required="false"
/>
</div>
<div v-else-if="field.type == 'nf-page-break'" class="border-b py-2 px-4">
<text-input name="next_btn_text"
:form="field"
label="Text of next button"
:required="true"
/>
<text-input name="previous_btn_text"
:form="field"
label="Text of previous button"
help="Shown on the next page"
:required="true"
/>
</div>
<div v-else-if="field.type == 'nf-divider'" class="border-b py-2 px-4">
<text-input name="name"
:form="field" :required="true"
label="Field Name"
/>
</div>
<div v-else-if="field.type == 'nf-image'" class="border-b py-2 px-4">
<text-input name="name"
:form="field" :required="true"
label="Field Name"
/>
<image-input name="image_block" class="mt-3"
:form="field" label="Upload Image" :required="false"
/>
</div>
<div v-else-if="field.type == 'nf-code'" class="border-b py-2 px-4">
<code-input name="content" :form="field" label="Content"
help="You can add any html code, including iframes" />
</div>
<div v-else class="border-b py-2 px-4">
<p>No settings found.</p>
</div>
<!-- Logic Block -->
<form-block-logic-editor class="py-2 px-4 border-b" :form="form" :field="field" v-model="form"/>
</div>
</template>
<script>
const FormBlockLogicEditor = () => import('../../components/form-logic-components/FormBlockLogicEditor.vue')
import CodeInput from '../../../../forms/CodeInput.vue'
export default {
name: 'BlockOptions',
components: {FormBlockLogicEditor, CodeInput},
props: {
field: {
type: Object,
required: false
},
form: {
type: Object,
required: false
}
},
data() {
return {
editorToolbarCustom: [
['bold', 'italic', 'underline', 'link'],
]
}
},
computed: {},
watch: {
'field.width': {
handler (val) {
if (val === undefined || val === null) {
this.$set(this.field, 'width', 'full')
}
},
immediate: true
},
'field.align': {
handler (val) {
if (val === undefined || val === null) {
this.$set(this.field, 'align', 'left')
}
},
immediate: true
}
},
created () {
if (this.field?.width === undefined || this.field?.width === null) {
this.$set(this.field, 'width', 'full')
}
},
mounted() {},
methods: {
onFieldHiddenChange(val) {
this.$set(this.field, 'hidden', val)
if (this.field.hidden) {
this.$set(this.field, 'required', false)
}
},
onFieldHelpPositionChange (val) {
if(!val){
this.$set(this.field, 'help_position', 'below_input')
}
}
}
}
</script>

View File

@ -1,12 +1,11 @@
<template> <template>
<dropdown dusk="nav-dropdown" v-if="changeTypeOptions.length > 0"> <dropdown dusk="nav-dropdown" v-if="changeTypeOptions.length > 0">
<template #trigger="{toggle}"> <template #trigger="{toggle}">
<v-button class="relative" size="small" color="light-gray" @click="toggle"> <v-button class="relative" :class="btnClasses" size="small" color="light-gray" @click="toggle">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-4 w-4 text-blue-600 inline mr-1 -mt-1"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="h-4 w-4 text-blue-600 inline mr-1 -mt-1">
<path stroke-linecap="round" stroke-linejoin="round" d="M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5" /> <path stroke-linecap="round" stroke-linejoin="round" d="M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5" />
</svg> </svg>
<span class="whitespace-nowrap">Change Type</span>
Change Field Type
</v-button> </v-button>
</template> </template>
@ -29,6 +28,10 @@ export default {
field: { field: {
type: Object, type: Object,
required: true required: true
},
btnClasses: {
type: String,
required: true
} }
}, },
data() { data() {

View File

@ -0,0 +1,518 @@
<template>
<div v-if="field" class="py-2">
<!-- General -->
<div class="border-b px-4">
<h3 class="font-semibold block text-lg">
General
</h3>
<p class="text-gray-400 mb-2 text-xs">
Exclude this field or make it required.
</p>
<v-checkbox v-model="field.hidden" class="mb-3"
:name="field.id+'_hidden'"
@input="onFieldHiddenChange"
>
Hidden
</v-checkbox>
<v-checkbox v-model="field.required" class="mb-3"
:name="field.id+'_required'"
@input="onFieldRequiredChange"
>
Required
</v-checkbox>
<v-checkbox v-model="field.disabled" class="mb-3"
:name="field.id+'_disabled'"
@input="onFieldDisabledChange"
>
Disabled
</v-checkbox>
</div>
<!-- Checkbox -->
<div v-if="field.type === 'checkbox'" class="border-b py-2 px-4">
<h3 class="font-semibold block text-lg">
Checkbox
</h3>
<p class="text-gray-400 mb-3 text-xs">
Advanced options for checkbox.
</p>
<v-checkbox v-model="field.use_toggle_switch" class="mt-3"
name="use_toggle_switch" help=""
>
Use toggle switch
</v-checkbox>
<p class="text-gray-400 mb-3 text-xs">
If enabled, checkbox will be replaced with a toggle switch
</p>
</div>
<!-- File Uploads -->
<div v-if="field.type === 'files'" class="border-b py-2 px-4">
<h3 class="font-semibold block text-lg">
File uploads
</h3>
<v-checkbox v-model="field.multiple" class="mt-3"
:name="field.id+'_multiple'"
>
Allow multiple files
</v-checkbox>
<text-input name="allowed_file_types" class="mt-3" :form="field"
label="Allowed file types" placeholder="jpg,jpeg,png,gif"
help="Comma separated values, leave blank to allow all file types"
/>
</div>
<!-- Number Options -->
<div v-if="field.type === 'number'" class="border-b py-2 px-4">
<h3 class="font-semibold block text-lg">
Number Options
</h3>
<v-checkbox v-model="field.is_rating" class="mt-3"
:name="field.id+'_is_rating'" @input="initRating"
>
Rating
</v-checkbox>
<p class="text-gray-400 mb-3 text-xs">
If enabled then this field will be star rating input.
</p>
<text-input v-if="field.is_rating" name="rating_max_value" native-type="number" :min="1" class="mt-3"
:form="field" required
label="Max rating value"
/>
</div>
<!-- Text Options -->
<div v-if="field.type === 'text' && displayBasedOnAdvanced" class="border-b py-2 px-4">
<h3 class="font-semibold block text-lg">
Text Options
</h3>
<p class="text-gray-400 mb-3 text-xs">
Keep it simple or make it a multi-lines input.
</p>
<v-checkbox v-model="field.multi_lines" class="mb-2"
:name="field.id+'_multi_lines'"
@input="$set(field,'multi_lines',$event)"
>
Multi-lines input
</v-checkbox>
</div>
<!-- Date Options -->
<div v-if="field.type === 'date'" class="border-b py-2 px-4">
<h3 class="font-semibold block text-lg">
Date Options
</h3>
<v-checkbox v-model="field.date_range" class="mt-3"
:name="field.id+'_date_range'"
@input="onFieldDateRangeChange"
>
Date Range
</v-checkbox>
<p class="text-gray-400 mb-3 text-xs">
Adds an end date. This cannot be used with the time option yet.
</p>
<v-checkbox v-model="field.with_time"
:name="field.id+'_with_time'"
@input="onFieldWithTimeChange"
>
Date with time
</v-checkbox>
<p class="text-gray-400 mb-3 text-xs">
Include time. Or not. This cannot be used with the date range option yet.
</p>
<select-input v-if="field.with_time" name="timezone" class="mt-3"
:form="field" :options="timezonesOptions"
label="Timezone" :searchable="true"
help="Make sure to select correct timezone. Leave blank otherwise."
/>
<v-checkbox v-model="field.prefill_today"
name="prefill_today"
@input="onFieldPrefillTodayChange"
>
Prefill with 'today'
</v-checkbox>
<p class="text-gray-400 mb-3 text-xs">
if enabled we will pre-fill this field with the current date
</p>
<v-checkbox v-model="field.disable_past_dates"
name="disable_past_dates" class="mb-3"
@input="onFieldDisablePastDatesChange"
>
Disable past dates
</v-checkbox>
<v-checkbox v-model="field.disable_future_dates"
name="disable_future_dates" class="mb-3"
@input="onFieldDisableFutureDatesChange"
>
Disable future dates
</v-checkbox>
</div>
<!-- select/multiselect Options -->
<div v-if="['select','multi_select'].includes(field.type)" class="border-b py-2 px-4">
<h3 class="font-semibold block text-lg">
Select Options
</h3>
<p class="text-gray-400 mb-3 text-xs">
Advanced options for your select/multiselect fields.
</p>
<text-area-input v-model="optionsText" :name="field.id+'_options_text'" class="mt-3"
@input="onFieldOptionsChange"
label="Set selection options"
help="Add one option per line"
/>
<v-checkbox v-model="field.allow_creation"
name="allow_creation" @input="onFieldAllowCreationChange" help=""
>
Allow respondent to create new options
</v-checkbox>
<v-checkbox v-model="field.without_dropdown" class="mt-3"
name="without_dropdown" @input="onFieldWithoutDropdownChange" help=""
>
Always show all select options
</v-checkbox>
<p class="text-gray-400 mb-3 text-xs">Options won't be in a dropdown anymore, but will all be visible</p>
</div>
<!-- Customization - Placeholder, Prefill, Relabel, Field Help -->
<div v-if="displayBasedOnAdvanced" class="border-b py-2 px-4">
<h3 class="font-semibold block text-lg">
Customization
</h3>
<p class="text-gray-400 mb-3 text-xs">
Change your form field name, pre-fill a value, add hints, etc.
</p>
<text-input name="name" class="mt-3"
:form="field" :required="true"
label="Field Name"
/>
<v-checkbox v-model="field.hide_field_name" class="mb-3"
:name="field.id+'_hide_field_name'"
>
Hide field name
</v-checkbox>
<!-- Pre-fill depends on type -->
<v-checkbox v-if="field.type=='checkbox'" v-model="field.prefill" class="mt-3"
:name="field.id+'_prefill'"
@input="$set(field,'prefill',$event)"
>
Pre-filled value
</v-checkbox>
<select-input v-else-if="['select','multi_select'].includes(field.type)" name="prefill" class="mt-3"
:form="field" :options="prefillSelectsOptions"
label="Pre-filled value"
:multiple="field.type==='multi_select'"
/>
<date-input v-else-if="field.type==='date' && field.prefill_today!==true" name="prefill" class="mt-3"
:form="field" :with-time="field.with_time===true"
:date-range="field.date_range===true"
label="Pre-filled value"
/>
<text-area-input v-else-if="field.type === 'text' && field.multi_lines"
name="prefill" class="mt-3"
:form="field"
label="Pre-filled value"
/>
<text-input v-else-if="field.type!=='files'" name="prefill" class="mt-3"
:form="field"
label="Pre-filled value"
:disabled="field.type==='date' && field.prefill_today===true"
/>
<div v-if="['select','multi_select'].includes(field.type)" class="-mt-3 mb-3 text-gray-400 dark:text-gray-500">
<small>
A problem? <a href="#" @click.prevent="field.prefill=null">Click here to clear your pre-fill</a>
</small>
</div>
<!-- Placeholder -->
<text-input v-if="hasPlaceholder" name="placeholder" class="mt-3"
:form="field"
label="Empty Input Text (Placeholder)"
/>
<select-input name="width" class="mt-3"
:options="[
{name:'Full',value:'full'},
{name:'1/2 (half width)',value:'1/2'},
{name:'1/3 (a third of the width)',value:'1/3'},
{name:'2/3 (two thirds of the width)',value:'2/3'},
{name:'1/4 (a quarter of the width)',value:'1/4'},
{name:'3/4 (three quarters of the width)',value:'3/4'},
]"
:form="field" label="Field Width"
/>
<!-- Help -->
<rich-text-area-input name="help" class="mt-3"
:form="field"
:editorToolbar="editorToolbarCustom"
label="Field Help"
help="Your field help will be shown below/above the field, just like this message."
:help-position="field.help_position"
/>
<select-input name="help_position" class="mt-3"
:options="[
{name:'Below input',value:'below_input'},
{name:'Above input',value:'above_input'},
]"
:form="field" label="Field Help Position"
@input="onFieldHelpPositionChange"
/>
<template v-if="['text','number','url','email','phone_number'].includes(field.type)">
<text-input v-model="field.max_char_limit" name="max_char_limit" native-type="number" :min="1" :max="2000"
:form="field"
label="Max character limit"
help="Maximum character limit of 2000"
:required="false"
/>
<checkbox-input name="show_char_limit" :form="field" class="mt-3"
label="Always show character limit"
/>
</template>
</div>
<!-- Advanced Options -->
<div v-if="field.type === 'text'" class="border-b py-2 px-4">
<h3 class="font-semibold block text-lg mb-3">
Advanced Options
</h3>
<v-checkbox v-model="field.generates_uuid"
:name="field.id+'_generates_uuid'"
@input="onFieldGenUIdChange"
>
Generates a unique id
</v-checkbox>
<p class="text-gray-400 mb-3 text-xs">
If you enable this, we will hide this field and fill it with a unique id (UUID format) on each new form submission
</p>
<v-checkbox v-model="field.generates_auto_increment_id"
:name="field.id+'_generates_auto_increment_id'"
@input="onFieldGenAutoIdChange"
>
Generates an auto-incremented id
</v-checkbox>
<p class="text-gray-400 mb-3 text-xs">
If you enable this, we will hide this field and fill it a unique incrementing number on each new form submission
</p>
</div>
<!-- Logic Block -->
<form-block-logic-editor class="py-2 px-4 border-b" v-model="form" :form="form" :field="field"/>
</div>
</template>
<script>
const FormBlockLogicEditor = () => import('../../components/form-logic-components/FormBlockLogicEditor.vue')
import timezones from '../../../../../../data/timezones.json'
export default {
name: 'FieldOptions',
components: {FormBlockLogicEditor},
props: {
field: {
type: Object,
required: false
},
form: {
type: Object,
required: false
}
},
data() {
return {
typesWithoutPlaceholder: ['date', 'checkbox', 'files'],
editorToolbarCustom: [
['bold', 'italic', 'underline', 'link'],
]
}
},
computed: {
hasPlaceholder() {
return !this.typesWithoutPlaceholder.includes(this.field.type)
},
prefillSelectsOptions() {
if (!['select', 'multi_select'].includes(this.field.type)) return {}
return this.field[this.field.type].options.map(option => {
return {
name: option.name,
value: option.id
}
})
},
timezonesOptions() {
if (this.field.type !== 'date') return []
return timezones.map((timezone) => {
return {
name: timezone.text,
value: timezone.utc[0]
}
})
},
displayBasedOnAdvanced() {
if (this.field.generates_uuid || this.field.generates_auto_increment_id) {
return false
}
return true
},
optionsText() {
return this.field[this.field.type].options.map(option => {
return option.name
}).join("\n")
},
},
watch: {
'field.width': {
handler (val) {
if (val === undefined || val === null) {
this.$set(this.field, 'width', 'full')
}
},
immediate: true
},
'field.align': {
handler (val) {
if (val === undefined || val === null) {
this.$set(this.field, 'align', 'left')
}
},
immediate: true
}
},
created () {
if (this.field?.width === undefined || this.field?.width === null) {
this.$set(this.field, 'width', 'full')
}
},
mounted() {
if (['text', 'number', 'url', 'email', 'phone_number'].includes(this.field?.type) && !this.field?.max_char_limit) {
this.field.max_char_limit = 2000
}
},
methods: {
onFieldDisabledChange (val) {
this.$set(this.field, 'disabled', val)
if (this.field.disabled) {
this.$set(this.field, 'hidden', false)
}
},
onFieldRequiredChange(val) {
this.$set(this.field, 'required', val)
if (this.field.required) {
this.$set(this.field, 'hidden', false)
}
},
onFieldHiddenChange(val) {
this.$set(this.field, 'hidden', val)
if (this.field.hidden) {
this.$set(this.field, 'required', false)
this.$set(this.field, 'disabled', false)
} else {
this.$set(this.field, 'generates_uuid', false)
this.$set(this.field, 'generates_auto_increment_id', false)
}
},
onFieldDateRangeChange(val) {
this.$set(this.field, 'date_range', val)
if (this.field.date_range) {
this.$set(this.field, 'with_time', false)
this.$set(this.field, 'prefill_today', false)
}
},
onFieldWithTimeChange(val) {
this.$set(this.field, 'with_time', val)
if (this.field.with_time) {
this.$set(this.field, 'date_range', false)
}
},
onFieldGenUIdChange(val) {
this.$set(this.field, 'generates_uuid', val)
if (this.field.generates_uuid) {
this.$set(this.field, 'generates_auto_increment_id', false)
this.$set(this.field, 'hidden', true)
}
},
onFieldGenAutoIdChange(val) {
this.$set(this.field, 'generates_auto_increment_id', val)
if (this.field.generates_auto_increment_id) {
this.$set(this.field, 'generates_uuid', false)
this.$set(this.field, 'hidden', true)
}
},
initRating() {
if (this.field.is_rating && !this.field.rating_max_value) {
this.$set(this.field, 'rating_max_value', 5)
}
},
onFieldOptionsChange(val) {
const vals = (val) ? val.trim().split("\n") : []
const tmpOpts = vals.map(name => {
return {
name: name,
id: name
}
})
this.$set(this.field, this.field.type, {'options': tmpOpts})
},
onFieldPrefillTodayChange(val) {
this.$set(this.field, 'prefill_today', val)
if (this.field.prefill_today) {
this.$set(this.field, 'prefill', 'Pre-filled with current date')
this.$set(this.field, 'date_range', false)
this.$set(this.field, 'disable_future_dates', false)
this.$set(this.field, 'disable_past_dates', false)
} else {
this.$set(this.field, 'prefill', null)
}
},
onFieldAllowCreationChange(val) {
this.$set(this.field, 'allow_creation', val)
if (this.field.allow_creation) {
this.$set(this.field, 'without_dropdown', false)
}
},
onFieldWithoutDropdownChange(val) {
this.$set(this.field, 'without_dropdown', val)
if (this.field.without_dropdown) {
this.$set(this.field, 'allow_creation', false)
}
},
onFieldDisablePastDatesChange(val) {
this.$set(this.field, 'disable_past_dates', val)
if (this.field.disable_past_dates) {
this.$set(this.field, 'disable_future_dates', false)
this.$set(this.field, 'prefill_today', false)
}
},
onFieldDisableFutureDatesChange(val) {
this.$set(this.field, 'disable_future_dates', val)
if (this.field.disable_future_dates) {
this.$set(this.field, 'disable_past_dates', false)
this.$set(this.field, 'prefill_today', false)
}
},
onFieldHelpPositionChange (val) {
if (!val) {
this.$set(this.field, 'help_position', 'below_input')
}
}
}
}
</script>

View File

@ -6,7 +6,7 @@ export const state = {
// Field being edited // Field being edited
selectedFieldIndex: null, selectedFieldIndex: null,
showEditFieldModal: null showEditFieldSidebar: null
} }
// mutations // mutations
@ -23,16 +23,13 @@ export const mutations = {
index = state.content.properties.findIndex(prop => prop.id === index.id) index = state.content.properties.findIndex(prop => prop.id === index.id)
} }
state.selectedFieldIndex = index state.selectedFieldIndex = index
state.showEditFieldModal = true state.showEditFieldSidebar = true
}, },
setSelectedFieldIndex (state, index) { setSelectedFieldIndex (state, index) {
state.selectedFieldIndex = index state.selectedFieldIndex = index
}, },
openEditFieldModal (state) { closeEditFieldSidebar (state) {
state.showEditFieldModal = true state.showEditFieldSidebar = false
}, state.selectedFieldIndex = null
closeEditFieldModal (state) {
state.showEditFieldModal = false
this.selectedFieldIndex = null
} }
} }

5
tailwind.config.js vendored
View File

@ -1,4 +1,5 @@
const colors = require('tailwindcss/colors') const colors = require('tailwindcss/colors')
const plugin = require('tailwindcss/plugin')
module.exports = { module.exports = {
content: [ content: [
@ -73,5 +74,9 @@ module.exports = {
}, },
plugins: [ plugins: [
require('@tailwindcss/aspect-ratio'), require('@tailwindcss/aspect-ratio'),
plugin(function({ addVariant }) {
addVariant('between', '&:not(:first-child):not(:last-child)')
addVariant('hocus', ['&:hover', '&:focus'])
})
] ]
} }