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"
|
||||
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="p-2 lg:pr-1 text-gray-300 hover:text-blue-500 cursor-pointer" role="button"
|
||||
<div class="flex flex-col bg-white rounded-md" :class="{'lg:flex-row':!fieldSideBarOpened, 'xl:flex-row':fieldSideBarOpened}">
|
||||
<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"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
|
||||
|
@ -20,7 +30,8 @@
|
|||
</svg>
|
||||
</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"
|
||||
>
|
||||
<svg
|
||||
|
@ -184,17 +195,24 @@ export default {
|
|||
}
|
||||
|
||||
return fieldsOptions
|
||||
},
|
||||
fieldSideBarOpened() {
|
||||
return this.adminPreview && (this.form && this.selectedFieldIndex !== null) ? (this.form.properties[this.selectedFieldIndex] && this.showEditFieldSidebar) : false
|
||||
}
|
||||
},
|
||||
|
||||
watch: {},
|
||||
|
||||
mounted () {},
|
||||
mounted() {
|
||||
},
|
||||
|
||||
methods: {
|
||||
editFieldOptions() {
|
||||
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
|
||||
*/
|
||||
|
|
|
@ -69,6 +69,7 @@
|
|||
<form-editor-preview/>
|
||||
|
||||
<form-field-edit-sidebar/>
|
||||
<add-form-block-sidebar/>
|
||||
|
||||
<!-- Form Error Modal -->
|
||||
<form-error-modal
|
||||
|
@ -85,6 +86,7 @@
|
|||
|
||||
<script>
|
||||
import {mapGetters} from 'vuex'
|
||||
import AddFormBlockSidebar from './form-components/AddFormBlockSidebar.vue'
|
||||
import FormFieldEditSidebar from '../fields/FormFieldEditSidebar.vue'
|
||||
import FormErrorModal from './form-components/FormErrorModal.vue'
|
||||
import FormInformation from './form-components/FormInformation.vue'
|
||||
|
@ -103,6 +105,7 @@ import fieldsLogic from '../../../../mixins/forms/fieldsLogic.js'
|
|||
export default {
|
||||
name: 'FormEditor',
|
||||
components: {
|
||||
AddFormBlockSidebar,
|
||||
FormFieldEditSidebar,
|
||||
FormEditorPreview,
|
||||
FormIntegrations,
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
<template>
|
||||
<div>
|
||||
<add-form-block-modal :form-blocks="formFields" :show="showAddBlock" @block-added="blockAdded"
|
||||
@close="showAddBlock=false"
|
||||
/>
|
||||
<v-button v-if="formFields && formFields.length > 8"
|
||||
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"
|
||||
class="bg-white overflow-hidden dark:bg-notion-dark-light rounded-md w-full mx-auto border transition-colors"
|
||||
|
@ -124,8 +131,8 @@
|
|||
</draggable>
|
||||
|
||||
<v-button
|
||||
class="w-full mt-4" color="light-gray"
|
||||
@click="showAddBlock=true">
|
||||
class="w-full mt-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"
|
||||
|
@ -139,7 +146,6 @@
|
|||
|
||||
<script>
|
||||
import draggable from 'vuedraggable'
|
||||
import AddFormBlockModal from './form-components/AddFormBlockModal.vue'
|
||||
import ProTag from '../../../common/ProTag.vue'
|
||||
import clonedeep from 'clone-deep'
|
||||
import EditableDiv from '../../../common/EditableDiv.vue'
|
||||
|
@ -150,7 +156,6 @@ export default {
|
|||
components: {
|
||||
VButton,
|
||||
ProTag,
|
||||
AddFormBlockModal,
|
||||
draggable,
|
||||
EditableDiv
|
||||
},
|
||||
|
@ -158,7 +163,6 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
formFields: [],
|
||||
showAddBlock: false,
|
||||
removing: null
|
||||
}
|
||||
},
|
||||
|
@ -172,7 +176,7 @@ export default {
|
|||
set(value) {
|
||||
this.$store.commit('open/working_form/set', value)
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
|
@ -289,10 +293,6 @@ export default {
|
|||
editOptions(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) {
|
||||
const newFields = clonedeep(this.formFields)
|
||||
newFields.splice(blockIndex, 1)
|
||||
|
@ -301,6 +301,9 @@ export default {
|
|||
},
|
||||
closeSidebar() {
|
||||
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
|
||||
selectedFieldIndex: null,
|
||||
showEditFieldSidebar: null
|
||||
showEditFieldSidebar: null,
|
||||
showAddFieldSidebar: null
|
||||
}
|
||||
|
||||
// mutations
|
||||
|
@ -24,12 +25,25 @@ export const mutations = {
|
|||
}
|
||||
state.selectedFieldIndex = index
|
||||
state.showEditFieldSidebar = true
|
||||
},
|
||||
setSelectedFieldIndex (state, index) {
|
||||
state.selectedFieldIndex = index
|
||||
state.showAddFieldSidebar = false
|
||||
},
|
||||
closeEditFieldSidebar (state) {
|
||||
state.showEditFieldSidebar = false
|
||||
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