style builder ui

pull/105/head
Alex Turchyn 2 years ago
parent c0caed53ce
commit d0c0da0939

@ -1,14 +1,35 @@
<template> <template>
<div <div
class="bg-red-100 absolute opacity-70" class="absolute overflow-visible group"
:style="positionStyle" :style="positionStyle"
@mousedown="startDrag" @pointerdown.stop
@mousedown.stop="startDrag"
> >
<div <div
v-if="field" v-if="field"
class="flex items-center justify-center h-full w-full" class="absolute bg-white rounded-t border overflow-visible whitespace-nowrap hidden group-hover:block group-hover:z-10"
style="top: -25px; height: 25px"
@mousedown.stop
@pointerdown.stop
> >
{{ field?.name || field.type }} <button
v-for="(component, type, index) in iconComponents"
:key="type"
class="px-0.5 hover:text-base-100 hover:bg-base-content transition-colors"
:class="{ 'bg-base-content text-base-100': field.type === type, 'rounded-tl': index === 0, 'rounded-tr': index === 4 }"
@click="changeTypeTo(type)"
>
<component
:is="component"
:width="20"
stroke-width="1.5"
/>
</button>
</div>
<div
class="bg-red-100 opacity-70 flex items-center justify-center h-full w-full"
>
{{ field?.name || field?.type }}
</div> </div>
<span <span
class="h-2 w-2 right-0 bottom-0 bg-red-900 absolute cursor-nwse-resize" class="h-2 w-2 right-0 bottom-0 bg-red-900 absolute cursor-nwse-resize"
@ -18,6 +39,8 @@
</template> </template>
<script> <script>
import { IconTextSize, IconWriting, IconCalendarEvent, IconPhoto, IconCheckbox } from '@tabler/icons-vue'
export default { export default {
name: 'FieldArea', name: 'FieldArea',
props: { props: {
@ -47,6 +70,15 @@ export default {
} }
}, },
computed: { computed: {
iconComponents () {
return {
text: IconTextSize,
signature: IconWriting,
date: IconCalendarEvent,
image: IconPhoto,
checkbox: IconCheckbox
}
},
positionStyle () { positionStyle () {
const { x, y, w, h } = this.bounds const { x, y, w, h } = this.bounds
@ -59,9 +91,14 @@ export default {
} }
}, },
methods: { methods: {
changeTypeTo (type) {
this.field.type = type
},
resize (e) { resize (e) {
this.bounds.w = e.layerX / e.toElement.clientWidth - this.bounds.x if (e.toElement.id === 'mask') {
this.bounds.h = e.layerY / e.toElement.clientHeight - this.bounds.y this.bounds.w = e.layerX / e.toElement.clientWidth - this.bounds.x
this.bounds.h = e.layerY / e.toElement.clientHeight - this.bounds.y
}
}, },
drag (e) { drag (e) {
if (e.toElement.id === 'mask') { if (e.toElement.id === 'mask') {

@ -1,9 +1,9 @@
<template> <template>
<div <div
style="max-width: 1600px" style="max-width: 1600px"
class="mx-auto px-4" class="mx-auto pl-4"
> >
<div class="flex justify-between py-1.5 items-center"> <div class="flex justify-between py-1.5 items-center pr-4">
<div class="flex space-x-3"> <div class="flex space-x-3">
<a href="/"> <a href="/">
<Logo /> <Logo />
@ -42,7 +42,7 @@
> >
<div <div
ref="previews" ref="previews"
class="overflow-auto w-52 flex-none pr-4 mt-0.5 pt-0.5" class="overflow-auto w-52 flex-none pr-3 mt-0.5 pt-0.5"
> >
<DocumentPreview <DocumentPreview
v-for="(item, index) in template.schema" v-for="(item, index) in template.schema"
@ -64,7 +64,7 @@
</div> </div>
</div> </div>
<div class="w-full overflow-y-auto overflow-x-hidden mt-0.5 pt-0.5"> <div class="w-full overflow-y-auto overflow-x-hidden mt-0.5 pt-0.5">
<div class="px-3"> <div class="pr-3.5 pl-0.5">
<Document <Document
v-for="document in sortedDocuments" v-for="document in sortedDocuments"
:key="document.uuid" :key="document.uuid"
@ -79,12 +79,12 @@
</div> </div>
</div> </div>
<div <div
class="relative w-72 flex-none" class="relative w-80 flex-none pt-0.5 pr-4"
:class="drawField ? 'overflow-hidden' : 'overflow-auto'" :class="drawField ? 'overflow-hidden' : 'overflow-auto'"
> >
<div <div
v-if="drawField" v-if="drawField"
class="sticky inset-0 bg-white h-full" class="sticky inset-0 bg-base-100 h-full"
> >
Draw {{ drawField.name }} field on the page Draw {{ drawField.name }} field on the page
<button @click="drawField = false"> <button @click="drawField = false">
@ -92,13 +92,13 @@
</button> </button>
</div> </div>
<div> <div>
FIelds
<Fields <Fields
ref="fields" ref="fields"
v-model:fields="template.fields" v-model:fields="template.fields"
@set-draw="drawField = $event" @set-draw="drawField = $event"
@set-drag="dragFieldType = $event" @set-drag="dragFieldType = $event"
@drag-end="dragFieldType = null" @drag-end="dragFieldType = null"
@scroll-to-area="scrollToArea"
/> />
</div> </div>
</div> </div>
@ -114,6 +114,7 @@ import Logo from './logo'
import Contenteditable from './contenteditable' import Contenteditable from './contenteditable'
import DocumentPreview from './preview' import DocumentPreview from './preview'
import { IconUsersPlus, IconDeviceFloppy } from '@tabler/icons-vue' import { IconUsersPlus, IconDeviceFloppy } from '@tabler/icons-vue'
import { v4 } from 'uuid'
export default { export default {
name: 'TemplateBuilder', name: 'TemplateBuilder',
@ -189,10 +190,22 @@ export default {
} }
}, },
onDraw (area) { onDraw (area) {
this.drawField.areas ||= [] if (this.drawField) {
this.drawField.areas.push(area) this.drawField.areas ||= []
this.drawField.areas.push(area)
this.drawField = null this.drawField = null
} else {
const field = {
name: '',
uuid: v4(),
required: true,
type: 'text',
areas: [area]
}
this.template.fields.push(field)
}
}, },
onDropfield (area) { onDropfield (area) {
this.$refs.fields.addField(this.dragFieldType, area) this.$refs.fields.addField(this.dragFieldType, area)
@ -245,6 +258,11 @@ export default {
this.isSaving = false this.isSaving = false
}) })
}, },
scrollToArea (area) {
const documentRef = this.documentRefs.find((a) => a.document.uuid === area.attachment_uuid)
documentRef.scrollToArea(area)
},
save () { save () {
return fetch(`/api/templates/${this.template.id}`, { return fetch(`/api/templates/${this.template.id}`, {
method: 'PUT', method: 'PUT',

@ -1,19 +1,25 @@
<template> <template>
<div class="group flex items-center relative overflow-visible"> <div
<div class="group relative overflow-visible"
:class="{ 'flex items-center': !iconInline }"
>
<span
ref="contenteditable" ref="contenteditable"
contenteditable contenteditable
style="min-width: 2px" style="min-width: 2px"
class="peer outline-none" :class="iconInline ? 'inline' : 'block'"
class="peer outline-none focus:block"
@keydown.enter.prevent="onEnter" @keydown.enter.prevent="onEnter"
@focus="$emit('focus', $event)"
@blur="onBlur" @blur="onBlur"
> >
{{ value }} {{ value }}
</div> </span>
<IconPencil <IconPencil
contenteditable="false" contenteditable="false"
class="absolute ml-1 cursor-pointer inline opacity-0 group-hover:opacity-100 peer-focus:opacity-0 align-middle" class="cursor-pointer ml-1 flex-none opacity-0 group-hover:opacity-100 align-middle peer-focus:hidden"
:style="{ right: -(1.1 * iconWidth) + 'px' }" :style="iconInline ? {} : { right: -(1.1 * iconWidth) + 'px' }"
:class="{ 'absolute': !iconInline, 'inline align-bottom': iconInline }"
:width="iconWidth" :width="iconWidth"
@click="onPencilClick" @click="onPencilClick"
/> />
@ -34,13 +40,18 @@ export default {
required: false, required: false,
default: '' default: ''
}, },
iconInline: {
type: Boolean,
required: false,
default: false
},
iconWidth: { iconWidth: {
type: Number, type: Number,
required: false, required: false,
default: 30 default: 30
} }
}, },
emits: ['update:model-value'], emits: ['update:model-value', 'focus', 'blur'],
data () { data () {
return { return {
value: '' value: ''
@ -59,6 +70,7 @@ export default {
this.value = this.$refs.contenteditable.innerText.trim() || this.modelValue this.value = this.$refs.contenteditable.innerText.trim() || this.modelValue
this.$emit('update:model-value', this.value) this.$emit('update:model-value', this.value)
this.$emit('blur', e)
}, },
onPencilClick () { onPencilClick () {
this.$refs.contenteditable.focus() this.$refs.contenteditable.focus()

@ -3,11 +3,11 @@
<Page <Page
v-for="(image, index) in sortedPreviewImages" v-for="(image, index) in sortedPreviewImages"
:key="image.id" :key="image.id"
:ref="setPageRefs"
:number="index" :number="index"
:areas="areasIndex[index]" :areas="areasIndex[index]"
:is-draw="isDraw" :is-draw="isDraw"
:is-drag="isDrag" :is-drag="isDrag"
:class="{ 'cursor-crosshair': isDraw }"
:image="image" :image="image"
@drop-field="$emit('drop-field', {...$event, attachment_uuid: document.uuid })" @drop-field="$emit('drop-field', {...$event, attachment_uuid: document.uuid })"
@draw="$emit('draw', {...$event, attachment_uuid: document.uuid })" @draw="$emit('draw', {...$event, attachment_uuid: document.uuid })"
@ -44,10 +44,28 @@ export default {
} }
}, },
emits: ['draw', 'drop-field'], emits: ['draw', 'drop-field'],
data () {
return {
pageRefs: []
}
},
computed: { computed: {
sortedPreviewImages () { sortedPreviewImages () {
return [...this.document.preview_images].sort((a, b) => parseInt(a.filename) - parseInt(b.filename)) return [...this.document.preview_images].sort((a, b) => parseInt(a.filename) - parseInt(b.filename))
} }
},
beforeUpdate () {
this.pageRefs = []
},
methods: {
scrollToArea (area) {
this.pageRefs[area.page].areaRefs.find((e) => e.bounds === area).$el.scrollIntoView({ behavior: 'smooth', block: 'center' })
},
setPageRefs (el) {
if (el) {
this.pageRefs.push(el)
}
}
} }
} }
</script> </script>

@ -1,86 +1,243 @@
<template> <template>
<div> <div
<button @click="$emit('remove', field)"> class="group pb-2"
Remove @mouseleave="closeDropdown"
</button> >
<div> <div
{{ field.type }} class="border border-base-content rounded rounded-tr-none relative group"
</div> >
<div v-if="field.type !== 'signature'"> <div class="flex items-center justify-between space-x-1">
<label>Name</label> <div class="flex items-center p-1 space-x-1">
<input <span class="dropdown">
v-model="field.name" <label
type="text" tabindex="0"
required title="Type"
> class="cursor-pointer"
</div> >
<div> <component
<div :is="fieldIcons[field.type]"
v-for="(option, index) in field.options" width="18"
:key="index" :stroke-width="1.6"
class="flex" />
> </label>
<input <ul
v-model="field.options[index]" tabindex="0"
type="text" class="mt-1.5 dropdown-content menu menu-xs p-2 shadow bg-base-100 rounded-box w-52"
required @click="closeDropdown"
> >
<button @click="field.options.splice(index, 1)"> <li
Remove v-for="(name, type) in fieldNames"
</button> :key="type"
>
<a
href="#"
class="text-sm py-1 px-2"
:class="{ 'active': type === field.type }"
@click.prevent="field.type = type"
>
<component
:is="fieldIcons[type]"
:stroke-width="1.6"
:width="20"
/>
{{ name }}
</a>
</li>
</ul>
</span>
<Contenteditable
ref="name"
:model-value="field.name || defaultName"
:icon-inline="true"
:icon-width="19"
@focus="onNameFocus"
@blur="onNameBlur"
/>
</div>
<div class="flex items-center space-x-1 opacity-0 group-hover:opacity-100">
<span class="dropdown dropdown-end">
<label
tabindex="0"
title="Areas"
class="cursor-pointer"
>
<IconShape
:width="20"
:stroke-width="1.6"
/>
</label>
<ul
tabindex="0"
class="mt-1.5 dropdown-content menu menu-xs p-2 shadow bg-base-100 rounded-box w-52"
@click="closeDropdown"
>
<li
v-for="(area, index) in field.areas || []"
:key="index"
>
<a
href="#"
class="text-sm py-1 px-2"
@click.prevent="$emit('scroll-to', area)"
>
<IconShape
:width="20"
:stroke-width="1.6"
/>
Page {{ area.page + 1 }}
</a>
</li>
<li>
<a
href="#"
class="text-sm py-1 px-2"
@click.prevent="$emit('set-draw', field)"
>
<IconNewSection
:width="20"
:stroke-width="1.6"
/>
Draw New Area
</a>
</li>
</ul>
</span>
<button @click="$emit('remove', field)">
<IconTrashX
:width="20"
:stroke-width="1.6"
/>
</button>
<div class="flex flex-col pr-1">
<button
title="Up"
style="font-size: 10px; margin-bottom: -2px"
@click="$emit('move-up')"
>
</button>
<button
title="Down"
style="font-size: 10px; margin-top: -2px"
@click="$emit('move-down')"
>
</button>
</div>
</div>
</div> </div>
<button
v-if="field.options"
@click="field.options.push('')"
>
Add option
</button>
</div>
<div>
<div <div
v-for="(area, index) in areas" v-if="field.options"
:key="index" class="border-t border-base-300 mx-2 pt-2 space-y-1.5"
> >
Area {{ index + 1 }} <div
<button @click="removeArea(area)"> v-for="(option, index) in field.options"
&times; :key="index"
class="flex space-x-1.5 items-center"
>
<span class="text-sm">
{{ index + 1 }}.
</span>
<input
v-model="field.options[index]"
class="w-full input input-primary input-xs text-sm"
type="text"
required
>
<button
class="text-sm"
@click="field.options.splice(index, 1)"
>
&times;
</button>
</div>
<button
v-if="field.options"
class="text-center text-sm w-full pb-1"
@click="field.options.push('')"
>
+ Add option
</button> </button>
</div> </div>
<button
class="block"
@click="$emit('set-draw', field)"
>
Draw area
</button>
</div>
<div>
<input
:id="`field_required_${field.uuid}`"
v-model="field.required"
type="checkbox"
required
>
<label :for="`field_required_${field.uuid}`">Required</label>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import Contenteditable from './contenteditable'
import { IconTextSize, IconWriting, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconShape, IconNewSection, IconTrashX } from '@tabler/icons-vue'
export default { export default {
name: 'TemplateField', name: 'TemplateField',
components: {
Contenteditable,
IconShape,
IconNewSection,
IconTrashX
},
props: { props: {
field: { field: {
type: Object, type: Object,
required: true required: true
},
typeIndex: {
type: Number,
required: false,
default: 0
} }
}, },
emits: ['set-draw', 'remove'], emits: ['set-draw', 'remove', 'move-up', 'move-down', 'scroll-to'],
computed: { computed: {
defaultName () {
return `${this.fieldNames[this.field.type]} Field ${this.typeIndex + 1}`
},
areas () { areas () {
return this.field.areas || [] return this.field.areas || []
},
fieldNames () {
return {
text: 'Text',
signature: 'Signature',
date: 'Date',
image: 'Image',
attachment: 'File',
select: 'Select',
checkbox: 'Checkbox',
radio: 'Radio'
}
},
fieldIcons () {
return {
text: IconTextSize,
signature: IconWriting,
date: IconCalendarEvent,
image: IconPhoto,
attachment: IconPaperclip,
select: IconSelect,
checkbox: IconCheckbox,
radio: IconCircleDot
}
} }
}, },
methods: { methods: {
onNameFocus (e) {
if (!this.field.name) {
setTimeout(() => {
this.$refs.name.$refs.contenteditable.innerText = ' '
}, 1)
}
},
closeDropdown () {
document.activeElement.blur()
},
onNameBlur (e) {
if (e.target.innerText.trim()) {
this.field.name = e.target.innerText.trim()
} else {
this.field.name = ''
this.$refs.name.$refs.contenteditable.innerText = this.defaultName
}
},
removeArea (area) { removeArea (area) {
this.field.areas.splice(this.field.areas.indexOf(area), 1) this.field.areas.splice(this.field.areas.indexOf(area), 1)
} }

@ -1,52 +1,67 @@
<template> <template>
<div class="space-y-2"> <div class="mb-2">
<Field <Field
v-for="field in fields" v-for="field in fields"
:key="field.uuid" :key="field.uuid"
class="border"
:field="field" :field="field"
:type-index="fields.filter((f) => f.type === field.type).indexOf(field)"
@remove="fields.splice(fields.indexOf($event), 1)" @remove="fields.splice(fields.indexOf($event), 1)"
@move-up="move(field, -1)"
@move-down="move(field, 1)"
@scroll-to="$emit('scroll-to-area', $event)"
@set-draw="$emit('set-draw', $event)" @set-draw="$emit('set-draw', $event)"
/> />
</div> </div>
<button <div class="grid grid-cols-3 gap-1">
v-for="item in fieldTypes" <button
:key="item.type" v-for="item in fieldTypes"
draggable="true" :key="item.type"
class="w-full flex items-center justify-center" draggable="true"
@dragstart="onDragstart(item.value)" class="flex items-center justify-center border border-dashed border-gray-300 bg-base-100 w-full rounded relative"
@dragend="$emit('drag-end')" @dragstart="onDragstart(item.value)"
@click="addField(item.value)" @dragend="$emit('drag-end')"
> @click="addField(item.value)"
<svg
xmlns="http://www.w3.org/2000/svg"
class="cursor-move"
width="18"
height="18"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
> >
<path <div class="w-0 absolute left-0">
stroke="none" <svg
d="M0 0h24v24H0z" xmlns="http://www.w3.org/2000/svg"
fill="none" class="cursor-grab"
/> width="18"
<path d="M4 6l16 0" /> height="18"
<path d="M4 12l16 0" /> viewBox="0 0 24 24"
<path d="M4 18l16 0" /> stroke-width="1.5"
</svg> stroke="currentColor"
Add {{ item.label }} fill="none"
&plus; stroke-linecap="round"
</button> stroke-linejoin="round"
>
<path
stroke="none"
d="M0 0h24v24H0z"
fill="none"
/>
<path d="M9 5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />
<path d="M9 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />
<path d="M9 19m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />
<path d="M15 5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />
<path d="M15 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />
<path d="M15 19m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />
</svg>
</div>
<div class="flex items-center flex-col px-2 py-2">
<component :is="item.icon" />
<span class="text-xs mt-1">
{{ item.label }}
</span>
</div>
</button>
</div>
</template> </template>
<script> <script>
import Field from './field' import Field from './field'
import { v4 } from 'uuid' import { v4 } from 'uuid'
import { IconTextSize, IconWriting, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot } from '@tabler/icons-vue'
export default { export default {
name: 'TemplateFields', name: 'TemplateFields',
@ -59,18 +74,18 @@ export default {
required: true required: true
} }
}, },
emits: ['set-draw', 'set-drag', 'drag-end'], emits: ['set-draw', 'set-drag', 'drag-end', 'scroll-to-area'],
computed: { computed: {
fieldTypes () { fieldTypes () {
return [ return [
{ label: 'Text', value: 'text' }, { label: 'Text', value: 'text', icon: IconTextSize },
{ label: 'Signature', value: 'signature' }, { label: 'Signature', value: 'signature', icon: IconWriting },
{ label: 'Date', value: 'date' }, { label: 'Date', value: 'date', icon: IconCalendarEvent },
{ label: 'Image', value: 'image' }, { label: 'Image', value: 'image', icon: IconPhoto },
{ label: 'Attachment', value: 'attachment' }, { label: 'File', value: 'attachment', icon: IconPaperclip },
{ label: 'Select', value: 'select' }, { label: 'Select', value: 'select', icon: IconSelect },
{ label: 'Checkbox', value: 'checkbox' }, { label: 'Checkbox', value: 'checkbox', icon: IconCheckbox },
{ label: 'Radio Group', value: 'radio' } { label: 'Radio', value: 'radio', icon: IconCircleDot }
] ]
} }
}, },
@ -78,6 +93,19 @@ export default {
onDragstart (fieldType) { onDragstart (fieldType) {
this.$emit('set-drag', fieldType) this.$emit('set-drag', fieldType)
}, },
move (field, direction) {
const currentIndex = this.fields.indexOf(field)
this.fields.splice(currentIndex, 1)
if (currentIndex + direction > this.fields.length) {
this.fields.unshift(field)
} else if (currentIndex + direction < 0) {
this.fields.push(field)
} else {
this.fields.splice(currentIndex + direction, 0, field)
}
},
addField (type, area = null) { addField (type, area = null) {
const field = { const field = {
name: type === 'signature' ? 'Signature' : '', name: type === 'signature' ? 'Signature' : '',

@ -1,5 +1,5 @@
<template> <template>
<div class="relative"> <div class="relative cursor-crosshair">
<img <img
ref="image" ref="image"
:src="image.url" :src="image.url"
@ -10,10 +10,12 @@
> >
<div <div
class="top-0 bottom-0 left-0 right-0 absolute" class="top-0 bottom-0 left-0 right-0 absolute"
@pointerdown="onStartDraw"
> >
<FieldArea <FieldArea
v-for="(item, i) in areas" v-for="(item, i) in areas"
:key="i" :key="i"
:ref="setAreaRefs"
:bounds="item.area" :bounds="item.area"
:field="item.field" :field="item.field"
@start-resize="showMask = true" @start-resize="showMask = true"
@ -27,11 +29,10 @@
/> />
</div> </div>
<div <div
v-show="isDraw || isDrag || showMask" v-show="isDrag || showMask"
id="mask" id="mask"
ref="mask" ref="mask"
class="top-0 bottom-0 left-0 right-0 absolute" class="top-0 bottom-0 left-0 right-0 absolute"
@pointerdown="onPointerdown"
@pointermove="onPointermove" @pointermove="onPointermove"
@dragover.prevent @dragover.prevent
@drop="onDrop" @drop="onDrop"
@ -58,11 +59,6 @@ export default {
required: false, required: false,
default: () => [] default: () => []
}, },
isDraw: {
type: Boolean,
required: false,
default: false
},
isDrag: { isDrag: {
type: Boolean, type: Boolean,
required: false, required: false,
@ -76,6 +72,7 @@ export default {
emits: ['draw', 'drop-field'], emits: ['draw', 'drop-field'],
data () { data () {
return { return {
areaRefs: [],
showMask: false, showMask: false,
newArea: null newArea: null
} }
@ -88,7 +85,15 @@ export default {
return this.image.metadata.height return this.image.metadata.height
} }
}, },
beforeUpdate () {
this.areaRefs = []
},
methods: { methods: {
setAreaRefs (el) {
if (el) {
this.areaRefs.push(el)
}
},
onDrop (e) { onDrop (e) {
this.$emit('drop-field', { this.$emit('drop-field', {
x: e.layerX / this.$refs.mask.clientWidth, x: e.layerX / this.$refs.mask.clientWidth,
@ -98,8 +103,10 @@ export default {
page: this.number page: this.number
}) })
}, },
onPointerdown (e) { onStartDraw (e) {
if (this.isDraw) { this.showMask = true
this.$nextTick(() => {
this.newArea = { this.newArea = {
initialX: e.layerX / this.$refs.mask.clientWidth, initialX: e.layerX / this.$refs.mask.clientWidth,
initialY: e.layerY / this.$refs.mask.clientHeight, initialY: e.layerY / this.$refs.mask.clientHeight,
@ -108,7 +115,7 @@ export default {
w: 0, w: 0,
h: 0 h: 0
} }
} })
}, },
onPointermove (e) { onPointermove (e) {
if (this.newArea) { if (this.newArea) {
@ -132,16 +139,17 @@ export default {
} }
}, },
onPointerup (e) { onPointerup (e) {
if (this.isDraw && this.newArea) { if (this.newArea) {
this.$emit('draw', { this.$emit('draw', {
x: this.newArea.x, x: this.newArea.x,
y: this.newArea.y, y: this.newArea.y,
w: Math.max(this.newArea.w, this.$refs.mask.clientWidth / 5 / this.$refs.mask.clientWidth), w: this.newArea.w,
h: Math.max(this.newArea.h, this.$refs.mask.clientWidth / 30 / this.$refs.mask.clientWidth), h: this.newArea.h,
page: this.number page: this.number
}) })
} }
this.showMask = false
this.newArea = null this.newArea = null
} }
} }

@ -46,10 +46,11 @@
</div> </div>
</div> </div>
</div> </div>
<div class="flex py-2"> <div class="flex pb-2 pt-1.5">
<Contenteditable <Contenteditable
:model-value="item.name" :model-value="item.name"
:icon-width="16" :icon-width="16"
style="max-width: 95%"
class="mx-auto" class="mx-auto"
@update:model-value="onUpdateName" @update:model-value="onUpdateName"
/> />

@ -15,7 +15,7 @@
<turbo-frame id="modal"></turbo-frame> <turbo-frame id="modal"></turbo-frame>
<%= render 'shared/navbar' %> <%= render 'shared/navbar' %>
<% if flash.present? %> <% if flash.present? %>
<div id="flash" class="absolute top-0 w-full"> <div id="flash" class="absolute top-0 w-full h-0">
<div class="max-w-xl mx-auto mt-1.5"> <div class="max-w-xl mx-auto mt-1.5">
<div class="alert py-3"> <div class="alert py-3">
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">

@ -5599,9 +5599,9 @@ yaml@^1.10.0:
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
yaml@^2.1.1: yaml@^2.1.1:
version "2.2.2" version "2.3.1"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.2.2.tgz#ec551ef37326e6d42872dad1970300f8eb83a073" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b"
integrity sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA== integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==
yocto-queue@^0.1.0: yocto-queue@^0.1.0:
version "0.1.0" version "0.1.0"

Loading…
Cancel
Save