Feat/filters sequence list

This commit is contained in:
Jean Andreani
2024-01-22 14:08:46 +00:00
parent cec383e424
commit 9b6bdc394b
24 changed files with 604 additions and 144 deletions

View File

@@ -19,6 +19,7 @@ COPY *.json *.js *.ts *.html ./
# Replace env variables by placeholder for dynamic change on container start
ENV VITE_INSTANCE_NAME=DOCKER_VITE_INSTANCE_NAME
ENV VITE_RASTER_TILE=DOCKER_VITE_RASTER_TILE
ENV VITE_API_URL=DOCKER_VITE_API_URL
ENV VITE_TILES=DOCKER_VITE_TILES
ENV VITE_MAX_ZOOM=DOCKER_VITE_MAX_ZOOM
@@ -50,6 +51,7 @@ ENV VITE_INSTANCE_NAME="GeoVisio/Docker"
ENV VITE_API_URL="https://panoramax.openstreetmap.fr"
ENV VITE_TILES="https://tile-vect.openstreetmap.fr/styles/basic/style.json"
ENV VITE_MAX_ZOOM=""
ENV VITE_RASTER_TILE=""
ENV VITE_ZOOM=""
ENV VITE_CENTER=""

View File

@@ -14,6 +14,7 @@ Available parameters are:
- `VITE_MAX_ZOOM`: the max zoom to use on the map (defaults to 24).
- `VITE_ZOOM`: the zoom to use at the initialization of the map (defaults to 0).
- `VITE_CENTER`: the center position to use at the initialization of the map (defaults to 0).
- `VITE_RASTER_TILE`: the raster tile. Example : `https://maplibre.org/maplibre-style-spec/sources/#raster`.
- Settings for the work environment:
- `NPM_CONFIG_PRODUCTION`: is it production environment (`true`, `false`)
- `YARN_PRODUCTION`: same as below, but if you use Yarn instead of NPM

View File

