mirror of https://github.com/docusealco/docuseal
parent
beb677734f
commit
8227dd4f6a
@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<template
|
||||
v-for="(field, fieldIndex) in fields"
|
||||
:key="field.uuid"
|
||||
>
|
||||
<template
|
||||
v-for="(area, areaIndex) in field.areas"
|
||||
:key="areaIndex"
|
||||
>
|
||||
<Teleport
|
||||
v-if="findPageElementForArea(area)"
|
||||
:to="findPageElementForArea(area)"
|
||||
>
|
||||
<FieldArea
|
||||
v-if="isMathLoaded"
|
||||
:model-value="calculateFormula(field)"
|
||||
:field="field"
|
||||
:area="area"
|
||||
:submittable="false"
|
||||
:field-index="fieldIndex"
|
||||
/>
|
||||
</Teleport>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FieldArea from './area'
|
||||
|
||||
export default {
|
||||
name: 'FormulaFieldAreas',
|
||||
components: {
|
||||
FieldArea
|
||||
},
|
||||
props: {
|
||||
fields: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
values: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isMathLoaded: false
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
const {
|
||||
create,
|
||||
evaluateDependencies,
|
||||
addDependencies,
|
||||
subtractDependencies,
|
||||
divideDependencies,
|
||||
multiplyDependencies,
|
||||
powDependencies,
|
||||
roundDependencies,
|
||||
absDependencies,
|
||||
sinDependencies,
|
||||
tanDependencies,
|
||||
cosDependencies
|
||||
} = await import('mathjs')
|
||||
|
||||
this.math = create({
|
||||
evaluateDependencies,
|
||||
addDependencies,
|
||||
subtractDependencies,
|
||||
divideDependencies,
|
||||
multiplyDependencies,
|
||||
powDependencies,
|
||||
roundDependencies,
|
||||
absDependencies,
|
||||
sinDependencies,
|
||||
tanDependencies,
|
||||
cosDependencies
|
||||
})
|
||||
|
||||
this.isMathLoaded = true
|
||||
},
|
||||
methods: {
|
||||
findPageElementForArea (area) {
|
||||
return (this.$root.$el?.parentNode?.getRootNode() || document).getElementById(`page-${area.attachment_uuid}-${area.page}`)
|
||||
},
|
||||
calculateFormula (field) {
|
||||
const transformedFormula = field.preferences.formula.replace(/{{(.*?)}}/g, (match, uuid) => {
|
||||
return this.values[uuid] || 0.0
|
||||
})
|
||||
|
||||
return this.math.evaluate(transformedFormula.toLowerCase())
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,216 @@
|
||||
<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>
|
||||
{{ t('formula') }}
|
||||
</span>
|
||||
<a
|
||||
href="#"
|
||||
class="text-xl"
|
||||
@click.prevent="$emit('close')"
|
||||
>×</a>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex-inline mb-2 gap-2 space-y-1">
|
||||
<button
|
||||
v-for="f in fields"
|
||||
:key="f.uuid"
|
||||
class="mr-1 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)}}}`)"
|
||||
>
|
||||
<IconCodePlus
|
||||
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"
|
||||
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('^')"
|
||||
>
|
||||
^
|
||||
</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"
|
||||
@click.prevent="validateSaveAndClose"
|
||||
>
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IconCodePlus } from '@tabler/icons-vue'
|
||||
|
||||
export default {
|
||||
name: 'FormulaModal',
|
||||
components: {
|
||||
IconCodePlus
|
||||
},
|
||||
inject: ['t', 'save', 'template'],
|
||||
props: {
|
||||
field: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
buildDefaultName: {
|
||||
type: Function,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: ['close'],
|
||||
data () {
|
||||
return {
|
||||
formula: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fields () {
|
||||
return this.template.fields.reduce((acc, f) => {
|
||||
if (f !== this.field && f.submitter_uuid === this.field.submitter_uuid && ['number'].includes(f.type) && !f.preferences?.formula) {
|
||||
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 () {
|
||||
const normalizedFormula = this.normalizeFormula(this.formula)
|
||||
|
||||
if (normalizedFormula.includes('FIELD NOT FOUND')) {
|
||||
alert('Some fields are missing in the formula.')
|
||||
} else {
|
||||
this.field.preferences.formula = normalizedFormula
|
||||
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
|
||||
|
||||
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>
|
||||
Loading…
Reference in new issue