Add block shortcut (#200)
* Add block shortcut * Add block shortcut * Add block modal as sidebar * add block sidebar UI changes * Clean spacing add form block sidebar --------- Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
parent
402e74eaad
commit
52c9f054a5
|
@ -6,8 +6,18 @@
|
||||||
<div v-if="adminPreview"
|
<div v-if="adminPreview"
|
||||||
class="absolute -translate-x-full top-0 bottom-0 opacity-0 group-hover/nffield:opacity-100 transition-opacity mb-4"
|
class="absolute -translate-x-full top-0 bottom-0 opacity-0 group-hover/nffield:opacity-100 transition-opacity mb-4"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col lg:flex-row bg-white rounded-md">
|
<div class="flex flex-col bg-white rounded-md" :class="{'lg:flex-row':!fieldSideBarOpened, 'xl:flex-row':fieldSideBarOpened}">
|
||||||
<div class="p-2 lg:pr-1 text-gray-300 hover:text-blue-500 cursor-pointer" role="button"
|
<div class="p-2 -mr-3 -mb-2 text-gray-300 hover:text-blue-500 cursor-pointer hidden xl:block" role="button"
|
||||||
|
:class="{'lg:block':!fieldSideBarOpened, 'xl:block':fieldSideBarOpened}"
|
||||||
|
@click.prevent="openAddFieldSidebar"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="3"
|
||||||
|
stroke="currentColor" class="w-5 h-5">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="p-2 text-gray-300 hover:text-blue-500 cursor-pointer" role="button"
|
||||||
|
:class="{'lg:-mr-2':!fieldSideBarOpened, 'xl:-mr-2':fieldSideBarOpened}"
|
||||||
@click.prevent="editFieldOptions"
|
@click.prevent="editFieldOptions"
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
|
||||||
|
@ -20,7 +30,8 @@
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="px-2 lg:pl-0 lg:pr-1 lg:pt-2 pb-2 bg-white rounded-md text-gray-300 hover:text-gray-500 cursor-grab draggable"
|
class="px-2 xl:pl-0 lg:pr-1 lg:pt-2 pb-2 bg-white rounded-md text-gray-300 hover:text-gray-500 cursor-grab draggable"
|
||||||
|
:class="{'lg:pr-1 lg:pl-0':!fieldSideBarOpened, 'xl:-mr-2':fieldSideBarOpened}"
|
||||||
role="button"
|
role="button"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
|
@ -38,23 +49,23 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<component :is="getFieldComponents" v-if="getFieldComponents"
|
<component :is="getFieldComponents" v-if="getFieldComponents"
|
||||||
v-bind="inputProperties(field)" :required="isFieldRequired"
|
v-bind="inputProperties(field)" :required="isFieldRequired"
|
||||||
:disabled="isFieldDisabled"
|
:disabled="isFieldDisabled"
|
||||||
/>
|
/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div v-if="field.type === 'nf-text' && field.content" :id="field.id" :key="field.id"
|
<div v-if="field.type === 'nf-text' && field.content" :id="field.id" :key="field.id"
|
||||||
class="nf-text w-full px-2 mb-3" :class="[getFieldAlignClasses(field)]"
|
class="nf-text w-full px-2 mb-3" :class="[getFieldAlignClasses(field)]"
|
||||||
v-html="field.content"
|
v-html="field.content"
|
||||||
/>
|
/>
|
||||||
<div v-if="field.type === 'nf-code' && field.content" :id="field.id" :key="field.id"
|
<div v-if="field.type === 'nf-code' && field.content" :id="field.id" :key="field.id"
|
||||||
class="nf-code w-full px-2 mb-3"
|
class="nf-code w-full px-2 mb-3"
|
||||||
v-html="field.content"
|
v-html="field.content"
|
||||||
/>
|
/>
|
||||||
<div v-if="field.type === 'nf-divider'" :id="field.id" :key="field.id"
|
<div v-if="field.type === 'nf-divider'" :id="field.id" :key="field.id"
|
||||||
class="border-b my-4 w-full mx-2"
|
class="border-b my-4 w-full mx-2"
|
||||||
/>
|
/>
|
||||||
<div v-if="field.type === 'nf-image' && (field.image_block || !isPublicFormPage)" :id="field.id"
|
<div v-if="field.type === 'nf-image' && (field.image_block || !isPublicFormPage)" :id="field.id"
|
||||||
:key="field.id" class="my-4 w-full px-2" :class="[getFieldAlignClasses(field)]"
|
:key="field.id" class="my-4 w-full px-2" :class="[getFieldAlignClasses(field)]"
|
||||||
>
|
>
|
||||||
<div v-if="!field.image_block" class="p-4 border border-dashed">
|
<div v-if="!field.image_block" class="p-4 border border-dashed">
|
||||||
Open <b>{{ field.name }}'s</b> block settings to upload image.
|
Open <b>{{ field.name }}'s</b> block settings to upload image.
|
||||||
|
@ -100,9 +111,9 @@ export default {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
adminPreview: { type: Boolean, default: false } // If used in FormEditorPreview
|
adminPreview: {type: Boolean, default: false} // If used in FormEditorPreview
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
return {}
|
return {}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -128,7 +139,7 @@ export default {
|
||||||
/**
|
/**
|
||||||
* Get the right input component for the field/options combination
|
* Get the right input component for the field/options combination
|
||||||
*/
|
*/
|
||||||
getFieldComponents() {
|
getFieldComponents() {
|
||||||
const field = this.field
|
const field = this.field
|
||||||
if (field.type === 'text' && field.multi_lines) {
|
if (field.type === 'text' && field.multi_lines) {
|
||||||
return 'TextAreaInput'
|
return 'TextAreaInput'
|
||||||
|
@ -150,27 +161,27 @@ export default {
|
||||||
}
|
}
|
||||||
return this.fieldComponents[field.type]
|
return this.fieldComponents[field.type]
|
||||||
},
|
},
|
||||||
isPublicFormPage () {
|
isPublicFormPage() {
|
||||||
return this.$route.name === 'forms.show_public'
|
return this.$route.name === 'forms.show_public'
|
||||||
},
|
},
|
||||||
isFieldHidden () {
|
isFieldHidden() {
|
||||||
return !this.showHidden && this.shouldBeHidden
|
return !this.showHidden && this.shouldBeHidden
|
||||||
},
|
},
|
||||||
shouldBeHidden () {
|
shouldBeHidden() {
|
||||||
return (new FormLogicPropertyResolver(this.field, this.dataFormValue)).isHidden()
|
return (new FormLogicPropertyResolver(this.field, this.dataFormValue)).isHidden()
|
||||||
},
|
},
|
||||||
isFieldRequired () {
|
isFieldRequired() {
|
||||||
return (new FormLogicPropertyResolver(this.field, this.dataFormValue)).isRequired()
|
return (new FormLogicPropertyResolver(this.field, this.dataFormValue)).isRequired()
|
||||||
},
|
},
|
||||||
isFieldDisabled () {
|
isFieldDisabled() {
|
||||||
return (new FormLogicPropertyResolver(this.field, this.dataFormValue)).isDisabled()
|
return (new FormLogicPropertyResolver(this.field, this.dataFormValue)).isDisabled()
|
||||||
},
|
},
|
||||||
beingEdited() {
|
beingEdited() {
|
||||||
return this.adminPreview && this.showEditFieldSidebar && this.form.properties.findIndex((item)=>{
|
return this.adminPreview && this.showEditFieldSidebar && this.form.properties.findIndex((item) => {
|
||||||
return item.id === this.field.id
|
return item.id === this.field.id
|
||||||
}) === this.selectedFieldIndex
|
}) === this.selectedFieldIndex
|
||||||
},
|
},
|
||||||
selectionFieldsOptions () {
|
selectionFieldsOptions() {
|
||||||
// For auto update hidden options
|
// For auto update hidden options
|
||||||
let fieldsOptions = []
|
let fieldsOptions = []
|
||||||
|
|
||||||
|
@ -184,21 +195,28 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
return fieldsOptions
|
return fieldsOptions
|
||||||
|
},
|
||||||
|
fieldSideBarOpened() {
|
||||||
|
return this.adminPreview && (this.form && this.selectedFieldIndex !== null) ? (this.form.properties[this.selectedFieldIndex] && this.showEditFieldSidebar) : false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {},
|
watch: {},
|
||||||
|
|
||||||
mounted () {},
|
mounted() {
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
editFieldOptions () {
|
editFieldOptions() {
|
||||||
this.$store.commit('open/working_form/openSettingsForField', this.field)
|
this.$store.commit('open/working_form/openSettingsForField', this.field)
|
||||||
},
|
},
|
||||||
|
openAddFieldSidebar() {
|
||||||
|
this.$store.commit('open/working_form/openAddFieldSidebar', this.field)
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Get the right input component for the field/options combination
|
* Get the right input component for the field/options combination
|
||||||
*/
|
*/
|
||||||
getFieldClasses () {
|
getFieldClasses() {
|
||||||
let classes = ''
|
let classes = ''
|
||||||
if (this.adminPreview) {
|
if (this.adminPreview) {
|
||||||
classes += '-mx-4 px-4 -my-1 py-1 group/nffield relative transition-colors'
|
classes += '-mx-4 px-4 -my-1 py-1 group/nffield relative transition-colors'
|
||||||
|
@ -209,7 +227,7 @@ export default {
|
||||||
}
|
}
|
||||||
return classes
|
return classes
|
||||||
},
|
},
|
||||||
getFieldWidthClasses (field) {
|
getFieldWidthClasses(field) {
|
||||||
if (!field.width || field.width === 'full') return 'w-full px-2'
|
if (!field.width || field.width === 'full') return 'w-full px-2'
|
||||||
else if (field.width === '1/2') {
|
else if (field.width === '1/2') {
|
||||||
return 'w-full sm:w-1/2 px-2'
|
return 'w-full sm:w-1/2 px-2'
|
||||||
|
@ -223,7 +241,7 @@ export default {
|
||||||
return 'w-full sm:w-3/4 px-2'
|
return 'w-full sm:w-3/4 px-2'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getFieldAlignClasses (field) {
|
getFieldAlignClasses(field) {
|
||||||
if (!field.align || field.align === 'left') return 'text-left'
|
if (!field.align || field.align === 'left') return 'text-left'
|
||||||
else if (field.align === 'right') {
|
else if (field.align === 'right') {
|
||||||
return 'text-right'
|
return 'text-right'
|
||||||
|
|
|
@ -69,6 +69,7 @@
|
||||||
<form-editor-preview/>
|
<form-editor-preview/>
|
||||||
|
|
||||||
<form-field-edit-sidebar/>
|
<form-field-edit-sidebar/>
|
||||||
|
<add-form-block-sidebar/>
|
||||||
|
|
||||||
<!-- Form Error Modal -->
|
<!-- Form Error Modal -->
|
||||||
<form-error-modal
|
<form-error-modal
|
||||||
|
@ -85,6 +86,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapGetters} from 'vuex'
|
import {mapGetters} from 'vuex'
|
||||||
|
import AddFormBlockSidebar from './form-components/AddFormBlockSidebar.vue'
|
||||||
import FormFieldEditSidebar from '../fields/FormFieldEditSidebar.vue'
|
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'
|
||||||
|
@ -103,6 +105,7 @@ import fieldsLogic from '../../../../mixins/forms/fieldsLogic.js'
|
||||||
export default {
|
export default {
|
||||||
name: 'FormEditor',
|
name: 'FormEditor',
|
||||||
components: {
|
components: {
|
||||||
|
AddFormBlockSidebar,
|
||||||
FormFieldEditSidebar,
|
FormFieldEditSidebar,
|
||||||
FormEditorPreview,
|
FormEditorPreview,
|
||||||
FormIntegrations,
|
FormIntegrations,
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<add-form-block-modal :form-blocks="formFields" :show="showAddBlock" @block-added="blockAdded"
|
<v-button v-if="formFields && formFields.length > 8"
|
||||||
@close="showAddBlock=false"
|
class="w-full mb-3" color="light-gray"
|
||||||
/>
|
@click="openAddFieldSidebar">
|
||||||
|
<svg class="w-4 h-4 text-nt-blue inline mr-1 -mt-1" viewBox="0 0 14 14" fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M7.00001 1.1665V12.8332M1.16667 6.99984H12.8333" stroke="currentColor" stroke-width="1.67"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
Add block
|
||||||
|
</v-button>
|
||||||
|
|
||||||
<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"
|
||||||
|
@ -124,8 +131,8 @@
|
||||||
</draggable>
|
</draggable>
|
||||||
|
|
||||||
<v-button
|
<v-button
|
||||||
class="w-full mt-4" color="light-gray"
|
class="w-full mt-3" color="light-gray"
|
||||||
@click="showAddBlock=true">
|
@click="openAddFieldSidebar">
|
||||||
<svg class="w-4 h-4 text-nt-blue inline mr-1 -mt-1" viewBox="0 0 14 14" fill="none"
|
<svg class="w-4 h-4 text-nt-blue inline mr-1 -mt-1" viewBox="0 0 14 14" fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M7.00001 1.1665V12.8332M1.16667 6.99984H12.8333" stroke="currentColor" stroke-width="1.67"
|
<path d="M7.00001 1.1665V12.8332M1.16667 6.99984H12.8333" stroke="currentColor" stroke-width="1.67"
|
||||||
|
@ -139,7 +146,6 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
import AddFormBlockModal from './form-components/AddFormBlockModal.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'
|
||||||
|
@ -150,7 +156,6 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
VButton,
|
VButton,
|
||||||
ProTag,
|
ProTag,
|
||||||
AddFormBlockModal,
|
|
||||||
draggable,
|
draggable,
|
||||||
EditableDiv
|
EditableDiv
|
||||||
},
|
},
|
||||||
|
@ -158,7 +163,6 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
formFields: [],
|
formFields: [],
|
||||||
showAddBlock: false,
|
|
||||||
removing: null
|
removing: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -172,7 +176,7 @@ export default {
|
||||||
set(value) {
|
set(value) {
|
||||||
this.$store.commit('open/working_form/set', value)
|
this.$store.commit('open/working_form/set', value)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -289,10 +293,6 @@ export default {
|
||||||
editOptions(index) {
|
editOptions(index) {
|
||||||
this.$store.commit('open/working_form/openSettingsForField', index)
|
this.$store.commit('open/working_form/openSettingsForField', index)
|
||||||
},
|
},
|
||||||
blockAdded(block) {
|
|
||||||
this.formFields.push(block)
|
|
||||||
this.$store.commit('open/working_form/openSettingsForField', this.formFields.length-1)
|
|
||||||
},
|
|
||||||
removeBlock(blockIndex) {
|
removeBlock(blockIndex) {
|
||||||
const newFields = clonedeep(this.formFields)
|
const newFields = clonedeep(this.formFields)
|
||||||
newFields.splice(blockIndex, 1)
|
newFields.splice(blockIndex, 1)
|
||||||
|
@ -301,6 +301,9 @@ export default {
|
||||||
},
|
},
|
||||||
closeSidebar() {
|
closeSidebar() {
|
||||||
this.$store.commit('open/working_form/closeEditFieldSidebar')
|
this.$store.commit('open/working_form/closeEditFieldSidebar')
|
||||||
|
},
|
||||||
|
openAddFieldSidebar() {
|
||||||
|
this.$store.commit('open/working_form/openAddFieldSidebar', null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,336 +0,0 @@
|
||||||
<template>
|
|
||||||
<modal :show="show" @close="close">
|
|
||||||
|
|
||||||
<p class="text-gray-500 uppercase text-xs font-semibold mb-2">Input Blocks</p>
|
|
||||||
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4">
|
|
||||||
|
|
||||||
<!-- Text Input -->
|
|
||||||
<div
|
|
||||||
class="bg-gray-50 border hover:bg-gray-100 dark:bg-gray-900 rounded-md dark:hover:bg-gray-800 p-2 flex flex-col"
|
|
||||||
role="button" @click.prevent="addBlock('text')"
|
|
||||||
>
|
|
||||||
<div class="mx-auto py-4">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-gray-500" fill="none" viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor" stroke-width="2">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h7"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="w-full text-xs text-gray-500 uppercase text-center font-semibold mb-4">Text Input</p>
|
|
||||||
</div>
|
|
||||||
<!-- Date Input -->
|
|
||||||
<div
|
|
||||||
class="bg-gray-50 border hover:bg-gray-100 dark:bg-gray-900 rounded-md dark:hover:bg-gray-800 p-2 flex flex-col"
|
|
||||||
role="button" @click.prevent="addBlock('date')"
|
|
||||||
>
|
|
||||||
<div class="mx-auto py-4">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-gray-500" fill="none" viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor" stroke-width="2">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round"
|
|
||||||
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="w-full text-xs text-gray-500 uppercase text-center font-semibold mb-4">Date Input</p>
|
|
||||||
</div>
|
|
||||||
<!-- Url Input -->
|
|
||||||
<div
|
|
||||||
class="bg-gray-50 border hover:bg-gray-100 dark:bg-gray-900 rounded-md dark:hover:bg-gray-800 p-2 flex flex-col"
|
|
||||||
role="button" @click.prevent="addBlock('url')"
|
|
||||||
>
|
|
||||||
<div class="mx-auto py-4">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-gray-500" fill="none" viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor" stroke-width="2">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round"
|
|
||||||
d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="w-full text-xs text-gray-500 uppercase text-center font-semibold mb-4">URL Input</p>
|
|
||||||
</div>
|
|
||||||
<!-- Phone Input -->
|
|
||||||
<div
|
|
||||||
class="bg-gray-50 border hover:bg-gray-100 dark:bg-gray-900 rounded-md dark:hover:bg-gray-800 p-2 flex flex-col"
|
|
||||||
role="button" @click.prevent="addBlock('phone_number')"
|
|
||||||
>
|
|
||||||
<div class="mx-auto py-4">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-gray-500" fill="none" viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor" stroke-width="2">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round"
|
|
||||||
d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="w-full text-xs text-gray-500 uppercase text-center font-semibold mb-4">Phone Input</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- email Input -->
|
|
||||||
<div
|
|
||||||
class="bg-gray-50 border hover:bg-gray-100 dark:bg-gray-900 rounded-md dark:hover:bg-gray-800 p-2 flex flex-col"
|
|
||||||
role="button" @click.prevent="addBlock('email')"
|
|
||||||
>
|
|
||||||
<div class="mx-auto py-4">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-gray-500" fill="none" viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor" stroke-width="2">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round"
|
|
||||||
d="M16 12a4 4 0 10-8 0 4 4 0 008 0zm0 0v1.5a2.5 2.5 0 005 0V12a9 9 0 10-9 9m4.5-1.206a8.959 8.959 0 01-4.5 1.207"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="w-full text-xs text-gray-500 uppercase text-center font-semibold mb-4">Email Input</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- checkbox Input -->
|
|
||||||
<div
|
|
||||||
class="bg-gray-50 border hover:bg-gray-100 dark:bg-gray-900 rounded-md dark:hover:bg-gray-800 p-2 flex flex-col"
|
|
||||||
role="button" @click.prevent="addBlock('checkbox')"
|
|
||||||
>
|
|
||||||
<div class="mx-auto py-4">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-gray-500" fill="none" viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor" stroke-width="2">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="w-full text-xs text-gray-500 uppercase text-center font-semibold mb-4">Checkbox Input</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- select Input -->
|
|
||||||
<div
|
|
||||||
class="bg-gray-50 border hover:bg-gray-100 dark:bg-gray-900 rounded-md dark:hover:bg-gray-800 p-2 flex flex-col"
|
|
||||||
role="button" @click.prevent="addBlock('select')"
|
|
||||||
>
|
|
||||||
<div class="mx-auto py-4">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-gray-500" fill="none" viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor" stroke-width="2">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8 9l4-4 4 4m0 6l-4 4-4-4"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="w-full text-xs text-gray-500 uppercase text-center font-semibold mb-4">Select Input</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- multiselect Input -->
|
|
||||||
<div
|
|
||||||
class="bg-gray-50 border hover:bg-gray-100 dark:bg-gray-900 rounded-md dark:hover:bg-gray-800 p-2 flex flex-col"
|
|
||||||
role="button" @click.prevent="addBlock('multi_select')"
|
|
||||||
>
|
|
||||||
<div class="mx-auto py-4">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-gray-500" fill="none" viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor" stroke-width="2">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8 9l4-4 4 4m0 6l-4 4-4-4"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="w-full text-xs text-gray-500 uppercase text-center font-semibold">Multi-select Input</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- number Input -->
|
|
||||||
<div
|
|
||||||
class="bg-gray-50 border hover:bg-gray-100 dark:bg-gray-900 rounded-md dark:hover:bg-gray-800 p-2 flex flex-col"
|
|
||||||
role="button" @click.prevent="addBlock('number')"
|
|
||||||
>
|
|
||||||
<div class="mx-auto py-4">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M7 20l4-16m2 16l4-16M6 9h14M4 15h14" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="w-full text-xs text-gray-500 uppercase text-center font-semibold mb-4">Number Input</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- files Input -->
|
|
||||||
<div
|
|
||||||
class="bg-gray-50 border hover:bg-gray-100 dark:bg-gray-900 rounded-md dark:hover:bg-gray-800 p-2 flex flex-col"
|
|
||||||
role="button" @click.prevent="addBlock('files')"
|
|
||||||
>
|
|
||||||
<div class="mx-auto py-4">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="w-full text-xs text-gray-500 uppercase text-center font-semibold mb-4">File Input</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Signature Block -->
|
|
||||||
<div
|
|
||||||
class="bg-gray-50 border hover:bg-gray-100 dark:bg-gray-900 rounded-md dark:hover:bg-gray-800 p-2 flex flex-col"
|
|
||||||
role="button" @click.prevent="addBlock('signature')"
|
|
||||||
>
|
|
||||||
<div class="mx-auto py-4">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="w-full text-xs text-gray-500 uppercase text-center font-semibold mb-4">Signature Input</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="text-gray-500 uppercase text-xs font-semibold mb-2 mt-6">Layout Blocks</p>
|
|
||||||
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4">
|
|
||||||
|
|
||||||
<!-- Text Block -->
|
|
||||||
<div
|
|
||||||
class="bg-gray-50 border hover:bg-gray-100 dark:bg-gray-900 rounded-md dark:hover:bg-gray-800 p-2 flex flex-col"
|
|
||||||
role="button" @click.prevent="addBlock('nf-text')"
|
|
||||||
>
|
|
||||||
<div class="mx-auto py-4">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h8m-8 6h16" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="w-full text-xs text-gray-500 uppercase text-center font-semibold mb-4">Text Block</p>
|
|
||||||
</div>
|
|
||||||
<!-- Page Break Block -->
|
|
||||||
<div
|
|
||||||
class="bg-gray-50 border hover:bg-gray-100 dark:bg-gray-900 rounded-md dark:hover:bg-gray-800 p-2 flex flex-col"
|
|
||||||
role="button" @click.prevent="addBlock('nf-page-break')"
|
|
||||||
>
|
|
||||||
<div class="mx-auto py-4">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="w-full text-xs text-gray-500 uppercase text-center font-semibold">Page-break Block</p>
|
|
||||||
</div>
|
|
||||||
<!-- Divider Block -->
|
|
||||||
<div
|
|
||||||
class="bg-gray-50 border hover:bg-gray-100 dark:bg-gray-900 rounded-md dark:hover:bg-gray-800 p-2 flex flex-col"
|
|
||||||
role="button" @click.prevent="addBlock('nf-divider')"
|
|
||||||
>
|
|
||||||
<div class="mx-auto py-4">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M20 12H4" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="w-full text-xs text-gray-500 uppercase text-center font-semibold mb-4">Divider block</p>
|
|
||||||
</div>
|
|
||||||
<!-- Image Block -->
|
|
||||||
<div
|
|
||||||
class="bg-gray-50 border hover:bg-gray-100 dark:bg-gray-900 rounded-md dark:hover:bg-gray-800 p-2 flex flex-col"
|
|
||||||
role="button" @click.prevent="addBlock('nf-image')"
|
|
||||||
>
|
|
||||||
<div class="mx-auto py-4">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="w-full text-xs text-gray-500 uppercase text-center font-semibold mb-4">Image Block</p>
|
|
||||||
</div>
|
|
||||||
<!-- Code Block -->
|
|
||||||
<div
|
|
||||||
class="bg-gray-50 border hover:bg-gray-100 dark:bg-gray-900 rounded-md dark:hover:bg-gray-800 p-2 flex flex-col"
|
|
||||||
role="button" @click.prevent="addBlock('nf-code')"
|
|
||||||
>
|
|
||||||
<div class="mx-auto py-4">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="w-full text-xs text-gray-500 uppercase text-center font-semibold mb-4">Code Block</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</modal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
import Form from 'vform'
|
|
||||||
import VButton from '../../../../common/Button.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'AddFormBlockModal',
|
|
||||||
components: {VButton},
|
|
||||||
props: {
|
|
||||||
formBlocks: {
|
|
||||||
type: Array,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
show: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
blockForm: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
defaultBlockNames() {
|
|
||||||
return {
|
|
||||||
'text': 'Your name',
|
|
||||||
'date': 'Date',
|
|
||||||
'url': 'Link',
|
|
||||||
'phone_number': 'Phone Number',
|
|
||||||
'number': 'Number',
|
|
||||||
'email': 'Email',
|
|
||||||
'checkbox': 'Checkbox',
|
|
||||||
'select': 'Select',
|
|
||||||
'multi_select': 'Multi Select',
|
|
||||||
'files': 'Files',
|
|
||||||
'signature': 'Signature',
|
|
||||||
'nf-text': 'Text Block',
|
|
||||||
'nf-page-break': 'Page Break',
|
|
||||||
'nf-divider': 'Divider',
|
|
||||||
'nf-image': 'Image',
|
|
||||||
'nf-code': 'Code Block',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.reset()
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
reset() {
|
|
||||||
this.blockForm = new Form({
|
|
||||||
type: null,
|
|
||||||
name: null
|
|
||||||
})
|
|
||||||
},
|
|
||||||
addBlock(type) {
|
|
||||||
this.blockForm.type = type
|
|
||||||
this.blockForm.name = this.defaultBlockNames[type]
|
|
||||||
const data = this.prefillDefault(this.blockForm.data())
|
|
||||||
data.id = this.generateUUID()
|
|
||||||
data.hidden = false
|
|
||||||
if (['select', 'multi_select'].includes(this.blockForm.type)) {
|
|
||||||
data[this.blockForm.type] = {'options': []}
|
|
||||||
}
|
|
||||||
data.help_position = 'below_input'
|
|
||||||
this.$emit('block-added', data)
|
|
||||||
this.close()
|
|
||||||
},
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
prefillDefault(data) {
|
|
||||||
if (data.type === 'nf-text') {
|
|
||||||
data.content = '<p>This is a text block.</p>'
|
|
||||||
} else if (data.type === 'nf-page-break') {
|
|
||||||
data.next_btn_text = 'Next'
|
|
||||||
data.previous_btn_text = 'Previous'
|
|
||||||
} else if (data.type === 'nf-code') {
|
|
||||||
data.content = '<div class="text-blue-500 italic">This is a code block.</div>'
|
|
||||||
} else if (data.type === 'signature') {
|
|
||||||
data.help = 'Draw your signature above'
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
},
|
|
||||||
close() {
|
|
||||||
this.$emit('close')
|
|
||||||
this.reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -0,0 +1,262 @@
|
||||||
|
<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">
|
||||||
|
<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">
|
||||||
|
Add Block
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="py-2 px-4">
|
||||||
|
<div>
|
||||||
|
<p class="text-gray-500 uppercase text-xs font-semibold mb-2">Input Blocks</p>
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
|
<div v-for="(block, i) in inputBlocks" :key="block.name"
|
||||||
|
class="bg-gray-50 border hover:bg-gray-100 dark:bg-gray-900 rounded-md dark:hover:bg-gray-800 py-2 flex flex-col"
|
||||||
|
role="button" @click.prevent="addBlock(block.name)"
|
||||||
|
>
|
||||||
|
<div class="mx-auto">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-500" fill="none" viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor" stroke-width="2" v-html="block.icon"></svg>
|
||||||
|
</div>
|
||||||
|
<p class="w-full text-xs text-gray-500 uppercase text-center font-semibold mt-1">{{ block.title }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="border-t mt-6">
|
||||||
|
<p class="text-gray-500 uppercase text-xs font-semibold mb-2 mt-6">Layout Blocks</p>
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
|
<div v-for="(block, i) in layoutBlocks" :key="block.name"
|
||||||
|
class="bg-gray-50 border hover:bg-gray-100 dark:bg-gray-900 rounded-md dark:hover:bg-gray-800 py-2 flex flex-col"
|
||||||
|
role="button" @click.prevent="addBlock(block.name)"
|
||||||
|
>
|
||||||
|
<div class="mx-auto">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-500" fill="none" viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor" stroke-width="2" v-html="block.icon"></svg>
|
||||||
|
</div>
|
||||||
|
<p class="w-full text-xs text-gray-500 uppercase text-center font-semibold mt-1">{{ block.title }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {mapState} from 'vuex'
|
||||||
|
import Form from 'vform'
|
||||||
|
import clonedeep from 'clone-deep'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AddFormBlockSidebar',
|
||||||
|
components: {},
|
||||||
|
props: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
blockForm: null,
|
||||||
|
inputBlocks: [
|
||||||
|
{
|
||||||
|
name: 'text',
|
||||||
|
title: 'Text Input',
|
||||||
|
icon: '<path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h7"/>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'date',
|
||||||
|
title: 'Date Input',
|
||||||
|
icon: '<path stroke-linecap="round" stroke-linejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'url',
|
||||||
|
title: 'URL Input',
|
||||||
|
icon: '<path stroke-linecap="round" stroke-linejoin="round" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'phone_number',
|
||||||
|
title: 'Phone Input',
|
||||||
|
icon: '<path stroke-linecap="round" stroke-linejoin="round" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"/>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
title: 'Email Input',
|
||||||
|
icon: '<path stroke-linecap="round" stroke-linejoin="round" d="M16 12a4 4 0 10-8 0 4 4 0 008 0zm0 0v1.5a2.5 2.5 0 005 0V12a9 9 0 10-9 9m4.5-1.206a8.959 8.959 0 01-4.5 1.207"/>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'checkbox',
|
||||||
|
title: 'Checkbox Input',
|
||||||
|
icon: '<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'select',
|
||||||
|
title: 'Select Input',
|
||||||
|
icon: '<path stroke-linecap="round" stroke-linejoin="round" d="M8 9l4-4 4 4m0 6l-4 4-4-4"/>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'multi_select',
|
||||||
|
title: 'Multi-select Input',
|
||||||
|
icon: '<path stroke-linecap="round" stroke-linejoin="round" d="M8 9l4-4 4 4m0 6l-4 4-4-4"/>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'number',
|
||||||
|
title: 'Number Input',
|
||||||
|
icon: '<path stroke-linecap="round" stroke-linejoin="round" d="M7 20l4-16m2 16l4-16M6 9h14M4 15h14"/>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'files',
|
||||||
|
title: 'File Input',
|
||||||
|
icon: '<path stroke-linecap="round" stroke-linejoin="round" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'signature',
|
||||||
|
title: 'Signature Input',
|
||||||
|
icon: '<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125" />',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
layoutBlocks: [
|
||||||
|
{
|
||||||
|
name: 'nf-text',
|
||||||
|
title: 'Text Block',
|
||||||
|
icon: '<path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h8m-8 6h16" />',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'nf-page-break',
|
||||||
|
title: 'Page-break Block',
|
||||||
|
icon: '<path stroke-linecap="round" stroke-linejoin="round" d="M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'nf-divider',
|
||||||
|
title: 'Divider Block',
|
||||||
|
icon: '<path stroke-linecap="round" stroke-linejoin="round" d="M20 12H4" />',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'nf-image',
|
||||||
|
title: 'Image Block',
|
||||||
|
icon: '<path stroke-linecap="round" stroke-linejoin="round" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'nf-code',
|
||||||
|
title: 'Code Block',
|
||||||
|
icon: '<path stroke-linecap="round" stroke-linejoin="round" d="M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5" />',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
selectedFieldIndex: state => state['open/working_form'].selectedFieldIndex,
|
||||||
|
showAddFieldSidebar: state => state['open/working_form'].showAddFieldSidebar
|
||||||
|
}),
|
||||||
|
form: {
|
||||||
|
get() {
|
||||||
|
return this.$store.state['open/working_form'].content
|
||||||
|
},
|
||||||
|
/* We add a setter */
|
||||||
|
set(value) {
|
||||||
|
this.$store.commit('open/working_form/set', value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showSidebar() {
|
||||||
|
return (this.form && this.showAddFieldSidebar) ?? false
|
||||||
|
},
|
||||||
|
|
||||||
|
defaultBlockNames() {
|
||||||
|
return {
|
||||||
|
'text': 'Your name',
|
||||||
|
'date': 'Date',
|
||||||
|
'url': 'Link',
|
||||||
|
'phone_number': 'Phone Number',
|
||||||
|
'number': 'Number',
|
||||||
|
'email': 'Email',
|
||||||
|
'checkbox': 'Checkbox',
|
||||||
|
'select': 'Select',
|
||||||
|
'multi_select': 'Multi Select',
|
||||||
|
'files': 'Files',
|
||||||
|
'signature': 'Signature',
|
||||||
|
'nf-text': 'Text Block',
|
||||||
|
'nf-page-break': 'Page Break',
|
||||||
|
'nf-divider': 'Divider',
|
||||||
|
'nf-image': 'Image',
|
||||||
|
'nf-code': 'Code Block',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.reset()
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
closeSidebar() {
|
||||||
|
this.$store.commit('open/working_form/closeAddFieldSidebar')
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
this.blockForm = new Form({
|
||||||
|
type: null,
|
||||||
|
name: null
|
||||||
|
})
|
||||||
|
},
|
||||||
|
addBlock(type) {
|
||||||
|
this.blockForm.type = type
|
||||||
|
this.blockForm.name = this.defaultBlockNames[type]
|
||||||
|
const newBlock = this.prefillDefault(this.blockForm.data())
|
||||||
|
newBlock.id = this.generateUUID()
|
||||||
|
newBlock.hidden = false
|
||||||
|
if (['select', 'multi_select'].includes(this.blockForm.type)) {
|
||||||
|
newBlock[this.blockForm.type] = {'options': []}
|
||||||
|
}
|
||||||
|
newBlock.help_position = 'below_input'
|
||||||
|
if(this.selectedFieldIndex === null || this.selectedFieldIndex === undefined){
|
||||||
|
const newFields = clonedeep(this.form.properties)
|
||||||
|
newFields.push(newBlock)
|
||||||
|
this.$set(this.form, 'properties', newFields)
|
||||||
|
this.$store.commit('open/working_form/openSettingsForField', this.form.properties.length-1)
|
||||||
|
} else {
|
||||||
|
const newFields = clonedeep(this.form.properties)
|
||||||
|
newFields.splice(this.selectedFieldIndex+1, 0, newBlock)
|
||||||
|
this.$set(this.form, 'properties', newFields)
|
||||||
|
this.$store.commit('open/working_form/openSettingsForField', this.selectedFieldIndex+1)
|
||||||
|
}
|
||||||
|
this.reset()
|
||||||
|
},
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
prefillDefault(data) {
|
||||||
|
if (data.type === 'nf-text') {
|
||||||
|
data.content = '<p>This is a text block.</p>'
|
||||||
|
} else if (data.type === 'nf-page-break') {
|
||||||
|
data.next_btn_text = 'Next'
|
||||||
|
data.previous_btn_text = 'Previous'
|
||||||
|
} else if (data.type === 'nf-code') {
|
||||||
|
data.content = '<div class="text-blue-500 italic">This is a code block.</div>'
|
||||||
|
} else if (data.type === 'signature') {
|
||||||
|
data.help = 'Draw your signature above'
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -6,7 +6,8 @@ export const state = {
|
||||||
|
|
||||||
// Field being edited
|
// Field being edited
|
||||||
selectedFieldIndex: null,
|
selectedFieldIndex: null,
|
||||||
showEditFieldSidebar: null
|
showEditFieldSidebar: null,
|
||||||
|
showAddFieldSidebar: null
|
||||||
}
|
}
|
||||||
|
|
||||||
// mutations
|
// mutations
|
||||||
|
@ -24,12 +25,25 @@ export const mutations = {
|
||||||
}
|
}
|
||||||
state.selectedFieldIndex = index
|
state.selectedFieldIndex = index
|
||||||
state.showEditFieldSidebar = true
|
state.showEditFieldSidebar = true
|
||||||
},
|
state.showAddFieldSidebar = false
|
||||||
setSelectedFieldIndex (state, index) {
|
},
|
||||||
state.selectedFieldIndex = index
|
|
||||||
},
|
|
||||||
closeEditFieldSidebar (state) {
|
closeEditFieldSidebar (state) {
|
||||||
state.showEditFieldSidebar = false
|
|
||||||
state.selectedFieldIndex = null
|
state.selectedFieldIndex = null
|
||||||
}
|
state.showEditFieldSidebar = false
|
||||||
|
state.showAddFieldSidebar = false
|
||||||
|
},
|
||||||
|
openAddFieldSidebar (state, index) {
|
||||||
|
// If field is passed, compute index
|
||||||
|
if (index !== null && typeof index === 'object') {
|
||||||
|
index = state.content.properties.findIndex(prop => prop.id === index.id)
|
||||||
|
}
|
||||||
|
state.selectedFieldIndex = index
|
||||||
|
state.showAddFieldSidebar = true
|
||||||
|
state.showEditFieldSidebar = false
|
||||||
|
},
|
||||||
|
closeAddFieldSidebar (state) {
|
||||||
|
state.selectedFieldIndex = null
|
||||||
|
state.showAddFieldSidebar = false
|
||||||
|
state.showEditFieldSidebar = false
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue