Merge branch 'dev' into feat/add_user_theme_view_controls
This commit is contained in:
@@ -1,22 +1,20 @@
|
||||
<template>
|
||||
<nav>
|
||||
|
||||
<NavHeader/>
|
||||
<NavHeader />
|
||||
|
||||
<!-- title -->
|
||||
<div class='text-center'>
|
||||
<nuxt-link id='title' v-text='settings.title' to='/' />
|
||||
<div class='text-body-1 font-weight-light' v-text='settings.description' />
|
||||
<div class="text-center">
|
||||
<nuxt-link id="title" v-text="settings.title" to="/" />
|
||||
<div
|
||||
class="text-body-1 font-weight-light"
|
||||
v-text="settings.description"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<NavSearch />
|
||||
|
||||
<NavBar />
|
||||
|
||||
</nav>
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
@@ -27,18 +25,24 @@ import NavSearch from './NavSearch.vue'
|
||||
export default {
|
||||
name: 'Appbar',
|
||||
components: { NavHeader, NavBar, NavSearch },
|
||||
computed: mapState(['settings'])
|
||||
computed: mapState(['settings']),
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
nav {
|
||||
background-image: linear-gradient(rgba(59, 0, 0, 0.4), rgba(0, 0, 0, 0.4)), url(/headerimage.png);
|
||||
background-image: linear-gradient(rgba(0, 0, 0, 0.8), rgba(20, 20, 20, 0.7)),
|
||||
url(/headerimage.png);
|
||||
background-position: center center;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.theme--light nav {
|
||||
background-image: linear-gradient(to bottom, rgba(255,230,230,.95), rgba(250,250,250,.95)), url(/headerimage.png);
|
||||
background-image: linear-gradient(
|
||||
to bottom,
|
||||
rgba(230, 230, 230, 0.95),
|
||||
rgba(250, 250, 250, 0.95)
|
||||
),
|
||||
url(/headerimage.png);
|
||||
}
|
||||
|
||||
#title {
|
||||
@@ -46,5 +50,4 @@ nav {
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -15,13 +15,14 @@
|
||||
aria-label='Calendar'
|
||||
is-expanded
|
||||
is-inline)
|
||||
template(v-slot="{ inputValue, inputEvents }")
|
||||
//- template(v-slot="{ inputValue, inputEvents }")
|
||||
v-btn#calendarButton(v-on='inputEvents' text tile :color='selectedDate ? "primary" : "" ') {{inputValue || $t('common.calendar')}}
|
||||
v-icon(v-if='selectedDate' v-text='mdiClose' right small icon @click.prevent.stop='selectedDate = null')
|
||||
v-icon(v-else v-text='mdiChevronDown' right small icon)
|
||||
template(v-slot:placeholder)
|
||||
v-btn#calendarButton(text tile) {{$t('common.calendar')}}
|
||||
v-icon(v-text='mdiChevronDown' right small icon)
|
||||
.calh.d-flex.justify-center.align-center(slot='placeholder')
|
||||
v-progress-circular(indeterminate)
|
||||
//- v-btn#calendarButton(text tile) {{$t('common.calendar')}}
|
||||
//- v-icon(v-text='mdiChevronDown' right small icon)
|
||||
|
||||
</template>
|
||||
|
||||
@@ -65,6 +66,16 @@ export default {
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.vc-container.vc-is-dark {
|
||||
--gray-900: #111;
|
||||
--gray-700: #333;
|
||||
}
|
||||
|
||||
.vc-container {
|
||||
--gray-400: #999 !important;
|
||||
--rounded-lg: 4px !important;
|
||||
}
|
||||
|
||||
.vc-opacity-0 {
|
||||
opacity: 0.3 !important;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ v-dialog(v-model='show'
|
||||
@keydown.esc='cancel')
|
||||
v-card
|
||||
v-card-title {{ title }}
|
||||
v-card-text(v-show='!!message') {{ message }}
|
||||
v-card-text(v-show='!!message' v-html='message')
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(outlined color='error' @click='cancel') {{$t('common.cancel')}}
|
||||
|
||||
@@ -3,12 +3,11 @@ 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_multidate_event' 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 }}
|
||||
|
||||
@@ -25,8 +24,9 @@ v-col(cols=12)
|
||||
is-inline
|
||||
is-expanded
|
||||
:min-date='type !== "recurrent" && new Date()')
|
||||
template(#placeholder)
|
||||
span.calc Loading
|
||||
//- template(#placeholder)
|
||||
.d-flex.calh.justify-center(slot='placeholder')
|
||||
v-progress-circular(indeterminate)
|
||||
|
||||
div.text-center.mb-2(v-if='type === "recurrent"')
|
||||
span(v-if='value.recurrent.frequency !== "1m" && value.recurrent.frequency !== "2m"') {{ whenPatterns }}
|
||||
@@ -60,7 +60,7 @@ v-col(cols=12)
|
||||
:allowedMinutes='allowedMinutes'
|
||||
format='24hr'
|
||||
@click:minute='menuFromHour = false'
|
||||
@change='hr => change("fromHour", hr)')
|
||||
@input='hr => change("fromHour", hr)')
|
||||
|
||||
|
||||
v-col.col-12.col-sm-6
|
||||
@@ -88,14 +88,14 @@ v-col(cols=12)
|
||||
:allowedMinutes='allowedMinutes'
|
||||
format='24hr'
|
||||
@click:minute='menuDueHour = false'
|
||||
@change='hr => change("dueHour", hr)')
|
||||
@input='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 { mapState, mapActions } from 'vuex'
|
||||
import List from '@/components/List'
|
||||
import { attributesFromEvents } from '../assets/helper'
|
||||
import { mdiClockTimeFourOutline, mdiClockTimeEightOutline, mdiClose } from '@mdi/js'
|
||||
@@ -114,7 +114,6 @@ export default {
|
||||
menuFromHour: false,
|
||||
menuDueHour: false,
|
||||
type: this.value.type || 'normal',
|
||||
events: [],
|
||||
frequencies: [
|
||||
{ value: '1w', text: this.$t('event.each_week') },
|
||||
{ value: '2w', text: this.$t('event.each_2w') },
|
||||
@@ -123,7 +122,7 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['settings']),
|
||||
...mapState(['settings', 'events']),
|
||||
fromDate () {
|
||||
if (this.value.from) {
|
||||
if (this.value.multidate) {
|
||||
@@ -139,7 +138,7 @@ export default {
|
||||
return this.events.filter(e => e.start_datetime >= start && e.start_datetime <= end)
|
||||
},
|
||||
attributes() {
|
||||
return attributesFromEvents(this.events)
|
||||
return attributesFromEvents(this.events.filter(e => e.id !== this.event.id))
|
||||
},
|
||||
whenPatterns() {
|
||||
if (!this.value.from) { return }
|
||||
@@ -193,13 +192,12 @@ export default {
|
||||
} 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)
|
||||
if (!this.events) {
|
||||
this.getEvents()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['getEvents']),
|
||||
updateRecurrent(value) {
|
||||
this.$emit('input', { ...this.value, recurrent: value || null })
|
||||
},
|
||||
@@ -235,6 +233,15 @@ export default {
|
||||
} else if (what === 'dueHour') {
|
||||
if (value) {
|
||||
this.value.due = this.value.due ? this.value.due : this.value.from
|
||||
const [hour, minute] = value.split(':')
|
||||
const [fromHour, fromMinute] = this.value.fromHour.split(':')
|
||||
if (!this.value.multidate) {
|
||||
if (hour < fromHour) {
|
||||
this.value.due = dayjs(this.value.from).add(1, 'day').toDate()
|
||||
} else {
|
||||
this.value.due = dayjs(this.value.from).toDate()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.value.due = null
|
||||
}
|
||||
|
||||
@@ -50,8 +50,8 @@ export default {
|
||||
data ({ $store }) {
|
||||
return {
|
||||
mdiWalk, mdiBike, mdiCar, mdiMapMarker,
|
||||
url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
attribution: '<a target="_blank" href="http://osm.org/copyright">OpenStreetMap</a> contributors',
|
||||
url: $store.state.settings.tilelayer_provider || 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
attribution: $store.state.settings.tilelayer_provider_attribution || "<a target=\"_blank\" href=\"http://osm.org/copyright\">OpenStreetMap</a> contributors",
|
||||
zoom: 14,
|
||||
center: [this.event.place.latitude, this.event.place.longitude],
|
||||
marker: {
|
||||
|
||||
@@ -1,19 +1,51 @@
|
||||
<template lang="pug">
|
||||
#navsearch.mt-2.mt-sm-4(v-if='showCollectionsBar || showSearchBar')
|
||||
v-text-field.mx-2(v-if='showSearchBar' outlined dense hide-details :placeholder='$t("common.search")' :append-icon='mdiMagnify' @input='search' clearable :clear-icon='mdiClose')
|
||||
template(v-slot:prepend-inner)
|
||||
Calendar(v-if='!settings.hide_calendar')
|
||||
v-btn.ml-2.mt-2.gap-2(v-if='showCollectionsBar' small outlined v-for='collection in collections' color='primary' :key='collection.id' :to='`/collection/${encodeURIComponent(collection.name)}`') {{collection.name}}
|
||||
#navsearch.mt-2.mt-sm-4(v-if='showCollectionsBar || showSearchBar || showCalendar')
|
||||
|
||||
div.mx-2
|
||||
client-only(v-if='showSearchBar')
|
||||
v-menu(offset-y :close-on-content-click='false' tile)
|
||||
template(v-slot:activator="{on ,attrs}")
|
||||
v-text-field(hide-details outlined
|
||||
:placeholder='$t("common.search")'
|
||||
@input="v => setFilter(['query', v])" clearable :clear-icon='mdiClose')
|
||||
template(v-slot:append v-if='settings.allow_recurrent_event || settings.allow_multidate_event')
|
||||
v-icon(v-text='mdiCog' v-bind='attrs' v-on='on')
|
||||
v-card(outlined :rounded='"0"')
|
||||
v-card-text
|
||||
v-row(dense)
|
||||
v-col(v-if='settings.allow_recurrent_event')
|
||||
v-switch.mt-0(v-model='show_recurrent' @change="v => setFilter(['show_recurrent', v])"
|
||||
hide-details :label="$t('event.show_recurrent')" inset)
|
||||
v-col(v-if='settings.allow_multidate_event')
|
||||
v-switch.mt-0(v-model='show_multidate' @change="v => setFilter(['show_multidate', v])"
|
||||
hide-details :label="$t('event.show_multidate')" inset)
|
||||
v-row(v-if='!showCalendar')
|
||||
v-col
|
||||
Calendar.mt-2
|
||||
v-text-field(slot='placeholder' outlined hide-details :placeholder="$t('common.search')" :append-icon='mdiCog')
|
||||
|
||||
span(v-if='showCollectionsBar')
|
||||
v-btn.mr-2.mt-2(small outlined v-for='collection in collections'
|
||||
color='primary' :key='collection.id'
|
||||
:to='`/collection/${encodeURIComponent(collection.name)}`') {{collection.name}}
|
||||
|
||||
Calendar.mt-2(v-if='showCalendar')
|
||||
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { mapState, mapActions } from 'vuex'
|
||||
import Calendar from '@/components/Calendar'
|
||||
import { mdiMagnify, mdiClose } from '@mdi/js'
|
||||
import { mdiClose, mdiCog } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
mdiMagnify, mdiClose,
|
||||
collections: []
|
||||
data: ({ $store }) => ({
|
||||
oldRoute: '',
|
||||
mdiClose, mdiCog,
|
||||
collections: [],
|
||||
show_recurrent: $store.state.settings.recurrent_event_visible,
|
||||
show_multidate: true,
|
||||
query: ''
|
||||
}),
|
||||
async fetch () {
|
||||
this.collections = await this.$axios.$get('collections').catch(_e => [])
|
||||
@@ -23,21 +55,27 @@ export default {
|
||||
showSearchBar () {
|
||||
return this.$route.name === 'index'
|
||||
},
|
||||
showCollectionsBar () {
|
||||
return ['index', 'collection-collection'].includes(this.$route.name)
|
||||
showCalendar () {
|
||||
return (!this.settings.hide_calendar && this.$route.name === 'index')
|
||||
},
|
||||
...mapState(['settings'])
|
||||
showCollectionsBar () {
|
||||
const show = ['index', 'collection-collection'].includes(this.$route.name)
|
||||
if (show && this.oldRoute !== this.$route.name) {
|
||||
this.oldRoute = this.$route.name
|
||||
this.$fetch()
|
||||
}
|
||||
return show
|
||||
},
|
||||
...mapState(['settings', 'filter'])
|
||||
},
|
||||
methods: {
|
||||
search (ev) {
|
||||
this.$root.$emit('search', ev)
|
||||
}
|
||||
...mapActions(['setFilter']),
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
#navsearch {
|
||||
margin: 0 auto;
|
||||
max-width: 800px;
|
||||
max-width: 700px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -22,16 +22,19 @@ v-row.mb-4
|
||||
v-list-item-title(v-text='item.name')
|
||||
v-list-item-subtitle(v-text='item.address')
|
||||
|
||||
//- 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")
|
||||
|
||||
v-col(cols=12 md=6)
|
||||
v-combobox(ref='address'
|
||||
v-text-field(v-if="!settings.allow_geolocation"
|
||||
ref='address'
|
||||
:prepend-icon='mdiMap'
|
||||
:disabled='disableAddress'
|
||||
:rules="[ v => disableAddress ? true : $validators.required('common.address')(v)]"
|
||||
:label="$t('common.address')"
|
||||
:hint="$t('event.address_description')"
|
||||
persistent-hint
|
||||
@change="changeAddress"
|
||||
:value="value.address")
|
||||
v-combobox(ref='address' v-else
|
||||
:prepend-icon='mdiMapSearch'
|
||||
:disabled='disableAddress'
|
||||
@input.native='searchAddress'
|
||||
@@ -44,9 +47,9 @@ v-row.mb-4
|
||||
@change='selectAddress'
|
||||
@focus='searchAddress'
|
||||
:items="addressList"
|
||||
:hint="$t('event.address_description' + (settings.allow_geolocation && '_osm'))")
|
||||
:hint="$t('event.address_description_osm')")
|
||||
template(v-slot:message="{message, key}")
|
||||
span(v-html='message' :key="key")
|
||||
span(v-html='message' :key="key")
|
||||
template(v-slot:item="{ item, attrs, on }")
|
||||
v-list-item(v-bind='attrs' v-on='on')
|
||||
v-icon.pr-4(v-text='loadCoordinatesResultIcon(item)')
|
||||
@@ -76,7 +79,7 @@ export default {
|
||||
props: {
|
||||
value: { type: Object, default: () => ({}) }
|
||||
},
|
||||
data () {
|
||||
data ( {$store} ) {
|
||||
return {
|
||||
mdiMap, mdiMapMarker, mdiPlus, mdiMapSearch, mdiLatitude, mdiLongitude, mdiRoadVariant, mdiHome, mdiCityVariant,
|
||||
place: { },
|
||||
@@ -91,7 +94,14 @@ export default {
|
||||
node: mdiMapMarker,
|
||||
relation: mdiCityVariant,
|
||||
},
|
||||
nominatim_class: ['amenity', 'shop', 'tourism', 'leisure', 'building']
|
||||
nominatim_class: ['amenity', 'shop', 'tourism', 'leisure', 'building'],
|
||||
photon_osm_key: ['amenity', 'shop', 'tourism', 'leisure', 'building'],
|
||||
photon_osm_type: {
|
||||
'W': mdiRoadVariant,
|
||||
'N': mdiMapMarker,
|
||||
'R': mdiCityVariant,
|
||||
},
|
||||
geocoding_provider_type: $store.state.settings.geocoding_provider_type || 'Nominatim'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -115,21 +125,33 @@ export default {
|
||||
return matches
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.$nextTick( () => {
|
||||
this.search()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
search: debounce(async function(ev) {
|
||||
const search = ev.target.value.trim().toLowerCase()
|
||||
const search = ev ? ev.target.value.trim().toLowerCase() : ''
|
||||
this.places = await this.$axios.$get(`place?search=${search}`)
|
||||
if (!search && this.places.length) { return this.places }
|
||||
const matches = this.places.find(p => search === p.name.toLocaleLowerCase())
|
||||
if (!matches && search) {
|
||||
this.places.unshift({ create: true, name: ev.target.value.trim() })
|
||||
}
|
||||
}, 100),
|
||||
}, 200),
|
||||
loadCoordinatesResultIcon(item) {
|
||||
if ( this.nominatim_class.includes(item.class)) {
|
||||
return this.mdiHome
|
||||
if (this.geocoding_provider_type == "Nominatim") {
|
||||
if ( this.nominatim_class.includes(item.class)) {
|
||||
return this.mdiHome
|
||||
}
|
||||
return this.nominatim_osm_type[item.type]
|
||||
} else if (this.geocoding_provider_type == "Photon") {
|
||||
if ( this.photon_osm_key.includes(item.class)) {
|
||||
return this.mdiHome
|
||||
}
|
||||
return this.photon_osm_type[item.type]
|
||||
}
|
||||
return this.nominatim_osm_type[item.type]
|
||||
},
|
||||
selectPlace (p) {
|
||||
if (!p) { return }
|
||||
@@ -168,11 +190,11 @@ export default {
|
||||
}
|
||||
this.$emit('input', { ...this.place })
|
||||
},
|
||||
// changeAddress (v) {
|
||||
// this.place.address = v
|
||||
// this.$emit('input', { ...this.place })
|
||||
// this.disableDetails = false
|
||||
// },
|
||||
changeAddress (v) {
|
||||
this.place.address = v
|
||||
this.$emit('input', { ...this.place })
|
||||
this.disableDetails = false
|
||||
},
|
||||
selectAddress (v) {
|
||||
if (!v) { return }
|
||||
if (typeof v === 'object') {
|
||||
@@ -220,22 +242,55 @@ export default {
|
||||
|
||||
if (searchCoordinates.length) {
|
||||
this.loading = true
|
||||
const ret = await this.$axios.$get(`placeNominatim/${searchCoordinates}`)
|
||||
if (ret && ret.length) {
|
||||
this.addressList = ret.map(v => {
|
||||
const name = get(v.namedetails, 'alt_name', get(v.namedetails, 'name'))
|
||||
const address = v.display_name ? v.display_name.replace(name, '').replace(/^, ?/, '') : ''
|
||||
return {
|
||||
class: v.class,
|
||||
type: v.osm_type,
|
||||
lat: v.lat,
|
||||
lon: v.lon,
|
||||
name,
|
||||
address
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.addressList = []
|
||||
const ret = await this.$axios.$get(`placeOSM/${this.geocoding_provider_type}/${searchCoordinates}`)
|
||||
if (this.geocoding_provider_type == "Nominatim") {
|
||||
if (ret && ret.length) {
|
||||
this.addressList = ret.map(v => {
|
||||
const name = get(v.namedetails, 'alt_name', get(v.namedetails, 'name'))
|
||||
const address = v.display_name ? v.display_name.replace(name, '').replace(/^, ?/, '') : ''
|
||||
return {
|
||||
class: v.class,
|
||||
type: v.osm_type,
|
||||
lat: v.lat,
|
||||
lon: v.lon,
|
||||
name,
|
||||
address
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.addressList = []
|
||||
}
|
||||
} else if (this.geocoding_provider_type == "Photon") {
|
||||
let photon_properties = ['housenumber', 'street', 'locality', 'district', 'city', 'county', 'state', 'postcode', 'country']
|
||||
|
||||
if (ret) {
|
||||
this.addressList = ret.features.map(v => {
|
||||
let pre_name = v.properties.name || v.properties.street || ''
|
||||
let pre_address = ''
|
||||
|
||||
photon_properties.forEach((item, i) => {
|
||||
let last = i == (photon_properties.length - 1)
|
||||
if (v.properties[item] && !last) {
|
||||
pre_address += v.properties[item]+', '
|
||||
} else if (v.properties[item]) {
|
||||
pre_address += v.properties[item]
|
||||
}
|
||||
});
|
||||
|
||||
let name = pre_name
|
||||
let address = pre_address
|
||||
return {
|
||||
class: v.properties.osm_key,
|
||||
type: v.properties.osm_type,
|
||||
lat: v.geometry.coordinates[1],
|
||||
lon: v.geometry.coordinates[0],
|
||||
name,
|
||||
address
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.addressList = []
|
||||
}
|
||||
}
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ v-container
|
||||
:prepend-icon="mdiTagMultiple"
|
||||
chips small-chips multiple deletable-chips hide-no-data hide-selected persistent-hint
|
||||
:disabled="!collection.id"
|
||||
placeholder='Tutte'
|
||||
placeholder='All'
|
||||
@input.native='searchTags'
|
||||
@focus='searchTags'
|
||||
:delimiters="[',', ';']"
|
||||
@@ -69,7 +69,7 @@ v-container
|
||||
//- v-list-item-subtitle(v-text='item.address')
|
||||
|
||||
v-col(cols=2)
|
||||
v-btn(color='primary' text @click='addFilter' :disabled='!collection.id || !filterPlaces.length && !filterTags.length') add <v-icon v-text='mdiPlus'></v-icon>
|
||||
v-btn(color='primary' :loading='loading' text @click='addFilter' :disabled='loading || !collection.id || !filterPlaces.length && !filterTags.length') add <v-icon v-text='mdiPlus'></v-icon>
|
||||
|
||||
v-data-table(
|
||||
:headers='filterHeaders'
|
||||
@@ -110,6 +110,9 @@ v-container
|
||||
<script>
|
||||
import get from 'lodash/get'
|
||||
import debounce from 'lodash/debounce'
|
||||
import isEqual from 'lodash/isEqual'
|
||||
import sortBy from 'lodash/sortBy'
|
||||
|
||||
import { mdiPencil, mdiChevronLeft, mdiChevronRight, mdiMagnify, mdiPlus, mdiTagMultiple, mdiMapMarker, mdiDeleteForever, mdiCloseCircle, mdiChevronDown } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
@@ -147,7 +150,7 @@ export default {
|
||||
|
||||
methods: {
|
||||
searchTags: debounce(async function (ev) {
|
||||
this.tags = await this.$axios.$get(`/tag?search=${ev.target.value}`)
|
||||
this.tags = await this.$axios.$get(`/tag?search=${encodeURIComponent(ev.target.value)}`)
|
||||
}, 100),
|
||||
searchPlaces: debounce(async function (ev) {
|
||||
this.places = await this.$axios.$get(`/place?search=${ev.target.value}`)
|
||||
@@ -163,9 +166,20 @@ export default {
|
||||
this.loading = true
|
||||
const tags = this.filterTags
|
||||
const places = this.filterPlaces.map(p => ({ id: p.id, name: p.name }))
|
||||
const filter = await this.$axios.$post('/filter', { collectionId: this.collection.id, tags, places })
|
||||
|
||||
const filter = { collectionId: this.collection.id, tags, places }
|
||||
|
||||
// tags and places are JSON field and there's no way to use them inside a unique constrain
|
||||
//
|
||||
const alreadyExists = this.filters.find(f =>
|
||||
isEqual(sortBy(f.places, 'id'), sortBy(filter.places, 'id')) && isEqual(sortBy(f.tags), sortBy(filter.tags))
|
||||
)
|
||||
|
||||
if (alreadyExists) return
|
||||
|
||||
const ret = await this.$axios.$post('/filter', filter )
|
||||
this.$fetch()
|
||||
this.filters.push(filter)
|
||||
this.filters.push(ret)
|
||||
this.filterTags = []
|
||||
this.filterPlaces = []
|
||||
this.loading = false
|
||||
|
||||
169
components/admin/Geolocation.vue
Normal file
169
components/admin/Geolocation.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<template lang="pug">
|
||||
v-card
|
||||
v-card-title {{$t('admin.geolocation')}}
|
||||
v-card-text
|
||||
p.mb-6(v-html="$t('admin.geolocation_description')")
|
||||
|
||||
v-form
|
||||
v-row
|
||||
v-col(md=3)
|
||||
v-autocomplete.mb-4(v-model='geocoding_provider_type'
|
||||
@blur="save('geocoding_provider_type', geocoding_provider_type )"
|
||||
:label="$t('admin.geocoding_provider_type')"
|
||||
:hint="$t('admin.geocoding_provider_type_help')"
|
||||
persistent-hint
|
||||
:items="geocoding_provider_type_items"
|
||||
:placeholder="geocoding_provider_type_default")
|
||||
|
||||
v-col(md=5)
|
||||
v-text-field.mb-4(v-model='geocoding_provider'
|
||||
@blur="save('geocoding_provider', geocoding_provider )"
|
||||
:label="$t('admin.geocoding_provider')"
|
||||
:hint="$t('admin.geocoding_provider_help')"
|
||||
persistent-hint
|
||||
:placeholder="geocoding_provider_default")
|
||||
|
||||
v-col(md=4)
|
||||
v-autocomplete.mb-6(v-model="geocoding_countrycodes" :disabled="!(geocoding_provider_type === null || geocoding_provider_type === 'Nominatim')"
|
||||
:append-icon='mdiChevronDown'
|
||||
@blur="save('geocoding_countrycodes', geocoding_countrycodes )"
|
||||
:label="$t('admin.geocoding_countrycodes')"
|
||||
:items="countries"
|
||||
multiple chips small-chips persistent-hint
|
||||
item-value="code"
|
||||
item-text="name"
|
||||
:hint="$t('admin.geocoding_countrycodes_help')")
|
||||
|
||||
v-row
|
||||
v-col(md=6)
|
||||
v-text-field.mb-4(v-model='tilelayer_provider'
|
||||
@blur="save('tilelayer_provider', tilelayer_provider )"
|
||||
:label="$t('admin.tilelayer_provider')"
|
||||
:hint="$t('admin.tilelayer_provider_help')"
|
||||
persistent-hint
|
||||
:placeholder="tilelayer_provider_default")
|
||||
|
||||
v-col(md=6)
|
||||
v-text-field(v-model='tilelayer_provider_attribution'
|
||||
@blur="save('tilelayer_provider_attribution', tilelayer_provider_attribution )"
|
||||
:label="$t('admin.tilelayer_provider_attribution')"
|
||||
:placeholder="tilelayer_provider_attribution_default")
|
||||
|
||||
div(id="leaflet-map-preview" max-height='10px')
|
||||
//- Map
|
||||
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(color='primary' @click='testGeocodingProvider' :loading='testGeocodingLoading' outlined ) {{$t('admin.geocoding_test_button')}}
|
||||
v-btn(color='primary' @click='testTileLayerProvider' :loading='testTileLayerLoading' outlined ) {{$t('admin.tilelayer_test_button')}}
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { mapActions, mapState } from 'vuex'
|
||||
import { isoCountries } from '../../server/helpers/geolocation'
|
||||
import { mdiChevronDown } from '@mdi/js'
|
||||
// import Map from '~/components/Map'
|
||||
import "leaflet/dist/leaflet.css"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
setup: { type: Boolean, default: false }
|
||||
},
|
||||
// components: { Map },
|
||||
data ({ $store }) {
|
||||
return {
|
||||
mdiChevronDown,
|
||||
loading: false,
|
||||
testGeocodingLoading: false,
|
||||
testTileLayerLoading: false,
|
||||
geocoding_provider_type_items: ['Nominatim', 'Photon'],
|
||||
geocoding_provider_type: $store.state.settings.geocoding_provider_type || '',
|
||||
geocoding_provider_type_default: 'Nominatim',
|
||||
geocoding_provider: $store.state.settings.geocoding_provider || '',
|
||||
geocoding_provider_default: "https://nominatim.openstreetmap.org/search" ,
|
||||
geocoding_countrycodes: $store.state.settings.geocoding_countrycodes || [],
|
||||
tilelayer_provider: $store.state.settings.tilelayer_provider || '',
|
||||
tilelayer_provider_default: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
tilelayer_provider_attribution: $store.state.settings.tilelayer_provider_attribution || '',
|
||||
tilelayer_provider_attribution_default: '<a target=\'_blank\' href=\"http://osm.org/copyright\">OpenStreetMap</a> contributors',
|
||||
countries: isoCountries,
|
||||
mapPreviewTest: null,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (process.client) {
|
||||
const L = require('leaflet')
|
||||
}
|
||||
},
|
||||
computed: mapState(['settings', 'events']),
|
||||
methods: {
|
||||
...mapActions(['setSetting']),
|
||||
async testGeocodingProvider () {
|
||||
this.testGeocodingLoading = true
|
||||
const geocodingProviderTest = this.geocoding_provider || this.geocoding_provider_default
|
||||
const geocodingSoftwareTest = this.geocoding_provider_type || this.geocoding_provider_type_default
|
||||
const geocodingQuery = 'building'
|
||||
|
||||
try {
|
||||
if (geocodingSoftwareTest === 'Nominatim') {
|
||||
const geolocation = await this.$axios.$get(`${geocodingProviderTest}`, {timeout: 3000, params: {q: `${geocodingQuery}`, format: 'json', limit: 1 }} )
|
||||
} else if (geocodingSoftwareTest === 'Photon') {
|
||||
const geolocation = await this.$axios.$get(`${geocodingProviderTest}`, {timeout: 3000, params: {q: `${geocodingQuery}`, limit: 1}} )
|
||||
}
|
||||
|
||||
this.$root.$message(this.$t('admin.geocoding_test_success', { service_name: geocodingProviderTest }), { color: 'success' })
|
||||
} catch (e) {
|
||||
this.$root.$message(this.$t('admin.tilelayer_test_error', { service_name: geocodingProviderTest }), { color: 'error' })
|
||||
}
|
||||
this.testGeocodingLoading = false
|
||||
},
|
||||
async testTileLayerProvider () {
|
||||
this.testTileLayerLoading = true
|
||||
const tileThis = this
|
||||
const tileLayerTest = this.tilelayer_provider || this.tilelayer_provider_default
|
||||
const tileLayerAttributionTest = this.tilelayer_provider_attribution || this.tilelayer_provider_attribution_default
|
||||
|
||||
// init tilelayer
|
||||
if (this.mapPreviewTest == null) {
|
||||
this.mapPreviewTest = L.map("leaflet-map-preview").setView([40,40],10);
|
||||
}
|
||||
this.tileLayer = L.tileLayer(`${tileLayerTest}`, {attribution: `${tileLayerAttributionTest}`})
|
||||
this.tileLayer.addTo(this.mapPreviewTest)
|
||||
|
||||
// tilelayer events inherited from gridlayer https://leafletjs.com/reference.html#gridlayer
|
||||
this.tileLayer.on('tileload', function (event) {
|
||||
tileThis.tileLayerTestSucess(event, tileLayerTest)
|
||||
});
|
||||
this.tileLayer.on('tileerror', function(error, tile) {
|
||||
tileThis.tileLayerTestError(event, tileLayerTest)
|
||||
tileThis.tileLayer = null
|
||||
});
|
||||
this.testTileLayerLoading = false
|
||||
},
|
||||
save (key, value) {
|
||||
if (this.settings[key] !== value) {
|
||||
this.setSetting({ key, value })
|
||||
}
|
||||
},
|
||||
done () {
|
||||
this.$emit('close')
|
||||
},
|
||||
geocodingTestError(event, tileLayerTest) {
|
||||
this.$root.$message(this.$t('admin.geocoding_test_error', { service_name: geocodingTest }), { color: 'error' })
|
||||
},
|
||||
tileLayerTestSucess(event, tileLayerTest) {
|
||||
this.$root.$message(this.$t('admin.tilelayer_test_success', { service_name: tileLayerTest }), { color: 'success' })
|
||||
},
|
||||
tileLayerTestError(event, tileLayerTest) {
|
||||
this.$root.$message(this.$t('admin.tilelayer_test_error', { service_name: tileLayerTest }), { color: 'error' })
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#leaflet-map-preview {
|
||||
height: 20rem;
|
||||
}
|
||||
</style>
|
||||
@@ -68,7 +68,7 @@ import debounce from 'lodash/debounce'
|
||||
import get from 'lodash/get'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
data( {$store} ) {
|
||||
return {
|
||||
mdiPencil, mdiChevronRight, mdiChevronLeft, mdiMagnify, mdiEye, mdiMapSearch, mdiChevronDown,
|
||||
loading: false,
|
||||
@@ -84,11 +84,12 @@ export default {
|
||||
{ value: 'address', text: this.$t('common.address') },
|
||||
{ value: 'map', text: 'Map' },
|
||||
{ value: 'actions', text: this.$t('common.actions'), align: 'right' }
|
||||
]
|
||||
],
|
||||
geocoding_provider_type: $store.state.settings.geocoding_provider_type || 'Nominatim'
|
||||
}
|
||||
},
|
||||
async fetch() {
|
||||
this.places = await this.$axios.$get('/place/all')
|
||||
this.places = await this.$axios.$get('/places')
|
||||
},
|
||||
computed: {
|
||||
...mapState(['settings']),
|
||||
@@ -124,7 +125,7 @@ export default {
|
||||
this.place.latitude = this.place.longitude = null
|
||||
}
|
||||
this.$emit('input', { ...this.place })
|
||||
},
|
||||
},
|
||||
searchAddress: debounce(async function(ev) {
|
||||
const pre_searchCoordinates = ev.target.value.trim().toLowerCase()
|
||||
// allow pasting coordinates lat/lon and lat,lon
|
||||
@@ -159,24 +160,59 @@ export default {
|
||||
|
||||
if (searchCoordinates.length) {
|
||||
this.loading = true
|
||||
const ret = await this.$axios.$get(`placeNominatim/${searchCoordinates}`)
|
||||
if (ret && ret.length) {
|
||||
this.addressList = ret.map(v => {
|
||||
const name = get(v.namedetails, 'alt_name', get(v.namedetails, 'name'))
|
||||
const address = v.display_name ? v.display_name.replace(name, '').replace(/^, ?/, '') : ''
|
||||
return {
|
||||
lat: v.lat,
|
||||
lon: v.lon,
|
||||
name,
|
||||
address
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.addressList = []
|
||||
const ret = await this.$axios.$get(`placeOSM/${this.geocoding_provider_type}/${searchCoordinates}`)
|
||||
if (this.geocoding_provider_type == "Nominatim") {
|
||||
if (ret && ret.length) {
|
||||
this.addressList = ret.map(v => {
|
||||
const name = get(v.namedetails, 'alt_name', get(v.namedetails, 'name'))
|
||||
const address = v.display_name ? v.display_name.replace(name, '').replace(/^, ?/, '') : ''
|
||||
return {
|
||||
class: v.class,
|
||||
type: v.osm_type,
|
||||
lat: v.lat,
|
||||
lon: v.lon,
|
||||
name,
|
||||
address
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.addressList = []
|
||||
}
|
||||
} else if (this.geocoding_provider_type == "Photon") {
|
||||
let photon_properties = ['housenumber', 'street', 'district', 'city', 'county', 'state', 'postcode', 'country']
|
||||
|
||||
if (ret) {
|
||||
this.addressList = ret.features.map(v => {
|
||||
let pre_name = v.properties.name || v.properties.street || ''
|
||||
let pre_address = ''
|
||||
|
||||
photon_properties.forEach((item, i) => {
|
||||
let last = i == (photon_properties.length - 1)
|
||||
if (v.properties[item] && !last) {
|
||||
pre_address += v.properties[item]+', '
|
||||
} else if (v.properties[item]) {
|
||||
pre_address += v.properties[item]
|
||||
}
|
||||
});
|
||||
|
||||
let name = pre_name
|
||||
let address = pre_address
|
||||
return {
|
||||
class: v.properties.osm_key,
|
||||
type: v.properties.osm_type,
|
||||
lat: v.geometry.coordinates[1],
|
||||
lon: v.geometry.coordinates[0],
|
||||
name,
|
||||
address
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.addressList = []
|
||||
}
|
||||
}
|
||||
this.loading = false
|
||||
}
|
||||
}, 300)
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -7,7 +7,9 @@ v-card
|
||||
|
||||
v-text-field(v-model='admin_email'
|
||||
@blur="save('admin_email', admin_email )"
|
||||
:label="$t('admin.sender_email')"
|
||||
:label="$t('admin.admin_email')"
|
||||
:hint="$t('admin.admin_email_help')"
|
||||
persistent-hint
|
||||
:rules="$validators.email")
|
||||
|
||||
v-switch(v-model='smtp.sendmail'
|
||||
|
||||
@@ -39,6 +39,10 @@ v-container
|
||||
inset
|
||||
:label="$t('admin.allow_anon_event')")
|
||||
|
||||
v-switch.mt-1(v-model='allow_multidate_event'
|
||||
inset
|
||||
:label="$t('admin.allow_multidate_event')")
|
||||
|
||||
v-switch.mt-1(v-model='allow_recurrent_event'
|
||||
inset
|
||||
:label="$t('admin.allow_recurrent_event')")
|
||||
@@ -57,32 +61,35 @@ v-container
|
||||
|
||||
v-card-actions
|
||||
v-btn(text @click='showSMTP=true')
|
||||
<v-icon v-if='!settings.admin_email' color='error' v-text='mdiAlert'></v-icon> {{$t('admin.show_smtp_setup')}}
|
||||
v-btn(text @click='$emit("complete")' color='primary' v-if='setup') {{$t('common.next')}}
|
||||
v-icon(v-text='mdiArrowRight')
|
||||
<v-icon v-if='!settings.admin_email' color='error' class="mr-2" v-text='mdiAlert'></v-icon> {{$t('admin.show_smtp_setup')}}
|
||||
|
||||
v-btn(text @click='$emit("complete")' color='primary' v-if='setup') {{$t('common.next')}}
|
||||
v-icon(v-text='mdiArrowRight')
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import SMTP from './SMTP.vue'
|
||||
import Geolocation from './Geolocation.vue'
|
||||
import { mapActions, mapState } from 'vuex'
|
||||
import moment from 'dayjs'
|
||||
import tzNames from './tz.json'
|
||||
import { mdiAlert, mdiArrowRight } from '@mdi/js'
|
||||
import { mdiAlert, mdiArrowRight, mdiMap } from '@mdi/js'
|
||||
const locales = require('../../locales/index')
|
||||
|
||||
export default {
|
||||
props: {
|
||||
setup: { type: Boolean, default: false }
|
||||
},
|
||||
components: { SMTP },
|
||||
components: { SMTP, Geolocation },
|
||||
name: 'Settings',
|
||||
data ({ $store }) {
|
||||
return {
|
||||
mdiAlert, mdiArrowRight,
|
||||
mdiAlert, mdiArrowRight, mdiMap,
|
||||
title: $store.state.settings.title,
|
||||
description: $store.state.settings.description,
|
||||
locales: Object.keys(locales).map(locale => ({ value: locale, text: locales[locale] })),
|
||||
showSMTP: false,
|
||||
showGeolocationConfigs: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -107,6 +114,10 @@ export default {
|
||||
get () { return this.settings.allow_recurrent_event },
|
||||
set (value) { this.setSetting({ key: 'allow_recurrent_event', value }) }
|
||||
},
|
||||
allow_multidate_event: {
|
||||
get () { return this.settings.allow_multidate_event },
|
||||
set (value) { this.setSetting({ key: 'allow_multidate_event', value }) }
|
||||
},
|
||||
recurrent_event_visible: {
|
||||
get () { return this.settings.recurrent_event_visible },
|
||||
set (value) { this.setSetting({ key: 'recurrent_event_visible', value }) }
|
||||
|
||||
114
components/admin/Tags.vue
Normal file
114
components/admin/Tags.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<template lang='pug'>
|
||||
v-container
|
||||
v-card-title {{ $t('common.tags') }}
|
||||
v-spacer
|
||||
v-text-field(v-model='search'
|
||||
:append-icon='mdiMagnify' outlined rounded
|
||||
:label="$t('common.search')"
|
||||
single-line hide-details)
|
||||
|
||||
v-dialog(v-model='dialog' width='600' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
v-card
|
||||
v-card-title {{$t('admin.edit_tag')}} -
|
||||
strong.ml-2 {{tag.tag}}
|
||||
v-card-subtitle {{$tc('admin.edit_tag_help', tag.count)}}
|
||||
v-card-text
|
||||
v-form(v-model='valid' ref='form' lazy-validation)
|
||||
v-combobox(v-model='newTag'
|
||||
:prepend-icon="mdiTag"
|
||||
hide-no-data
|
||||
persistent-hint
|
||||
:items="tags"
|
||||
:return-object='false'
|
||||
item-value='tag'
|
||||
item-text='tag'
|
||||
:label="$t('common.tags')")
|
||||
template(v-slot:item="{ item, on, attrs }")
|
||||
span "{{item.tag}}" <small>({{item.count}})</small>
|
||||
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(@click='dialog = false' outlined color='warning') {{ $t('common.cancel') }}
|
||||
v-btn(@click='saveTag' color='primary' outlined :loading='loading'
|
||||
:disable='!valid || loading') {{ $t('common.save') }}
|
||||
|
||||
v-card-text
|
||||
v-data-table(
|
||||
:headers='headers'
|
||||
:items='tags'
|
||||
:hide-default-footer='tags.length < 5'
|
||||
:header-props='{ sortIcon: mdiChevronDown }'
|
||||
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
|
||||
:search='search')
|
||||
template(v-slot:item.map='{ item }')
|
||||
span {{item.latitude && item.longitude && 'YEP' }}
|
||||
template(v-slot:item.actions='{ item }')
|
||||
v-btn(@click='editTag(item)' color='primary' icon)
|
||||
v-icon(v-text='mdiPencil')
|
||||
nuxt-link(:to='`/tag/${item.tag}`')
|
||||
v-icon(v-text='mdiEye')
|
||||
v-btn(@click='removeTag(item)' color='primary' icon)
|
||||
v-icon(v-text='mdiDeleteForever')
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { mdiPencil, mdiChevronLeft, mdiChevronRight, mdiMagnify, mdiEye, mdiMapSearch, mdiChevronDown, mdiDeleteForever, mdiTag } from '@mdi/js'
|
||||
import { mapState } from 'vuex'
|
||||
import get from 'lodash/get'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
mdiPencil, mdiChevronRight, mdiChevronLeft, mdiMagnify, mdiEye, mdiMapSearch, mdiChevronDown, mdiDeleteForever, mdiTag,
|
||||
loading: false,
|
||||
dialog: false,
|
||||
valid: false,
|
||||
tag: {},
|
||||
newTag: '',
|
||||
tags: [],
|
||||
search: '',
|
||||
headers: [
|
||||
{ value: 'tag', text: this.$t('common.tag') },
|
||||
{ value: 'count', text: 'N.' },
|
||||
{ value: 'actions', text: this.$t('common.actions'), align: 'right' }
|
||||
]
|
||||
}
|
||||
},
|
||||
async fetch() {
|
||||
this.tags = await this.$axios.$get('/tags')
|
||||
},
|
||||
computed: {
|
||||
...mapState(['settings']),
|
||||
},
|
||||
methods: {
|
||||
editTag(item) {
|
||||
this.tag.tag = item.tag
|
||||
this.tag.count = item.count
|
||||
this.dialog = true
|
||||
},
|
||||
async saveTag() {
|
||||
if (!this.$refs.form.validate()) return
|
||||
this.loading = true
|
||||
this.$nextTick( async () => {
|
||||
await this.$axios.$put('/tag', { tag: this.tag.tag, newTag: this.newTag })
|
||||
await this.$fetch()
|
||||
this.newTag = ''
|
||||
this.loading = false
|
||||
this.dialog = false
|
||||
})
|
||||
},
|
||||
async removeTag(tag) {
|
||||
const ret = await this.$root.$confirm('admin.delete_tag_confirm', { tag: tag.tag, n: tag.count })
|
||||
if (!ret) { return }
|
||||
try {
|
||||
await this.$axios.$delete(`/tag/${encodeURIComponent(tag.tag)}`)
|
||||
await this.$fetch()
|
||||
} catch (e) {
|
||||
const err = get(e, 'response.data.errors[0].message', e)
|
||||
this.$root.$message(this.$t(err), { color: 'error' })
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -16,6 +16,7 @@ v-container
|
||||
:label="$t('admin.hide_calendar')")
|
||||
|
||||
v-card-title {{$t('admin.default_images')}}
|
||||
v-card-subtitle(v-html="$t('admin.default_images_help')")
|
||||
v-card-text
|
||||
v-row
|
||||
v-col(cols='4')
|
||||
@@ -109,12 +110,13 @@ import { mdiDeleteForever, mdiRestore, mdiPlus, mdiChevronUp } from '@mdi/js'
|
||||
export default {
|
||||
name: 'Theme',
|
||||
data () {
|
||||
const t = new Date().getMilliseconds()
|
||||
return {
|
||||
mdiDeleteForever, mdiRestore, mdiPlus, mdiChevronUp,
|
||||
valid: false,
|
||||
logoKey: 0,
|
||||
fallbackImageKey: 0,
|
||||
headerImageKey: 0,
|
||||
logoKey: t,
|
||||
fallbackImageKey: t,
|
||||
headerImageKey: t,
|
||||
link: { href: '', label: '' },
|
||||
linkModal: false
|
||||
// menu: [false, false, false, false]
|
||||
|
||||
Reference in New Issue
Block a user