add strikethrough

This commit is contained in:
Pete Matsyburka
2025-09-26 20:52:44 +03:00
parent e0469b10a7
commit 9cc92b59b3
17 changed files with 311 additions and 58 deletions
+59
View File
@@ -219,6 +219,48 @@
>
{{ formatNumber(modelValue, field.preferences?.format) }}
</span>
<span
v-else-if="field.type === 'strikethrough'"
class="w-full h-full flex items-center justify-center"
>
<svg
v-if="(((1000.0 / pageWidth) * pageHeight) * area.h) < 40.0"
xmlns="http://www.w3.org/2000/svg"
width="100%"
height="100%"
>
<line
x1="0"
y1="50%"
x2="100%"
y2="50%"
:stroke="field.preferences?.color || 'red'"
:stroke-width="strikethroughWidth"
/>
</svg>
<svg
v-else
xmlns="http://www.w3.org/2000/svg"
:style="{ overflow: 'visible', width: `calc(100% - ${strikethroughWidth})`, height: `calc(100% - ${strikethroughWidth})` }"
>
<line
x1="0"
y1="0"
x2="100%"
y2="100%"
:stroke="field.preferences?.color || 'red'"
:stroke-width="strikethroughWidth"
/>
<line
x1="100%"
y1="0"
x2="0"
y2="100%"
:stroke="field.preferences?.color || 'red'"
:stroke-width="strikethroughWidth"
/>
</svg>
</span>
<span
v-else
class="whitespace-pre-wrap"
@@ -260,6 +302,16 @@ export default {
required: false,
default: false
},
pageWidth: {
type: Number,
required: false,
default: 0
},
pageHeight: {
type: Number,
required: false,
default: 0
},
isValueSet: {
type: Boolean,
required: false,
@@ -342,6 +394,13 @@ export default {
verification: this.t('verify_id')
}
},
strikethroughWidth () {
if (this.isInlineSize) {
return '0.6cqmin'
} else {
return 'clamp(0px, 0.5vw, 6px)'
}
},
isShowSignatureId () {
if ([true, false].includes(this.field.preferences?.with_signature_id)) {
return this.field.preferences.with_signature_id
+30 -23
View File
@@ -11,30 +11,37 @@
v-for="(area, areaIndex) in field.areas"
:key="areaIndex"
>
<Teleport
v-if="findPageElementForArea(area)"
:to="findPageElementForArea(area)"
<template
v-for="(pageElem, index) in [findPageElementForArea(area)]"
:key="index"
>
<FieldArea
:ref="setAreaRef"
v-model="values[field.uuid]"
:values="values"
:field="field"
:area="area"
:submittable="submittable"
:field-index="fieldIndex"
:is-inline-size="isInlineSize"
:scroll-padding="scrollPadding"
:submitter="submitter"
:with-field-placeholder="withFieldPlaceholder"
:with-signature-id="withSignatureId"
:is-active="currentStep === step"
:with-label="withLabel && !withFieldPlaceholder && step.length < 2"
:is-value-set="step.some((f) => f.uuid in values)"
:attachments-index="attachmentsIndex"
@click="[$emit('focus-step', stepIndex), maybeScrollOnClick(field, area)]"
/>
</Teleport>
<Teleport
v-if="pageElem"
:to="pageElem"
>
<FieldArea
:ref="setAreaRef"
v-model="values[field.uuid]"
:values="values"
:field="field"
:area="area"
:submittable="submittable"
:page-width="1400"
:page-height="(1400.0 / pageElem.offsetWidth) * pageElem.offsetHeight"
:field-index="fieldIndex"
:is-inline-size="isInlineSize"
:scroll-padding="scrollPadding"
:submitter="submitter"
:with-field-placeholder="withFieldPlaceholder"
:with-signature-id="withSignatureId"
:is-active="currentStep === step"
:with-label="withLabel && !withFieldPlaceholder && step.length < 2"
:is-value-set="step.some((f) => f.uuid in values)"
:attachments-index="attachmentsIndex"
@click="[$emit('focus-step', stepIndex), maybeScrollOnClick(field, area)]"
/>
</Teleport>
</template>
</template>
</template>
</template>
+93 -8
View File
@@ -10,7 +10,7 @@
<div
v-if="isSelected || isDraw"
class="top-0 bottom-0 right-0 left-0 absolute border border-1.5 pointer-events-none"
:class="field.type === 'heading' ? '' : borderColors[submitterIndex % borderColors.length]"
:class="activeBorderClasses"
/>
<div
v-if="field.type === 'cells' && (isSelected || isDraw)"
@@ -39,7 +39,7 @@
@pointerdown.stop
>
<FieldSubmitter
v-if="field.type != 'heading'"
v-if="field.type != 'heading' && field.type != 'strikethrough'"
v-model="field.submitter_uuid"
class="border-r roles-dropdown"
:compact="true"
@@ -164,16 +164,58 @@
ref="touchValueTarget"
class="flex h-full w-full field-area"
dir="auto"
:class="[isValueInput ? 'cursor-text' : '', isValueInput || isCheckboxInput || isSelectInput ? 'bg-opacity-50' : 'bg-opacity-80', field.type === 'heading' ? 'bg-gray-50' : bgColors[submitterIndex % bgColors.length], isDefaultValuePresent || isValueInput || (withFieldPlaceholder && field.areas) ? fontClasses : 'justify-center items-center']"
:class="[isValueInput ? 'cursor-text' : '', isValueInput || isCheckboxInput || isSelectInput ? 'bg-opacity-50' : 'bg-opacity-80', bgClasses, isDefaultValuePresent || isValueInput || (withFieldPlaceholder && field.areas) ? fontClasses : 'justify-center items-center']"
@click="focusValueInput"
>
<span
v-if="field"
class="flex justify-center items-center space-x-1"
:class="{ 'w-full': isWFullType, 'h-full': !isValueInput && !isDefaultValuePresent }"
:class="{ 'w-full': isWFullType, 'h-full': !isValueInput && (!isDefaultValuePresent || field.type === 'strikethrough') }"
>
<div
v-if="isDefaultValuePresent || isValueInput || isSelectInput || (withFieldPlaceholder && field.areas && field.type !== 'checkbox')"
v-if="field.type === 'strikethrough'"
class="w-full h-full flex items-center justify-center"
>
<svg
v-if="(((basePageWidth / pageWidth) * pageHeight) * area.h) < 41.6"
xmlns="http://www.w3.org/2000/svg"
width="100%"
height="100%"
>
<line
x1="0"
y1="50%"
x2="100%"
y2="50%"
:stroke="field.preferences?.color || 'red'"
:stroke-width="strikethroughWidth"
/>
</svg>
<svg
v-else
xmlns="http://www.w3.org/2000/svg"
:style="{ overflow: 'visible', width: `calc(100% - ${strikethroughWidth})`, height: `calc(100% - ${strikethroughWidth})` }"
>
<line
x1="0"
y1="0"
x2="100%"
y2="100%"
:stroke="field.preferences?.color || 'red'"
:stroke-width="strikethroughWidth"
/>
<line
x1="100%"
y1="0"
x2="0"
y2="100%"
:stroke="field.preferences?.color || 'red'"
:stroke-width="strikethroughWidth"
/>
</svg>
</div>
<div
v-else-if="isDefaultValuePresent || isValueInput || isSelectInput || (withFieldPlaceholder && field.areas && field.type !== 'checkbox')"
:class="{ 'w-full h-full': isWFullType }"
:style="fontStyle"
>
@@ -397,6 +439,16 @@ export default {
required: false,
default: false
},
pageWidth: {
type: Number,
required: false,
default: 0
},
pageHeight: {
type: Number,
required: false,
default: 0
},
defaultSubmitters: {
type: Array,
required: false,
@@ -436,8 +488,33 @@ export default {
fieldNames: FieldType.computed.fieldNames,
fieldLabels: FieldType.computed.fieldLabels,
fieldIcons: FieldType.computed.fieldIcons,
bgClasses () {
if (this.field.type === 'heading') {
return 'bg-gray-50'
} else if (this.field.type === 'strikethrough') {
return 'bg-transparent'
} else {
return this.bgColors[this.submitterIndex % this.bgColors.length]
}
},
activeBorderClasses () {
if (this.field.type === 'heading') {
return ''
} else if (this.field.type === 'strikethrough') {
return 'border-dashed border-gray-300'
} else {
return this.borderColors[this.submitterIndex % this.borderColors.length]
}
},
isWFullType () {
return ['cells', 'checkbox', 'radio', 'multiple', 'select'].includes(this.field.type)
return ['cells', 'checkbox', 'radio', 'multiple', 'select', 'strikethrough'].includes(this.field.type)
},
strikethroughWidth () {
if (this.isInlineSize) {
return '0.6cqmin'
} else {
return 'clamp(0px, 0.5vw, 6px)'
}
},
fontStyle () {
let fontSize = ''
@@ -471,8 +548,11 @@ export default {
lineHeight () {
return 1.3
},
basePageWidth () {
return 1040.0
},
fontScale () {
return 1040 / 612.0
return this.basePageWidth / 612.0
},
isDefaultValuePresent () {
return this.field?.default_value || this.field?.default_value === 0
@@ -742,10 +822,15 @@ export default {
delete this.field.options
}
if (['heading'].includes(this.field.type)) {
if (this.field.type === 'heading') {
this.field.readonly = true
}
if (this.field.type === 'strikethrough') {
this.field.readonly = true
this.field.default_value = true
}
if (['select', 'multiple', 'radio'].includes(this.field.type)) {
this.field.options ||= [{ value: '', uuid: v4() }]
}
+23 -3
View File
@@ -402,7 +402,10 @@
:style="{ backgroundColor }"
>
<div class="bg-base-200 rounded-lg p-5 text-center space-y-4 draw-field-container">
<p>
<p v-if="(drawField?.type || drawFieldType) === 'strikethrough'">
{{ t('draw_strikethrough_the_document') }}
</p>
<p v-else>
{{ t('draw_field_on_the_document') }}
</p>
<div>
@@ -413,7 +416,7 @@
{{ t('cancel') }}
</button>
<a
v-if="!drawField && !drawOption && !['stamp', 'signature', 'initials', 'heading'].includes(drawField?.type || drawFieldType)"
v-if="!drawField && !drawOption && !['stamp', 'signature', 'initials', 'heading', 'strikethrough'].includes(drawField?.type || drawFieldType)"
href="#"
class="link block mt-3 text-sm"
@click.prevent="[addField(drawFieldType), drawField = null, drawOption = null, withSelectedFieldType ? '' : drawFieldType = '', showDrawField = false]"
@@ -1084,6 +1087,11 @@ export default {
}
}
if (field.type === 'strikethrough') {
field.readonly = true
field.default_value = true
}
if (type === 'signature' && [true, false].includes(this.withSignatureId)) {
field.preferences ||= {}
field.preferences.with_signature_id = this.withSignatureId
@@ -1357,6 +1365,9 @@ export default {
} else if (type === 'initials') {
area.w = pageMask.clientWidth / 10 / pageMask.clientWidth
area.h = (pageMask.clientWidth / 35 / pageMask.clientWidth)
} else if (type === 'strikethrough') {
area.w = pageMask.clientWidth / 5 / pageMask.clientWidth
area.h = (pageMask.clientWidth / 70 / pageMask.clientWidth)
} else {
area.w = pageMask.clientWidth / 5 / pageMask.clientWidth
area.h = (pageMask.clientWidth / 35 / pageMask.clientWidth)
@@ -1500,8 +1511,12 @@ export default {
field.default_value = '{{date}}'
}
if (['stamp', 'heading'].includes(field.type)) {
if (['stamp', 'heading', 'strikethrough'].includes(field.type)) {
field.readonly = true
if (field.type === 'strikethrough') {
field.default_value = true
}
}
if (field.type === 'date') {
@@ -1589,6 +1604,11 @@ export default {
w: area.maskW / 10 / area.maskW,
h: area.maskW / 35 / area.maskW
}
} else if (fieldType === 'strikethrough') {
baseArea = {
w: area.maskW / 5 / area.maskW,
h: area.maskW / 70 / area.maskW
}
} else {
baseArea = {
w: area.maskW / 5 / area.maskW,
@@ -177,10 +177,13 @@ export default {
}
},
computed: {
excludeTypes () {
return ['heading', 'strikethrough']
},
fields () {
if (this.item.submitter_uuid) {
return this.template.fields.reduce((acc, f) => {
if (f !== this.item && (!f.conditions?.length || !f.conditions.find((c) => c.field_uuid === this.item.uuid))) {
if (f !== this.item && !this.excludeTypes.includes(f.type) && (!f.conditions?.length || !f.conditions.find((c) => c.field_uuid === this.item.uuid))) {
acc.push(f)
}
+8 -4
View File
@@ -14,7 +14,7 @@
<div class="flex items-center p-1 space-x-1">
<FieldType
v-model="field.type"
:editable="editable && !defaultField && field.type != 'heading'"
:editable="editable && !defaultField"
:button-width="20"
:menu-classes="'mt-1.5'"
:menu-style="{ backgroundColor: dropdownBgColor }"
@@ -97,7 +97,6 @@
@click-formula="isShowFormulaModal = true"
/>
<span
v-else-if="field.type !== 'heading'"
class="dropdown dropdown-end field-settings-dropdown"
@mouseenter="renderDropdown = true"
@touchstart="renderDropdown = true"
@@ -421,7 +420,7 @@ export default {
} else {
const typeIndex = fields.filter((f) => f.type === field.type).indexOf(field)
if (field.type === 'heading') {
if (field.type === 'heading' || field.type === 'strikethrough') {
return `${this.fieldNames[field.type]} ${typeIndex + 1}`
} else {
return `${this.fieldLabels[field.type]} ${typeIndex + 1}`
@@ -485,10 +484,15 @@ export default {
this.field.options ||= [{ value: '', uuid: v4() }]
}
if (['heading'].includes(this.field.type)) {
if (this.field.type === 'heading') {
this.field.readonly = true
}
if (this.field.type === 'strikethrough') {
this.field.readonly = true
this.field.default_value = true
}
(this.field.areas || []).forEach((area) => {
if (this.field.type === 'cells') {
area.cell_w = area.w * 2 / Math.floor(area.w / area.h)
@@ -380,7 +380,7 @@
</label>
</li>
<li
v-if="withRequired && field.type !== 'phone' && field.type !== 'stamp' && field.type !== 'verification'"
v-if="withRequired && field.type !== 'phone' && field.type !== 'stamp' && field.type !== 'verification' && field.type !== 'strikethrough' && field.type !== 'heading'"
@click.stop
>
<label class="cursor-pointer py-1.5">
@@ -470,7 +470,7 @@
v-if="field.type != 'stamp'"
class="pb-0.5 mt-0.5"
>
<li v-if="['text', 'number', 'date', 'select'].includes(field.type)">
<li v-if="['text', 'number', 'date', 'select', 'heading'].includes(field.type)">
<label
class="label-text cursor-pointer text-center w-full flex items-center"
@click="$emit('click-font')"
@@ -484,7 +484,7 @@
</label>
</li>
<li
v-if="field.type != 'stamp'"
v-if="field.type != 'stamp' && field.type != 'heading' && field.type != 'strikethrough'"
>
<label
class="label-text cursor-pointer text-center w-full flex items-center"
@@ -499,7 +499,7 @@
</label>
</li>
<li
v-if="field.type != 'stamp'"
v-if="field.type != 'stamp' && field.type != 'heading'"
>
<label
class="label-text cursor-pointer text-center w-full flex items-center"
@@ -51,7 +51,7 @@
</template>
<script>
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconColumns3, IconPhoneCheck, IconLetterCaseUpper, IconCreditCard, IconRubberStamp, IconSquareNumber1, IconHeading, IconId, IconCalendarCheck } from '@tabler/icons-vue'
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconColumns3, IconPhoneCheck, IconLetterCaseUpper, IconCreditCard, IconRubberStamp, IconSquareNumber1, IconHeading, IconId, IconCalendarCheck, IconStrikethrough } from '@tabler/icons-vue'
export default {
name: 'FiledTypeDropdown',
@@ -97,6 +97,7 @@ export default {
fieldNames () {
return {
heading: this.t('heading'),
strikethrough: this.t('strikeout'),
text: this.t('text'),
signature: this.t('signature'),
initials: this.t('initials'),
@@ -139,6 +140,7 @@ export default {
fieldIcons () {
return {
heading: IconHeading,
strikethrough: IconStrikethrough,
text: IconTextSize,
signature: IconWritingSign,
initials: IconLetterCaseUpper,
@@ -158,6 +160,9 @@ export default {
verification: IconId
}
},
skipTypes () {
return ['heading', 'datenow', 'strikethrough']
},
fieldIconsSorted () {
if (this.fieldTypes.length) {
return this.fieldTypes.reduce((acc, type) => {
@@ -166,7 +171,7 @@ export default {
return acc
}, {})
} else {
return Object.fromEntries(Object.entries(this.fieldIcons).filter(([key]) => key !== 'heading' && key !== 'datenow'))
return Object.fromEntries(Object.entries(this.fieldIcons).filter(([key]) => !this.skipTypes.includes(key)))
}
}
},
+4 -1
View File
@@ -351,6 +351,9 @@ export default {
return acc
}, {})
},
skipTypes () {
return ['heading', 'datenow', 'strikethrough']
},
fieldIconsSorted () {
if (this.fieldTypes.length) {
return this.fieldTypes.reduce((acc, type) => {
@@ -359,7 +362,7 @@ export default {
return acc
}, {})
} else {
return Object.fromEntries(Object.entries(this.fieldIcons).filter(([key]) => key !== 'heading' && key !== 'datenow'))
return Object.fromEntries(Object.entries(this.fieldIcons).filter(([key]) => !this.skipTypes.includes(key)))
}
},
submitterFields () {
+12
View File
@@ -1,4 +1,6 @@
const en = {
strikeout: 'Strikeout',
draw_strikethrough_the_document: 'Draw strikethrough the document',
quantity: 'Quantity',
prefillable: 'Prefillable',
signature_id: 'Signature ID',
@@ -177,6 +179,8 @@ const en = {
}
const es = {
strikeout: 'Tachar',
draw_strikethrough_the_document: 'Dibujar una línea de tachado en el documento',
quantity: 'Cantidad',
prefillable: 'Rellenable',
signature_id: 'ID de Firma',
@@ -355,6 +359,8 @@ const es = {
}
const it = {
strikeout: 'Barrato',
draw_strikethrough_the_document: 'Disegna una linea barrata sul documento',
quantity: 'Quantità',
prefillable: 'Precompilabile',
signature_id: 'ID firma',
@@ -533,6 +539,8 @@ const it = {
}
const pt = {
strikeout: 'Tachado',
draw_strikethrough_the_document: 'Desenhe uma linha de tachado no documento',
quantity: 'Quantidade',
prefillable: 'Pré-preenchível',
signature_id: 'ID da Assinatura',
@@ -711,6 +719,8 @@ const pt = {
}
const fr = {
strikeout: 'Barrer',
draw_strikethrough_the_document: 'Tracer une ligne de suppression sur le document',
quantity: 'Quantité',
prefillable: 'Pré-remplissable',
signature_id: 'ID de signature',
@@ -889,6 +899,8 @@ const fr = {
}
const de = {
strikeout: 'Streichung',
draw_strikethrough_the_document: 'Ziehe eine Streichung auf das Dokument',
quantity: 'Menge',
prefillable: 'Vorausfüllbar',
signature_id: 'Signatur-ID',
+4
View File
@@ -24,6 +24,8 @@
:ref="setAreaRefs"
:area="item.area"
:input-mode="inputMode"
:page-width="width"
:page-height="height"
:field="item.field"
:editable="editable"
:with-field-placeholder="withFieldPlaceholder"
@@ -40,6 +42,8 @@
<FieldArea
v-if="newArea"
:is-draw="true"
:page-width="width"
:page-height="height"
:field="{ submitter_uuid: selectedSubmitter.uuid, type: drawField?.type || dragFieldPlaceholder?.type || defaultFieldType }"
:area="newArea"
/>