add template submitters
This commit is contained in:
@@ -15,7 +15,8 @@ module Api
|
|||||||
def template_params
|
def template_params
|
||||||
params.require(:template).permit(:name,
|
params.require(:template).permit(:name,
|
||||||
schema: [%i[attachment_uuid name]],
|
schema: [%i[attachment_uuid name]],
|
||||||
fields: [[:uuid, :name, :type, :required,
|
submitters: [%i[name uuid]],
|
||||||
|
fields: [[:uuid, :submitter_uuid, :name, :type, :required,
|
||||||
{ options: [], areas: [%i[x y w h attachment_uuid page]] }]])
|
{ options: [], areas: [%i[x y w h attachment_uuid page]] }]])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -48,6 +48,6 @@ window.customElements.define('template-builder', class extends HTMLElement {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
document.addEventListener('clipboard-copy', function(event) {
|
document.addEventListener('clipboard-copy', function (event) {
|
||||||
console.log('Copied to clipboard') // TODO: Add a toast message
|
console.log('Copied to clipboard') // TODO: Add a toast message
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,44 +6,32 @@
|
|||||||
@mousedown.stop="startDrag"
|
@mousedown.stop="startDrag"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="field"
|
v-if="isSelected"
|
||||||
|
class="top-0 bottom-0 right-0 left-0 absolute border border-1.5 pointer-events-none"
|
||||||
|
:class="borderColors[submitterIndex]"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="field?.type"
|
||||||
class="absolute bg-white rounded-t border overflow-visible whitespace-nowrap group-hover:flex group-hover:z-10"
|
class="absolute bg-white rounded-t border overflow-visible whitespace-nowrap group-hover:flex group-hover:z-10"
|
||||||
:class="{ flex: isNameFocus || isSelected, hidden: !isNameFocus && !isSelected }"
|
:class="{ 'flex z-10': isNameFocus || isSelected, hidden: !isNameFocus && !isSelected }"
|
||||||
style="top: -25px; height: 25px"
|
style="top: -25px; height: 25px"
|
||||||
@mousedown.stop
|
@mousedown.stop
|
||||||
@pointerdown.stop
|
@pointerdown.stop
|
||||||
>
|
>
|
||||||
<span class="dropdown dropdown-start border-r">
|
<FieldSubmitter
|
||||||
<label
|
v-model="field.submitter_uuid"
|
||||||
tabindex="0"
|
class="border-r"
|
||||||
title="Submitter"
|
:compact="true"
|
||||||
class="cursor-pointer text-base-100"
|
:menu-classes="'dropdown-content bg-white menu menu-xs p-2 shadow rounded-box w-52 rounded-t-none -left-[1px]'"
|
||||||
@click="selectedAreaRef.value = area"
|
:submitters="template.submitters"
|
||||||
>
|
@click="selectedAreaRef.value = area"
|
||||||
<button class="mx-1 w-3 h-3 rounded-full bg-yellow-600" />
|
/>
|
||||||
</label>
|
|
||||||
<ul
|
|
||||||
tabindex="0"
|
|
||||||
class="dropdown-content bg-white menu menu-xs p-2 shadow rounded-box w-52 rounded-t-none"
|
|
||||||
style="left: -1px"
|
|
||||||
@click="closeDropdown"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
class="text-sm py-1 px-2"
|
|
||||||
@click.prevent
|
|
||||||
>
|
|
||||||
Submitter 1
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</span>
|
|
||||||
<FieldType
|
<FieldType
|
||||||
v-model="field.type"
|
v-model="field.type"
|
||||||
:button-width="27"
|
:button-width="27"
|
||||||
:button-classes="'px-1'"
|
:button-classes="'px-1'"
|
||||||
:menu-classes="'bg-white rounded-t-none'"
|
:menu-classes="'bg-white rounded-t-none'"
|
||||||
|
@update:model-value="maybeDeleteOptions"
|
||||||
@click="selectedAreaRef.value = area"
|
@click="selectedAreaRef.value = area"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
@@ -64,7 +52,8 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="bg-red-100 opacity-50 flex items-center justify-center h-full w-full"
|
class="opacity-50 flex items-center justify-center h-full w-full"
|
||||||
|
:class="bgColors[submitterIndex]"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-if="field"
|
v-if="field"
|
||||||
@@ -74,23 +63,25 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="absolute top-0 bottom-0 right-0 left-0"
|
class="absolute top-0 bottom-0 right-0 left-0 cursor-pointer"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
class="h-2 w-2 -right-1 rounded-full -bottom-1 bg-red-900 absolute cursor-nwse-resize"
|
class="h-2.5 w-2.5 -right-1.5 rounded-full -bottom-1.5 bg-white shadow border absolute cursor-nwse-resize"
|
||||||
@mousedown.stop="startResize"
|
@mousedown.stop="startResize"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import FieldSubmitter from './field_submitter'
|
||||||
import FieldType from './field_type'
|
import FieldType from './field_type'
|
||||||
import Field from './field'
|
import Field from './field'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FieldArea',
|
name: 'FieldArea',
|
||||||
components: {
|
components: {
|
||||||
FieldType
|
FieldType,
|
||||||
|
FieldSubmitter
|
||||||
},
|
},
|
||||||
inject: ['template', 'selectedAreaRef'],
|
inject: ['template', 'selectedAreaRef'],
|
||||||
props: {
|
props: {
|
||||||
@@ -115,6 +106,30 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
defaultName: Field.computed.defaultName,
|
defaultName: Field.computed.defaultName,
|
||||||
fieldIcons: FieldType.computed.fieldIcons,
|
fieldIcons: FieldType.computed.fieldIcons,
|
||||||
|
submitter () {
|
||||||
|
return this.template.submitters.find((s) => s.uuid === this.field.submitter_uuid)
|
||||||
|
},
|
||||||
|
submitterIndex () {
|
||||||
|
return this.template.submitters.indexOf(this.submitter)
|
||||||
|
},
|
||||||
|
borderColors () {
|
||||||
|
return [
|
||||||
|
'border-red-500',
|
||||||
|
'border-sky-500',
|
||||||
|
'border-emerald-500',
|
||||||
|
'border-yellow-300',
|
||||||
|
'border-purple-600'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
bgColors () {
|
||||||
|
return [
|
||||||
|
'bg-red-100',
|
||||||
|
'bg-sky-100',
|
||||||
|
'bg-emerald-100',
|
||||||
|
'bg-yellow-100',
|
||||||
|
'bg-purple-100'
|
||||||
|
]
|
||||||
|
},
|
||||||
withName () {
|
withName () {
|
||||||
return !['checkbox', 'radio'].includes(this.field.type) || this.field.options
|
return !['checkbox', 'radio'].includes(this.field.type) || this.field.options
|
||||||
},
|
},
|
||||||
@@ -145,6 +160,11 @@ export default {
|
|||||||
}, 1)
|
}, 1)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
maybeDeleteOptions () {
|
||||||
|
if (!['radio', 'select', 'checkbox'].includes(this.field.type)) {
|
||||||
|
delete this.field.options
|
||||||
|
}
|
||||||
|
},
|
||||||
onNameBlur (e) {
|
onNameBlur (e) {
|
||||||
this.isNameFocus = false
|
this.isNameFocus = false
|
||||||
this.$refs.name.style.minWidth = ''
|
this.$refs.name.style.minWidth = ''
|
||||||
|
|||||||
@@ -74,8 +74,8 @@
|
|||||||
:key="document.uuid"
|
:key="document.uuid"
|
||||||
:ref="setDocumentRefs"
|
:ref="setDocumentRefs"
|
||||||
:areas-index="fieldAreasIndex[document.uuid]"
|
:areas-index="fieldAreasIndex[document.uuid]"
|
||||||
|
:selected-submitter="selectedSubmitter"
|
||||||
:document="document"
|
:document="document"
|
||||||
:is-draw="!!drawField"
|
|
||||||
:is-drag="!!dragFieldType"
|
:is-drag="!!dragFieldType"
|
||||||
@draw="onDraw"
|
@draw="onDraw"
|
||||||
@drop-field="onDropfield"
|
@drop-field="onDropfield"
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="relative w-80 flex-none pt-0.5 pr-4"
|
class="relative w-80 flex-none pt-0.5 pr-4 pl-0.5"
|
||||||
:class="drawField ? 'overflow-hidden' : 'overflow-auto'"
|
:class="drawField ? 'overflow-hidden' : 'overflow-auto'"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -99,9 +99,12 @@
|
|||||||
<div>
|
<div>
|
||||||
<Fields
|
<Fields
|
||||||
ref="fields"
|
ref="fields"
|
||||||
v-model:fields="template.fields"
|
:fields="template.fields"
|
||||||
|
:submitters="template.submitters"
|
||||||
|
:selected-submitter="selectedSubmitter"
|
||||||
@set-draw="drawField = $event"
|
@set-draw="drawField = $event"
|
||||||
@set-drag="dragFieldType = $event"
|
@set-drag="dragFieldType = $event"
|
||||||
|
@change-submitter="selectedSubmitter = $event"
|
||||||
@drag-end="dragFieldType = null"
|
@drag-end="dragFieldType = null"
|
||||||
@scroll-to-area="scrollToArea"
|
@scroll-to-area="scrollToArea"
|
||||||
/>
|
/>
|
||||||
@@ -154,6 +157,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
documentRefs: [],
|
documentRefs: [],
|
||||||
isSaving: false,
|
isSaving: false,
|
||||||
|
selectedSubmitter: null,
|
||||||
drawField: null,
|
drawField: null,
|
||||||
dragFieldType: null
|
dragFieldType: null
|
||||||
}
|
}
|
||||||
@@ -183,11 +187,14 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
created () {
|
||||||
|
this.selectedSubmitter = this.template.submitters[0]
|
||||||
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
document.addEventListener('keyup', this.disableDrawOnEsc)
|
document.addEventListener('keyup', this.onKeyUp)
|
||||||
},
|
},
|
||||||
unmounted () {
|
unmounted () {
|
||||||
document.removeEventListener('keyup', this.disableDrawOnEsc)
|
document.removeEventListener('keyup', this.onKeyUp)
|
||||||
},
|
},
|
||||||
beforeUpdate () {
|
beforeUpdate () {
|
||||||
this.documentRefs = []
|
this.documentRefs = []
|
||||||
@@ -203,12 +210,21 @@ export default {
|
|||||||
|
|
||||||
ref.$el.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
ref.$el.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||||
},
|
},
|
||||||
disableDrawOnEsc (e) {
|
onKeyUp (e) {
|
||||||
if (e.code === 'Escape') {
|
if (e.code === 'Escape') {
|
||||||
this.drawField = null
|
this.drawField = null
|
||||||
|
selectedAreaRef.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['Backspace', 'Delete'].includes(e.key) && selectedAreaRef.value && document.activeElement === document.body) {
|
||||||
|
this.removeArea({ area: selectedAreaRef.value })
|
||||||
|
|
||||||
|
selectedAreaRef.value = null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeArea ({ field, area }) {
|
removeArea ({ area }) {
|
||||||
|
const field = this.template.fields.find((f) => f.areas?.includes(area))
|
||||||
|
|
||||||
field.areas.splice(field.areas.indexOf(area), 1)
|
field.areas.splice(field.areas.indexOf(area), 1)
|
||||||
|
|
||||||
if (!field.areas.length) {
|
if (!field.areas.length) {
|
||||||
@@ -241,19 +257,21 @@ export default {
|
|||||||
uuid: v4(),
|
uuid: v4(),
|
||||||
required: true,
|
required: true,
|
||||||
type,
|
type,
|
||||||
|
submitter_uuid: this.selectedSubmitter.uuid,
|
||||||
areas: [area]
|
areas: [area]
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedAreaRef.value = area
|
|
||||||
|
|
||||||
this.template.fields.push(field)
|
this.template.fields.push(field)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selectedAreaRef.value = area
|
||||||
},
|
},
|
||||||
onDropfield (area) {
|
onDropfield (area) {
|
||||||
const field = {
|
const field = {
|
||||||
name: '',
|
name: '',
|
||||||
type: this.dragFieldType,
|
type: this.dragFieldType,
|
||||||
uuid: v4(),
|
uuid: v4(),
|
||||||
|
submitter_uuid: this.selectedSubmitter.uuid,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,7 +288,7 @@ export default {
|
|||||||
|
|
||||||
if (this.selectedField?.type === this.dragFieldType) {
|
if (this.selectedField?.type === this.dragFieldType) {
|
||||||
baseArea = selectedAreaRef.value
|
baseArea = selectedAreaRef.value
|
||||||
} else if (previousField) {
|
} else if (previousField?.areas) {
|
||||||
baseArea = previousField.areas[previousField.areas.length - 1]
|
baseArea = previousField.areas[previousField.areas.length - 1]
|
||||||
} else {
|
} else {
|
||||||
if (['checkbox', 'radio'].includes(this.dragFieldType)) {
|
if (['checkbox', 'radio'].includes(this.dragFieldType)) {
|
||||||
@@ -344,7 +362,7 @@ export default {
|
|||||||
this.isSaving = true
|
this.isSaving = true
|
||||||
|
|
||||||
this.save().then(() => {
|
this.save().then(() => {
|
||||||
window.Turbo.visit('/')
|
// window.Turbo.visit('/')
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.isSaving = false
|
this.isSaving = false
|
||||||
})
|
})
|
||||||
@@ -353,6 +371,8 @@ export default {
|
|||||||
const documentRef = this.documentRefs.find((a) => a.document.uuid === area.attachment_uuid)
|
const documentRef = this.documentRefs.find((a) => a.document.uuid === area.attachment_uuid)
|
||||||
|
|
||||||
documentRef.scrollToArea(area)
|
documentRef.scrollToArea(area)
|
||||||
|
|
||||||
|
selectedAreaRef.value = area
|
||||||
},
|
},
|
||||||
save () {
|
save () {
|
||||||
this.$el.closest('template-builder').dataset.template = JSON.stringify(this.template)
|
this.$el.closest('template-builder').dataset.template = JSON.stringify(this.template)
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
:ref="setPageRefs"
|
:ref="setPageRefs"
|
||||||
:number="index"
|
:number="index"
|
||||||
:areas="areasIndex[index]"
|
:areas="areasIndex[index]"
|
||||||
:is-draw="isDraw"
|
|
||||||
:is-drag="isDrag"
|
:is-drag="isDrag"
|
||||||
|
:selected-submitter="selectedSubmitter"
|
||||||
:image="image"
|
:image="image"
|
||||||
@drop-field="$emit('drop-field', {...$event, attachment_uuid: document.uuid })"
|
@drop-field="$emit('drop-field', {...$event, attachment_uuid: document.uuid })"
|
||||||
@remove-area="$emit('remove-area', $event)"
|
@remove-area="$emit('remove-area', $event)"
|
||||||
@@ -33,10 +33,9 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: () => ({})
|
default: () => ({})
|
||||||
},
|
},
|
||||||
isDraw: {
|
selectedSubmitter: {
|
||||||
type: Boolean,
|
type: Object,
|
||||||
required: false,
|
required: true
|
||||||
default: false
|
|
||||||
},
|
},
|
||||||
isDrag: {
|
isDrag: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|||||||
@@ -4,13 +4,14 @@
|
|||||||
@click="field.areas?.[0] && $emit('scroll-to', field.areas[0])"
|
@click="field.areas?.[0] && $emit('scroll-to', field.areas[0])"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="border border-gray-300 rounded rounded-tr-none relative group"
|
class="border border-base-300 rounded rounded-tr-none relative group"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between space-x-1">
|
<div class="flex items-center justify-between space-x-1">
|
||||||
<div class="flex items-center p-1 space-x-1">
|
<div class="flex items-center p-1 space-x-1">
|
||||||
<FieldType
|
<FieldType
|
||||||
v-model="field.type"
|
v-model="field.type"
|
||||||
:button-width="20"
|
:button-width="20"
|
||||||
|
@update:model-value="maybeDeleteOptions"
|
||||||
/>
|
/>
|
||||||
<Contenteditable
|
<Contenteditable
|
||||||
ref="name"
|
ref="name"
|
||||||
@@ -36,7 +37,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<ul
|
<ul
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="mt-1.5 dropdown-content menu menu-xs p-2 shadow bg-base-100 rounded-box w-52"
|
class="mt-1.5 dropdown-content menu menu-xs p-2 shadow bg-base-100 rounded-box w-52 z-10"
|
||||||
@click="closeDropdown"
|
@click="closeDropdown"
|
||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
@@ -178,6 +179,11 @@ export default {
|
|||||||
closeDropdown () {
|
closeDropdown () {
|
||||||
document.activeElement.blur()
|
document.activeElement.blur()
|
||||||
},
|
},
|
||||||
|
maybeDeleteOptions () {
|
||||||
|
if (!['radio', 'select', 'checkbox'].includes(this.field.type)) {
|
||||||
|
delete this.field.options
|
||||||
|
}
|
||||||
|
},
|
||||||
onNameBlur (e) {
|
onNameBlur (e) {
|
||||||
if (e.target.innerText.trim()) {
|
if (e.target.innerText.trim()) {
|
||||||
this.field.name = e.target.innerText.trim()
|
this.field.name = e.target.innerText.trim()
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
<template>
|
||||||
|
<div class="dropdown">
|
||||||
|
<label
|
||||||
|
v-if="compact"
|
||||||
|
tabindex="0"
|
||||||
|
:title="selectedSubmitter.name"
|
||||||
|
class="cursor-pointer text-base-100"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="mx-1 w-3 h-3 rounded-full"
|
||||||
|
:class="colors[submitters.indexOf(selectedSubmitter)]"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
v-else
|
||||||
|
tabindex="0"
|
||||||
|
class="rounded-md p-2 border border-base-300 w-full flex justify-between"
|
||||||
|
>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<span
|
||||||
|
class="w-3 h-3 rounded-full"
|
||||||
|
:class="colors[submitters.indexOf(selectedSubmitter)]"
|
||||||
|
/>
|
||||||
|
<Contenteditable
|
||||||
|
v-model="selectedSubmitter.name"
|
||||||
|
:icon-inline="true"
|
||||||
|
:icon-width="18"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span>
|
||||||
|
▼
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<ul
|
||||||
|
tabindex="0"
|
||||||
|
:class="menuClasses"
|
||||||
|
@click="closeDropdown"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
v-for="(submitter, index) in submitters"
|
||||||
|
:key="submitter.uuid"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="flex px-2 group justify-between items-center"
|
||||||
|
:class="{ 'active': submitter === selectedSubmitter }"
|
||||||
|
@click.prevent="selectSubmitter(submitter)"
|
||||||
|
>
|
||||||
|
<span class="py-1 flex items-center">
|
||||||
|
<span
|
||||||
|
class="rounded-full w-3 h-3 ml-1 mr-3"
|
||||||
|
:class="colors[index]"
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
{{ submitter.name }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
v-if="!compact && submitters.length > 1"
|
||||||
|
class="hidden group-hover:block px-2"
|
||||||
|
@click.stop="remove(submitter)"
|
||||||
|
>
|
||||||
|
<IconTrashX :width="18" />
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li v-if="submitters.length < 5">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="flex px-2"
|
||||||
|
@click.prevent="addSubmitter"
|
||||||
|
>
|
||||||
|
<IconUserPlus
|
||||||
|
:width="20"
|
||||||
|
:stroke-width="1.6"
|
||||||
|
/>
|
||||||
|
<span class="py-1">
|
||||||
|
Add Submitter
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { IconUserPlus, IconTrashX } from '@tabler/icons-vue'
|
||||||
|
import Contenteditable from './contenteditable'
|
||||||
|
import { v4 } from 'uuid'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'FieldSubmitter',
|
||||||
|
components: {
|
||||||
|
IconUserPlus,
|
||||||
|
Contenteditable,
|
||||||
|
IconTrashX
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
submitters: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
compact: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
menuClasses: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'dropdown-content menu p-2 shadow bg-base-100 rounded-box w-full z-10'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['update:model-value', 'remove'],
|
||||||
|
computed: {
|
||||||
|
colors () {
|
||||||
|
return [
|
||||||
|
'bg-red-500',
|
||||||
|
'bg-sky-500',
|
||||||
|
'bg-emerald-500',
|
||||||
|
'bg-yellow-300',
|
||||||
|
'bg-purple-600'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
selectedSubmitter () {
|
||||||
|
return this.submitters.find((e) => e.uuid === this.modelValue)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectSubmitter (submitter) {
|
||||||
|
this.$emit('update:model-value', submitter.uuid)
|
||||||
|
},
|
||||||
|
remove (submitter) {
|
||||||
|
if (window.confirm('Are you sure?')) {
|
||||||
|
this.$emit('remove', submitter)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addSubmitter () {
|
||||||
|
const newSubmitter = {
|
||||||
|
name: `Submitter ${this.submitters.length + 1}`,
|
||||||
|
uuid: v4()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.submitters.push(newSubmitter)
|
||||||
|
|
||||||
|
this.$emit('update:model-value', newSubmitter.uuid)
|
||||||
|
},
|
||||||
|
closeDropdown () {
|
||||||
|
document.activeElement.blur()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<ul
|
<ul
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="dropdown-content menu menu-xs p-2 shadow rounded-box w-52"
|
class="dropdown-content menu menu-xs p-2 shadow rounded-box w-52 z-10"
|
||||||
:class="menuClasses"
|
:class="menuClasses"
|
||||||
@click="closeDropdown"
|
@click="closeDropdown"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mb-1">
|
<FieldSubmitter
|
||||||
|
:model-value="selectedSubmitter.uuid"
|
||||||
|
class="w-full"
|
||||||
|
:submitters="submitters"
|
||||||
|
@remove="removeSubmitter"
|
||||||
|
@update:model-value="$emit('change-submitter', submitters.find((s) => s.uuid === $event))"
|
||||||
|
/>
|
||||||
|
<div class="mb-1 mt-2">
|
||||||
<Field
|
<Field
|
||||||
v-for="field in fields"
|
v-for="field in submitterFields"
|
||||||
:key="field.uuid"
|
:key="field.uuid"
|
||||||
:field="field"
|
:field="field"
|
||||||
:type-index="fields.filter((f) => f.type === field.type).indexOf(field)"
|
:type-index="fields.filter((f) => f.type === field.type).indexOf(field)"
|
||||||
@remove="fields.splice(fields.indexOf($event), 1)"
|
@remove="removeField"
|
||||||
@move-up="move(field, -1)"
|
@move-up="move(field, -1)"
|
||||||
@move-down="move(field, 1)"
|
@move-down="move(field, 1)"
|
||||||
@scroll-to="$emit('scroll-to-area', $event)"
|
@scroll-to="$emit('scroll-to-area', $event)"
|
||||||
@@ -17,7 +24,7 @@
|
|||||||
v-for="(icon, type) in fieldIcons"
|
v-for="(icon, type) in fieldIcons"
|
||||||
:key="type"
|
:key="type"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
class="flex items-center justify-center border border-dashed border-gray-300 bg-base-100 w-full rounded relative"
|
class="flex items-center justify-center border border-dashed border-base-300 bg-base-100 w-full rounded relative"
|
||||||
@dragstart="onDragstart(type)"
|
@dragstart="onDragstart(type)"
|
||||||
@dragend="$emit('drag-end')"
|
@dragend="$emit('drag-end')"
|
||||||
@click="addField(type)"
|
@click="addField(type)"
|
||||||
@@ -62,26 +69,52 @@
|
|||||||
import Field from './field'
|
import Field from './field'
|
||||||
import { v4 } from 'uuid'
|
import { v4 } from 'uuid'
|
||||||
import FieldType from './field_type'
|
import FieldType from './field_type'
|
||||||
|
import FieldSubmitter from './field_submitter'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TemplateFields',
|
name: 'TemplateFields',
|
||||||
components: {
|
components: {
|
||||||
Field
|
Field,
|
||||||
|
FieldSubmitter
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
fields: {
|
fields: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
submitters: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
selectedSubmitter: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['set-draw', 'set-drag', 'drag-end', 'scroll-to-area'],
|
emits: ['set-draw', 'set-drag', 'drag-end', 'scroll-to-area', 'change-submitter'],
|
||||||
computed: {
|
computed: {
|
||||||
fieldIcons: FieldType.computed.fieldIcons
|
fieldIcons: FieldType.computed.fieldIcons,
|
||||||
|
submitterFields () {
|
||||||
|
return this.fields.filter((f) => f.submitter_uuid === this.selectedSubmitter.uuid)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onDragstart (fieldType) {
|
onDragstart (fieldType) {
|
||||||
this.$emit('set-drag', fieldType)
|
this.$emit('set-drag', fieldType)
|
||||||
},
|
},
|
||||||
|
removeSubmitter (submitter) {
|
||||||
|
[...this.fields].forEach((field) => {
|
||||||
|
if (field.submitter_uuid === submitter.uuid) {
|
||||||
|
this.removeField(field)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.submitters.splice(this.submitters.indexOf(submitter), 1)
|
||||||
|
|
||||||
|
if (this.selectedSubmitter === submitter) {
|
||||||
|
this.$emit('change-submitter', this.submitters[0])
|
||||||
|
}
|
||||||
|
},
|
||||||
move (field, direction) {
|
move (field, direction) {
|
||||||
const currentIndex = this.fields.indexOf(field)
|
const currentIndex = this.fields.indexOf(field)
|
||||||
|
|
||||||
@@ -95,11 +128,15 @@ export default {
|
|||||||
this.fields.splice(currentIndex + direction, 0, field)
|
this.fields.splice(currentIndex + direction, 0, field)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
removeField (field) {
|
||||||
|
this.fields.splice(this.fields.indexOf(field), 1)
|
||||||
|
},
|
||||||
addField (type, area = null) {
|
addField (type, area = null) {
|
||||||
const field = {
|
const field = {
|
||||||
name: '',
|
name: '',
|
||||||
uuid: v4(),
|
uuid: v4(),
|
||||||
required: true,
|
required: true,
|
||||||
|
submitter_uuid: this.selectedSubmitter.uuid,
|
||||||
type
|
type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,14 +18,15 @@
|
|||||||
:ref="setAreaRefs"
|
:ref="setAreaRefs"
|
||||||
:area="item.area"
|
:area="item.area"
|
||||||
:field="item.field"
|
:field="item.field"
|
||||||
@start-resize="showMask = true"
|
@start-resize="[showMask = true, isResize = true]"
|
||||||
@stop-resize="showMask = false"
|
@stop-resize="[showMask = false, isResize = false]"
|
||||||
@start-drag="showMask = true"
|
@start-drag="[showMask = true, isMove = true]"
|
||||||
@stop-drag="showMask = false"
|
@stop-drag="[showMask = false, isMove = false]"
|
||||||
@remove="$emit('remove-area', item)"
|
@remove="$emit('remove-area', item)"
|
||||||
/>
|
/>
|
||||||
<FieldArea
|
<FieldArea
|
||||||
v-if="newArea"
|
v-if="newArea"
|
||||||
|
:field="{ submitter_uuid: selectedSubmitter.uuid }"
|
||||||
:area="newArea"
|
:area="newArea"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -33,7 +34,8 @@
|
|||||||
v-show="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 z-10"
|
||||||
|
:class="{ 'cursor-grab': isDrag || isMove, ' cursor-nwse-resize': isResize }"
|
||||||
@pointermove="onPointermove"
|
@pointermove="onPointermove"
|
||||||
@dragover.prevent
|
@dragover.prevent
|
||||||
@drop="onDrop"
|
@drop="onDrop"
|
||||||
@@ -60,10 +62,9 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
selectedArea: {
|
selectedSubmitter: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: true
|
||||||
default: () => ({})
|
|
||||||
},
|
},
|
||||||
isDrag: {
|
isDrag: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -80,6 +81,8 @@ export default {
|
|||||||
return {
|
return {
|
||||||
areaRefs: [],
|
areaRefs: [],
|
||||||
showMask: false,
|
showMask: false,
|
||||||
|
isMove: false,
|
||||||
|
isResize: false,
|
||||||
newArea: null
|
newArea: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
# name :string not null
|
# name :string not null
|
||||||
# schema :string not null
|
# schema :string not null
|
||||||
# slug :string not null
|
# slug :string not null
|
||||||
|
# submitters :string not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# account_id :bigint not null
|
# account_id :bigint not null
|
||||||
@@ -27,15 +28,19 @@
|
|||||||
# fk_rails_... (author_id => users.id)
|
# fk_rails_... (author_id => users.id)
|
||||||
#
|
#
|
||||||
class Template < ApplicationRecord
|
class Template < ApplicationRecord
|
||||||
|
DEFAULT_SUBMITTER_NAME = 'Submitter 1'
|
||||||
|
|
||||||
belongs_to :author, class_name: 'User'
|
belongs_to :author, class_name: 'User'
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
|
|
||||||
attribute :fields, :string, default: -> { [] }
|
attribute :fields, :string, default: -> { [] }
|
||||||
attribute :schema, :string, default: -> { [] }
|
attribute :schema, :string, default: -> { [] }
|
||||||
|
attribute :submitters, :string, default: -> { [{ name: DEFAULT_SUBMITTER_NAME, uuid: SecureRandom.uuid }] }
|
||||||
attribute :slug, :string, default: -> { SecureRandom.base58(8) }
|
attribute :slug, :string, default: -> { SecureRandom.base58(8) }
|
||||||
|
|
||||||
serialize :fields, JSON
|
serialize :fields, JSON
|
||||||
serialize :schema, JSON
|
serialize :schema, JSON
|
||||||
|
serialize :submitters, JSON
|
||||||
|
|
||||||
has_many_attached :documents
|
has_many_attached :documents
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<div class="space-x-6">
|
<div class="space-x-6">
|
||||||
<%= link_to 'Settings', settings_storage_index_path, class: 'font-medium text-lg' %>
|
<%= link_to 'Settings', settings_storage_index_path, class: 'font-medium text-lg' %>
|
||||||
<div class="dropdown dropdown-end z-50">
|
<div class="dropdown dropdown-end z-50">
|
||||||
<label tabindex="0" class="cursor-pointer bg-neutral-focus text-neutral-content rounded-full w-8 p-2">
|
<label tabindex="0" class="cursor-pointer bg-base-content text-purple-300 rounded-full w-8 p-2">
|
||||||
<span class="text-sm align-text-top"><%= current_user.initials %></span>
|
<span class="text-sm align-text-top"><%= current_user.initials %></span>
|
||||||
</label>
|
</label>
|
||||||
<ul tabindex="0" class="dropdown-content p-2 mt-2 shadow menu bg-base-100 rounded-box whitespace-nowrap">
|
<ul tabindex="0" class="dropdown-content p-2 mt-2 shadow menu bg-base-100 rounded-box whitespace-nowrap">
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ class CreateTemplates < ActiveRecord::Migration[7.0]
|
|||||||
t.string :name, null: false
|
t.string :name, null: false
|
||||||
t.string :schema, null: false
|
t.string :schema, null: false
|
||||||
t.string :fields, null: false
|
t.string :fields, null: false
|
||||||
|
t.string :submitters, null: false
|
||||||
|
|
||||||
t.references :author, null: false, foreign_key: { to_table: :users }, index: true
|
t.references :author, null: false, foreign_key: { to_table: :users }, index: true
|
||||||
t.references :account, null: false, foreign_key: true, index: true
|
t.references :account, null: false, foreign_key: true, index: true
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_05_19_144036) do
|
|||||||
t.string "name", null: false
|
t.string "name", null: false
|
||||||
t.string "schema", null: false
|
t.string "schema", null: false
|
||||||
t.string "fields", null: false
|
t.string "fields", null: false
|
||||||
|
t.string "submitters", null: false
|
||||||
t.bigint "author_id", null: false
|
t.bigint "author_id", null: false
|
||||||
t.bigint "account_id", null: false
|
t.bigint "account_id", null: false
|
||||||
t.datetime "deleted_at"
|
t.datetime "deleted_at"
|
||||||
|
|||||||
Reference in New Issue
Block a user