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.
docuseal/app/javascript/template_builder/documents_editor_page.vue

225 lines
6.1 KiB

<template>
<div class="relative">
<div
class="relative"
:style="boxStyle"
>
<img
:src="imageUrl"
:width="metadata.width"
:height="metadata.height"
class="rounded border pointer-events-none outline outline-1 -outline-offset-1 transition-[outline-color] duration-75"
:class="[
page.rotate % 180 ? 'absolute inset-0 m-auto w-full' : 'w-full',
selected ? 'outline-neutral-400' : 'outline-transparent'
]"
:style="imageStyle"
:loading="lazy ? 'lazy' : 'eager'"
>
<div
v-if="areas.length || page.redact.length"
class="absolute pointer-events-none"
:style="overlayStyle"
>
<div
v-for="(item, areaIndex) in areas"
:key="areaIndex"
class="absolute border rounded-sm opacity-70"
:class="[areaBorderColor(item.submitterIndex), areaBgColor(item.submitterIndex)]"
:style="{
left: `${item.area.x * 100}%`,
top: `${item.area.y * 100}%`,
width: `${item.area.w * 100}%`,
height: `${item.area.h * 100}%`
}"
/>
<div
v-for="(rect, rectIndex) in page.redact"
:key="`redact-${rectIndex}`"
class="absolute bg-black"
:style="{
left: `${rect.x * 100}%`,
top: `${rect.y * 100}%`,
width: `${rect.w * 100}%`,
height: `${rect.h * 100}%`
}"
/>
</div>
<div
v-if="withActions"
class="absolute top-1 right-1 flex space-x-1 group-hover:opacity-100"
:class="selected ? 'opacity-100' : 'opacity-0'"
>
<span
v-if="extraAction"
class="tooltip tooltip-bottom"
:data-tip="t(extraAction)"
>
<button
class="btn border-gray-300 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content transition-colors p-0"
style="width: 22px; height: 22px; min-height: 22px"
@click.stop.prevent="$emit(extraAction)"
>
<IconEraser
v-if="extraAction === 'redact'"
:width="14"
:height="14"
:stroke-width="1.6"
/>
<IconCrop
v-else
:width="20"
:height="20"
:stroke-width="1.2"
/>
</button>
</span>
<span
class="tooltip tooltip-bottom"
:data-tip="t('rotate')"
>
<button
class="btn border-gray-300 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content transition-colors p-0"
style="width: 22px; height: 22px; min-height: 22px"
@click.stop.prevent="$emit('rotate')"
>
<IconRotateClockwise
:width="14"
:height="14"
:stroke-width="1.6"
/>
</button>
</span>
<span
class="tooltip tooltip-bottom"
:data-tip="t('remove')"
>
<button
class="btn border-gray-300 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content transition-colors p-0"
style="width: 22px; height: 22px; min-height: 22px"
@click.stop.prevent="$emit('remove')"
>
<IconX
:width="14"
:height="14"
:stroke-width="1.6"
/>
</button>
</span>
</div>
</div>
<div
v-if="pageNumber"
class="text-center text-sm pt-1 pointer-events-none"
>
{{ t('page') }} {{ pageNumber }}
</div>
</div>
</template>
<script>
import { IconRotateClockwise, IconX, IconEraser, IconCrop } from '@tabler/icons-vue'
import Area from './area.vue'
export default {
name: 'DocumentsEditorPage',
components: {
IconRotateClockwise,
IconX,
IconEraser,
IconCrop
},
inject: ['t'],
props: {
page: {
type: Object,
required: true
},
imageUrl: {
type: String,
required: true
},
metadata: {
type: Object,
required: true
},
areas: {
type: Array,
required: false,
default: () => []
},
selected: {
type: Boolean,
required: false,
default: false
},
withActions: {
type: Boolean,
required: false,
default: false
},
pageNumber: {
type: Number,
required: false,
default: null
},
lazy: {
type: Boolean,
required: false,
default: true
},
extraAction: {
type: String,
required: false,
default: null
}
},
emits: ['rotate', 'remove', 'redact', 'crop'],
computed: {
borderColors: Area.computed.borderColors,
bgColors: Area.computed.bgColors,
boxStyle () {
if (!this.page.rotate || !(this.page.rotate % 180)) {
return null
}
return { aspectRatio: `${this.metadata.height} / ${this.metadata.width}` }
},
imageStyle () {
if (!this.page.rotate) {
return null
}
let transform = `rotate(${this.page.rotate}deg)`
if (this.page.rotate % 180) {
transform += ` scale(${this.metadata.width / this.metadata.height})`
}
return { transform }
},
overlayStyle () {
if (!this.page.rotate || !(this.page.rotate % 180)) {
return { inset: '0', transform: this.page.rotate ? `rotate(${this.page.rotate}deg)` : undefined }
}
return {
left: '50%',
top: '50%',
width: '100%',
aspectRatio: `${this.metadata.width} / ${this.metadata.height}`,
transform: `translate(-50%, -50%) rotate(${this.page.rotate}deg) scale(${this.metadata.width / this.metadata.height})`
}
}
},
methods: {
areaBorderColor (submitterIndex) {
return this.borderColors[Math.max(submitterIndex, 0) % this.borderColors.length]
},
areaBgColor (submitterIndex) {
return this.bgColors[Math.max(submitterIndex, 0) % this.bgColors.length]
}
}
}
</script>