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.
		
		
		
		
		
			
		
			
				
					
					
						
							308 lines
						
					
					
						
							7.8 KiB
						
					
					
				
			
		
		
	
	
							308 lines
						
					
					
						
							7.8 KiB
						
					
					
				<template>
 | 
						|
  <div
 | 
						|
    class="relative select-none mb-4 before:border before:rounded before:top-0 before:bottom-0 before:left-0 before:right-0 before:absolute"
 | 
						|
    :class="{ 'cursor-crosshair': allowDraw, 'touch-none': !!drawField }"
 | 
						|
    style="container-type: size"
 | 
						|
    :style="{ aspectRatio: `${width} / ${height}`}"
 | 
						|
  >
 | 
						|
    <img
 | 
						|
      ref="image"
 | 
						|
      loading="lazy"
 | 
						|
      :src="image.url"
 | 
						|
      :width="width"
 | 
						|
      :height="height"
 | 
						|
      class="rounded"
 | 
						|
      @load="onImageLoad"
 | 
						|
    >
 | 
						|
    <div
 | 
						|
      class="top-0 bottom-0 left-0 right-0 absolute"
 | 
						|
      @pointerdown="onStartDraw"
 | 
						|
    >
 | 
						|
      <FieldArea
 | 
						|
        v-for="(item, i) in areas"
 | 
						|
        :key="i"
 | 
						|
        :ref="setAreaRefs"
 | 
						|
        :area="item.area"
 | 
						|
        :input-mode="inputMode"
 | 
						|
        :field="item.field"
 | 
						|
        :editable="editable"
 | 
						|
        :with-field-placeholder="withFieldPlaceholder"
 | 
						|
        :default-field="defaultFieldsIndex[item.field.name]"
 | 
						|
        :default-submitters="defaultSubmitters"
 | 
						|
        :max-page="totalPages - 1"
 | 
						|
        @start-resize="resizeDirection = $event"
 | 
						|
        @stop-resize="resizeDirection = null"
 | 
						|
        @remove="$emit('remove-area', item.area)"
 | 
						|
        @scroll-to="$emit('scroll-to', $event)"
 | 
						|
      />
 | 
						|
      <FieldArea
 | 
						|
        v-if="newArea"
 | 
						|
        :is-draw="true"
 | 
						|
        :field="{ submitter_uuid: selectedSubmitter.uuid, type: drawField?.type || dragFieldPlaceholder?.type || defaultFieldType }"
 | 
						|
        :area="newArea"
 | 
						|
      />
 | 
						|
    </div>
 | 
						|
    <div
 | 
						|
      v-show="resizeDirection || isDrag || showMask || (drawField && isMobile) || fieldsDragFieldRef.value"
 | 
						|
      id="mask"
 | 
						|
      ref="mask"
 | 
						|
      class="top-0 bottom-0 left-0 right-0 absolute"
 | 
						|
      :class="{ 'z-10': !isMobile, 'cursor-grab': isDrag, 'cursor-nwse-resize': drawField, [resizeDirectionClasses[resizeDirection]]: !!resizeDirectionClasses }"
 | 
						|
      @pointermove="onPointermove"
 | 
						|
      @pointerdown="onStartDraw"
 | 
						|
      @dragover.prevent="onDragover"
 | 
						|
      @dragenter="onDragenter"
 | 
						|
      @dragleave="newArea = null"
 | 
						|
      @drop="onDrop"
 | 
						|
      @pointerup="onPointerup"
 | 
						|
    />
 | 
						|
  </div>
 | 
						|
</template>
 | 
						|
 | 
						|
<script>
 | 
						|
import FieldArea from './area'
 | 
						|
 | 
						|
