|
|
@ -4,15 +4,19 @@ import { RelationTypes, UITypes, isVirtualCol } from 'nocodb-sdk' |
|
|
|
import { SwipeDirection, breakpointsTailwind } from '@vueuse/core' |
|
|
|
import { SwipeDirection, breakpointsTailwind } from '@vueuse/core' |
|
|
|
import { |
|
|
|
import { |
|
|
|
DropZoneRef, |
|
|
|
DropZoneRef, |
|
|
|
|
|
|
|
IsSurveyFormInj, |
|
|
|
computed, |
|
|
|
computed, |
|
|
|
|
|
|
|
isValidURL, |
|
|
|
onKeyStroke, |
|
|
|
onKeyStroke, |
|
|
|
onMounted, |
|
|
|
onMounted, |
|
|
|
provide, |
|
|
|
provide, |
|
|
|
ref, |
|
|
|
ref, |
|
|
|
useBreakpoints, |
|
|
|
useBreakpoints, |
|
|
|
|
|
|
|
useI18n, |
|
|
|
usePointerSwipe, |
|
|
|
usePointerSwipe, |
|
|
|
useSharedFormStoreOrThrow, |
|
|
|
useSharedFormStoreOrThrow, |
|
|
|
useStepper, |
|
|
|
useStepper, |
|
|
|
|
|
|
|
validateEmail, |
|
|
|
} from '#imports' |
|
|
|
} from '#imports' |
|
|
|
|
|
|
|
|
|
|
|
enum TransitionDirection { |
|
|
|
enum TransitionDirection { |
|
|
@ -32,6 +36,8 @@ const { md } = useBreakpoints(breakpointsTailwind) |
|
|
|
const { v$, formState, formColumns, submitForm, submitted, secondsRemain, sharedFormView, sharedViewMeta, onReset } = |
|
|
|
const { v$, formState, formColumns, submitForm, submitted, secondsRemain, sharedFormView, sharedViewMeta, onReset } = |
|
|
|
useSharedFormStoreOrThrow() |
|
|
|
useSharedFormStoreOrThrow() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const { t } = useI18n() |
|
|
|
|
|
|
|
|
|
|
|
const isTransitioning = ref(false) |
|
|
|
const isTransitioning = ref(false) |
|
|
|
|
|
|
|
|
|
|
|
const transitionName = ref<TransitionDirection>(TransitionDirection.Left) |
|
|
|
const transitionName = ref<TransitionDirection>(TransitionDirection.Left) |
|
|
@ -40,10 +46,14 @@ const animationTarget = ref<AnimationTarget>(AnimationTarget.ArrowRight) |
|
|
|
|
|
|
|
|
|
|
|
const isAnimating = ref(false) |
|
|
|
const isAnimating = ref(false) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const editEnabled = ref<boolean[]>([]) |
|
|
|
|
|
|
|
|
|
|
|
const el = ref<HTMLDivElement>() |
|
|
|
const el = ref<HTMLDivElement>() |
|
|
|
|
|
|
|
|
|
|
|
provide(DropZoneRef, el) |
|
|
|
provide(DropZoneRef, el) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
provide(IsSurveyFormInj, ref(true)) |
|
|
|
|
|
|
|
|
|
|
|
const transitionDuration = computed(() => sharedViewMeta.value.transitionDuration || 50) |
|
|
|
const transitionDuration = computed(() => sharedViewMeta.value.transitionDuration || 50) |
|
|
|
|
|
|
|
|
|
|
|
const steps = computed(() => { |
|
|
|
const steps = computed(() => { |
|
|
@ -64,6 +74,8 @@ const { index, goToPrevious, goToNext, isFirst, isLast, goTo } = useStepper(step |
|
|
|
|
|
|
|
|
|
|
|
const field = computed(() => formColumns.value?.[index.value]) |
|
|
|
const field = computed(() => formColumns.value?.[index.value]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const columnValidationError = ref(false) |
|
|
|
|
|
|
|
|
|
|
|
function isRequired(column: ColumnType, required = false) { |
|
|
|
function isRequired(column: ColumnType, required = false) { |
|
|
|
let columnObj = column |
|
|
|
let columnObj = column |
|
|
|
if ( |
|
|
|
if ( |
|
|
@ -105,7 +117,29 @@ function animate(target: AnimationTarget) { |
|
|
|
}, transitionDuration.value / 2) |
|
|
|
}, transitionDuration.value / 2) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function validateColumn() { |
|
|
|
|
|
|
|
const f = field.value! |
|
|
|
|
|
|
|
if (parseProp(f.meta)?.validate && formState.value[f.title!]) { |
|
|
|
|
|
|
|
if (f.uidt === UITypes.Email) { |
|
|
|
|
|
|
|
if (!validateEmail(formState.value[f.title!])) { |
|
|
|
|
|
|
|
columnValidationError.value = true |
|
|
|
|
|
|
|
message.error(t('msg.error.invalidEmail')) |
|
|
|
|
|
|
|
return false |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else if (f.uidt === UITypes.URL) { |
|
|
|
|
|
|
|
if (!isValidURL(formState.value[f.title!])) { |
|
|
|
|
|
|
|
columnValidationError.value = true |
|
|
|
|
|
|
|
message.error(t('msg.error.invalidURL')) |
|
|
|
|
|
|
|
return false |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async function goNext(animationTarget?: AnimationTarget) { |
|
|
|
async function goNext(animationTarget?: AnimationTarget) { |
|
|
|
|
|
|
|
columnValidationError.value = false |
|
|
|
|
|
|
|
|
|
|
|
if (isLast.value || submitted.value) return |
|
|
|
if (isLast.value || submitted.value) return |
|
|
|
|
|
|
|
|
|
|
|
if (!field.value || !field.value.title) return |
|
|
|
if (!field.value || !field.value.title) return |
|
|
@ -117,6 +151,8 @@ async function goNext(animationTarget?: AnimationTarget) { |
|
|
|
if (!isValid) return |
|
|
|
if (!isValid) return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!(await validateColumn())) return |
|
|
|
|
|
|
|
|
|
|
|
animate(animationTarget || AnimationTarget.ArrowRight) |
|
|
|
animate(animationTarget || AnimationTarget.ArrowRight) |
|
|
|
|
|
|
|
|
|
|
|
setTimeout( |
|
|
|
setTimeout( |
|
|
@ -159,9 +195,8 @@ function resetForm() { |
|
|
|
goTo(steps.value[0]) |
|
|
|
goTo(steps.value[0]) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function submit() { |
|
|
|
async function submit() { |
|
|
|
if (submitted.value) return |
|
|
|
if (submitted.value || !(await validateColumn())) return |
|
|
|
|
|
|
|
|
|
|
|
submitForm() |
|
|
|
submitForm() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -177,7 +212,7 @@ onKeyStroke(['Enter', 'Space'], () => { |
|
|
|
if (isLast.value) { |
|
|
|
if (isLast.value) { |
|
|
|
submit() |
|
|
|
submit() |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
goNext(AnimationTarget.OkButton) |
|
|
|
goNext(AnimationTarget.OkButton, true) |
|
|
|
} |
|
|
|
} |
|
|
|
}) |
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
@ -263,7 +298,10 @@ onMounted(() => { |
|
|
|
class="nc-input" |
|
|
|
class="nc-input" |
|
|
|
:data-testid="`nc-survey-form__input-${field.title.replaceAll(' ', '')}`" |
|
|
|
:data-testid="`nc-survey-form__input-${field.title.replaceAll(' ', '')}`" |
|
|
|
:column="field" |
|
|
|
:column="field" |
|
|
|
:edit-enabled="true" |
|
|
|
:edit-enabled="editEnabled[index]" |
|
|
|
|
|
|
|
@click="editEnabled[index] = true" |
|
|
|
|
|
|
|
@cancel="editEnabled[index] = false" |
|
|
|
|
|
|
|
@update:edit-enabled="editEnabled[index] = $event" |
|
|
|
/> |
|
|
|
/> |
|
|
|
|
|
|
|
|
|
|
|
<div class="flex flex-col gap-2 text-slate-500 dark:text-slate-300 text-[0.75rem] my-2 px-1"> |
|
|
|
<div class="flex flex-col gap-2 text-slate-500 dark:text-slate-300 text-[0.75rem] my-2 px-1"> |
|
|
@ -315,7 +353,7 @@ onMounted(() => { |
|
|
|
class="bg-opacity-100 scaling-btn flex items-center gap-1" |
|
|
|
class="bg-opacity-100 scaling-btn flex items-center gap-1" |
|
|
|
data-testid="nc-survey-form__btn-next" |
|
|
|
data-testid="nc-survey-form__btn-next" |
|
|
|
:class="[ |
|
|
|
:class="[ |
|
|
|
v$.localState[field.title]?.$error ? 'after:!bg-gray-100 after:!ring-red-500' : '', |
|
|
|
v$.localState[field.title]?.$error || columnValidationError ? 'after:!bg-gray-100 after:!ring-red-500' : '', |
|
|
|
animationTarget === AnimationTarget.OkButton && isAnimating |
|
|
|
animationTarget === AnimationTarget.OkButton && isAnimating |
|
|
|
? 'transform translate-y-[2px] translate-x-[2px] after:(!ring !ring-accent !ring-opacity-100)' |
|
|
|
? 'transform translate-y-[2px] translate-x-[2px] after:(!ring !ring-accent !ring-opacity-100)' |
|
|
|
: '', |
|
|
|
: '', |
|
|
@ -327,7 +365,10 @@ onMounted(() => { |
|
|
|
</Transition> |
|
|
|
</Transition> |
|
|
|
|
|
|
|
|
|
|
|
<Transition name="slide-right" mode="out-in"> |
|
|
|
<Transition name="slide-right" mode="out-in"> |
|
|
|
<MdiCloseCircleOutline v-if="v$.localState[field.title]?.$error" class="text-red-500 md:text-md" /> |
|
|
|
<MdiCloseCircleOutline |
|
|
|
|
|
|
|
v-if="v$.localState[field.title]?.$error || columnValidationError" |
|
|
|
|
|
|
|
class="text-red-500 md:text-md" |
|
|
|
|
|
|
|
/> |
|
|
|
<MdiCheck v-else class="text-white md:text-md" /> |
|
|
|
<MdiCheck v-else class="text-white md:text-md" /> |
|
|
|
</Transition> |
|
|
|
</Transition> |
|
|
|
</button> |
|
|
|
</button> |
|
|
|