forked from Ivasoft/geovisio-website
Feat/pagination sequence list
This commit is contained in:
@@ -35,7 +35,7 @@ defineProps({
|
||||
}
|
||||
}
|
||||
.default {
|
||||
height: toRem(3.5);
|
||||
height: toRem(3);
|
||||
min-width: toRem(3.5);
|
||||
@include text(s-regular);
|
||||
display: flex;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<ul>
|
||||
<li v-for="item in uploadErrors" class="error-item">
|
||||
<span>{{ item.name }} - </span>
|
||||
<span>{{ item.message }}</span>
|
||||
<span>{{ item.details.error }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -108,17 +108,21 @@ onMounted(async () => {
|
||||
style
|
||||
}
|
||||
}
|
||||
const bbox = [props.bbox[0], props.bbox[1], props.bbox[2], props.bbox[3]]
|
||||
viewer.value = new StandaloneMap(
|
||||
'viewer', // Div ID
|
||||
`${import.meta.env.VITE_API_URL}/api/search`,
|
||||
{
|
||||
...paramsMap,
|
||||
bounds: [props.bbox[0], props.bbox[1], props.bbox[2], props.bbox[3]],
|
||||
bounds: bbox,
|
||||
zoom: 9
|
||||
}
|
||||
)
|
||||
viewer.value.addEventListener('ready', () => {
|
||||
viewer.value.setFilters({ user: props.userId }, true)
|
||||
viewer.value.fitBounds(bbox, {
|
||||
padding: { top: 70, bottom: 70, left: 70, right: 70 }
|
||||
})
|
||||
})
|
||||
}
|
||||
mapIsLoaded.value = true
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { useCookies } from 'vue3-cookies'
|
||||
import type {
|
||||
RouteRecordRaw,
|
||||
NavigationGuardNext,
|
||||
@@ -15,7 +14,6 @@ import MySequenceView from '../views/MySequenceView.vue'
|
||||
import SharePicturesView from '../views/SharePicturesView.vue'
|
||||
import UploadPicturesView from '../views/UploadPicturesView.vue'
|
||||
import Ay11View from '../views/Ay11View.vue'
|
||||
const { cookies } = useCookies()
|
||||
let routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/',
|
||||
|
||||
@@ -30,7 +30,13 @@ describe('Template', () => {
|
||||
})
|
||||
it('should render the props filled', async () => {
|
||||
document.body.innerHTML = '<div id="bs-modal"></div>'
|
||||
const uploadErrors = [{ message: 'my message', name: 'my name' }]
|
||||
const uploadErrors = [
|
||||
{
|
||||
details: { error: 'my error' },
|
||||
message: 'my message',
|
||||
name: 'my name'
|
||||
}
|
||||
]
|
||||
const wrapper = shallowMount(Modal, {
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
@@ -45,7 +51,7 @@ describe('Template', () => {
|
||||
|
||||
expect(wrapper.vm.uploadErrors).toEqual(uploadErrors)
|
||||
expect(wrapper.html()).contains('my name - ')
|
||||
expect(wrapper.html()).contains('my message')
|
||||
expect(wrapper.html()).contains('my error')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -429,7 +429,7 @@ function fullImagesToDelete(): ResponseUserPhotoInterface[] {
|
||||
return pictures.value.filter((el) => picturesToDelete.value.includes(el.id))
|
||||
}
|
||||
|
||||
async function goToNextPage(value: string) {
|
||||
async function goToNextPage(value: string): Promise<void> {
|
||||
isLoading.value = true
|
||||
const { data } = await fetchCollectionItemsWithFullUrl(value)
|
||||
selfLink.value = data.links.filter((el) => el.rel === 'self')
|
||||
|
||||
@@ -26,61 +26,52 @@
|
||||
/>
|
||||
</vue-draggable-resizable>
|
||||
</section>
|
||||
<section :style="{ width: `${listWidth}px` }" class="section-sequence">
|
||||
<h1 class="sequences-title">{{ $t('pages.sequences.title') }}</h1>
|
||||
<ul v-if="!isLoading" class="sequence-list">
|
||||
<li class="sequence-item sequence-item-head">
|
||||
<div class="sequence-header-item"></div>
|
||||
<div class="sequence-header-item">
|
||||
<Button
|
||||
:text="$t('pages.sequences.sequence_name')"
|
||||
look="link--grey"
|
||||
icon="bi bi-arrow-down-up"
|
||||
data-test="button-sort-title"
|
||||
@trigger="sortAlpha('title')"
|
||||
/>
|
||||
</div>
|
||||
<div class="sequence-header-item">
|
||||
<Button
|
||||
:text="$t('pages.sequences.sequence_photos')"
|
||||
look="link--grey"
|
||||
icon="bi bi-arrow-down-up"
|
||||
data-test="button-sort-number"
|
||||
@trigger="sortNum('num')"
|
||||
/>
|
||||
</div>
|
||||
<div class="sequence-header-item">
|
||||
<Button
|
||||
:text="$t('pages.sequences.sequence_date')"
|
||||
look="link--grey"
|
||||
icon="bi bi-arrow-down-up"
|
||||
data-test="button-sort-date"
|
||||
@trigger="sortNum('date')"
|
||||
/>
|
||||
</div>
|
||||
<div class="sequence-header-item">
|
||||
<Button
|
||||
:text="$t('pages.sequences.sequence_creation')"
|
||||
look="link--grey"
|
||||
icon="bi bi-arrow-down-up"
|
||||
data-test="button-sort-date"
|
||||
@trigger="sortNum('date', 'created')"
|
||||
/>
|
||||
</div>
|
||||
<div class="sequence-header-item">
|
||||
<Button
|
||||
:text="$t('pages.sequences.sequence_status')"
|
||||
look="link--grey"
|
||||
icon="bi bi-arrow-down-up"
|
||||
data-test="button-sort-date"
|
||||
@trigger="sortAlpha('geovisio:status')"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
<section
|
||||
:style="{ width: `${listWidth}px` }"
|
||||
class="section-sequence"
|
||||
@scroll="handleScroll"
|
||||
>
|
||||
<h1 id="sequenceTitle" class="sequences-title">
|
||||
{{ $t('pages.sequences.title') }}
|
||||
</h1>
|
||||
<div
|
||||
ref="headerList"
|
||||
:class="['sequence-item sequence-item-head', headerListClass]"
|
||||
:style="{ width: `${listWidth}px`, borderRadius: '0px' }"
|
||||
>
|
||||
<div class="sequence-header-item"></div>
|
||||
<div class="sequence-header-item">
|
||||
<span>{{ $t('pages.sequences.sequence_name') }}</span>
|
||||
</div>
|
||||
<div class="sequence-header-item">
|
||||
<span>{{ $t('pages.sequences.sequence_photos') }}</span>
|
||||
</div>
|
||||
<div class="sequence-header-item">
|
||||
<Button
|
||||
:text="$t('pages.sequences.sequence_date')"
|
||||
look="link--grey"
|
||||
icon="bi bi-arrow-down-up"
|
||||
data-test="button-sort-date"
|
||||
@trigger="sortList('datetime')"
|
||||
/>
|
||||
</div>
|
||||
<div class="sequence-header-item">
|
||||
<Button
|
||||
:text="$t('pages.sequences.sequence_creation')"
|
||||
look="link--grey"
|
||||
icon="bi bi-arrow-down-up"
|
||||
data-test="button-sort-date"
|
||||
@trigger="sortList('created')"
|
||||
/>
|
||||
</div>
|
||||
<div class="sequence-header-item">
|
||||
<span>{{ $t('pages.sequences.sequence_status') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<ul v-if="!isLoading" ref="list" class="sequence-list">
|
||||
<li
|
||||
v-if="userSequences.length"
|
||||
v-for="(item, i) in userSequences"
|
||||
:id="`el-list${i}`"
|
||||
v-for="item in userSequences"
|
||||
:class="[
|
||||
'sequence-item',
|
||||
item.id === seqId ? 'button-item-hover' : ''
|
||||
@@ -179,17 +170,36 @@
|
||||
<div v-else class="loader">
|
||||
<Loader look="sm" :is-loaded="false" />
|
||||
</div>
|
||||
<div class="entry-pagination">
|
||||
<Pagination
|
||||
v-for="item in paginationLinks"
|
||||
:type="item.rel"
|
||||
:href="item.href"
|
||||
:self-link="selfLink[0]"
|
||||
@trigger="goToNextPage"
|
||||
/>
|
||||
</div>
|
||||
<Toast :text="toastText" :look="toastLook" @trigger="toastText = ''" />
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watchEffect, computed } from 'vue'
|
||||
import {
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
ref,
|
||||
watchEffect,
|
||||
computed,
|
||||
nextTick
|
||||
} from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useSequenceStore } from '@/store/sequence'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { scrollIntoSelected } from '@/views/utils/sequence/index'
|
||||
import {
|
||||
scrollIntoSelected,
|
||||
formatPaginationItems
|
||||
} from '@/views/utils/sequence/index'
|
||||
import { useCookies } from 'vue3-cookies'
|
||||
import axios from 'axios'
|
||||
import Viewer from '@/components/Viewer.vue'
|
||||
@@ -197,23 +207,33 @@ 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 type {
|
||||
SequenceLinkInterface,
|
||||
ExtentSequenceLinkInterface
|
||||
} from './interfaces/MySequencesView'
|
||||
import Pagination from '@/components/Pagination.vue'
|
||||
import type { SequenceLinkInterface } from './interfaces/MySequencesView'
|
||||
import type { ResponseUserPhotoLinksInterface } from './interfaces/MySequenceView'
|
||||
import { formatDate } from '@/utils/dates'
|
||||
import {
|
||||
deleteACollection,
|
||||
patchACollection
|
||||
} from '@/views/utils/sequence/request'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { cookies } = useCookies()
|
||||
const sequenceStore = useSequenceStore()
|
||||
const { toastText, toastLook } = storeToRefs(sequenceStore)
|
||||
interface PositionInterface {
|
||||
bottom: number
|
||||
top: number
|
||||
right: number
|
||||
left: number
|
||||
y: number
|
||||
x: number
|
||||
}
|
||||
let userSequences = ref<SequenceLinkInterface[]>([])
|
||||
let paginationLinks = ref<ResponseUserPhotoLinksInterface[] | []>([])
|
||||
let selfLink = ref<ResponseUserPhotoLinksInterface[] | []>([])
|
||||
let collectionBbox = ref<number[]>([])
|
||||
let isSorted = ref<boolean>(false)
|
||||
let isLoading = ref<boolean>(false)
|
||||
let isSorted = ref<boolean>(false)
|
||||
let seqId = ref<string>('')
|
||||
let width = ref<number>(0)
|
||||
let mapWidth = ref<number>(window.innerWidth / 3)
|
||||
@@ -221,6 +241,10 @@ let listWidth = ref<number>(window.innerWidth / 1.5)
|
||||
const windowWidth = ref<number>(window.innerWidth)
|
||||
const windowHeight = ref<number>(window.innerHeight - 80)
|
||||
const viewerRef = ref<any>(null)
|
||||
const headerList = ref<any>(null)
|
||||
const list = ref<any>(null)
|
||||
const listPos = ref<PositionInterface | null>(null)
|
||||
const headerLisPos = ref<PositionInterface | null>(null)
|
||||
|
||||
async function fetchAndFormatSequence(): Promise<void> {
|
||||
const { data } = await axios.get('api/users/me/collection')
|
||||
@@ -254,61 +278,79 @@ function sequenceStatus(status: string): string {
|
||||
if (status === 'hidden') return t('pages.sequences.sequence_hidden')
|
||||
return t('pages.sequences.sequence_waiting')
|
||||
}
|
||||
function sortAlpha<TKey extends keyof SequenceLinkInterface>(key: TKey): void {
|
||||
const sorted = userSequences.value.sort(
|
||||
(
|
||||
a: { [K in TKey]: SequenceLinkInterface[TKey] },
|
||||
b: { [K in TKey]: SequenceLinkInterface[TKey] }
|
||||
) => {
|
||||
if (a[key] < b[key]) return !isSorted.value ? -1 : 1
|
||||
if (a[key] > b[key]) return !isSorted.value ? 1 : -1
|
||||
return 0
|
||||
}
|
||||
)
|
||||
isSorted.value = !isSorted.value
|
||||
userSequences.value = sorted
|
||||
}
|
||||
function onResizeMap(width: any) {
|
||||
function onResizeMap(width: any): void {
|
||||
if (width) {
|
||||
width.value = width
|
||||
mapWidth.value = width.value.width
|
||||
listWidth.value = window.innerWidth - width.value.width
|
||||
}
|
||||
}
|
||||
function sortNum(type: string, dateToSort?: string): void {
|
||||
let aa, bb: number
|
||||
const sorted = userSequences.value.sort(
|
||||
(a: ExtentSequenceLinkInterface, b: ExtentSequenceLinkInterface) => {
|
||||
aa = new Date(a.extent.temporal.interval[0][0]).getTime()
|
||||
bb = new Date(b.extent.temporal.interval[0][0]).getTime()
|
||||
if (dateToSort === 'created') {
|
||||
aa = new Date(a.created).getTime()
|
||||
bb = new Date(b.created).getTime()
|
||||
}
|
||||
if (type === 'num') {
|
||||
aa = Number(a['stats:items'].count)
|
||||
bb = Number(b['stats:items'].count)
|
||||
}
|
||||
if (aa < bb) return !isSorted.value ? -1 : 1
|
||||
if (aa > bb) return !isSorted.value ? 1 : -1
|
||||
return 0
|
||||
}
|
||||
)
|
||||
isSorted.value = !isSorted.value
|
||||
userSequences.value = sorted
|
||||
const handleScroll = async () => {
|
||||
await nextTick()
|
||||
if (headerList.value && headerList.value.getBoundingClientRect()) {
|
||||
headerLisPos.value = headerList.value.getBoundingClientRect()
|
||||
}
|
||||
if (list.value && list.value.getBoundingClientRect()) {
|
||||
listPos.value = list.value.getBoundingClientRect()
|
||||
}
|
||||
}
|
||||
function goToSequence(sequence: SequenceLinkInterface) {
|
||||
async function sortList(dateToSort: string): Promise<void> {
|
||||
isLoading.value = true
|
||||
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)
|
||||
isSorted.value = !isSorted.value
|
||||
scrollToElement()
|
||||
isLoading.value = false
|
||||
}
|
||||
function goToSequence(sequence: SequenceLinkInterface): void {
|
||||
seqId.value = sequence.id
|
||||
viewerRef.value.viewer.select(seqId.value)
|
||||
}
|
||||
function getRelChild(sequences: SequenceLinkInterface[]) {
|
||||
function getRelChild(
|
||||
sequences: SequenceLinkInterface[]
|
||||
): SequenceLinkInterface[] {
|
||||
return sequences.filter((el: SequenceLinkInterface) => el.rel === 'child')
|
||||
}
|
||||
function scrollToElement(): void {
|
||||
const elementTarget = document.querySelector('#sequenceTitle')
|
||||
if (elementTarget) elementTarget.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
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
|
||||
}
|
||||
const getUserId = computed<string>(() => cookies.get('user_id'))
|
||||
const headerListClass = computed<string>(() => {
|
||||
if (headerLisPos.value && listPos.value) {
|
||||
return headerLisPos.value.y != 0 && listPos.value.top < 180
|
||||
? 'item-head-fixed'
|
||||
: ''
|
||||
}
|
||||
return ''
|
||||
})
|
||||
onMounted(async () => {
|
||||
isLoading.value = true
|
||||
try {
|
||||
const { data } = await axios.get('api/users/me/collection')
|
||||
const { data } = await axios.get('api/users/me/collection?limit=50')
|
||||
selfLink.value = data.links.filter(
|
||||
(el: SequenceLinkInterface) => el.rel === 'self'
|
||||
)
|
||||
paginationLinks.value = formatPaginationItems(data.links)
|
||||
collectionBbox.value = data.extent.spatial.bbox[0]
|
||||
userSequences.value = getRelChild(data.links)
|
||||
isLoading.value = false
|
||||
@@ -387,25 +429,21 @@ watchEffect(() => {
|
||||
margin: auto;
|
||||
background-color: var(--blue-pale);
|
||||
padding: toRem(2);
|
||||
&:first-child {
|
||||
margin-bottom: toRem(1);
|
||||
padding: toRem(1) toRem(2);
|
||||
border-bottom: toRem(0.1) solid var(--grey);
|
||||
border-radius: toRem(2) toRem(2) 0rem 0rem;
|
||||
background-color: var(--white);
|
||||
}
|
||||
&:nth-child(2n) {
|
||||
background-color: var(--white);
|
||||
}
|
||||
}
|
||||
.wrapper-button {
|
||||
position: absolute;
|
||||
right: toRem(2);
|
||||
bottom: toRem(2);
|
||||
right: toRem(1);
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
.button--white:first-child {
|
||||
margin-right: toRem(1);
|
||||
margin-bottom: toRem(1);
|
||||
}
|
||||
}
|
||||
.button-item-hover {
|
||||
@@ -420,8 +458,21 @@ watchEffect(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
.item-head-fixed {
|
||||
position: fixed;
|
||||
top: toRem(8);
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
.sequence-item-head {
|
||||
margin-bottom: toRem(1);
|
||||
padding: toRem(1) toRem(2);
|
||||
border-bottom: toRem(0.1) solid var(--grey);
|
||||
border-radius: toRem(2) toRem(2) 0rem 0rem;
|
||||
background-color: var(--white);
|
||||
}
|
||||
.sequence-item-head:hover {
|
||||
background-color: initial;
|
||||
background-color: var(--white);
|
||||
color: initial;
|
||||
}
|
||||
.wrapper-title {
|
||||
@@ -497,7 +548,6 @@ watchEffect(() => {
|
||||
}
|
||||
.sequence-header-item {
|
||||
width: calc(31% - #{toRem(4.75)});
|
||||
margin-left: toRem(-1);
|
||||
&:first-child {
|
||||
margin-right: toRem(2);
|
||||
}
|
||||
@@ -533,6 +583,13 @@ watchEffect(() => {
|
||||
margin-left: auto;
|
||||
width: fit-content;
|
||||
}
|
||||
.entry-pagination {
|
||||
margin-top: toRem(4);
|
||||
margin-bottom: toRem(4);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
@media (max-width: toRem(102.4)) {
|
||||
.section-viewer {
|
||||
width: 30%;
|
||||
@@ -554,7 +611,7 @@ watchEffect(() => {
|
||||
.section-sequence {
|
||||
width: 100% !important;
|
||||
}
|
||||
.sequence-item:first-child {
|
||||
.sequence-item-head {
|
||||
display: none;
|
||||
}
|
||||
.button-item {
|
||||
@@ -576,11 +633,17 @@ watchEffect(() => {
|
||||
flex-direction: column;
|
||||
}
|
||||
.wrapper-button {
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
justify-content: center;
|
||||
margin-top: toRem(1);
|
||||
margin-bottom: 0;
|
||||
.button--white:first-child {
|
||||
margin-right: toRem(1);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -148,7 +148,7 @@ onUnmounted(() => {
|
||||
window.onbeforeunload = null
|
||||
})
|
||||
onBeforeRouteLeave((to, from, next) => {
|
||||
if (isLoading.value) {
|
||||
if (!isLoaded.value && isLoading.value) {
|
||||
const answer = window.confirm(t('pages.upload.leave_message'))
|
||||
if (answer) return next()
|
||||
return next(false)
|
||||
@@ -238,6 +238,7 @@ async function uploadPicture(): Promise<void> {
|
||||
picturesUploadingSize.value = picturesUploadingSize.value + el.size
|
||||
const picturesOnError = {
|
||||
message: err.response.data.message,
|
||||
details: { error: err.response.data.details.error },
|
||||
name: el.name
|
||||
}
|
||||
uploadedSequence.value.picturesOnError = [
|
||||
|
||||
@@ -8,5 +8,6 @@ export interface SequenceInterface {
|
||||
}
|
||||
export interface uploadErrorInterface {
|
||||
message: string
|
||||
details: { error: string }
|
||||
name: string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user