@@ -25,10 +25,11 @@
"axios": "^1.2.3",
"bootstrap": "^5.2.3",
"bootstrap-icons": "^1.10.3",
"geovisio": "2.3.0",
"geovisio": "2.3.1",
"moment": "^2.29.4",
"pako": "^2.1.0",
"pinia": "^2.1.4",
"v-calendar": "^3.1.2",
"vue": "^3.2.45",
"vue-axios": "^3.5.2",
"vue-draggable-resizable-vue3": "^2.3.1-beta.13",

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -59,6 +59,10 @@ defineProps({
color: var(--white);
background-color: var(--black);
}
.button-border--black {
background-color: var(--white);
border: toRem(0.1) solid var(--black);
}
.button--blue {
color: var(--white);
background-color: var(--blue);
@@ -93,10 +97,7 @@ defineProps({
.icon {
font-size: toRem(1.4);
color: var(--blue);
margin-right: 0;
}
.text {
margin-left: toRem(1);
margin-right: toRem(1);
}
}
.no-text {
@@ -104,7 +105,6 @@ defineProps({
width: toRem(3);
padding: 0;
.icon {
color: var(--black);
font-size: toRem(1.8);
margin-right: 0;
}
@@ -113,6 +113,16 @@ defineProps({
color: var(--white);
margin-right: 0;
}
.no-text-blue-dark .icon {
color: var(--blue-dark);
margin-right: 0;
font-size: toRem(1.6);
}
.no-text-green .icon {
color: var(--blue);
margin-right: 0;
font-size: toRem(1.6);
}
.background-white {
background-color: var(--white);
}
@@ -123,6 +133,13 @@ defineProps({
color: var(--grey-semi-dark);
}
}
.link--black {
color: var(--black);
.icon {
font-size: toRem(1.4);
color: var(--black);
}
}
.link--red {
color: var(--red);
background-color: var(--white);

View File

@@ -101,7 +101,7 @@ const isDisabled = computed<boolean>(() => props.isLoading && !props.isLoaded)
width: 100%;
}
.edit-button {
background-color: var(--grey);
background-color: var(--blue-dark);
border-radius: 50%;
height: toRem(3.5);
width: toRem(3.5);

View File

@@ -13,15 +13,10 @@
<i class="bi bi-x-circle-fill"></i>
</button>
<div class="modal-header">
<h5>{{ $t('pages.upload.modal_error_title') }}</h5>
<h5>{{ title }}</h5>
</div>
<div class="modal-body">
<ul>
<li v-for="item in uploadErrors" class="error-item">
<span>{{ item.name }} - </span>
<span>{{ item.details.error }}</span>
</li>
</ul>
<slot name="body"></slot>
</div>
</div>
</div>
@@ -30,15 +25,13 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import type { PropType } from 'vue'
import { Modal } from 'bootstrap'
let bsModal = ref()
import type { uploadErrorInterface } from '@/views/interfaces/UploadPicturesView'
defineProps({
uploadErrors: {
type: Array as PropType<uploadErrorInterface[]>,
default: []
title: {
type: String,
default: ''
}
})
@@ -62,12 +55,6 @@ ul {
.modal {
background: rgba(10, 31, 105, 0.6);
}
.error-item {
padding: toRem(1);
&:nth-child(odd) {
background-color: var(--grey);
}
}
.modal-content {
border-radius: toRem(1.5);
}

View File

@@ -74,7 +74,16 @@ async function setupViewerMap(tiles: string): Promise<void> {
const maxZoom = import.meta.env.VITE_MAX_ZOOM
const zoom = import.meta.env.VITE_ZOOM
const center = import.meta.env.VITE_CENTER
const raster = import.meta.env.VITE_RASTER_TILE
let paramsViewer: ViewerInterface = { map: { startWide: true } }
if (raster && raster !== '') {
paramsViewer = {
map: {
...paramsViewer.map,
raster: JSON.parse(raster)
}
}
}
if (center && center !== '') {
const centerMap = center.split(',').map((el: string) => parseInt(el))
paramsViewer = {

View File

@@ -0,0 +1,166 @@
<template>
<div class="wrapper-calendar">
<div class="inputs-wrapper">
<div class="input-wrapper">
<i class="bi bi-calendar-plus"></i>
<input
:value="
range && range.start
? formatDate(new Date(range.start), 'YYYY-MM-DD')
: null
"
:placeholder="$t('pages.sequences.radio_date_placeholder')"
@input="setStartValue"
class="input"
/>
</div>
<i class="bi bi-arrow-right"></i>
<div class="input-wrapper">
<i class="bi bi-calendar-plus"></i>
<input
:value="
range && range.end
? formatDate(new Date(range.end), 'YYYY-MM-DD')
: null
"
:placeholder="$t('pages.sequences.radio_date_placeholder')"
@input="setEndValue"
class="input"
/>
</div>
</div>
</div>
<v-date-picker
ref="datePicker"
v-model="range"
mode="date"
:masks="{
input: 'YYYY-MM-DD'
}"
is-range
expanded
:max-date="new Date()"
/>
<div class="footer-modal">
<Button
:text="$t('pages.sequences.filter_date_close_button')"
look="button--transparent"
@trigger="$emit('triggerCloseModal')"
/>
<Button
v-if="range && (range.start || range.end)"
:text="$t('pages.sequences.filter_date_reset_button')"
icon="bi bi-trash"
look="button--red"
@trigger="resetCalendar"
/>
</div>
</template>
<script setup lang="ts">
import moment from 'moment'
import { ref, watch } from 'vue'
import type { PropType } from 'vue'
import { formatDate } from '@/utils/dates'
import Button from '@/components/Button.vue'
interface CalendarDateInterface {
start: Date | string | null
end: Date | string | null
type: string
}
const emit = defineEmits(['triggerDate', 'triggerCloseModal'])
const props = defineProps({
type: { type: String, default: '' },
rangeSelected: {
type: Object as PropType<CalendarDateInterface>,
default: { start: null, end: null, type: '' }
}
})
let range = ref<CalendarDateInterface>({
start: props.rangeSelected.start,
end: props.rangeSelected.end,
type: props.rangeSelected.type
})
const datePicker = ref()
function checkValidityDate(dateToValid: string): boolean {
const date = moment(dateToValid, 'YYYY-MM-DD', true)
return date.isValid() && date.format('YYYY-MM-DD') === dateToValid
}
function setStartValue(event: Event): void {
const value = (event.target as HTMLInputElement).value
if (checkValidityDate(value)) {
range.value.start = new Date(value)
const startDate = `${formatDate(new Date(value), 'YYYY-MM-DD')} 12:00 AM`
if (range && range.value.end && range.value.start) {
range.value = {
start: new Date(startDate),
end: range.value.end,
type: props.type
}
datePicker.value.updateValue(range.value)
}
}
}
function setEndValue(event: Event): void {
const value = (event.target as HTMLInputElement).value
if (checkValidityDate(value)) {
range.value.end = new Date(value)
const endDate = `${formatDate(new Date(value), 'YYYY-MM-DD')} 11:59 PM`
if (range && range.value.end && range.value.start) {
range.value = {
end: new Date(endDate),
start: range.value.start,
type: props.type
}
datePicker.value.updateValue(range.value)
}
}
}
function resetCalendar(): void {
range.value = { start: null, end: null, type: '' }
emit('triggerDate', { start: null, end: null, type: props.type })
}
watch(range, (range) => {
if (range && range.start && range.end) {
const startDate = `${formatDate(range.start, 'YYYY-MM-DD')} 12:00 AM`
const endDate = `${formatDate(range.end, 'YYYY-MM-DD')} 11:59 PM`
range.type = props.type
emit('triggerDate', { start: startDate, end: endDate, type: props.type })
}
})
</script>
<style scoped lang="scss">
.wrapper-calendar {
margin-bottom: toRem(2);
}
.inputs-wrapper {
display: flex;
align-items: center;
@include text(xs-r-regular);
}
.input-wrapper {
position: relative;
width: 100%;
}
.footer-modal {
margin-top: toRem(2);
display: flex;
justify-content: space-between;
}
.bi-arrow-right {
margin-right: toRem(1);
margin-left: toRem(1);
}
.bi-calendar-plus {
position: absolute;
left: 5%;
top: 20%;
}
.input {
padding: toRem(0.5) toRem(0.5) toRem(0.5) toRem(2.5);
border-radius: toRem(0.3);
border: toRem(0.1) solid var(--grey-pale);
width: 100%;
}
</style>

View File

@@ -76,10 +76,20 @@
},
"sequences": {
"title": "My sequences",
"filter_date_upload_title": "Filter by upload date",
"filter_date_title": "Filter by shooting date :",
"radio_date_placeholder": "03/01/2024",
"radio_datetime_placeholder": "03/01/2024 12:00 AM",
"radio_date": "date",
"filter_date_reset_button": "Reset",
"filter_date_close_button": "Close",
"no_sequence_found": "No sequence found",
"sequence_name": "Name",
"sequence_photos": "Photos",
"sequence_date": "Shot on",
"sequence_creation": "Upload",
"sequence_creation_tooltip": "Filter by uploaded date",
"sequence_date_tooltip": "Filter by shooting date",
"sequence_status": "Status",
"sequence_published": "Published",
"sequence_waiting": "Still processing",

View File

@@ -76,10 +76,20 @@
},
"sequences": {
"title": "Mes séquences de photos",
"filter_date_upload_title": "Filtrer par date de versement :",
"filter_date_title": "Filtrer par date de prise de vue :",
"radio_date_placeholder": "2024-01-03",
"radio_date": "date",
"filter_date_reset_button": "Réinitialiser",
"filter_date_close_button": "Fermer",
"no_sequence_found": "Aucune séquence trouvée",
"sequence_name": "Nom",
"sequence_photos": "Photos",
"sequence_date": "Prise de vue",
"sequence_creation": "Versement",
"sequence_creation_tooltip": "Filtre par date de versement",
"sequence_date_tooltip": "Filtre par date de prise de vue",
"reset_filter_button": "Réinitialiser tous les filtres",
"sequence_status": "Statut",
"sequence_published": "Publiée",
"sequence_waiting": "En cours de publication",

View File

@@ -76,10 +76,20 @@
},
"sequences": {
"title": "Saját fényképsorozatok",
"filter_date_upload_title": "Szűrés feltöltés dátuma szerint :",
"filter_date_title": "Szűrés forgatás dátuma szerint :",
"radio_date_placeholder": "03/01/2024",
"radio_datetime_placeholder": "03/01/2024 12:00 AM",
"radio_date": "dátum",
"filter_date_reset_button": "Visszaállítás",
"filter_date_close_button": "Bezárás",
"no_sequence_found": "Nem található felvétel",
"sequence_name": "Név",
"sequence_photos": "Fényképek",
"sequence_date": "Elkészítés ideje",
"sequence_creation": "Feltöltés ideje",
"sequence_creation_tooltip": "Szűrés feltöltés dátuma szerint",
"sequence_date_tooltip": "Szűrés forgatás dátuma szerint",
"sequence_status": "Állapot",
"sequence_published": "Közzétéve",
"sequence_waiting": "Feldolgozás alatt",

View File

@@ -6,6 +6,8 @@ import axios from 'axios'
import VueAxios from 'vue-axios'
import { createMetaManager } from 'vue-meta'
import { VueDraggableResizable } from 'vue-draggable-resizable-vue3'
import VCalendar from 'v-calendar'
import 'v-calendar/style.css'
import { pinia } from './store'
import fr from './locales/fr.json'
import en from './locales/en.json'
@@ -40,4 +42,5 @@ app.use(VueAxios, axios)
app.provide('axios', app.config.globalProperties.axios)
app.use(createMetaManager())
app.use(VueDraggableResizable)
app.use(VCalendar)
app.mount('#app')

View File

@@ -26,17 +26,10 @@ describe('Template', () => {
}
}
})
expect(wrapper.vm.uploadErrors).toEqual([])
expect(wrapper.vm.title).toEqual('')
})
it('should render the props filled', async () => {
document.body.innerHTML = '<div id="bs-modal"></div>'
const uploadErrors = [
{
details: { error: 'my error' },
message: 'my message',
name: 'my name'
}
]
const wrapper = shallowMount(Modal, {
global: {
plugins: [i18n],
@@ -45,13 +38,12 @@ describe('Template', () => {
}
},
props: {
uploadErrors
title: 'My title'
}
})
expect(wrapper.vm.uploadErrors).toEqual(uploadErrors)
expect(wrapper.html()).contains('my name - ')
expect(wrapper.html()).contains('my error')
expect(wrapper.vm.title).toEqual('My title')
expect(wrapper.html()).contains('My title')
})
})
})

View File

@@ -81,7 +81,7 @@ describe('Template', () => {
spyPicture.mockReturnValue({ data: {} })
const sequenceTitle = `Séquence du ${formatDate(
new Date(),
'Do MMMM YYYY, hh:mm:ss'
'Do MMMM YYYY, HH:mm:ss'
)}`
const body = new FormData()
body.append('position', '1')

View File

@@ -1,7 +1,7 @@
import moment from 'moment'
import 'moment/dist/locale/fr'
function formatDate(date: Date, formatType: string): string {
function formatDate(date: Date | null | string, formatType: string): string {
const formatDate = moment(date)
return formatDate.locale(window.navigator.language).format(formatType)
}

View File

@@ -9,8 +9,9 @@
<script lang="ts" setup>
import { ref } from 'vue'
import Viewer from '@/components/Viewer.vue'
import type ViewerType from '@/components/Viewer.vue'
const viewerRef = ref<unknown>(null)
const viewerRef = ref<InstanceType<typeof ViewerType>>()
</script>
<style scoped lang="scss">
.entry-page {

View File

@@ -125,7 +125,7 @@
</div>
<div class="action-buttons">
<Button
look="button--white background-white"
look="button--white background-white no-text"
:icon="
picturesToDeleteStatus === 'hidden' ||
imagesSelectedHaveDifferentStatus
@@ -140,7 +140,7 @@
/>
<div class="button-hidde">
<Button
look="button--red background-white"
look="button--red background-white no-text"
icon="bi bi-trash"
:tooltip="$t('pages.sequence.delete_photo_tooltip')"
:disabled="!picturesToDelete.length"
@@ -201,6 +201,7 @@ import InputCheckbox from '@/components/InputCheckbox.vue'
import Loader from '@/components/Loader.vue'
import ImageItem from '@/components/ImageItem.vue'
import Viewer from '@/components/Viewer.vue'
import type ViewerType from '@/components/Viewer.vue'
import { durationCalc, formatDate } from '@/utils/dates'
import {
deleteACollectionItem,
@@ -245,7 +246,7 @@ let isLoading = ref<boolean>(false)
const collapseMenu = ref<HTMLDivElement>()
const deleteAll = ref<HTMLDivElement>()
const menuHeight = ref<string>()
const viewerRef = ref<InstanceType<typeof Viewer>>()
const viewerRef = ref<InstanceType<typeof ViewerType>>()
onMounted(async () => {
try {
@@ -616,7 +617,7 @@ async function patchOrDeleteCollectionItems(
.menu-right {
width: 50vw;
height: calc(100vh - #{toRem(8)});
overflow: hidden;
overflow: auto;
}
.wrapper-loader {
display: flex;

View File

@@ -24,6 +24,13 @@
:bbox="collectionBbox"
ref="viewerRef"
/>
<div v-else class="no-map">
<img
src="@/assets/images/how-to-share-map.png"
:alt="$t('pages.share_pictures.alt_img_map')"
class="no-map-img"
/>
</div>
</vue-draggable-resizable>
</section>
<section
@@ -31,9 +38,17 @@
class="section-sequence"
@scroll="handleScroll"
>
<h1 id="sequenceTitle" class="sequences-title">
{{ $t('pages.sequences.title') }}
</h1>
<div class="header-title">
<h1 id="sequenceTitle" class="sequences-title">
{{ $t('pages.sequences.title') }}
</h1>
<Button
v-if="filterDate.start || filterDate.end || sortDate.sortBy"
look="button-border--black"
:text="$t('pages.sequences.reset_filter_button')"
@trigger="resetAllFilter"
/>
</div>
<div
ref="headerList"
:class="['sequence-item sequence-item-head', headerListClass]"
@@ -48,21 +63,39 @@
</div>
<div class="sequence-header-item">
<Button
:text="$t('pages.sequences.sequence_date')"
look="link--grey"
icon="bi bi-arrow-down-up"
look="link--black no-text"
:icon="iconButtonSort('datetime')"
data-test="button-sort-date"
@trigger="sortList('datetime')"
/>
<span class="title">{{ $t('pages.sequences.sequence_date') }}</span>
<div class="button-filter">
<Button
:look="filterClass"
:icon="iconButtonFilter('datetime')"
:tooltip="$t('pages.sequences.sequence_date_tooltip')"
@trigger="displayModal('datetime')"
/>
</div>
</div>
<div class="sequence-header-item">
<Button
:text="$t('pages.sequences.sequence_creation')"
look="link--grey"
icon="bi bi-arrow-down-up"
look="link--black no-text"
:icon="iconButtonSort('created')"
data-test="button-sort-date"
@trigger="sortList('created')"
/>
<span class="title">{{
$t('pages.sequences.sequence_creation')
}}</span>
<div class="button-filter">
<Button
:look="filterClass"
:icon="iconButtonFilter('created')"
:tooltip="$t('pages.sequences.sequence_creation_tooltip')"
@trigger="displayModal('created')"
/>
</div>
</div>
<div class="sequence-header-item">
<span>{{ $t('pages.sequences.sequence_status') }}</span>
@@ -136,7 +169,7 @@
<div class="wrapper-button">
<Button
:tooltip="$t('pages.sequence.hide_sequence_tooltip')"
look="button--white background-white"
look="button--white background-white no-text"
:icon="
item['geovisio:status'] === 'ready'
? 'bi bi-eye-slash'
@@ -147,12 +180,17 @@
/>
<Button
:tooltip="$t('pages.sequence.delete_sequence_tooltip')"
look="button--red background-white"
look="button--red background-white no-text"
icon="bi bi-trash"
@trigger="deleteCollection(item)"
/>
</div>
</li>
<div v-else-if="!userSequences.length && noSequencesFound">
<p class="no-sequence-found">
{{ $t('pages.sequences.no_sequence_found') }}
</p>
</div>
<div v-else class="no-sequence">
<p class="no-sequence-text">
{{ $t('pages.sequences.no_sequences_text') }}
@@ -178,6 +216,28 @@
</div>
<Toast :text="toastText" :look="toastLook" @trigger="toastText = ''" />
</section>
<Modal ref="modal" :title="modalTitle">
<template v-slot:body>
<CalendarFilter
v-if="calendarType === filterDate.type"
:type="calendarType"
:range-selected="{
start: filterDate.start,
end: filterDate.end,
type: filterDate.type
}"
@triggerCloseModal="closeModal"
@triggerDate="updateFilters"
/>
<CalendarFilter
v-else
:type="calendarType"
:range-selected="{ start: null, end: null, type: '' }"
@triggerCloseModal="closeModal"
@triggerDate="updateFilters"
/>
</template>
</Modal>
</main>
</template>
@@ -193,10 +253,13 @@ import {
import { useCookies } from 'vue3-cookies'
import axios from 'axios'
import Viewer from '@/components/Viewer.vue'
import type ViewerType from '@/components/Viewer.vue'
import Button from '@/components/Button.vue'
import Link from '@/components/Link.vue'
import Toast from '@/components/Toast.vue'
import Loader from '@/components/Loader.vue'
import Modal from '@/components/Modal.vue'
import CalendarFilter from '@/components/filters/CalendarFilter.vue'
import Pagination from '@/components/Pagination.vue'
import type { SequenceLinkInterface } from './interfaces/MySequencesView'
import type { ResponseUserPhotoLinksInterface } from './interfaces/MySequenceView'
@@ -223,23 +286,65 @@ let paginationLinks = ref<ResponseUserPhotoLinksInterface[] | []>([])
let selfLink = ref<ResponseUserPhotoLinksInterface[] | []>([])
let collectionBbox = ref<number[]>([])
let isLoading = ref<boolean>(false)
let sortedBy = ref<string>('')
let isSorted = ref<boolean>(false)
let noSequencesFound = ref<boolean>(false)
let seqId = ref<string>('')
let calendarType = ref<string>('')
let width = ref<number>(0)
let mapWidth = ref<number>(window.innerWidth / 3)
let listWidth = ref<number>(window.innerWidth / 1.5)
let filterDate = ref<{
end: string | null
start: string | null
type: string
}>({
start: null,
end: null,
type: ''
})
let sortDate = ref<{ sortBy: string | null }>({ sortBy: null })
let uri = ref<string>('api/users/me/collection?limit=50')
let modal = ref()
const windowWidth = ref<number>(window.innerWidth)
const windowHeight = ref<number>(window.innerHeight - 80)
const viewerRef = ref<InstanceType<typeof Viewer>>()
const viewerRef = ref<InstanceType<typeof ViewerType>>()
const headerList = ref<HTMLDivElement | null>(null)
const list = ref<HTMLDListElement | null>(null)
const listPos = ref<PositionInterface | null>(null)
const headerLisPos = ref<PositionInterface | null>(null)
async function resetAllFilter(): Promise<void> {
filterDate.value = { start: null, end: null, type: '' }
sortDate.value = { sortBy: null }
formatUri()
await updateSequence(uri.value)
}
function displayModal(type: string): void {
calendarType.value = type
if (modal.value) modal.value.show()
}
function closeModal(): void {
if (modal.value) modal.value.close()
}
function iconButtonSort(type: string): string {
if (isSorted.value && sortedBy.value === type) return 'bi bi-sort-numeric-up'
return 'bi bi-sort-numeric-down'
}
function iconButtonFilter(type: string): string {
if (
filterDate.value.start &&
filterDate.value.end &&
filterDate.value.type === type
) {
return 'bi bi-funnel-fill'
}
return 'bi bi-funnel'
}
async function fetchAndFormatSequence(): Promise<void> {
const { data } = await axios.get('api/users/me/collection')
const { data } = await axios.get('api/users/me/collection?limit=50')
collectionBbox.value = data.extent.spatial.bbox[0]
userSequences.value = getRelChild(data.links)
userSequences.value = getLinkByRel(data.links, 'child')
}
async function patchCollection(sequence: SequenceLinkInterface): Promise<void> {
@@ -269,7 +374,7 @@ function sequenceStatus(status: string): string {
return t('pages.sequences.sequence_waiting')
}
function onResizeMap(width: any): void {
if (width) {
if (width && collectionBbox.value.length) {
width.value = width
mapWidth.value = width.value.width
listWidth.value = window.innerWidth - width.value.width
@@ -284,21 +389,29 @@ const handleScroll = async () => {
listPos.value = list.value.getBoundingClientRect()
}
}
async function updateSequence(uri: string) {
try {
const { data } = await axios.get(uri)
selfLink.value = getLinkByRel(data.links, 'self')
paginationLinks.value = formatPaginationItems(data.links)
userSequences.value = getLinkByRel(data.links, 'child')
scrollToElement()
isLoading.value = false
} catch (e) {
userSequences.value = []
noSequencesFound.value = true
isLoading.value = false
}
}
async function sortList(dateToSort: string): Promise<void> {
isLoading.value = true
sortedBy.value = dateToSort
let sortBy = `+${dateToSort}`
if (isSorted.value) sortBy = `-${dateToSort}`
const { data } = await axios.get(
`api/users/me/collection?limit=50&sortby=${encodeURIComponent(sortBy)}`
)
selfLink.value = data.links.filter(
(el: SequenceLinkInterface) => el.rel === 'self'
)
paginationLinks.value = formatPaginationItems(data.links)
userSequences.value = getRelChild(data.links)
sortDate.value.sortBy = encodeURIComponent(sortBy)
formatUri()
await updateSequence(uri.value)
isSorted.value = !isSorted.value
scrollToElement()
isLoading.value = false
}
function bboxIsInsideOther(mainBbox: number[], bboxInside: number[]): boolean {
@@ -334,10 +447,11 @@ function goToSequence(sequence: SequenceLinkInterface): void {
duration: 0
})
}
function getRelChild(
sequences: SequenceLinkInterface[]
function getLinkByRel(
sequences: SequenceLinkInterface[],
rel: string
): SequenceLinkInterface[] {
return sequences.filter((el: SequenceLinkInterface) => el.rel === 'child')
return sequences.filter((el: SequenceLinkInterface) => el.rel === rel)
}
function scrollToElement(): void {
const elementTarget = document.querySelector('#sequenceTitle')
@@ -345,21 +459,22 @@ function scrollToElement(): void {
}
async function goToNextPage(value: string): Promise<void> {
isLoading.value = true
const { data } = await axios.get(value)
selfLink.value = data.links.filter(
(el: SequenceLinkInterface) => el.rel === 'self'
)
paginationLinks.value = formatPaginationItems(data.links)
userSequences.value = getRelChild(data.links)
scrollToElement()
isLoading.value = false
await updateSequence(value)
}
const filterClass = computed<string>((): string => {
return 'no-text-green'
})
const modalTitle = computed<string>((): string => {
if (calendarType.value === 'datetime') {
return t('pages.sequences.filter_date_title')
}
return t('pages.sequences.filter_date_upload_title')
})
const getUserId = computed<string>((): string => cookies.get('user_id'))
const headerListClass = computed<string>((): string => {
if (headerLisPos.value && listPos.value) {
return headerLisPos.value.y != 0 && listPos.value.top < 180
? 'item-head-fixed'
: ''
const classCondition = headerLisPos.value.y != 0 && listPos.value.top < 174
return classCondition ? 'item-head-fixed' : ''
}
return ''
})
@@ -367,23 +482,55 @@ onMounted(async () => {
isLoading.value = true
try {
const { data } = await axios.get('api/users/me/collection?limit=50')
selfLink.value = data.links.filter(
(el: SequenceLinkInterface) => el.rel === 'self'
)
selfLink.value = getLinkByRel(data.links, 'self')
paginationLinks.value = formatPaginationItems(data.links)
userSequences.value = getRelChild(data.links)
collectionBbox.value = [
userSequences.value[0].extent.spatial.bbox[0][0],
userSequences.value[0].extent.spatial.bbox[0][1],
userSequences.value[0].extent.spatial.bbox[0][2],
userSequences.value[0].extent.spatial.bbox[0][3]
]
userSequences.value = getLinkByRel(data.links, 'child')
if (userSequences.value[0]) {
collectionBbox.value = [
userSequences.value[0].extent.spatial.bbox[0][0],
userSequences.value[0].extent.spatial.bbox[0][1],
userSequences.value[0].extent.spatial.bbox[0][2],
userSequences.value[0].extent.spatial.bbox[0][3]
]
}
isLoading.value = false
} catch (err) {
isLoading.value = false
console.log(err)
}
})
function formatUri(): void {
let params: string[] = []
if (filterDate.value.start && filterDate.value.end) {
const rangeFilter = encodeURIComponent(
`${filterDate.value.type} BETWEEN '${filterDate.value.start}' AND '${filterDate.value.end}'`
)
params = [...params, `&filter=${rangeFilter}`]
}
if (sortDate.value.sortBy) {
params = [...params, `&sortby=${sortDate.value.sortBy}`]
}
if (params.length) {
const constructParams = params.join('')
uri.value = `api/users/me/collection?limit=50${constructParams}`
} else uri.value = 'api/users/me/collection?limit=50'
}
async function updateFilters(value: {
start: null
end: null
type: ''
}): Promise<void> {
isLoading.value = true
noSequencesFound.value = false
if (value.start && value.end) {
filterDate.value = { start: value.start, end: value.end, type: value.type }
formatUri()
} else {
uri.value = 'api/users/me/collection?limit=50'
filterDate.value = { end: null, start: null, type: '' }
}
await updateSequence(uri.value)
}
watchEffect(() => {
if (viewerRef.value && viewerRef.value.viewer) {
viewerRef.value.viewer.addEventListener(
@@ -431,18 +578,38 @@ watchEffect(() => {
height: 100%;
position: relative;
}
.no-map {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
background-color: var(--blue-pale);
}
.section-sequence {
overflow-y: auto;
overflow-x: hidden;
height: 100%;
position: relative;
}
.header-title {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
position: relative;
margin: toRem(3);
}
.sequences-title {
@include text(h1);
color: var(--blue-dark);
margin-bottom: toRem(5);
margin-top: toRem(3);
margin-left: toRem(3);
}
.date-filter-title {
@include text(s-r-regular);
}
.wrapper-date-filter-title {
display: flex;
justify-content: space-between;
align-items: center;
}
.sequence-list {
box-shadow: 0px 2px 30px 0px #0000000f;
@@ -574,7 +741,10 @@ watchEffect(() => {
margin-right: toRem(0.5);
}
.sequence-header-item {
display: flex;
align-items: center;
width: calc(31% - #{toRem(4.75)});
color: var(--grey-semi-dark);
&:first-child {
margin-right: toRem(2);
}
@@ -584,6 +754,15 @@ watchEffect(() => {
&:nth-child(3) {
width: toRem(13);
}
.title {
color: var(--black);
}
}
.button-filter {
margin-left: toRem(-1);
.icon {
font-size: toRem(1);
}
}
.no-sequence {
padding-top: toRem(2);
@@ -599,6 +778,10 @@ watchEffect(() => {
.no-sequence-text {
margin-bottom: toRem(4);
}
.no-sequence-found {
text-align: center;
padding: toRem(2);
}
.loader {
display: flex;
justify-content: center;
@@ -639,7 +822,20 @@ watchEffect(() => {
width: 100% !important;
}
.sequence-item-head {
display: none;
width: 100% !important;
flex-direction: row !important;
padding-right: 0;
padding-left: 0;
}
.sequence-header-item {
width: 50%;
justify-content: center;
&:first-child,
&:last-child,
&:nth-child(2),
&:nth-child(3) {
display: none;
}
}
.button-item {
flex-direction: column;

View File

@@ -90,7 +90,20 @@
v-if="uploadedSequence && uploadedSequence.picturesOnError"
ref="modal"
:upload-errors="uploadedSequence.picturesOnError"
/>
:title="$t('pages.upload.modal_error_title')"
>
<template v-slot:body>
<ul>
<li
v-for="item in uploadedSequence.picturesOnError"
class="error-item"
>
<span>{{ item.name }} - </span>
<span>{{ item.details.error }}</span>
</li>
</ul>
</template>
</Modal>
</main>
</template>
@@ -169,7 +182,7 @@ function setNewSequenceTitle(value: string | null): void {
function formatSequenceTitle(): string {
return `${t('pages.upload.sequence_title')}${formatDate(
new Date(),
'Do MMMM YYYY, hh:mm:ss'
'Do MMMM YYYY, HH:mm:ss'
)}`
}
function picturesToUploadSizeText(): void {
@@ -300,6 +313,12 @@ h3 {
margin-right: toRem(2);
}
}
.error-item {
padding: toRem(1);
&:nth-child(odd) {
background-color: var(--grey);
}
}
.subtitle {
@include text(h3);
color: var(--blue-dark);
@@ -340,6 +359,9 @@ h3 {
align-items: center;
}
@media (max-width: toRem(76.8)) {
.section {
display: none;
}
.entry-page {
padding-top: toRem(15);
}

View File

@@ -39,5 +39,5 @@ export interface ResponseUserSequenceInterface extends UserSequenceInterface {
export interface CheckboxInterface {
isChecked: boolean
isIndeterminate: boolean
isIndeterminate?: boolean
}

View File

@@ -6,6 +6,12 @@ export interface MapInterface {
zoom?: number
center?: number[]
bounds?: number[]
raster?: {
type: string
tiles: string[]
attribution: string
tileSize: number
}
}
export interface ViewerInterface {

1
v-calendar.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
declare module 'v-calendar'

113
yarn.lock
View File

@@ -272,6 +272,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.22.5"
"@babel/runtime@^7.21.0":
version "7.23.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.7.tgz#dd7c88deeb218a0f8bd34d5db1aa242e0f203193"
integrity sha512-w06OXVOFso7LcbzMiDGt+3X7Rh7Ho8MmgPoWU3rarH+8upf+wSU/grlGbWzQyr3DkdN6ZeuMFjpdwW0Q+HxobA==
dependencies:
regenerator-runtime "^0.14.0"
"@babel/template@^7.22.15", "@babel/template@^7.3.3":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
@@ -888,16 +895,6 @@
resolved "https://registry.yarnpkg.com/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz#497c67a1cef50d1a2459ba60f315e448d2ad87fe"
integrity sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==
"@maplibre/maplibre-gl-geocoder@^1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@maplibre/maplibre-gl-geocoder/-/maplibre-gl-geocoder-1.5.0.tgz#6b413525b361b4759df0fd17429e12b78f03b3a4"
integrity sha512-PsAbV7WFIOu5QYZne95FiXoV7AV1/6ULMjQxgInhZ5DdB0hDLjciQPegnyDgkzI8JfeqoUMZVS/MglZnSZYhyQ==
dependencies:
lodash.debounce "^4.0.6"
subtag "^0.5.0"
suggestions-list "^0.0.2"
xtend "^4.0.1"
"@maplibre/maplibre-gl-style-spec@^19.3.3":
version "19.3.3"
resolved "https://registry.yarnpkg.com/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-19.3.3.tgz#a106248bd2e25e77c963a362aeaf630e00f924e9"
@@ -1184,6 +1181,11 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
"@types/lodash@^4.14.165":
version "4.14.202"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.202.tgz#f09dbd2fb082d507178b2f2a5c7e74bd72ff98f8"
integrity sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==
"@types/mapbox__point-geometry@*", "@types/mapbox__point-geometry@^0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz#0ef017b75eedce02ff6243b4189210e2e6d5e56d"
@@ -1244,6 +1246,11 @@
resolved "https://registry.yarnpkg.com/@types/pbf/-/pbf-3.0.5.tgz#a9495a58d8c75be4ffe9a0bd749a307715c07404"
integrity sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==
"@types/resize-observer-browser@^0.1.7":
version "0.1.11"
resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.11.tgz#d3c98d788489d8376b7beac23863b1eebdd3c13c"
integrity sha512-cNw5iH8JkMkb3QkCoe7DaZiawbDQEUX8t7iuQaRTyLOyQCR2h+ibBD4GJt7p5yhUHrlOeL7ZtbxNHeipqNsBzQ==
"@types/semver@^7.3.12":
version "7.5.6"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.6.tgz#c65b2bfce1bec346582c07724e3f8c1017a20339"
@@ -2679,6 +2686,18 @@ data-urls@^3.0.2:
whatwg-mimetype "^3.0.0"
whatwg-url "^11.0.0"
date-fns-tz@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-2.0.0.tgz#1b14c386cb8bc16fc56fe333d4fc34ae1d1099d5"
integrity sha512-OAtcLdB9vxSXTWHdT8b398ARImVwQMyjfYGkKD2zaGpHseG2UPHbHjXELReErZFxWdSLph3c2zOaaTyHfOhERQ==
date-fns@^2.16.1:
version "2.30.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0"
integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
dependencies:
"@babel/runtime" "^7.21.0"
dayjs@^1.10.4:
version "1.11.10"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0"
@@ -3604,6 +3623,11 @@ fd-slicer@~1.1.0:
dependencies:
pend "~1.2.0"
fflate@^0.8.0:
version "0.8.1"
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.1.tgz#1ed92270674d2ad3c73f077cd0acf26486dae6c9"
integrity sha512-/exOvEuc+/iaUm105QIiOt4LpBdMTWsXxqR0HDF35vx3fmaKzw7354gTilCh5rkzEt8WYyG//ku3h3nRmd7CHQ==
figures@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
@@ -3779,11 +3803,6 @@ functions-have-names@^1.2.3:
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
fuzzy@^0.1.1:
version "0.1.3"
resolved "https://registry.yarnpkg.com/fuzzy/-/fuzzy-0.1.3.tgz#4c76ec2ff0ac1a36a9dccf9a00df8623078d4ed8"
integrity sha512-/gZffu4ykarLrCiP3Ygsa86UAo1E5vEVlvTrpkKywXSbP9Xhln3oSp9QSV57gEq3JFFpGJ4GZ+5zdEp3FcUh4w==
gensync@^1.0.0-beta.2:
version "1.0.0-beta.2"
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
@@ -3794,14 +3813,13 @@ geojson-vt@^3.2.1:
resolved "https://registry.yarnpkg.com/geojson-vt/-/geojson-vt-3.2.1.tgz#f8adb614d2c1d3f6ee7c4265cad4bbf3ad60c8b7"
integrity sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==
geovisio@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/geovisio/-/geovisio-2.3.0.tgz#45108c32557ac9ebcd0a595cf1b13488cc03cf84"
integrity sha512-M43onTVE6ulek23M0FyTLKN5JEdRfSfKgTxo2b3wIplb5u20TAuliP3wQpeHGYeT/bii0r/L81uX4K4EJldF3g==
geovisio@2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/geovisio/-/geovisio-2.3.1.tgz#4c4151ebfb597edf558dcd7a069f6c87a947bf0b"
integrity sha512-k9JA6Pw1Ro+ONdCWiRSmVMzxAiIB4+K7bwg0astdhBoc0SQsU9Jtu+Nc/scjHrenm+kJZvm42kMre5buNa3lLg==
dependencies:
"@fortawesome/fontawesome-svg-core" "^6.4.0"
"@fortawesome/free-solid-svg-icons" "^6.4.0"
"@maplibre/maplibre-gl-geocoder" "^1.5.0"
"@photo-sphere-viewer/core" "^5.5.0"
"@photo-sphere-viewer/equirectangular-tiles-adapter" "^5.5.0"
"@photo-sphere-viewer/gallery-plugin" "^5.5.0"
@@ -3809,6 +3827,7 @@ geovisio@2.3.0:
"@photo-sphere-viewer/virtual-tour-plugin" "^5.5.0"
documentation "^14.0.1"
maplibre-gl "^3.3.0"
pmtiles "^2.11.0"
get-caller-file@^2.0.5:
version "2.0.5"
@@ -5367,11 +5386,6 @@ locate-path@^7.1.0:
dependencies:
p-locate "^6.0.0"
lodash.debounce@^4.0.6:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
@@ -5382,7 +5396,7 @@ lodash.once@^4.1.1:
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
lodash@^4.17.21:
lodash@^4.17.20, lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -6574,6 +6588,13 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0:
dependencies:
find-up "^4.0.0"
pmtiles@^2.11.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/pmtiles/-/pmtiles-2.11.0.tgz#53aac29408e001a73b15b1c8cad0b17c944ab7bd"
integrity sha512-dU9SzzaqmCGpdEuTnIba6bDHT6j09ZJFIXxwGpvkiEnce3ZnBB1VKt6+EOmJGueriweaZLAMTUmKVElU2CBe0g==
dependencies:
fflate "^0.8.0"
postcss-selector-parser@^6.0.13:
version "6.0.13"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b"
@@ -6786,6 +6807,11 @@ readdirp@~3.6.0:
dependencies:
picomatch "^2.2.1"
regenerator-runtime@^0.14.0:
version "0.14.1"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f"
integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==
regexp.prototype.flags@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e"
@@ -7439,19 +7465,6 @@ strip-literal@^1.0.0:
dependencies:
acorn "^8.10.0"
subtag@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/subtag/-/subtag-0.5.0.tgz#1728a8df5257fb98ded4fb981b2ac7af10cf58ba"
integrity sha512-CaIBcTSb/nyk4xiiSOtZYz1B+F12ZxW8NEp54CdT+84vmh/h4sUnHGC6+KQXUfED8u22PQjCYWfZny8d2ELXwg==
suggestions-list@^0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/suggestions-list/-/suggestions-list-0.0.2.tgz#3c5f501833e697a726a1bf58fbc454d57ffa0e98"
integrity sha512-Yw0fdq14c6RQWQIfE1/8WEi9Dp8rjyCD6FhYA/Tit2/ADbE9Y4ADG4ezlvivsa8Civ5nz++pyVVBMjOMlgIUJw==
dependencies:
fuzzy "^0.1.1"
xtend "^4.0.0"
supercluster@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-8.0.1.tgz#9946ba123538e9e9ab15de472531f604e7372df5"
@@ -7931,6 +7944,18 @@ uvu@^0.5.0:
kleur "^4.0.3"
sade "^1.7.3"
v-calendar@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/v-calendar/-/v-calendar-3.1.2.tgz#fb47320a5469973454f030d850134eebcd2307eb"
integrity sha512-QDWrnp4PWCpzUblctgo4T558PrHgHzDtQnTeUNzKxfNf29FkCeFpwGd9bKjAqktaa2aJLcyRl45T5ln1ku34kg==
dependencies:
"@types/lodash" "^4.14.165"
"@types/resize-observer-browser" "^0.1.7"
date-fns "^2.16.1"
date-fns-tz "^2.0.0"
lodash "^4.17.20"
vue-screen-utils "^1.0.0-beta.13"
v8-to-istanbul@^9.0.1:
version "9.2.0"
resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz#2ed7644a245cddd83d4e087b9b33b3e62dfd10ad"
@@ -8137,6 +8162,11 @@ vue-router@^4.1.6:
dependencies:
"@vue/devtools-api" "^6.5.0"
vue-screen-utils@^1.0.0-beta.13:
version "1.0.0-beta.13"
resolved "https://registry.yarnpkg.com/vue-screen-utils/-/vue-screen-utils-1.0.0-beta.13.tgz#0c739e19f6ffbffab63184aba7b6d710b6a63681"
integrity sha512-EJ/8TANKhFj+LefDuOvZykwMr3rrLFPLNb++lNBqPOpVigT2ActRg6icH9RFQVm4nHwlHIHSGm5OY/Clar9yIg==
vue-template-compiler@^2.7.14, vue-template-compiler@^2.7.8:
version "2.7.15"
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.7.15.tgz#ec88ba8ceafe0f17a528b89c57e01e02da92b0de"
@@ -8375,11 +8405,6 @@ xmlchars@^2.2.0:
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
xtend@^4.0.0, xtend@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
y18n@^5.0.5:
version "5.0.8"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"