Vue 3 better animation (#257)
* vue-3-better-animation * Working on migration to vueuse/motion * Form sidebar animations * Clean code * Added animations for modal * Finished implementing better animations --------- Co-authored-by: Forms Dev <chirag+new@notionforms.io>
This commit is contained in:
parent
24276f0b95
commit
f970557b76
File diff suppressed because it is too large
Load Diff
|
@ -13,6 +13,7 @@
|
||||||
"@vue/compat": "^3.3.9",
|
"@vue/compat": "^3.3.9",
|
||||||
"@vueuse/components": "^10.5.0",
|
"@vueuse/components": "^10.5.0",
|
||||||
"@vueuse/core": "^10.5.0",
|
"@vueuse/core": "^10.5.0",
|
||||||
|
"@vueuse/motion": "^2.0.0",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"chart.js": "^4.4.0",
|
"chart.js": "^4.4.0",
|
||||||
"clone-deep": "^4.0.1",
|
"clone-deep": "^4.0.1",
|
||||||
|
|
|
@ -1,32 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<Teleport to="body">
|
<portal to="modals" :order="portalOrder">
|
||||||
<transition leave-active-class="duration-200" name="fade" appear>
|
<transition @leave="(el,done) => motions.backdrop.leave(done)">
|
||||||
<div v-if="show" class="fixed z-30 top-0 inset-x-0 px-4 pt-6 sm:px-0 sm:flex sm:items-top sm:justify-center">
|
<div v-if="show" v-motion="'backdrop'" :variants="motionFadeIn"
|
||||||
<transition enter-active-class="transition-all delay-75 linear duration-300"
|
class="fixed z-30 top-0 inset-0 px-4 sm:px-0 flex items-top justify-center bg-gray-700/75 w-full h-screen overflow-y-scroll"
|
||||||
enter-from-class="opacity-0"
|
:class="{'backdrop-blur-sm':backdropBlur}"
|
||||||
enter-to-class="opacity-100"
|
@click.self="close"
|
||||||
leave-active-class="transition-all linear duration-100"
|
|
||||||
leave-from-class="opacity-100"
|
|
||||||
leave-to-class="opacity-0"
|
|
||||||
appear @after-leave="leaveCallback"
|
|
||||||
>
|
>
|
||||||
<div v-if="show" class="fixed inset-0 transform" @click="close">
|
<div ref="content" v-motion="'body'" :variants="motionSlideBottom"
|
||||||
<div class="absolute inset-0 bg-gray-500 opacity-75" />
|
class="self-start bg-white dark:bg-notion-dark w-full relative p-4 md:p-6 my-6 rounded-xl shadow-xl"
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
|
|
||||||
<transition enter-active-class="delay-75 linear duration-300"
|
|
||||||
enter-from-class="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
|
||||||
enter-to-class="opacity-100 translate-y-0 sm:scale-100"
|
|
||||||
leave-active-class="linear duration-200" appear
|
|
||||||
leave-from-class="opacity-100 translate-y-0 sm:scale-100"
|
|
||||||
leave-to-class="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
|
||||||
>
|
|
||||||
<div v-if="show"
|
|
||||||
class="modal-content bg-white dark:bg-notion-dark rounded-lg overflow-y-auto shadow-xl transform transition-all sm:w-full"
|
|
||||||
:class="maxWidthClass"
|
:class="maxWidthClass"
|
||||||
>
|
>
|
||||||
<div class="bg-white relative dark:bg-notion-dark p-4 md:p-6">
|
|
||||||
<div v-if="closeable" class="absolute top-4 right-4">
|
<div v-if="closeable" class="absolute top-4 right-4">
|
||||||
<button class="text-gray-500 hover:text-gray-900 cursor-pointer" @click.prevent="close">
|
<button class="text-gray-500 hover:text-gray-900 cursor-pointer" @click.prevent="close">
|
||||||
<svg class="h-6 w-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg class="h-6 w-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
@ -37,7 +20,7 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="sm:flex sm:flex-col sm:items-start">
|
<div class="sm:flex sm:flex-col sm:items-start">
|
||||||
<div v-if="$slots.hasOwnProperty('icon')" class="flex w-full justify-center mb-4">
|
<div v-if="$scopedSlots.hasOwnProperty('icon')" class="flex w-full justify-center mb-4">
|
||||||
<div class="w-14 h-14 rounded-full flex justify-center items-center"
|
<div class="w-14 h-14 rounded-full flex justify-center items-center"
|
||||||
:class="'bg-'+iconColor+'-100 text-'+iconColor+'-600'"
|
:class="'bg-'+iconColor+'-100 text-'+iconColor+'-600'"
|
||||||
>
|
>
|
||||||
|
@ -45,7 +28,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3 text-center sm:mt-0 w-full">
|
<div class="mt-3 text-center sm:mt-0 w-full">
|
||||||
<h2 v-if="$slots.hasOwnProperty('title')"
|
<h2 v-if="$scopedSlots.hasOwnProperty('title')"
|
||||||
class="text-2xl font-semibold text-center text-gray-900"
|
class="text-2xl font-semibold text-center text-gray-900"
|
||||||
>
|
>
|
||||||
<slot name="title" />
|
<slot name="title" />
|
||||||
|
@ -53,22 +36,22 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-2 w-full">
|
<div class="w-full">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="$slots.hasOwnProperty('footer')" class="px-6 py-4 bg-gray-100 text-right">
|
<div v-if="$scopedSlots.hasOwnProperty('footer')" class="px-6 py-4 bg-gray-100 text-right">
|
||||||
<slot name="footer" />
|
<slot name="footer" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</Teleport>
|
</portal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { useMotions } from '@vueuse/motion'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Modal',
|
name: 'Modal',
|
||||||
|
|
||||||
|
@ -76,6 +59,10 @@ export default {
|
||||||
show: {
|
show: {
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
backdropBlur: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
iconColor: {
|
iconColor: {
|
||||||
default: 'blue'
|
default: 'blue'
|
||||||
},
|
},
|
||||||
|
@ -87,10 +74,12 @@ export default {
|
||||||
},
|
},
|
||||||
portalOrder: {
|
portalOrder: {
|
||||||
default: 1
|
default: 1
|
||||||
|
}
|
||||||
},
|
},
|
||||||
afterLeave: {
|
|
||||||
type: Function,
|
setup () {
|
||||||
required: false
|
return {
|
||||||
|
motions: useMotions()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -103,52 +92,87 @@ export default {
|
||||||
xl: 'sm:max-w-xl',
|
xl: 'sm:max-w-xl',
|
||||||
'2xl': 'sm:max-w-2xl'
|
'2xl': 'sm:max-w-2xl'
|
||||||
}[this.maxWidth]
|
}[this.maxWidth]
|
||||||
|
},
|
||||||
|
motionFadeIn () {
|
||||||
|
return {
|
||||||
|
initial: {
|
||||||
|
opacity: 0,
|
||||||
|
transition: {
|
||||||
|
delay: 100,
|
||||||
|
duration: 200,
|
||||||
|
ease: 'easeIn'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enter: {
|
||||||
|
opacity: 1,
|
||||||
|
transition: {
|
||||||
|
duration: 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
motionSlideBottom () {
|
||||||
|
return {
|
||||||
|
initial: {
|
||||||
|
y: 150,
|
||||||
|
opacity: 0,
|
||||||
|
transition: {
|
||||||
|
ease: 'easeIn',
|
||||||
|
duration: 200
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enter: {
|
||||||
|
y: 0,
|
||||||
|
opacity: 1,
|
||||||
|
transition: {
|
||||||
|
duration: 250,
|
||||||
|
ease: 'easeOut',
|
||||||
|
delay: 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
show: {
|
show (newVal, oldVal) {
|
||||||
immediate: true,
|
if (newVal !== oldVal) {
|
||||||
handler: (show) => {
|
if (newVal) {
|
||||||
if (show) {
|
document.body.classList.add('overflow-hidden')
|
||||||
document.body.style.overflow = 'hidden'
|
|
||||||
} else {
|
} else {
|
||||||
document.body.style.overflow = null
|
document.body.classList.remove('overflow-hidden')
|
||||||
|
this.motions.body.apply('initial')
|
||||||
|
this.motions.backdrop.apply('initial')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
created () {
|
|
||||||
document.addEventListener('keydown', this.closeOnEscape)
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeUnmount () {
|
beforeUnmount () {
|
||||||
document.removeEventListener('keydown', this.closeOnEscape)
|
document.body.classList.remove('overflow-hidden')
|
||||||
|
},
|
||||||
|
|
||||||
|
created () {
|
||||||
|
const closeOnEscape = (e) => {
|
||||||
|
if (e.key === 'Escape' && this.show) {
|
||||||
|
this.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('keydown', closeOnEscape)
|
||||||
|
|
||||||
|
this.$once('hook:destroyed', () => {
|
||||||
|
document.removeEventListener('keydown', closeOnEscape)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
close () {
|
close () {
|
||||||
if (this.closeable) {
|
if (this.closeable) {
|
||||||
|
document.body.classList.remove('overflow-hidden')
|
||||||
this.$emit('close')
|
this.$emit('close')
|
||||||
}
|
}
|
||||||
},
|
|
||||||
leaveCallback () {
|
|
||||||
if (this.afterLeave) {
|
|
||||||
this.afterLeave()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
closeOnEscape (e) {
|
|
||||||
if (e.key === 'Escape' && this.show) {
|
|
||||||
this.close()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.modal-content {
|
|
||||||
max-height: calc(100vh - 40px);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -49,7 +49,7 @@
|
||||||
<template #trigger="{toggle}">
|
<template #trigger="{toggle}">
|
||||||
<button id="dropdown-menu-button" type="button"
|
<button id="dropdown-menu-button" type="button"
|
||||||
class="flex items-center justify-center w-full rounded-md px-4 py-2 text-sm text-gray-700 dark:text-gray-50 hover:bg-gray-50 dark:hover:bg-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-gray-500"
|
class="flex items-center justify-center w-full rounded-md px-4 py-2 text-sm text-gray-700 dark:text-gray-50 hover:bg-gray-50 dark:hover:bg-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-gray-500"
|
||||||
dusk="nav-dropdown-button" @click.prevent="toggle()"
|
dusk="nav-dropdown-button" @click.stop="toggle()"
|
||||||
>
|
>
|
||||||
<img :src="user.photo_url" class="rounded-full w-6 h-6">
|
<img :src="user.photo_url" class="rounded-full w-6 h-6">
|
||||||
<p class="ml-2 hidden sm:inline">
|
<p class="ml-2 hidden sm:inline">
|
||||||
|
@ -133,9 +133,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useAuthStore } from '../stores/auth';
|
import { useAuthStore } from '../stores/auth'
|
||||||
import { useFormsStore } from '../stores/forms';
|
import { useFormsStore } from '../stores/forms'
|
||||||
import { useWorkspacesStore } from '../stores/workspaces';
|
import { useWorkspacesStore } from '../stores/workspaces'
|
||||||
import Dropdown from './common/Dropdown.vue'
|
import Dropdown from './common/Dropdown.vue'
|
||||||
import WorkspaceDropdown from './WorkspaceDropdown.vue'
|
import WorkspaceDropdown from './WorkspaceDropdown.vue'
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ export default {
|
||||||
authStore,
|
authStore,
|
||||||
formsStore,
|
formsStore,
|
||||||
workspacesStore,
|
workspacesStore,
|
||||||
user : computed(() => authStore.user)
|
user: computed(() => authStore.user)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
dusk="workspace-dropdown"
|
dusk="workspace-dropdown"
|
||||||
>
|
>
|
||||||
<template v-if="workspace" #trigger="{toggle}">
|
<template v-if="workspace" #trigger="{toggle}">
|
||||||
<div class="flex items-center cursor group" role="button" @click.prevent="toggle()">
|
<div class="flex items-center cursor group" role="button" @click.stop="toggle()">
|
||||||
<div class="rounded-full h-8 8">
|
<div class="rounded-full h-8 8">
|
||||||
<img v-if="isUrl(workspace.icon)"
|
<img v-if="isUrl(workspace.icon)"
|
||||||
:src="workspace.icon"
|
:src="workspace.icon"
|
||||||
|
@ -61,9 +61,9 @@ export default {
|
||||||
return {
|
return {
|
||||||
formsStore,
|
formsStore,
|
||||||
workspacesStore,
|
workspacesStore,
|
||||||
user : computed(() => authStore.user),
|
user: computed(() => authStore.user),
|
||||||
workspaces : computed(() => workspacesStore.content),
|
workspaces: computed(() => workspacesStore.content),
|
||||||
loading : computed(() => workspacesStore.loading)
|
loading: computed(() => workspacesStore.loading)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -5,25 +5,23 @@
|
||||||
:open="open"
|
:open="open"
|
||||||
:close="close"
|
:close="close"
|
||||||
/>
|
/>
|
||||||
<transition name="fade">
|
|
||||||
<div v-if="isOpen" v-on-click-outside="close" :class="dropdownClass">
|
<collapsible v-model="isOpen" :class="dropdownClass">
|
||||||
<div class="py-1 " role="menu" aria-orientation="vertical" aria-labelledby="options-menu">
|
<div class="py-1 " role="menu" aria-orientation="vertical" aria-labelledby="options-menu">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</collapsible>
|
||||||
</transition>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { vOnClickOutside } from '@vueuse/components'
|
import Collapsible from './transitions/Collapsible.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Dropdown',
|
name: 'Dropdown',
|
||||||
directives: {
|
components: { Collapsible },
|
||||||
onClickOutside: vOnClickOutside
|
directives: {},
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
dropdownClass: {
|
dropdownClass: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -45,14 +43,11 @@ export default {
|
||||||
isOpen.value = !isOpen.value
|
isOpen.value = !isOpen.value
|
||||||
}
|
}
|
||||||
|
|
||||||
const dropdownRef = ref(null)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isOpen,
|
isOpen,
|
||||||
open,
|
open,
|
||||||
close,
|
close,
|
||||||
toggle,
|
toggle
|
||||||
dropdownRef
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
<template>
|
||||||
|
<transition @leave="(el,done) => motions.collapsible.leave(done)">
|
||||||
|
<div
|
||||||
|
v-if="modelValue"
|
||||||
|
key="dropdown"
|
||||||
|
v-motion="'collapsible'"
|
||||||
|
v-on-click-outside.bubble="close"
|
||||||
|
:variants="motionCollapse"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { vOnClickOutside } from '@vueuse/components'
|
||||||
|
import { useMotions } from '@vueuse/motion'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Collapsible',
|
||||||
|
directives: {
|
||||||
|
onClickOutside: vOnClickOutside
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
modelValue: { type: Boolean },
|
||||||
|
closeOnClickAway: { type: Boolean, default: true }
|
||||||
|
},
|
||||||
|
setup () {
|
||||||
|
return {
|
||||||
|
motions: useMotions()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
motionCollapse () {
|
||||||
|
return {
|
||||||
|
enter: {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
height: 'auto',
|
||||||
|
transition: { duration: 150, ease: 'easeOut' }
|
||||||
|
},
|
||||||
|
initial: {
|
||||||
|
opacity: 0,
|
||||||
|
y: -10,
|
||||||
|
height: 0,
|
||||||
|
transition: { duration: 75, ease: 'easeIn' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
close () {
|
||||||
|
if (this.closeOnClickAway) {
|
||||||
|
this.$emit('update:modelValue', false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,11 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-on-click-outside="closeDropdown" class="v-select relative">
|
<div class="v-select relative">
|
||||||
<span class="inline-block w-full rounded-md">
|
<span class="inline-block w-full rounded-md">
|
||||||
<button type="button" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label"
|
<button type="button" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
:style="inputStyle"
|
:style="inputStyle"
|
||||||
:class="[theme.SelectInput.input,{'py-2': !multiple || loading,'py-1': multiple, '!ring-red-500 !ring-2': hasError, '!cursor-not-allowed !bg-gray-200': disabled}, inputClass]"
|
:class="[theme.SelectInput.input,{'py-2': !multiple || loading,'py-1': multiple, '!ring-red-500 !ring-2': hasError, '!cursor-not-allowed !bg-gray-200': disabled}, inputClass]"
|
||||||
@click="openDropdown"
|
@click.stop="toggleDropdown"
|
||||||
>
|
>
|
||||||
<div :class="{'h-6': !multiple, 'min-h-8': multiple && !loading}">
|
<div :class="{'h-6': !multiple, 'min-h-8': multiple && !loading}">
|
||||||
<transition name="fade" mode="out-in">
|
<transition name="fade" mode="out-in">
|
||||||
|
@ -31,11 +31,11 @@
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
<div v-show="isOpen"
|
<collapsible v-model="isOpen"
|
||||||
class="absolute mt-1 rounded-md bg-white dark:bg-notion-dark-light shadow-lg z-10"
|
class="absolute mt-1 rounded-md bg-white dark:bg-notion-dark-light shadow-xl z-10"
|
||||||
:class="dropdownClass"
|
:class="dropdownClass"
|
||||||
>
|
>
|
||||||
<ul tabindex="-1" role="listbox" aria-labelled by="listbox-label" aria-activedescendant="listbox-item-3"
|
<ul tabindex="-1" role="listbox"
|
||||||
class="rounded-md text-base leading-6 shadow-xs overflow-auto focus:outline-none sm:text-sm sm:leading-5 relative"
|
class="rounded-md text-base leading-6 shadow-xs overflow-auto focus:outline-none sm:text-sm sm:leading-5 relative"
|
||||||
:class="{'max-h-42 py-1': !isSearchable,'max-h-48 pb-1': isSearchable}"
|
:class="{'max-h-42 py-1': !isSearchable,'max-h-48 pb-1': isSearchable}"
|
||||||
>
|
>
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
<template v-if="filteredOptions.length > 0">
|
<template v-if="filteredOptions.length > 0">
|
||||||
<li v-for="item in filteredOptions" :key="item[optionKey]" role="option" :style="optionStyle"
|
<li v-for="item in filteredOptions" :key="item[optionKey]" role="option" :style="optionStyle"
|
||||||
:class="{'px-3 pr-9': multiple, 'px-3': !multiple}"
|
:class="{'px-3 pr-9': multiple, 'px-3': !multiple}"
|
||||||
class="text-gray-900 cursor-default select-none relative py-2 cursor-pointer group hover:text-white hover-bg-form-color focus:outline-none focus-text-white focus-nt-blue"
|
class="text-gray-900 cursor-default select-none relative py-2 cursor-pointer group hover:text-white hover:bg-form-color focus:outline-none focus-text-white focus-nt-blue"
|
||||||
@click="select(item)"
|
@click="select(item)"
|
||||||
>
|
>
|
||||||
<slot name="option" :option="item" :selected="isSelected(item)" />
|
<slot name="option" :option="item" :selected="isSelected(item)" />
|
||||||
|
@ -61,29 +61,27 @@
|
||||||
</p>
|
</p>
|
||||||
<li v-if="allowCreation && searchTerm" role="option" :style="optionStyle"
|
<li v-if="allowCreation && searchTerm" role="option" :style="optionStyle"
|
||||||
:class="{'px-3 pr-9': multiple, 'px-3': !multiple}"
|
:class="{'px-3 pr-9': multiple, 'px-3': !multiple}"
|
||||||
class="text-gray-900 cursor-default select-none relative py-2 cursor-pointer group hover:text-white hover-bg-form-color focus:outline-none focus-text-white focus-nt-blue"
|
class="text-gray-900 cursor-default select-none relative py-2 cursor-pointer group hover:text-white hover:bg-form-color focus:outline-none focus-text-white focus-nt-blue"
|
||||||
@click="createOption(searchTerm)"
|
@click="createOption(searchTerm)"
|
||||||
>
|
>
|
||||||
Create <b class="px-1 bg-gray-300 rounded group-hover-text-black">{{ searchTerm }}</b>
|
Create <b class="px-1 bg-gray-300 rounded group-hover-text-black">{{ searchTerm }}</b>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</collapsible>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { vOnClickOutside } from '@vueuse/components'
|
import Collapsible from '../../common/transitions/Collapsible.vue'
|
||||||
import TextInput from '../TextInput.vue'
|
|
||||||
import Fuse from 'fuse.js'
|
|
||||||
import { themes } from '../../../config/form-themes'
|
import { themes } from '../../../config/form-themes'
|
||||||
|
import TextInput from '../TextInput.vue'
|
||||||
import debounce from 'debounce'
|
import debounce from 'debounce'
|
||||||
|
import Fuse from 'fuse.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'VSelect',
|
name: 'VSelect',
|
||||||
components: { TextInput },
|
components: { Collapsible, TextInput },
|
||||||
directives: {
|
directives: {},
|
||||||
onClickOutside: vOnClickOutside
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
data: Array,
|
data: Array,
|
||||||
modelValue: { default: null },
|
modelValue: { default: null },
|
||||||
|
@ -169,16 +167,19 @@ export default {
|
||||||
}
|
}
|
||||||
return this.modelValue === value
|
return this.modelValue === value
|
||||||
},
|
},
|
||||||
closeDropdown () {
|
toggleDropdown () {
|
||||||
|
if (this.disabled) {
|
||||||
this.isOpen = false
|
this.isOpen = false
|
||||||
|
}
|
||||||
|
this.isOpen = !this.isOpen
|
||||||
|
if (!this.isOpen) {
|
||||||
this.searchTerm = ''
|
this.searchTerm = ''
|
||||||
},
|
}
|
||||||
openDropdown () {
|
|
||||||
this.isOpen = this.disabled ? false : !this.isOpen
|
|
||||||
},
|
},
|
||||||
select (value) {
|
select (value) {
|
||||||
if (!this.multiple) {
|
if (!this.multiple) {
|
||||||
this.closeDropdown()
|
// Close after select
|
||||||
|
this.toggleDropdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.emitKey) {
|
if (this.emitKey) {
|
||||||
|
|
|
@ -21,4 +21,7 @@ export function registerComponents (app) {
|
||||||
app.component('NotionPage', defineAsyncComponent(() =>
|
app.component('NotionPage', defineAsyncComponent(() =>
|
||||||
import('./open/NotionPage.vue')
|
import('./open/NotionPage.vue')
|
||||||
))
|
))
|
||||||
|
app.component('FormBlockLogicEditor', defineAsyncComponent(() =>
|
||||||
|
import('./open/forms/components/form-logic-components/FormBlockLogicEditor.vue')
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
<template>
|
||||||
|
<transition @leave="(el,done) => motions.slide.leave(done)">
|
||||||
|
<div v-if="show" v-motion-slide-right="'slide'"
|
||||||
|
class="absolute shadow-lg shadow-gray-800/30 top-0 h-[calc(100vh-53px)] 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 z-50"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { useMotions } from '@vueuse/motion'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'EditorRightSidebar',
|
||||||
|
props: {
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup (props) {
|
||||||
|
return {
|
||||||
|
motions: useMotions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -49,27 +49,25 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full flex grow overflow-y-scroll relative">
|
<div class="w-full flex grow overflow-y-scroll relative bg-gray-50">
|
||||||
<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 bg-white 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.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form-information/>
|
<form-information />
|
||||||
<form-structure/>
|
<form-structure />
|
||||||
<form-customization/>
|
<form-customization />
|
||||||
<form-notifications/>
|
<form-notifications />
|
||||||
<form-about-submission/>
|
<form-about-submission />
|
||||||
<form-access />
|
<form-access />
|
||||||
<form-security-privacy/>
|
<form-security-privacy />
|
||||||
<form-custom-seo />
|
<form-custom-seo />
|
||||||
<form-custom-code/>
|
<form-custom-code />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form-editor-preview />
|
<form-editor-preview />
|
||||||
|
<form-editor-sidebar />
|
||||||
<form-field-edit-sidebar />
|
|
||||||
<add-form-block-sidebar />
|
|
||||||
|
|
||||||
<!-- Form Error Modal -->
|
<!-- Form Error Modal -->
|
||||||
<form-error-modal
|
<form-error-modal
|
||||||
|
@ -86,12 +84,11 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useAuthStore } from '../../../../stores/auth';
|
import { useAuthStore } from '../../../../stores/auth'
|
||||||
import { useFormsStore } from '../../../../stores/forms';
|
import { useFormsStore } from '../../../../stores/forms'
|
||||||
import { useWorkingFormStore } from '../../../../stores/working_form';
|
import { useWorkingFormStore } from '../../../../stores/working_form'
|
||||||
import { useWorkspacesStore } from '../../../../stores/workspaces';
|
import { useWorkspacesStore } from '../../../../stores/workspaces'
|
||||||
import AddFormBlockSidebar from './form-components/AddFormBlockSidebar.vue'
|
import FormEditorSidebar from './form-components/FormEditorSidebar.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'
|
||||||
import FormStructure from './form-components/FormStructure.vue'
|
import FormStructure from './form-components/FormStructure.vue'
|
||||||
|
@ -109,8 +106,7 @@ import fieldsLogic from '../../../../mixins/forms/fieldsLogic.js'
|
||||||
export default {
|
export default {
|
||||||
name: 'FormEditor',
|
name: 'FormEditor',
|
||||||
components: {
|
components: {
|
||||||
AddFormBlockSidebar,
|
FormEditorSidebar,
|
||||||
FormFieldEditSidebar,
|
|
||||||
FormEditorPreview,
|
FormEditorPreview,
|
||||||
FormNotifications,
|
FormNotifications,
|
||||||
FormAboutSubmission,
|
FormAboutSubmission,
|
||||||
|
@ -156,7 +152,7 @@ export default {
|
||||||
formsStore,
|
formsStore,
|
||||||
workingFormStore,
|
workingFormStore,
|
||||||
workspacesStore,
|
workspacesStore,
|
||||||
user : computed(() => authStore.user)
|
user: computed(() => authStore.user)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="showSidebar"
|
<div>
|
||||||
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="p-4 border-b sticky top-0 z-10 bg-white">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<button class="text-gray-500 hover:text-gray-900 cursor-pointer" @click.prevent="closeSidebar">
|
<button class="text-gray-500 hover:text-gray-900 cursor-pointer" @click.prevent="closeSidebar">
|
||||||
|
@ -69,7 +67,7 @@ import { computed } from 'vue'
|
||||||
import { useWorkingFormStore } from '../../../../../stores/working_form'
|
import { useWorkingFormStore } from '../../../../../stores/working_form'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AddFormBlockSidebar',
|
name: 'AddFormBlock',
|
||||||
components: {},
|
components: {},
|
||||||
props: {},
|
props: {},
|
||||||
|
|
||||||
|
@ -77,8 +75,7 @@ export default {
|
||||||
const workingFormStore = useWorkingFormStore()
|
const workingFormStore = useWorkingFormStore()
|
||||||
return {
|
return {
|
||||||
workingFormStore,
|
workingFormStore,
|
||||||
selectedFieldIndex : computed(() => workingFormStore.selectedFieldIndex),
|
selectedFieldIndex : computed(() => workingFormStore.selectedFieldIndex)
|
||||||
showAddFieldSidebar : computed(() => workingFormStore.showAddFieldSidebar)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -183,9 +180,6 @@ export default {
|
||||||
this.workingFormStore.set(value)
|
this.workingFormStore.set(value)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
showSidebar () {
|
|
||||||
return (this.form && this.showAddFieldSidebar) ?? false
|
|
||||||
},
|
|
||||||
|
|
||||||
defaultBlockNames () {
|
defaultBlockNames () {
|
||||||
return {
|
return {
|
|
@ -0,0 +1,49 @@
|
||||||
|
<template>
|
||||||
|
<editor-right-sidebar :show="form && (showEditFieldSidebar || showAddFieldSidebar)">
|
||||||
|
<transition mode="out-in">
|
||||||
|
<form-field-edit v-if="showEditFieldSidebar" :key="editFieldIndex" v-motion-fade="'fade'" />
|
||||||
|
<add-form-block v-else-if="showAddFieldSidebar" v-motion-fade="'fade'" />
|
||||||
|
</transition>
|
||||||
|
</editor-right-sidebar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useWorkingFormStore } from '../../../../../stores/working_form'
|
||||||
|
import EditorRightSidebar from '../../../editors/EditorRightSidebar.vue'
|
||||||
|
import FormFieldEdit from '../../fields/FormFieldEdit.vue'
|
||||||
|
import AddFormBlock from './AddFormBlock.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'FormEditorSidebar',
|
||||||
|
components: { EditorRightSidebar, AddFormBlock, FormFieldEdit },
|
||||||
|
props: {},
|
||||||
|
setup () {
|
||||||
|
const workingFormStore = useWorkingFormStore()
|
||||||
|
return {
|
||||||
|
workingFormStore,
|
||||||
|
editFieldIndex: computed(() => workingFormStore.selectedFieldIndex),
|
||||||
|
showEditFieldSidebar: computed(() => workingFormStore.showEditFieldSidebar),
|
||||||
|
showAddFieldSidebar: computed(() => workingFormStore.showAddFieldSidebar)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
form: {
|
||||||
|
get () {
|
||||||
|
return this.workingFormStore.content
|
||||||
|
},
|
||||||
|
/* We add a setter */
|
||||||
|
set (value) {
|
||||||
|
this.workingFormStore.set(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {},
|
||||||
|
mounted () {
|
||||||
|
},
|
||||||
|
methods: {}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -6,13 +6,13 @@
|
||||||
</div>
|
</div>
|
||||||
<SelectInput v-model="content.operator" class="w-full" :options="operators"
|
<SelectInput v-model="content.operator" class="w-full" :options="operators"
|
||||||
:name="'operator_'+property.id" placeholder="Comparison operator"
|
:name="'operator_'+property.id" placeholder="Comparison operator"
|
||||||
@input="operatorChanged()"
|
@update:modelValue="operatorChanged()"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<template v-if="hasInput">
|
<template v-if="hasInput">
|
||||||
<component v-bind="inputComponentData" :is="inputComponentData.component" v-model="content.value" class="w-full"
|
<component v-bind="inputComponentData" :is="inputComponentData.component" v-model="content.value" class="w-full"
|
||||||
:name="'value_'+property.id" placeholder="Filter Value"
|
:name="'value_'+property.id" placeholder="Filter Value"
|
||||||
@input="$emit('input',castContent(content))"
|
@update:modelValue="emitInput()"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
@ -131,7 +131,7 @@ export default {
|
||||||
} else if (typeof this.content.value === 'boolean' || typeof this.content.value === 'object') {
|
} else if (typeof this.content.value === 'boolean' || typeof this.content.value === 'object') {
|
||||||
this.content.value = null
|
this.content.value = null
|
||||||
}
|
}
|
||||||
this.$emit('input', this.castContent(this.content))
|
this.emitInput()
|
||||||
},
|
},
|
||||||
needsInput () {
|
needsInput () {
|
||||||
const operator = this.selectedOperator()
|
const operator = this.selectedOperator()
|
||||||
|
@ -165,6 +165,9 @@ export default {
|
||||||
return key.split('_').map(function (item) {
|
return key.split('_').map(function (item) {
|
||||||
return item.charAt(0).toUpperCase() + item.substring(1)
|
return item.charAt(0).toUpperCase() + item.substring(1)
|
||||||
}).join(' ')
|
}).join(' ')
|
||||||
|
},
|
||||||
|
emitInput () {
|
||||||
|
this.$emit('update:modelValue', this.castContent(this.content))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<query-builder v-model="query" :rules="rules" :config="config" @input="onChange">
|
<query-builder v-model="query" :rules="rules" :config="config" @update:modelValue="onChange">
|
||||||
<template #groupOperator="props">
|
<template #groupOperator="props">
|
||||||
<div class="query-builder-group-slot__group-selection flex items-center px-5 border-b py-1 mb-1 flex">
|
<div class="query-builder-group-slot__group-selection flex items-center px-5 border-b py-1 mb-1 flex">
|
||||||
<p class="mr-2 font-semibold">
|
<p class="mr-2 font-semibold">
|
||||||
|
@ -7,13 +7,13 @@
|
||||||
</p>
|
</p>
|
||||||
<select-input
|
<select-input
|
||||||
wrapper-class="relative"
|
wrapper-class="relative"
|
||||||
:value="props.currentOperator"
|
:model-value="props.currentOperator"
|
||||||
:options="props.operators"
|
:options="props.operators"
|
||||||
emit-key="identifier"
|
emit-key="identifier"
|
||||||
option-key="identifier"
|
option-key="identifier"
|
||||||
name="operator-input"
|
name="operator-input"
|
||||||
margin-bottom=""
|
margin-bottom=""
|
||||||
@input="props.updateCurrentOperator($event)"
|
@update:modelValue="props.updateCurrentOperator($event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -23,17 +23,17 @@
|
||||||
<template #rule="ruleCtrl">
|
<template #rule="ruleCtrl">
|
||||||
<component
|
<component
|
||||||
:is="ruleCtrl.ruleComponent"
|
:is="ruleCtrl.ruleComponent"
|
||||||
:value="ruleCtrl.ruleData"
|
:model-value="ruleCtrl.ruleData"
|
||||||
@input="ruleCtrl.updateRuleData"
|
@update:modelValue="ruleCtrl.updateRuleData"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</query-builder>
|
</query-builder>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
import QueryBuilder from 'query-builder-vue-3'
|
import QueryBuilder from 'query-builder-vue-3'
|
||||||
import ColumnCondition from './ColumnCondition.vue'
|
import ColumnCondition from './ColumnCondition.vue'
|
||||||
import Vue from 'vue'
|
|
||||||
import GroupControlSlot from './GroupControlSlot.vue'
|
import GroupControlSlot from './GroupControlSlot.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -66,7 +66,8 @@ export default {
|
||||||
identifier: property.id,
|
identifier: property.id,
|
||||||
name: property.name,
|
name: property.name,
|
||||||
component: (function () {
|
component: (function () {
|
||||||
return Vue.extend(ColumnCondition).extend({
|
return defineComponent({
|
||||||
|
extends: ColumnCondition,
|
||||||
computed: {
|
computed: {
|
||||||
property () {
|
property () {
|
||||||
return property
|
return property
|
||||||
|
@ -111,7 +112,7 @@ export default {
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
onChange () {
|
onChange () {
|
||||||
this.$emit('input', this.query)
|
this.$emit('update:modelValue', this.query)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ProTag from '../../../../common/ProTag.vue'
|
|
||||||
import ConditionEditor from './ConditionEditor.vue'
|
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'
|
||||||
|
@ -75,7 +74,7 @@ import clonedeep from 'clone-deep'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FormBlockLogicEditor',
|
name: 'FormBlockLogicEditor',
|
||||||
components: { SelectInput, Modal, ProTag, ConditionEditor },
|
components: { SelectInput, Modal, ConditionEditor },
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="showSidebar"
|
<div>
|
||||||
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 overflow-x-hidden"
|
|
||||||
>
|
|
||||||
<div class="p-4 border-b sticky top-0 z-10 bg-white">
|
<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">
|
<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">
|
<svg class="h-6 w-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
@ -78,15 +76,14 @@ import FieldOptions from './components/FieldOptions.vue'
|
||||||
import BlockOptions from './components/BlockOptions.vue'
|
import BlockOptions from './components/BlockOptions.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FormFieldEditSidebar',
|
name: 'FormFieldEdit',
|
||||||
components: { ChangeFieldType, FieldOptions, BlockOptions },
|
components: { ChangeFieldType, FieldOptions, BlockOptions },
|
||||||
props: {},
|
props: {},
|
||||||
setup () {
|
setup () {
|
||||||
const workingFormStore = useWorkingFormStore()
|
const workingFormStore = useWorkingFormStore()
|
||||||
return {
|
return {
|
||||||
workingFormStore,
|
workingFormStore,
|
||||||
selectedFieldIndex : computed(() => workingFormStore.selectedFieldIndex),
|
selectedFieldIndex : computed(() => workingFormStore.selectedFieldIndex)
|
||||||
showEditFieldSidebar : computed(() => workingFormStore.showEditFieldSidebar)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
|
@ -108,9 +105,6 @@ export default {
|
||||||
field () {
|
field () {
|
||||||
return (this.form && this.selectedFieldIndex !== null) ? this.form.properties[this.selectedFieldIndex] : null
|
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 () {
|
isBlockField () {
|
||||||
return this.field && this.field.type.startsWith('nf')
|
return this.field && this.field.type.startsWith('nf')
|
||||||
},
|
},
|
|
@ -86,17 +86,16 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Logic Block -->
|
<!-- Logic Block -->
|
||||||
<form-block-logic-editor class="py-2 px-4 border-b" :form="form" :field="field" />
|
<!-- <form-block-logic-editor class="py-2 px-4 border-b" :form="form" :field="field" />-->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import CodeInput from '../../../../forms/CodeInput.vue'
|
import CodeInput from '../../../../forms/CodeInput.vue'
|
||||||
const FormBlockLogicEditor = () => import('../../components/form-logic-components/FormBlockLogicEditor.vue')
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'BlockOptions',
|
name: 'BlockOptions',
|
||||||
components: { FormBlockLogicEditor, CodeInput },
|
components: { CodeInput },
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<dropdown dusk="nav-dropdown" v-if="changeTypeOptions.length > 0">
|
<dropdown v-if="changeTypeOptions.length > 0" dusk="nav-dropdown">
|
||||||
<template #trigger="{toggle}">
|
<template #trigger="{toggle}">
|
||||||
<v-button class="relative" :class="btnClasses" size="small" color="light-gray" @click="toggle">
|
<v-button class="relative" :class="btnClasses" size="small" color="light-gray" @click.stop="toggle">
|
||||||
<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">
|
<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>
|
||||||
|
@ -9,7 +9,7 @@
|
||||||
</v-button>
|
</v-button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<a href="#" v-for="(op, index) in changeTypeOptions" :key="index"
|
<a v-for="(op, index) in changeTypeOptions" :key="index" href="#"
|
||||||
class="block px-4 py-2 text-md text-gray-700 dark:text-white hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
|
class="block px-4 py-2 text-md text-gray-700 dark:text-white hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
|
||||||
@click.prevent="changeType(op.value)"
|
@click.prevent="changeType(op.value)"
|
||||||
>
|
>
|
||||||
|
@ -23,7 +23,7 @@ import Dropdown from '../../../../common/Dropdown.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ChangeFieldType',
|
name: 'ChangeFieldType',
|
||||||
components: {Dropdown},
|
components: { Dropdown },
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -34,25 +34,25 @@ export default {
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data () {
|
||||||
return {}
|
return {}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
changeTypeOptions() {
|
changeTypeOptions () {
|
||||||
var newTypes = []
|
let newTypes = []
|
||||||
if (['text', 'email', 'phone', 'number'].includes(this.field.type)) {
|
if (['text', 'email', 'phone', 'number'].includes(this.field.type)) {
|
||||||
newTypes = [
|
newTypes = [
|
||||||
{'name': 'Text Input', 'value': 'text'},
|
{ name: 'Text Input', value: 'text' },
|
||||||
{'name': 'Email Input', 'value': 'email'},
|
{ name: 'Email Input', value: 'email' },
|
||||||
{'name': 'Phone Input', 'value': 'phone'},
|
{ name: 'Phone Input', value: 'phone' },
|
||||||
{'name': 'Number Input', 'value': 'number'}
|
{ name: 'Number Input', value: 'number' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
if (['select', 'multi_select'].includes(this.field.type)) {
|
if (['select', 'multi_select'].includes(this.field.type)) {
|
||||||
newTypes = [
|
newTypes = [
|
||||||
{'name': 'Select Input', 'value': 'select'},
|
{ name: 'Select Input', value: 'select' },
|
||||||
{'name': 'Multi-Select Input', 'value': 'multi_select'}
|
{ name: 'Multi-Select Input', value: 'multi_select' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
return newTypes.filter((item) => {
|
return newTypes.filter((item) => {
|
||||||
|
@ -68,11 +68,11 @@ export default {
|
||||||
|
|
||||||
watch: {},
|
watch: {},
|
||||||
|
|
||||||
mounted() {
|
mounted () {
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
changeType(newType) {
|
changeType (newType) {
|
||||||
if (newType) {
|
if (newType) {
|
||||||
this.$emit('changeType', newType)
|
this.$emit('changeType', newType)
|
||||||
}
|
}
|
||||||
|
|
|
@ -293,7 +293,7 @@
|
||||||
<file-input v-else-if="field.type==='files'" name="prefill" class="mt-4"
|
<file-input v-else-if="field.type==='files'" name="prefill" class="mt-4"
|
||||||
:form="field"
|
:form="field"
|
||||||
label="Pre-filled file"
|
label="Pre-filled file"
|
||||||
:multiple="field.multiple===true" :moveToFormAssets="true"
|
:multiple="field.multiple===true" :move-to-form-assets="true"
|
||||||
/>
|
/>
|
||||||
<text-input v-else-if="!['files', 'signature'].includes(field.type)" name="prefill" class="mt-3"
|
<text-input v-else-if="!['files', 'signature'].includes(field.type)" name="prefill" class="mt-3"
|
||||||
:form="field"
|
:form="field"
|
||||||
|
@ -382,7 +382,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Logic Block -->
|
<!-- Logic Block -->
|
||||||
<form-block-logic-editor class="py-2 px-4 border-b" :form="form" :field="field" />
|
<!-- <form-block-logic-editor class="py-2 px-4 border-b" :form="form" :field="field" />-->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -391,11 +391,9 @@ import timezones from '../../../../../../data/timezones.json'
|
||||||
import countryCodes from '../../../../../../data/country_codes.json'
|
import countryCodes from '../../../../../../data/country_codes.json'
|
||||||
import CountryFlag from 'vue-country-flag-next'
|
import CountryFlag from 'vue-country-flag-next'
|
||||||
|
|
||||||
const FormBlockLogicEditor = () => import('../../components/form-logic-components/FormBlockLogicEditor.vue')
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FieldOptions',
|
name: 'FieldOptions',
|
||||||
components: { FormBlockLogicEditor, CountryFlag },
|
components: { CountryFlag },
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -533,7 +531,7 @@ export default {
|
||||||
this.field.hidden = true
|
this.field.hidden = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
initRating() {
|
initRating () {
|
||||||
if (this.field.is_rating) {
|
if (this.field.is_rating) {
|
||||||
this.$set(this.field, 'is_scale', false)
|
this.$set(this.field, 'is_scale', false)
|
||||||
if (!this.field.rating_max_value) {
|
if (!this.field.rating_max_value) {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
</div>
|
</div>
|
||||||
<dropdown v-else class="inline" dusk="nav-dropdown">
|
<dropdown v-else class="inline" dusk="nav-dropdown">
|
||||||
<template #trigger="{toggle}">
|
<template #trigger="{toggle}">
|
||||||
<v-button color="white" class="mr-2" @click="toggle">
|
<v-button color="white" class="mr-2" @click.stop="toggle">
|
||||||
<svg class="w-4 h-4 inline -mt-1" viewBox="0 0 16 4" fill="none"
|
<svg class="w-4 h-4 inline -mt-1" viewBox="0 0 16 4" fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
|
@ -159,7 +159,7 @@ export default {
|
||||||
const formsStore = useFormsStore()
|
const formsStore = useFormsStore()
|
||||||
return {
|
return {
|
||||||
formsStore,
|
formsStore,
|
||||||
user : computed(() => authStore.user)
|
user: computed(() => authStore.user)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -171,11 +171,11 @@ export default {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
formEndpoint: () => '/api/open/forms/{id}',
|
formEndpoint: () => '/api/open/forms/{id}'
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
copyLink(){
|
copyLink () {
|
||||||
const el = document.createElement('textarea')
|
const el = document.createElement('textarea')
|
||||||
el.value = this.form.share_url
|
el.value = this.form.share_url
|
||||||
document.body.appendChild(el)
|
document.body.appendChild(el)
|
||||||
|
@ -184,26 +184,26 @@ export default {
|
||||||
document.body.removeChild(el)
|
document.body.removeChild(el)
|
||||||
this.alertSuccess('Copied!')
|
this.alertSuccess('Copied!')
|
||||||
},
|
},
|
||||||
duplicateForm() {
|
duplicateForm () {
|
||||||
if (this.loadingDuplicate) return
|
if (this.loadingDuplicate) return
|
||||||
this.loadingDuplicate = true
|
this.loadingDuplicate = true
|
||||||
axios.post(this.formEndpoint.replace('{id}', this.form.id) + '/duplicate').then((response) => {
|
axios.post(this.formEndpoint.replace('{id}', this.form.id) + '/duplicate').then((response) => {
|
||||||
this.formsStore.addOrUpdate(response.data.new_form)
|
this.formsStore.addOrUpdate(response.data.new_form)
|
||||||
this.$router.push({name: 'forms.show', params: {slug: response.data.new_form.slug}})
|
this.$router.push({ name: 'forms.show', params: { slug: response.data.new_form.slug } })
|
||||||
this.alertSuccess('Form was successfully duplicated.')
|
this.alertSuccess('Form was successfully duplicated.')
|
||||||
this.loadingDuplicate = false
|
this.loadingDuplicate = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
deleteForm() {
|
deleteForm () {
|
||||||
if (this.loadingDelete) return
|
if (this.loadingDelete) return
|
||||||
this.loadingDelete = true
|
this.loadingDelete = true
|
||||||
axios.delete(this.formEndpoint.replace('{id}', this.form.id)).then(() => {
|
axios.delete(this.formEndpoint.replace('{id}', this.form.id)).then(() => {
|
||||||
this.formsStore.remove(this.form)
|
this.formsStore.remove(this.form)
|
||||||
this.$router.push({name: 'home'})
|
this.$router.push({ name: 'home' })
|
||||||
this.alertSuccess('Form was deleted.')
|
this.alertSuccess('Form was deleted.')
|
||||||
this.loadingDelete = false
|
this.loadingDelete = false
|
||||||
})
|
})
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import './axios'
|
import './axios'
|
||||||
import { registerLogEventOnApp } from './amplitude'
|
import { registerLogEventOnApp } from './amplitude'
|
||||||
|
import { MotionPlugin } from '@vueuse/motion'
|
||||||
import './vapor'
|
import './vapor'
|
||||||
import './sentry'
|
import './sentry'
|
||||||
|
|
||||||
|
@ -11,6 +12,7 @@ function registerPlugin (app) {
|
||||||
|
|
||||||
app.use(Notifications)
|
app.use(Notifications)
|
||||||
app.use(metaManager)
|
app.use(metaManager)
|
||||||
|
app.use(MotionPlugin)
|
||||||
registerLogEventOnApp(app)
|
registerLogEventOnApp(app)
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,13 +31,13 @@ export const useAppStore = defineStore('app', {
|
||||||
this.loaderHide()
|
this.loaderHide()
|
||||||
},
|
},
|
||||||
loaderSetTimer (timerVal) {
|
loaderSetTimer (timerVal) {
|
||||||
this._timer = timerVal
|
this.loader._timer = timerVal
|
||||||
},
|
},
|
||||||
loaderPause () {
|
loaderPause () {
|
||||||
clearInterval(this.loader._timer)
|
clearInterval(this.loader._timer)
|
||||||
},
|
},
|
||||||
loaderHide () {
|
loaderHide () {
|
||||||
clearInterval(this.loader._timer)
|
this.loaderPause()
|
||||||
this.loader._timer = null
|
this.loader._timer = null
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.loader.show = false
|
this.loader.show = false
|
||||||
|
|
Loading…
Reference in New Issue