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.
225 lines
6.1 KiB
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>
|