export default {
 | 
						|
  name: 'TemplatePage',
 | 
						|
  components: {
 | 
						|
    FieldArea
 | 
						|
  },
 | 
						|
  inject: ['fieldTypes', 'defaultDrawFieldType', 'fieldsDragFieldRef', 'assignDropAreaSize'],
 | 
						|
  props: {
 | 
						|
    image: {
 | 
						|
      type: Object,
 | 
						|
      required: true
 | 
						|
    },
 | 
						|
    dragFieldPlaceholder: {
 | 
						|
      type: Object,
 | 
						|
      required: false,
 | 
						|
      default: null
 | 
						|
    },
 | 
						|
    areas: {
 | 
						|
      type: Array,
 | 
						|
      required: false,
 | 
						|
      default: () => []
 | 
						|
    },
 | 
						|
    inputMode: {
 | 
						|
      type: Boolean,
 | 
						|
      required: false,
 | 
						|
      default: false
 | 
						|
    },
 | 
						|
    defaultFields: {
 | 
						|
      type: Array,
 | 
						|
      required: false,
 | 
						|
      default: () => []
 | 
						|
    },
 | 
						|
    withFieldPlaceholder: {
 | 
						|
      type: Boolean,
 | 
						|
      required: false,
 | 
						|
      default: false
 | 
						|
    },
 | 
						|
    totalPages: {
 | 
						|
      type: Number,
 | 
						|
      required: true
 | 
						|
    },
 | 
						|
    drawFieldType: {
 | 
						|
      type: String,
 | 
						|
      required: false,
 | 
						|
      default: ''
 | 
						|
    },
 | 
						|
    allowDraw: {
 | 
						|
      type: Boolean,
 | 
						|
      required: false,
 | 
						|
      default: true
 | 
						|
    },
 | 
						|
    selectedSubmitter: {
 | 
						|
      type: Object,
 | 
						|
      required: true
 | 
						|
    },
 | 
						|
    defaultSubmitters: {
 | 
						|
      type: Array,
 | 
						|
      required: false,
 | 
						|
      default: () => []
 | 
						|
    },
 | 
						|
    drawField: {
 | 
						|
      type: Object,
 | 
						|
      required: false,
 | 
						|
      default: null
 | 
						|
    },
 | 
						|
    editable: {
 | 
						|
      type: Boolean,
 | 
						|
      required: false,
 | 
						|
      default: true
 | 
						|
    },
 | 
						|
    isDrag: {
 | 
						|
      type: Boolean,
 | 
						|
      required: false,
 | 
						|
      default: false
 | 
						|
    },
 | 
						|
    number: {
 | 
						|
      type: Number,
 | 
						|
      required: true
 | 
						|
    }
 | 
						|
  },
 | 
						|
  emits: ['draw', 'drop-field', 'remove-area', 'scroll-to'],
 | 
						|
  data () {
 | 
						|
    return {
 | 
						|
      areaRefs: [],
 | 
						|
      showMask: false,
 | 
						|
      resizeDirection: null,
 | 
						|
      newArea: null
 | 
						|
    }
 | 
						|
  },
 | 
						|
  computed: {
 | 
						|
    defaultFieldsIndex () {
 | 
						|
      return this.defaultFields.reduce((acc, field) => {
 | 
						|
        acc[field.name] = field
 | 
						|
 | 
						|
        return acc
 | 
						|
      }, {})
 | 
						|
    },
 | 
						|
    defaultFieldType () {
 | 
						|
      if (this.drawFieldType) {
 | 
						|
        return this.drawFieldType
 | 
						|
      } else if (this.defaultDrawFieldType && this.defaultDrawFieldType !== 'text') {
 | 
						|
        return this.defaultDrawFieldType
 | 
						|
      } else if (this.fieldTypes.length !== 0 && !this.fieldTypes.includes('text')) {
 | 
						|
        return this.fieldTypes[0]
 | 
						|
      } else {
 | 
						|
        return 'text'
 | 
						|
      }
 | 
						|
    },
 | 
						|
    isMobile () {
 | 
						|
      const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)
 | 
						|
 | 
						|
      return isMobileSafariIos || /android|iphone|ipad/i.test(navigator.userAgent)
 | 
						|
    },
 | 
						|
    resizeDirectionClasses () {
 | 
						|
      return {
 | 
						|
        nwse: 'cursor-nwse-resize',
 | 
						|
        ew: 'cursor-ew-resize'
 | 
						|
      }
 | 
						|
    },
 | 
						|
    width () {
 | 
						|
      return this.image.metadata.width
 | 
						|
    },
 | 
						|
    height () {
 | 
						|
      return this.image.metadata.height
 | 
						|
    }
 | 
						|
  },
 | 
						|
  beforeUpdate () {
 | 
						|
    this.areaRefs = []
 | 
						|
  },
 | 
						|
  methods: {
 | 
						|
    onImageLoad (e) {
 | 
						|
      this.image.metadata.width = e.target.naturalWidth
 | 
						|
      this.image.metadata.height = e.target.naturalHeight
 | 
						|
    },
 | 
						|
    setAreaRefs (el) {
 | 
						|
      if (el) {
 | 
						|
        this.areaRefs.push(el)
 | 
						|
      }
 | 
						|
    },
 | 
						|
    onDragenter (e) {
 | 
						|
      this.newArea = {}
 | 
						|
 | 
						|
      this.assignDropAreaSize(this.newArea, this.dragFieldPlaceholder, {
 | 
						|
        maskW: this.$refs.mask.clientWidth,
 | 
						|
        maskH: this.$refs.mask.clientHeight
 | 
						|
      })
 | 
						|
 | 
						|
      this.newArea.x = (e.offsetX - 6) / this.$refs.mask.clientWidth
 | 
						|
      this.newArea.y = e.offsetY / this.$refs.mask.clientHeight - this.newArea.h / 2
 | 
						|
    },
 | 
						|
    onDragover (e) {
 | 
						|
      this.newArea.x = (e.offsetX - 6) / this.$refs.mask.clientWidth
 | 
						|
      this.newArea.y = e.offsetY / this.$refs.mask.clientHeight - this.newArea.h / 2
 | 
						|
    },
 | 
						|
    onDrop (e) {
 | 
						|
      this.newArea = null
 | 
						|
 | 
						|
      this.$emit('drop-field', {
 | 
						|
        x: e.offsetX,
 | 
						|
        y: e.offsetY,
 | 
						|
        maskW: this.$refs.mask.clientWidth,
 | 
						|
        maskH: this.$refs.mask.clientHeight,
 | 
						|
        page: this.number
 | 
						|
      })
 | 
						|
    },
 | 
						|
    onStartDraw (e) {
 | 
						|
      if (!this.allowDraw) {
 | 
						|
        return
 | 
						|
      }
 | 
						|
 | 
						|
      if (this.isMobile && !this.drawField) {
 | 
						|
        return
 | 
						|
      }
 | 
						|
 | 
						|
      if (!this.editable) {
 | 
						|
        return
 | 
						|
      }
 | 
						|
 | 
						|
      this.showMask = true
 | 
						|
 | 
						|
      this.$nextTick(() => {
 | 
						|
        this.newArea = {
 | 
						|
          initialX: e.offsetX / this.$refs.mask.clientWidth,
 | 
						|
          initialY: e.offsetY / this.$refs.mask.clientHeight,
 | 
						|
          x: e.offsetX / this.$refs.mask.clientWidth,
 | 
						|
          y: e.offsetY / this.$refs.mask.clientHeight,
 | 
						|
          w: 0,
 | 
						|
          h: 0
 | 
						|
        }
 | 
						|
      })
 | 
						|
    },
 | 
						|
    onPointermove (e) {
 | 
						|
      if (this.newArea) {
 | 
						|
        const dx = e.offsetX / this.$refs.mask.clientWidth - this.newArea.initialX
 | 
						|
        const dy = e.offsetY / this.$refs.mask.clientHeight - this.newArea.initialY
 | 
						|
 | 
						|
        if (dx > 0) {
 | 
						|
          this.newArea.x = this.newArea.initialX
 | 
						|
        } else {
 | 
						|
          this.newArea.x = e.offsetX / this.$refs.mask.clientWidth
 | 
						|
        }
 | 
						|
 | 
						|
        if (dy > 0) {
 | 
						|
          this.newArea.y = this.newArea.initialY
 | 
						|
        } else {
 | 
						|
          this.newArea.y = e.offsetY / this.$refs.mask.clientHeight
 | 
						|
        }
 | 
						|
 | 
						|
        if ((this.drawField?.type || this.drawFieldType) === 'cells') {
 | 
						|
          this.newArea.cell_w = this.newArea.h * (this.$refs.mask.clientHeight / this.$refs.mask.clientWidth)
 | 
						|
        }
 | 
						|
 | 
						|
        this.newArea.w = Math.abs(dx)
 | 
						|
        this.newArea.h = Math.abs(dy)
 | 
						|
      }
 | 
						|
    },
 | 
						|
    onPointerup (e) {
 | 
						|
      if (this.newArea) {
 | 
						|
        const area = {
 | 
						|
          x: this.newArea.x,
 | 
						|
          y: this.newArea.y,
 | 
						|
          w: this.newArea.w,
 | 
						|
          h: this.newArea.h,
 | 
						|
          page: this.number
 | 
						|
        }
 | 
						|
 | 
						|
        if ('cell_w' in this.newArea) {
 | 
						|
          area.cell_w = this.newArea.cell_w
 | 
						|
        }
 | 
						|
 | 
						|
        const dx = Math.abs(e.offsetX - this.$refs.mask.clientWidth * this.newArea.initialX)
 | 
						|
        const dy = Math.abs(e.offsetY - this.$refs.mask.clientHeight * this.newArea.initialY)
 | 
						|
 | 
						|
        const isTooSmall = dx < 8 && dy < 8
 | 
						|
 | 
						|
        this.$emit('draw', { area, isTooSmall })
 | 
						|
      }
 | 
						|
 | 
						|
      this.showMask = false
 | 
						|
      this.newArea = null
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
</script>
 |