mirror of https://github.com/docusealco/docuseal
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
249 lines
6.9 KiB
249 lines
6.9 KiB
<template>
|
|
<div
|
|
class="modal modal-open items-start !animate-none overflow-y-auto"
|
|
>
|
|
<div
|
|
class="absolute top-0 bottom-0 right-0 left-0"
|
|
@click.prevent="$emit('close')"
|
|
/>
|
|
<div class="modal-box pt-4 pb-6 px-6 mt-20 max-h-none w-full max-w-xl">
|
|
<div class="flex justify-between items-center border-b pb-2 mb-2 font-medium">
|
|
<span class="modal-title">
|
|
{{ t('formula') }} - {{ (defaultField ? (defaultField.title || field.title || field.name) : field.name) || buildDefaultName(field, template.fields) }}
|
|
</span>
|
|
<a
|
|
href="#"
|
|
class="text-xl modal-close-button"
|
|
@click.prevent="$emit('close')"
|
|
>×</a>
|
|
</div>
|
|
<div>
|
|
<div
|
|
v-if="!withFormula"
|
|
class="bg-base-300 rounded-xl py-2 px-3 text-center"
|
|
>
|
|
<a
|
|
href="https://www.docuseal.com/pricing"
|
|
target="_blank"
|
|
class="link"
|
|
>{{ t('available_in_pro') }}</a>
|
|
</div>
|
|
<div class="flex flex-wrap mb-2 gap-y-1 pt-1">
|
|
<button
|
|
v-for="f in fields"
|
|
:key="f.uuid"
|
|
class="mr-1 flex btn btn-neutral btn-outline border-base-content/20 btn-sm normal-case font-normal bg-white !rounded-xl"
|
|
@click.prevent="insertTextUnderCursor(`{{${f.name || buildDefaultName(f, template.fields)}}}`)"
|
|
>
|
|
<IconMathFunction
|
|
v-if="f.preferences?.formula"
|
|
width="17"
|
|
height="17"
|
|
stroke-width="1.5"
|
|
/>
|
|
<IconCodePlus
|
|
v-else
|
|
width="20"
|
|
height="20"
|
|
stroke-width="1.5"
|
|
/>
|
|
{{ f.name || buildDefaultName(f, template.fields) }}
|
|
</button>
|
|
</div>
|
|
<div>
|
|
<div class="flex">
|
|
<textarea
|
|
ref="textarea"
|
|
v-model="formula"
|
|
class="base-textarea !rounded-xl !text-base font-mono w-full !outline-0 !ring-0 !px-3"
|
|
:readonly="!editable"
|
|
required="true"
|
|
@input="resizeTextarea"
|
|
/>
|
|
</div>
|
|
<div class="mb-3 mt-1">
|
|
<div
|
|
target="blank"
|
|
class="text-sm mb-2 inline space-x-2"
|
|
>
|
|
<button
|
|
class="bg-base-200 px-2 rounded-xl"
|
|
@click="insertTextUnderCursor(' + ')"
|
|
>
|
|
+
|
|
</button>
|
|
<button
|
|
class="bg-base-200 px-2 rounded-xl"
|
|
@click="insertTextUnderCursor(' - ')"
|
|
>
|
|
-
|
|
</button>
|
|
<button
|
|
class="bg-base-200 px-2 rounded-xl"
|
|
@click="insertTextUnderCursor(' * ')"
|
|
>
|
|
*
|
|
</button>
|
|
<button
|
|
class="bg-base-200 px-2 rounded-xl"
|
|
@click="insertTextUnderCursor(' / ')"
|
|
>
|
|
/
|
|
</button>
|
|
<button
|
|
class="bg-base-200 px-2 rounded-xl"
|
|
@click="insertTextUnderCursor('^')"
|
|
>
|
|
^
|
|
</button>
|
|
<button
|
|
class="bg-base-200 px-2 rounded-xl"
|
|
@click="insertTextUnderCursor('round()')"
|
|
>
|
|
round(n, d)
|
|
</button>
|
|
<button
|
|
class="bg-base-200 px-2 rounded-xl"
|
|
@click="insertTextUnderCursor('abs()')"
|
|
>
|
|
abs(n)
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<button
|
|
class="base-button w-full modal-save-button"
|
|
@click.prevent="validateSaveAndClose"
|
|
>
|
|
{{ t('save') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { IconCodePlus, IconMathFunction } from '@tabler/icons-vue'
|
|
|
|
export default {
|
|
name: 'FormulaModal',
|
|
components: {
|
|
IconCodePlus,
|
|
IconMathFunction
|
|
},
|
|
inject: ['t', 'save', 'template', 'withFormula'],
|
|
props: {
|
|
field: {
|
|
type: Object,
|
|
required: true
|
|
},
|
|
defaultField: {
|
|
type: Object,
|
|
required: false,
|
|
default: null
|
|
},
|
|
editable: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: true
|
|
},
|
|
buildDefaultName: {
|
|
type: Function,
|
|
required: true
|
|
}
|
|
},
|
|
emits: ['close'],
|
|
data () {
|
|
return {
|
|
formula: ''
|
|
}
|
|
},
|
|
computed: {
|
|
fields () {
|
|
return this.template.fields.reduce((acc, f) => {
|
|
if (f !== this.field && ['number'].includes(f.type) && (!f.preferences?.formula || !f.preferences.formula.includes(this.field.uuid))) {
|
|
acc.push(f)
|
|
}
|
|
|
|
return acc
|
|
}, [])
|
|
}
|
|
},
|
|
created () {
|
|
this.field.preferences ||= {}
|
|
},
|
|
mounted () {
|
|
this.formula = this.humanizeFormula(this.field.preferences.formula || '')
|
|
},
|
|
methods: {
|
|
humanizeFormula (text) {
|
|
return text.replace(/{{(.*?)}}/g, (match, uuid) => {
|
|
const foundField = this.fields.find((f) => f.uuid === uuid)
|
|
|
|
if (foundField) {
|
|
return `{{${foundField.name || this.buildDefaultName(foundField, this.template.fields)}}}`
|
|
} else {
|
|
return '{{FIELD NOT FOUND}}'
|
|
}
|
|
})
|
|
},
|
|
normalizeFormula (text) {
|
|
return text.replace(/{{(.*?)}}/g, (match, name) => {
|
|
const foundField = this.fields.find((f) => {
|
|
return (f.name || this.buildDefaultName(f, this.template.fields)).trim() === name.trim()
|
|
})
|
|
|
|
if (foundField) {
|
|
return `{{${foundField.uuid}}}`
|
|
} else {
|
|
return '{{FIELD NOT FOUND}}'
|
|
}
|
|
})
|
|
},
|
|
validateSaveAndClose () {
|
|
if (!this.withFormula) {
|
|
return alert(this.t('available_only_in_pro'))
|
|
}
|
|
|
|
const normalizedFormula = this.normalizeFormula(this.formula)
|
|
|
|
if (normalizedFormula.includes('FIELD NOT FOUND')) {
|
|
alert(this.t('some_fields_are_missing_in_the_formula'))
|
|
} else {
|
|
this.field.preferences.formula = normalizedFormula
|
|
|
|
if (this.field.type !== 'payment') {
|
|
this.field.readonly = !!normalizedFormula
|
|
}
|
|
|
|
this.save()
|
|
|
|
this.$emit('close')
|
|
}
|
|
},
|
|
insertTextUnderCursor (textToInsert) {
|
|
const textarea = this.$refs.textarea
|
|
|
|
const selectionEnd = textarea.selectionEnd
|
|
const cursorPos = selectionEnd
|
|
|
|
const newText = textarea.value.substring(0, cursorPos) + textToInsert + textarea.value.substring(cursorPos)
|
|
|
|
this.formula = newText
|
|
|
|
this.$nextTick(() => {
|
|
textarea.setSelectionRange(cursorPos + textToInsert.length, cursorPos + textToInsert.length)
|
|
|
|
textarea.focus()
|
|
})
|
|
},
|
|
resizeTextarea () {
|
|
const textarea = this.$refs.textarea
|
|
|
|
textarea.style.height = 'auto'
|
|
textarea.style.height = textarea.scrollHeight + 'px'
|
|
}
|
|
}
|
|
}
|
|
</script>
|