move components where they belong
This commit is contained in:
@@ -1,256 +0,0 @@
|
||||
<template lang="pug">
|
||||
v-col(cols=12)
|
||||
.text-center
|
||||
v-btn-toggle.v-col-6.flex-column.flex-sm-row(v-model='type' color='primary' @change='type => change("type", type)')
|
||||
v-btn(value='normal' label="normal") {{$t('event.normal')}}
|
||||
v-btn(value='multidate' label='multidate') {{$t('event.multidate')}}
|
||||
v-btn(v-if='settings.allow_recurrent_event' value='recurrent' label="recurrent") {{$t('event.recurrent')}}
|
||||
|
||||
p {{$t(`event.${type}_description`)}}
|
||||
|
||||
v-btn-toggle.v-col-6.flex-column.flex-sm-row(v-if='type === "recurrent"' color='primary' :value='value.recurrent.frequency' @change='fq => change("frequency", fq)')
|
||||
v-btn(v-for='f in frequencies' :key='f.value' :value='f.value') {{f.text}}
|
||||
|
||||
client-only
|
||||
.datePicker.mt-3
|
||||
v-input(:value='fromDate'
|
||||
:rules="[$validators.required('common.when')]")
|
||||
vc-date-picker(
|
||||
:value='fromDate'
|
||||
@input="date => change('date', date)"
|
||||
:is-range='type === "multidate"'
|
||||
:attributes='attributes'
|
||||
:locale='$i18n.locale'
|
||||
:from-page.sync='page'
|
||||
:is-dark="settings['theme.is_dark']"
|
||||
is-inline
|
||||
is-expanded
|
||||
:min-date='type !== "recurrent" && new Date()')
|
||||
|
||||
div.text-center.mb-2(v-if='type === "recurrent"')
|
||||
span(v-if='value.recurrent.frequency !== "1m" && value.recurrent.frequency !== "2m"') {{whenPatterns}}
|
||||
v-btn-toggle.mt-1.flex-column.flex-sm-row(v-else :value='value.recurrent.type' color='primary' @change='fq => change("recurrentType", fq)')
|
||||
v-btn(v-for='whenPattern in whenPatterns' :value='whenPattern.key' :key='whenPatterns.key' small) {{whenPattern.label}}
|
||||
|
||||
v-row.mt-3.col-md-6.mx-auto
|
||||
v-col.col-12.col-sm-6
|
||||
v-select(dense :label="$t('event.from')" :value='fromHour' clearable
|
||||
:disabled='!value.from'
|
||||
:rules="[$validators.required('event.from')]"
|
||||
:items='hourList' @change='hr => change("fromHour", hr)')
|
||||
|
||||
v-col.col-12.col-sm-6
|
||||
v-select(dense :label="$t('event.due')"
|
||||
:disabled='!fromHour'
|
||||
:value='dueHour' clearable
|
||||
:items='hourList' @change='hr => change("dueHour", hr)')
|
||||
|
||||
List(v-if='type==="normal" && todayEvents.length' :events='todayEvents' :title='$t("event.same_day")')
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import dayjs from 'dayjs'
|
||||
import { mapState } from 'vuex'
|
||||
import List from '@/components/List'
|
||||
import { attributesFromEvents } from '../../assets/helper'
|
||||
|
||||
export default {
|
||||
name: 'DateInput',
|
||||
components: { List },
|
||||
props: {
|
||||
value: { type: Object, default: () => ({ from: null, due: null, recurrent: null }) },
|
||||
event: { type: Object, default: () => null }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
type: 'normal',
|
||||
page: null,
|
||||
events: [],
|
||||
frequencies: [
|
||||
{ value: '1w', text: this.$t('event.each_week') },
|
||||
{ value: '2w', text: this.$t('event.each_2w') },
|
||||
{ value: '1m', text: this.$t('event.each_month') }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['settings', 'tags']),
|
||||
todayEvents () {
|
||||
const start = dayjs(this.value.from).startOf('day').unix()
|
||||
const end = dayjs(this.value.from).endOf('day').unix()
|
||||
return this.events.filter(e => e.start_datetime >= start && e.start_datetime <= end)
|
||||
},
|
||||
attributes () {
|
||||
return attributesFromEvents(this.events, this.tags)
|
||||
},
|
||||
fromDate () {
|
||||
if (this.value.multidate) {
|
||||
return ({ start: dayjs(this.value.from).toDate(), end: dayjs(this.value.due).toDate() })
|
||||
}
|
||||
return this.value.from ? dayjs(this.value.from).toDate() : null
|
||||
},
|
||||
|
||||
fromHour () {
|
||||
return this.value.from && this.value.fromHour ? dayjs.tz(this.value.from).format('HH:mm') : null
|
||||
},
|
||||
dueHour () {
|
||||
return this.value.due && this.value.dueHour ? dayjs.tz(this.value.due).format('HH:mm') : null
|
||||
},
|
||||
hourList () {
|
||||
const hourList = []
|
||||
const leftPad = h => ('00' + h).slice(-2)
|
||||
for (let h = 0; h < 24; h++) {
|
||||
const textHour = leftPad(h < 13 ? h : h - 12)
|
||||
hourList.push({ text: textHour + ':00 ' + (h <= 12 ? 'AM' : 'PM'), value: leftPad(h) + ':00' })
|
||||
hourList.push({ text: textHour + ':30 ' + (h <= 12 ? 'AM' : 'PM'), value: leftPad(h) + ':30' })
|
||||
}
|
||||
|
||||
return hourList
|
||||
},
|
||||
whenPatterns () {
|
||||
if (!this.value.from) { return }
|
||||
const date = dayjs(this.value.from)
|
||||
|
||||
const freq = this.value.recurrent.frequency
|
||||
const weekDay = date.format('dddd')
|
||||
if (freq === '1w' || freq === '2w') {
|
||||
return this.$t(`event.recurrent_${freq}_days`, { days: weekDay }).toUpperCase()
|
||||
} else if (freq === '1m' || freq === '2m') {
|
||||
const monthDay = date.format('D')
|
||||
const n = Math.floor((monthDay - 1) / 7) + 1
|
||||
|
||||
const patterns = [
|
||||
{ label: this.$t(`event.recurrent_${freq}_days`, { days: monthDay }), key: 'ordinal' }
|
||||
// { label: this.$tc(`event.recurrent_${freq}_ordinal`, { n, days: weekDay }), key: 'weekday' }
|
||||
]
|
||||
|
||||
if (n < 5) {
|
||||
patterns.push(
|
||||
{
|
||||
label: this.$t(`event.recurrent_${freq}_ordinal`, { n: this.$t(`ordinal.${n}`), days: weekDay }),
|
||||
key: n
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// if selected day is in last week, propose also this type of selection
|
||||
const lastWeek = date.daysInMonth() - monthDay < 7
|
||||
if (lastWeek) {
|
||||
patterns.push(
|
||||
{
|
||||
label: this.$t(`event.recurrent_${freq}_ordinal`, { n: this.$t('ordinal.-1'), days: weekDay }),
|
||||
key: -1
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return patterns
|
||||
} else if (freq === '1d') {
|
||||
return this.$t('event.recurrent_each_day')
|
||||
}
|
||||
return ''
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
if (this.value.multidate) {
|
||||
this.type = 'multidate'
|
||||
} else if (this.value.recurrent) {
|
||||
this.type = 'recurrent'
|
||||
} else {
|
||||
this.type = 'normal'
|
||||
}
|
||||
this.events = await this.$api.getEvents({
|
||||
start: dayjs().unix(),
|
||||
show_recurrent: true
|
||||
})
|
||||
this.events = this.events.filter(e => e.id !== this.event.id)
|
||||
},
|
||||
methods: {
|
||||
updateRecurrent (value) {
|
||||
this.$emit('input', { ...this.value, recurrent: value || null })
|
||||
},
|
||||
change (what, value) {
|
||||
// change event's type
|
||||
if (what === 'type') {
|
||||
if (typeof value === 'undefined') { this.type = 'normal' }
|
||||
if (value === 'recurrent') {
|
||||
this.$emit('input', { ...this.value, recurrent: { frequency: '1w' }, multidate: false })
|
||||
} else if (value === 'multidate') {
|
||||
this.$emit('input', { ...this.value, recurrent: null, multidate: true })
|
||||
} else {
|
||||
let from = this.value.from
|
||||
if (from && from.start) {
|
||||
from = from.start
|
||||
}
|
||||
let due = this.value.due
|
||||
if (due && due.start) {
|
||||
due = due.start
|
||||
}
|
||||
this.$emit('input', { ...this.value, from, due, recurrent: null, multidate: false })
|
||||
}
|
||||
} else if (what === 'frequency') {
|
||||
this.$emit('input', { ...this.value, recurrent: { ...this.value.recurrent, frequency: value } })
|
||||
} else if (what === 'recurrentType') {
|
||||
this.$emit('input', { ...this.value, recurrent: { ...this.value.recurrent, type: value } })
|
||||
} else if (what === 'fromHour') {
|
||||
if (value) {
|
||||
const [hour, minute] = value.split(':')
|
||||
const from = dayjs.tz(this.value.from).hour(hour).minute(minute).second(0)
|
||||
this.$emit('input', { ...this.value, from, fromHour: true })
|
||||
} else {
|
||||
this.$emit('input', { ...this.value, fromHour: false })
|
||||
}
|
||||
} else if (what === 'dueHour') {
|
||||
if (value) {
|
||||
const [hour, minute] = value.split(':')
|
||||
const fromHour = dayjs.tz(this.value.from).hour()
|
||||
|
||||
// add a day
|
||||
let due = dayjs(this.value.from)
|
||||
if (fromHour > Number(hour) && !this.value.multidate) {
|
||||
due = due.add(1, 'day')
|
||||
}
|
||||
due = due.hour(hour).minute(minute).second(0)
|
||||
this.$emit('input', { ...this.value, due, dueHour: true })
|
||||
} else {
|
||||
this.$emit('input', { ...this.value, due: null, dueHour: false })
|
||||
}
|
||||
// change date in calendar (could be a range or a recurrent event...)
|
||||
} else if (what === 'date') {
|
||||
if (value === null) {
|
||||
this.$emit('input', { ...this.value, from: null, fromHour: false })
|
||||
return
|
||||
}
|
||||
if (this.value.multidate) {
|
||||
let from = value.start
|
||||
let due = value.end
|
||||
if (this.value.fromHour) {
|
||||
from = dayjs.tz(value.start).hour(dayjs.tz(this.value.from).hour())
|
||||
}
|
||||
if (this.value.dueHour) {
|
||||
due = dayjs.tz(value.end).hour(dayjs.tz(this.value.due).hour())
|
||||
}
|
||||
this.$emit('input', { ...this.value, from, due })
|
||||
} else {
|
||||
let from = value
|
||||
let due = this.value.due
|
||||
if (this.value.fromHour) {
|
||||
from = dayjs.tz(value).hour(dayjs.tz(this.value.from).hour())
|
||||
}
|
||||
if (this.value.dueHour && this.value.due) {
|
||||
due = dayjs.tz(value).hour(dayjs.tz(this.value.due).hour())
|
||||
}
|
||||
this.$emit('input', { ...this.value, from, due })
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.datePicker {
|
||||
max-width: 500px !important;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
@@ -1,80 +0,0 @@
|
||||
<template lang="pug">
|
||||
v-row
|
||||
v-col.col-6
|
||||
v-menu(v-model='startTimeMenu'
|
||||
:close-on-content-click="false"
|
||||
transition="slide-x-transition"
|
||||
ref='startTimeMenu'
|
||||
:return-value.sync="value.start"
|
||||
offset-y
|
||||
absolute
|
||||
top
|
||||
max-width="290px"
|
||||
min-width="290px")
|
||||
template(v-slot:activator='{ on }')
|
||||
v-text-field(
|
||||
:label="$t('event.from')"
|
||||
prepend-icon='mdi-clock'
|
||||
:rules="[$validators.required('event.from')]"
|
||||
:value='value.start'
|
||||
v-on='on'
|
||||
clearable)
|
||||
v-time-picker(
|
||||
v-if='startTimeMenu'
|
||||
:label="$t('event.from')"
|
||||
format="24hr"
|
||||
ref='time_start'
|
||||
:allowed-minutes="[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55]"
|
||||
v-model='value.start'
|
||||
@click:minute="selectTime('start')")
|
||||
|
||||
v-col.col-6
|
||||
v-menu(v-model='endTimeMenu'
|
||||
:close-on-content-click="false"
|
||||
transition="slide-x-transition"
|
||||
ref='endTimeMenu'
|
||||
:return-value.sync="time.end"
|
||||
offset-y
|
||||
absolute
|
||||
top
|
||||
max-width="290px"
|
||||
min-width="290px")
|
||||
template(v-slot:activator='{ on }')
|
||||
v-text-field(
|
||||
prepend-icon='mdi-clock'
|
||||
:label="$t('event.due')"
|
||||
:value='value.end'
|
||||
v-on='on'
|
||||
clearable
|
||||
readonly)
|
||||
v-time-picker(
|
||||
v-if='endTimeMenu'
|
||||
:label="$t('event.due')"
|
||||
format="24hr"
|
||||
:allowed-minutes="[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55]"
|
||||
v-model='value.end'
|
||||
@click:minute="selectTime('end')")
|
||||
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'HourInput',
|
||||
props: {
|
||||
value: { type: Object, default: () => { } }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
// time: { start: this.value.start, end: this.value.end },
|
||||
time: {},
|
||||
startTimeMenu: false,
|
||||
endTimeMenu: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectTime (type) {
|
||||
this.$refs[`${type}TimeMenu`].save(this.value[type])
|
||||
this.$emit('input', this.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,103 +0,0 @@
|
||||
<template lang="pug">
|
||||
v-card
|
||||
v-card-title {{$t('common.import')}}
|
||||
v-card-text
|
||||
p(v-html="$t('event.import_description')")
|
||||
v-form(v-model='valid' ref='form' lazy-validation @submit.prevent='importGeneric')
|
||||
v-row
|
||||
.col-xl-5.col-lg-5.col-md-7.col-sm-12.col-xs-12
|
||||
v-text-field(v-model='URL'
|
||||
:label="$t('common.url')"
|
||||
:hint="$t('event.import_URL')"
|
||||
persistent-hint
|
||||
:loading='loading' :error='error'
|
||||
:error-messages='errorMessage')
|
||||
.col
|
||||
v-file-input(
|
||||
v-model='file'
|
||||
accept=".ics"
|
||||
:label="$t('event.ics')"
|
||||
:hint="$t('event.import_ICS')"
|
||||
persistent-hint)
|
||||
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(text @click='$emit("close")' color='warning') {{$t('common.cancel')}}
|
||||
v-btn(text @click='importGeneric' :loading='loading' :disabled='loading'
|
||||
color='primary') {{$t('common.import')}}
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import ical from 'ical.js'
|
||||
import get from 'lodash/get'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'ImportDialog',
|
||||
data () {
|
||||
return {
|
||||
file: null,
|
||||
errorMessage: '',
|
||||
error: false,
|
||||
loading: false,
|
||||
valid: false,
|
||||
URL: '',
|
||||
event: {}
|
||||
}
|
||||
},
|
||||
computed: mapState(['places']),
|
||||
methods: {
|
||||
importGeneric () {
|
||||
if (this.file) {
|
||||
this.importICS()
|
||||
} else {
|
||||
this.importURL()
|
||||
}
|
||||
},
|
||||
importICS () {
|
||||
const reader = new FileReader()
|
||||
reader.readAsText(this.file)
|
||||
reader.onload = () => {
|
||||
const ret = ical.parse(reader.result)
|
||||
const component = new ical.Component(ret)
|
||||
const events = component.getAllSubcomponents('vevent')
|
||||
const event = new ical.Event(events[0])
|
||||
this.event = {
|
||||
title: get(event, 'summary', ''),
|
||||
description: get(event, 'description', ''),
|
||||
place: { name: get(event, 'location', '') },
|
||||
start_datetime: get(event, 'startDate', '').toUnixTime(),
|
||||
end_datetime: get(event, 'endDate', '').toUnixTime()
|
||||
}
|
||||
|
||||
this.$emit('imported', this.event)
|
||||
}
|
||||
},
|
||||
async importURL () {
|
||||
if (!this.URL) {
|
||||
this.errorMessage = this.$validators.required('common.url')('')
|
||||
this.error = true
|
||||
return
|
||||
}
|
||||
if (!this.URL.match(/^https?:\/\//)) {
|
||||
this.URL = `https://${this.URL}`
|
||||
}
|
||||
this.error = false
|
||||
this.errorMessage = ''
|
||||
this.loading = true
|
||||
|
||||
try {
|
||||
const ret = await this.$axios.$get('/event/import', { params: { URL: this.URL } })
|
||||
this.events = ret
|
||||
// check if contain an h-event
|
||||
this.$emit('imported', ret[0])
|
||||
} catch (e) {
|
||||
this.error = true
|
||||
this.errorMessage = String(e)
|
||||
}
|
||||
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,177 +0,0 @@
|
||||
<template lang="pug">
|
||||
span
|
||||
v-dialog(v-model='openMediaDetails' :fullscreen="$vuetify.breakpoint.xsOnly" width='1000px')
|
||||
v-card
|
||||
v-card-title {{$t('common.media')}}
|
||||
v-card-text
|
||||
v-row.mt-1
|
||||
v-col#focalPointSelector(
|
||||
@mousedown='handleStart' @touchstart='handleStart'
|
||||
@mousemove='handleMove' @touchmove='handleMove'
|
||||
@mouseup='handleStop' @touchend='handleStop'
|
||||
)
|
||||
div.focalPoint(:style="{ top, left }")
|
||||
img(v-if='mediaPreview' :src='mediaPreview')
|
||||
|
||||
v-col.col-12.col-sm-4
|
||||
p {{$t('event.choose_focal_point')}}
|
||||
img.mediaPreview.d-none.d-sm-block(v-if='mediaPreview'
|
||||
:src='mediaPreview' :style="{ 'object-position': position }")
|
||||
|
||||
v-textarea.mt-4(type='text'
|
||||
label='Alternative text'
|
||||
persistent-hint
|
||||
@input='v => name=v'
|
||||
:value='value.name' filled
|
||||
:hint='$t("event.alt_text_description")')
|
||||
br
|
||||
v-card-actions.justify-space-between
|
||||
v-btn(text @click='openMediaDetails=false' color='warning') Cancel
|
||||
v-btn(text color='primary' @click='save') Save
|
||||
|
||||
h3.mb-3.font-weight-regular(v-if='mediaPreview') {{$t('common.media')}}
|
||||
v-card-actions(v-if='mediaPreview')
|
||||
v-spacer
|
||||
v-btn(text color='primary' @click='openMediaDetails = true') {{$t('common.edit')}}
|
||||
v-btn(text color='error' @click='remove') {{$t('common.remove')}}
|
||||
div(v-if='mediaPreview')
|
||||
img.mediaPreview.col-12.ml-3(:src='mediaPreview' :style="{ 'object-position': savedPosition }")
|
||||
span.float-right {{event.media[0].name}}
|
||||
v-file-input(
|
||||
v-else
|
||||
:label="$t('common.media')"
|
||||
:hint="$t('event.media_description')"
|
||||
:prepend-icon="mdiCamera"
|
||||
:value='value.image'
|
||||
@change="selectMedia"
|
||||
persistent-hint
|
||||
accept='image/*')
|
||||
</template>
|
||||
<script>
|
||||
import { mdiCamera } from '@mdi/js'
|
||||
export default {
|
||||
name: 'MediaInput',
|
||||
props: {
|
||||
value: { type: Object, default: () => ({ image: null }) },
|
||||
event: { type: Object, default: () => ({}) }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
mdiCamera,
|
||||
openMediaDetails: false,
|
||||
name: this.value.name || '',
|
||||
focalpoint: this.value.focalpoint || [0, 0],
|
||||
dragging: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
mediaPreview () {
|
||||
if (!this.value.url && !this.value.image) {
|
||||
return false
|
||||
}
|
||||
const url = this.value.image ? URL.createObjectURL(this.value.image) : /^https?:\/\//.test(this.value.url) ? this.value.url : `/media/thumb/${this.value.url}`
|
||||
return url
|
||||
},
|
||||
top () {
|
||||
return ((this.focalpoint[1] + 1) * 50) + '%'
|
||||
},
|
||||
left () {
|
||||
return ((this.focalpoint[0] + 1) * 50) + '%'
|
||||
},
|
||||
savedPosition () {
|
||||
const focalpoint = this.value.focalpoint || [0, 0]
|
||||
return `${(focalpoint[0] + 1) * 50}% ${(focalpoint[1] + 1) * 50}%`
|
||||
},
|
||||
position () {
|
||||
const focalpoint = this.focalpoint || [0, 0]
|
||||
return `${(focalpoint[0] + 1) * 50}% ${(focalpoint[1] + 1) * 50}%`
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
save () {
|
||||
this.$emit('input', { url: this.value.url, image: this.value.image, name: this.name || (this.event.title) || '', focalpoint: [...this.focalpoint] })
|
||||
this.openMediaDetails = false
|
||||
},
|
||||
async remove () {
|
||||
const ret = await this.$root.$confirm('event.remove_media_confirmation')
|
||||
if (!ret) { return }
|
||||
this.$emit('remove')
|
||||
},
|
||||
selectMedia (v) {
|
||||
this.$emit('input', { image: v, name: this.event.title, focalpoint: [0, 0] })
|
||||
},
|
||||
handleStart (ev) {
|
||||
ev.preventDefault()
|
||||
this.dragging = true
|
||||
this.handleMove(ev, true)
|
||||
return false
|
||||
},
|
||||
handleStop (ev) {
|
||||
this.dragging = false
|
||||
},
|
||||
handleMove (ev, manual = false) {
|
||||
if (!this.dragging && !manual) return
|
||||
ev.stopPropagation()
|
||||
const boundingClientRect = document.getElementById('focalPointSelector').getBoundingClientRect()
|
||||
|
||||
const clientX = ev.changedTouches ? ev.changedTouches[0].clientX : ev.clientX
|
||||
const clientY = ev.changedTouches ? ev.changedTouches[0].clientY : ev.clientY
|
||||
|
||||
// get relative coordinate
|
||||
let x = Math.ceil(clientX - boundingClientRect.left)
|
||||
let y = Math.ceil(clientY - boundingClientRect.top)
|
||||
|
||||
// snap to border
|
||||
x = x < 30 ? 0 : x > boundingClientRect.width - 30 ? boundingClientRect.width : x
|
||||
y = y < 30 ? 0 : y > boundingClientRect.height - 30 ? boundingClientRect.height : y
|
||||
|
||||
// this.relativeFocalpoint = [x + 'px', y + 'px']
|
||||
|
||||
// map to real image coordinate
|
||||
const posY = -1 + (y / boundingClientRect.height) * 2
|
||||
const posX = -1 + (x / boundingClientRect.width) * 2
|
||||
|
||||
this.focalpoint = [posX, posY]
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.cursorPointer {
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.mediaPreview {
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
object-position: top;
|
||||
aspect-ratio: 1.7778;
|
||||
}
|
||||
|
||||
#focalPointSelector {
|
||||
position: relative;
|
||||
cursor: move;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-self: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#focalPointSelector img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.focalPoint {
|
||||
position: absolute;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
top: 100px;
|
||||
left: 100px;
|
||||
transform: translate(-25px, -25px);
|
||||
border-radius: 50%;
|
||||
border: 1px solid #ff6d408e;
|
||||
box-shadow: 0 0 0 9999em rgba(0, 0, 0, .65);
|
||||
}
|
||||
</style>
|
||||
@@ -1,106 +0,0 @@
|
||||
<template lang="pug">
|
||||
v-row
|
||||
v-col(cols=12 md=6)
|
||||
v-combobox(ref='place'
|
||||
:rules="[$validators.required('common.where')]"
|
||||
:label="$t('common.where')"
|
||||
:hint="$t('event.where_description')"
|
||||
:search-input.sync="placeName"
|
||||
:prepend-icon='mdiMapMarker'
|
||||
persistent-hint
|
||||
:value="value.name"
|
||||
:items="filteredPlaces"
|
||||
no-filter
|
||||
item-text='name'
|
||||
@change='selectPlace')
|
||||
template(v-slot:item="{ item, attrs, on }")
|
||||
v-list-item(v-bind='attrs' v-on='on')
|
||||
v-list-item-content(two-line v-if='item.create')
|
||||
v-list-item-title <v-icon color='primary' v-text='mdiPlus' :aria-label='$t("common.add")'></v-icon> {{item.name}}
|
||||
v-list-item-content(two-line v-else)
|
||||
v-list-item-title(v-text='item.name')
|
||||
v-list-item-subtitle(v-text='item.address')
|
||||
|
||||
v-col(cols=12 md=6)
|
||||
v-text-field(ref='address'
|
||||
:prepend-icon='mdiMap'
|
||||
:disabled='disableAddress'
|
||||
:rules="[ v => disableAddress ? true : $validators.required('common.address')(v)]"
|
||||
:label="$t('common.address')"
|
||||
@change="changeAddress"
|
||||
:value="value.address")
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { mdiMap, mdiMapMarker, mdiPlus } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
name: 'WhereInput',
|
||||
props: {
|
||||
value: { type: Object, default: () => {} }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
mdiMap, mdiMapMarker, mdiPlus,
|
||||
place: { },
|
||||
placeName: '',
|
||||
disableAddress: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['places']),
|
||||
filteredPlaces () {
|
||||
if (!this.placeName) { return this.places }
|
||||
const placeName = this.placeName.trim().toLowerCase()
|
||||
let nameMatch = false
|
||||
const matches = this.places.filter(p => {
|
||||
const tmpName = p.name.toLowerCase()
|
||||
const tmpAddress = p.address.toLowerCase()
|
||||
if (tmpName.includes(placeName)) {
|
||||
if (tmpName === placeName) { nameMatch = true }
|
||||
return true
|
||||
}
|
||||
return tmpAddress.includes(placeName)
|
||||
})
|
||||
if (!nameMatch) {
|
||||
matches.unshift({ create: true, name: this.placeName })
|
||||
}
|
||||
return matches
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectPlace (p) {
|
||||
if (!p) { return }
|
||||
if (typeof p === 'object' && !p.create) {
|
||||
this.place.name = p.name.trim()
|
||||
this.place.address = p.address
|
||||
this.place.id = p.id
|
||||
this.disableAddress = true
|
||||
} else { // this is a new place
|
||||
this.place.name = p.name || p
|
||||
const tmpPlace = this.place.name.trim().toLowerCase()
|
||||
// search for a place with the same name
|
||||
const place = this.places.find(p => p.name.toLowerCase() === tmpPlace)
|
||||
if (place) {
|
||||
this.place.name = place.name
|
||||
this.place.id = place.id
|
||||
this.place.address = place.address
|
||||
this.disableAddress = true
|
||||
} else {
|
||||
delete this.place.id
|
||||
this.place.address = ''
|
||||
this.disableAddress = false
|
||||
this.$refs.place.blur()
|
||||
this.$refs.address.focus()
|
||||
}
|
||||
}
|
||||
this.$emit('input', { ...this.place })
|
||||
},
|
||||
changeAddress (v) {
|
||||
this.place.address = v
|
||||
this.$emit('input', { ...this.place })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user