3 Commits

Author SHA1 Message Date
Andreani Jean
02e145456e fix pr 2023-11-21 15:57:55 +01:00
Andreani Jean
7341883c14 fix test 2023-11-21 15:50:17 +01:00
Andreani Jean
230f978ffa add fix for cookie 2023-11-21 14:56:54 +01:00
21 changed files with 543 additions and 910 deletions

View File

@@ -7,27 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Before _0.1.0_, website development was on rolling release, meaning there are no version tags.
## [2.3.0] - 2023-12-06
### Added
- Add the possibility to an user to select a sequence in the list using the map : https://gitlab.com/geovisio/website/-/merge_requests/100
- For a selected sequence in the list, if the sequence is not displayed in the map, fly to the sequence on the map : https://gitlab.com/geovisio/website/-/merge_requests/108
- Add the pagination to the sequence with sort with API routes : https://gitlab.com/geovisio/website/-/merge_requests/107
- Add Hungarian translation : https://gitlab.com/geovisio/website/-/merge_requests/105
- Add the possibility to hide/delete a sequence in the sequence list : https://gitlab.com/geovisio/website/-/merge_requests/101
- Add the possibility for the user to change the title of the sequence before upload the pictures : https://gitlab.com/geovisio/website/-/merge_requests/101
- Add the possibility to resize the sequence page by dragging the blocs : https://gitlab.com/geovisio/website/-/merge_requests/101
### Changed
- GeoVisio web viewer updated to [2.3.0](https://gitlab.com/geovisio/web-viewer/-/compare/2.2.1...2.3.0)
### Fixed
- Nginx server in Docker container was not recognizing routes other than `/` on first loading.
- Fix the cookie bug by decoding flask cookie : https://gitlab.com/geovisio/website/-/merge_requests/102
## [2.2.3] - 2023-11-03
### Added
@@ -37,7 +16,6 @@ Before _0.1.0_, website development was on rolling release, meaning there are no
### Changed
- Page My Sequences, add the possibility to select a sequence in the list with the map :
- the user can only see his sequences on the list
- display the selected sequence in blue in the map
- display a thumbnail the hovered sequence
@@ -147,8 +125,7 @@ Before _0.1.0_, website development was on rolling release, meaning there are no
- Header have now a new entry `Mes photos` when the user is logged to access to the sequence list
- The router guard for logged pages has been changed to not call the api to check the token
[unreleased]: https://gitlab.com/geovisio/website/-/compare/2.3.0...develop
[2.2.1]: https://gitlab.com/geovisio/website/-/compare/2.2.3...2.3.0
[unreleased]: https://gitlab.com/geovisio/website/-/compare/2.2.3...develop
[2.2.1]: https://gitlab.com/geovisio/website/-/compare/2.2.2...2.2.3
[2.2.1]: https://gitlab.com/geovisio/website/-/compare/2.2.1...2.2.2
[2.2.1]: https://gitlab.com/geovisio/website/-/compare/2.2.0...2.2.1

View File

@@ -18,10 +18,5 @@ http {
root /usr/share/nginx/html;
gzip_static on;
location / {
root /usr/share/nginx/html;
try_files $uri /index.html;
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "geovisio-website",
"version": "2.3.0",
"version": "2.2.3",
"engines": {
"node": "18.16.0"
},
@@ -25,7 +25,7 @@
"axios": "^1.2.3",
"bootstrap": "^5.2.3",
"bootstrap-icons": "^1.10.3",
"geovisio": "2.3.0",
"geovisio": "2.2.1-develop-acb9989e",
"moment": "^2.29.4",
"pako": "^2.1.0",
"pinia": "^2.1.4",

View File

@@ -6,10 +6,12 @@ import { RouterView } from 'vue-router'
import { useMeta } from 'vue-meta'
import { useI18n } from 'vue-i18n'
import { hasASessionCookieDecoded } from '@/utils/auth'
import { useCookies } from 'vue3-cookies'
import { title } from '@/utils/index'
import authConfig from './composables/auth'
const { authConf } = authConfig()
const { t } = useI18n()
const { cookies } = useCookies()
let focusMap = ref<string>('focus-map')

View File

@@ -35,7 +35,7 @@ defineProps({
}
}
.default {
height: toRem(3);
height: toRem(3.5);
min-width: toRem(3.5);
@include text(s-regular);
display: flex;

View File

@@ -1,7 +1,7 @@
<template>
<div class="entry-edit">
<form
v-if="isEditTitle && !isDisabled"
v-if="isEditTitle"
@submit.prevent="isEditTitle = false"
class="edit-form"
>
@@ -34,7 +34,6 @@
look="no-text"
icon="bi bi-pen"
:tooltip="$t('pages.upload.edit_title_tooltip')"
:disabled="isDisabled"
@trigger="goToEditMode"
/>
</div>
@@ -50,9 +49,7 @@ const emit = defineEmits<{
(e: 'triggerNewText', value: string | null): void
}>()
const props = defineProps({
defaultText: { type: String, default: null },
isLoading: { type: Boolean, default: false },
isLoaded: { type: Boolean, default: false }
defaultText: { type: String, default: null }
})
let titleToEdit = ref<string | null>(null)
let isEditTitle = ref<boolean>(false)
@@ -78,8 +75,6 @@ const text = computed<string | null>(() => {
if (props.defaultText) return props.defaultText
return null
})
const isDisabled = computed<boolean>(() => props.isLoading && !props.isLoaded)
</script>
<style scoped lang="scss">

View File

@@ -11,7 +11,7 @@
<script lang="ts" setup>
const emit = defineEmits<{ (e: 'input', value: string): void }>()
defineProps({
const props = defineProps({
text: { type: String, default: null },
placeholder: { type: String, default: '' }
})

View File

@@ -19,7 +19,7 @@
<ul>
<li v-for="item in uploadErrors" class="error-item">
<span>{{ item.name }} - </span>
<span>{{ item.details.error }}</span>
<span>{{ item.message }}</span>
</li>
</ul>
</div>

View File

@@ -108,23 +108,17 @@ 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: bbox,
zoom: 14
bounds: [props.bbox[0], props.bbox[1], props.bbox[2], props.bbox[3]],
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 },
maxZoom: 14,
speed: 10
})
})
}
mapIsLoaded.value = true

View File

@@ -110,7 +110,7 @@ ul {
width: toRem(25);
top: toRem(8);
right: 0;
z-index: 3;
z-index: 2;
background-color: var(--white);
box-shadow: 0 toRem(0.2) toRem(0.4) rgb(0 0 0 / 10%);
border-radius: toRem(1);

View File

@@ -137,7 +137,7 @@
"description_title_sequence": "Le titre d'une séquence est par défaut la date du jour. Vous pouvez, si vous le souhaitez le modifier ci-dessous.",
"text_import": "Déposez ici vos fichiers jpg. Chaque image ou série dimages constitue une « séquence ». Vous pourrez ensuite les retrouver dans la section « mes images » et choisir de les masquer, les afficher ou les supprimer.",
"subtitle_process": "Traitements de l'import",
"uploading_process": "Envoi en cours...",
"uploading_process": "Téléchargement en cours...",
"sequence_title": "Séquence du ",
"import": "Imports",
"upload_pending": "Transfert en cours...",
@@ -148,7 +148,7 @@
"edit_title_tooltip": "Modifier le titre de la séquence",
"edit_placeholder_input": "Modifier le titre de la séquence",
"ok_button": "Valider",
"pictures_error": "{count} image n'a pas pu être chargée| {count} images n'ont pas pu être chargées",
"pictures_error": "{count} image n'a pas pu être chargée| {count} image n'ont pas pu être chargées",
"sequence_loading_information": "Une fois chargée, la séquence sera en traitement et accessible sur Panoramax dans les prochaines minutes.",
"sequence_loaded_information": "La séquence est chargée et est en cours de traitement. Elle sera accessible sur Panoramax dans quelques minutes.",
"leave_message": "⚠️ Attention, le téléchargement sera interrompu si vous quittez la page avant la fin.",

View File

@@ -1,184 +0,0 @@
{
"general": {
"title": "Panoramax-példány",
"meta": {
"title": "Panoramax-példány",
"description": "Panoramax, szabad alternatíva a világ fotós feltérképezéséhez"
},
"header": {
"login_text": "Kapcsolódás",
"register_text": "Regisztráció",
"contribute_text": "Miért működjön közre?",
"my_account": "Saját fiók",
"upload_text": "+ Fényképek megosztása",
"sequences_text": "Saját fényképek",
"alt_logo": "A példány logója",
"alt_photos": "Képek ikon",
"alt_information": "Felhasználó ikon",
"alt_settings": "Paraméterek ikon",
"alt_logout": "Kijelentkezés ikon",
"title": "Panoramax",
"beta_text": "Béta verzió",
"logout_text": "Kijelentkezés",
"my_information_text": "Saját adatok",
"my_settings_text": "Saját beállítások",
"burger_menu_aria_label_open": "A menü megjelenítése",
"burger_menu_aria_label_closed": "A menü bezárása"
},
"footer": {
"panoramax_site": "A Panoramax felfedezése",
"information_gitlab": "Forráskód megjelenítése",
"gitlab_logo": "Gitlab logó",
"ay11_text": "Akadálymentesítés: nem felel meg"
},
"error_text": "Hiba történt",
"success_text": "Sikeres frissítés"
},
"pages": {
"home": {
"report_mail": "?subject=⚠️ Report on picture {picId}&body=Hello, %0D%0A%0D%0A Problem on image (keep type of problem reported) : %0D%0A%0D%0A %0D%0A%0D%0A inappropriate content / lack of blurring on an element to be anonymized or blurred for security reasons / overblurring (too much blurring) %0D%0A%0D%0A Link to affected photo : {link} %0D%0A%0D%0A Details of affected elements (especially for blurring problems - what should be blurred or unblurred?) :",
"report_button_text": "Fénykép jelentése"
},
"settings": {
"title": "Saját tokenek",
"setting_tooltip": "A token megjelenítése vagy elrejtése"
},
"sequence": {
"sequence_published": "Közzétéve",
"sequence_waiting": "Feldolgozás alatt",
"sequence_hidden": "Rejtett",
"hide_sequence_tooltip": "A sorozat elrejtése",
"delete_sequence_tooltip": "A sorozat végleges törlése",
"hide_photo_tooltip": "A kiválasztott fényképek elrejtése",
"delete_photo_tooltip": "A kiválasztottt fényképek végleges törlése",
"confirm_pictures_dialog": "⚠️ A kiválasztott fényképek véglegesen elvesznek",
"confirm_sequence_dialog": "⚠️ A kiválasztott sorozat véglegesen elvész",
"created": "Feltöltés ideje:",
"taken": "Elkészítés ideje:",
"duration": "Hossz:",
"duration_begin": "Kezdet:",
"duration_end": "Vég:",
"camera": "Kamera:",
"button_delete": "Törlés",
"button_disable": "Elrejtés",
"button_enable": "Megjelenítés",
"picture_selected": "{count} fénykép kiválasztva| {count} fénykép kiválasztva",
"hours": "{count} óra| {count} óra",
"minutes": "{count} perc| {count} perc",
"seconds": "{count} másodperc| {count} másodperc",
"select_text": "Kiválasztás",
"unselect_text": "Kiválasztás törlése",
"select_shift_text": "Több elemet a Shift segítségével választhat ki",
"waiting_process": "A fénykép feldolgozás alatt áll",
"broken": "Fénykép-feldolgozási hiba",
"no_image": "Nincsenek fényképek ebben a sorozatban"
},
"sequences": {
"title": "Saját fényképsorozatok",
"sequence_name": "Név",
"sequence_photos": "Fényképek",
"sequence_date": "Elkészítés ideje",
"sequence_creation": "Feltöltés ideje",
"sequence_status": "Állapot",
"sequence_published": "Közzétéve",
"sequence_waiting": "Feldolgozás alatt",
"sequence_hidden": "Rejtett",
"no_sequences_text": "Még nincsenek közzétett fényképei \uD83D\uDE22",
"button_upload": "Fényképek feltöltése",
"sequence_deleted": "A sorozat törlésre került"
},
"share_pictures": {
"title": "Miért működjön közre a Panoramaxban?",
"description": "A Panoramaxban való közreműködés azt jelenti, hogy részt vesz egy közös, független, szabad és újrafelhasználható digitális erőforrás fejlesztésében. A Panoramaxon található minden georeferált fénykép bárki által használható, számos célra, például egy önkormányzat megtekintheti az utai állapotát, vagy egy távközlési vállalat felkészülhet a beavatkozásaira.\n\nMinden közreműködő beküldheti a saját képsorozatait, módosíthatja és megnézheti azokat, ahogyan ezt teheti a közösség más tagjai által feltöltött képekkel is. Az arcok és rendszámtáblák kötelező kitakarását maga a platform automatizálja.",
"alt_img_map": "Illusztráció egy nőről, aki egy térképet néz a földrajzi helymeghatározással rendelkező okostelefonján",
"card_photo1": "Közútról látható helyek",
"card_photo2": "Közzétett fényképek 360°-os vagy más formátumban",
"card_photo3": "Könnyen újrafelhasználható fényképek",
"card_photo4": "Képek gyors és egyszerű megosztása",
"card_alt_photo1": "Egy épületet tartalmazó kép",
"card_alt_photo2": "Egy 360°-ot megjelenítő kép",
"card_alt_photo3": "Egy kép egy térképről mutatóval",
"card_alt_photo4": "Egy mutatót ábrázoló kép",
"card_description1": "Minden közútról készült kép elfogadható, ha az georeferált és a földről készült.",
"card_description2": "A 360°-os képek nem kötelezők: az okostelefonnal készült képek is megfelelők. Az előfeltételek a dátum, a helyszín és a jpg formátum használata.",
"card_description3": "Minden fénykép fiók nélkül is könnyedén elérhető és felhasználható: a weboldalon vagy egy szabványos API-n keresztül (STAC szabvány).",
"card_description4": "Számos eszköz elérhető a közreműködések lehetővé tételéhez, köztük egy parancssoros és egy webes felület is.",
"upload_subtitle": "Töltse fel egyszerűen a fényképeit",
"upload_illustration_alt": "Online képfeltöltést ábrázoló illusztráció",
"upload_description": "A Panoramax webalkalmazása lehetővé teszi, hogy egy gombnyomással feltöltse az összes fényképét JPG formátumban. Programozói készségek nem szükségesek. Sok kép esetén viszont a parancssoros eszköz használatát javasoljuk.",
"upload_button": "+ Képek feltöltése",
"command_line_subtitle": "Parancssoros eszköz",
"comment_install": "A geovisio parancssoros eszköz telepítése",
"comment_upload": "A képfeltöltési parancs indítása a kiválasztott mappán",
"description_terminal": "<a href='https://gitlab.com/geovisio/cli' target='_blank' style='color:black'>A parancssor</a> lehetővé teszi, hogy nagy számú képet osszon meg. A folyamat egyszerű, és <a target='_blank' href='https://www.python.org/downloads/' style='color:black'>Pythont (3.8-as vagy újabb verzió)</a> igényli.\n\nAz importálás előtt az eszköz bekéri a bejelentkezési adatait. Amint a képek felöltésre kerültek, azok közzététele előtt feldolgozási idő szükséges.",
"terminal_install": "pip install geovisio_cli",
"terminal_text": "geovisio upload --api-url {webcím} <FÉNYKÉPMAPPA>",
"button_copy": "Másolás",
"information_subtitle": "Itt a fényképek mindenki számára elérhetők: ",
"information_text1": "Automatikusan kitakarva, a törvényi előírásoknak megfelelően.",
"information_text2": "A feltöltött képek a következő alatt lesznek közzétéve: {word}",
"information_text3": "Az eredeti formátumban és felbontásban, számos újrafeldolgozáshoz.",
"information_about_title": "Hozzá kell férnie a fényképeihez?",
"information_about_description": "Az API elérhető az összes metaadat és kép letöltéséhez. <a href='{docLink}' target='_blank' style='color:#0a1f69'>\nTudjon meg többet itt.</a>\nAz adatok <a href='{docTiles}' target='_blank' style='color:#0a1f69'>vektorcsempék</a> formájában is megjelennek.",
"doc_subtitle": "Segítségre van szüksége a Panoramaxban történő közreműködéshez?",
"doc_description": "A Panoramax dokumentációja elérhető tőlünk, oktatóanyagokat pedig a geo-commons fórumán találhat.",
"doc_button": "A dokumentáció megtekintése",
"doc_illustration_alt": "Illusztráció egy karakterről dokumentumokkal"
},
"upload": {
"title": "Közreműködés a Panoramax projektben",
"description": "Nagy számú fényképhez a parancssoros eszköz jobban megfelelő.",
"know_more_button": "További tudnivalók",
"input_label": "Húzza ide a képeket, vagy kattintson a",
"import_word": "feltöltésre",
"import_type": "Csak JPEG formátum",
"subtitle_import": "Képfeltöltés",
"title_sequence": "Sorozat címe",
"description_title_sequence": "Alapértelmezés szerint a sorozat cím a nap dátuma lesz. Ha akarja, itt szerkesztheti a címet.",
"text_import": "Ide töltse fel a JPG-fájljait. Minden kép vagy képsorozat egy „sorozatot” alkot. Megtalálhatja azokat a „saját fényképek” szakaszban, és elrejtheti, megjelenítheti vagy törölheti azokat.",
"subtitle_process": "Feltöltés feldolgozása",
"uploading_process": "Feltöltés folyamatban…",
"sequence_title": "Sorozat ",
"import": "Feltöltések",
"upload_pending": "Feltöltés folyamatban…",
"images_count_text": "Feltöltött képek",
"no_img_text": "még nem volt képfeltöltés",
"upload_done": "A sorozat feltöltése elkészült",
"sequence_link": "A sorozat megjelenítése",
"edit_title_tooltip": "A sorozat címének szerkesztése",
"edit_placeholder_input": "A sorozat címének szerkesztése",
"ok_button": "OK",
"pictures_error": "{count} kép feltöltése nem sikerült| {count} kép feltöltése nem sikerült",
"sequence_loading_information": "Amint feltöltötte, a sorozat feldolgozásra, majd közzétételre került a Panoramaxon (általában néhány perc múlva).",
"sequence_loaded_information": "A sorozatok fel lettek töltve, és feldolgozás alatt vannak. Néhány perc múlva nyilvánosan elérhetőnek kellene lenniük a Panoramaxon.",
"leave_message": "⚠️ FIGYELMEZTETÉS, a feltöltés meg fog szakadni, ha a végezte előtt hagyja el a lapot.",
"error_button": "Hibák megjelenítése",
"modal_error_title": "A hibában érintett képek"
},
"ay11": {
"title": "Déclaration daccessibilité",
"date": "Établie le 18 septembre 2023.",
"introduction": "IGN sengage à rendre son service accessible, conformément à larticle 47 de la loi n° 2005-102 du 11 février 2005.\n Cette déclaration daccessibilité sapplique à Panoramax Instance IGN : https://panoramax.ign.fr",
"subtitle_conformity": "État de conformité",
"conformity_text": "Panoramax Instance IGN est non conforme avec le ",
"conformity_text2": "Le site na encore pas été audité.",
"subtitle_conformity2": "Contenus non accessibles",
"subtitle_increase": "Amélioration et contact",
"increase_text": "Si vous narrivez pas à accéder à un contenu ou à un service, vous pouvez\n contacter le responsable de Panoramax Instance IGN pour être orienté vers une alternative accessible ou obtenir le contenu sous une autre forme.",
"phone": "Téléphone : +33 14 398 84 61",
"email_text": "E-mail :",
"email": "signalement.ign@panoramax.fr",
"address": "Adresse : IGN, Saint-Mandé",
"increase_info": "Nous essayons de répondre dans les 5 jours ouvrés.",
"subtitle_to_do": "Voie de recours",
"to_do_text": "Cette procédure est à utiliser dans le cas suivant : vous avez signalé au responsable du site internet un défaut daccessibilité qui vous\n empêche daccéder à un contenu ou à un des services du portail et vous navez pas obtenu de réponse satisfaisante. \n vous pouvez :",
"write_message": "Écrire un message au",
"defenseur_droits": "Défenseur des droits",
"contact": "Contacter",
"contact_text": "le délégué du Défenseur des droits dans votre région",
"send_letter": "Envoyer un courrier par la poste (gratuit, ne pas mettre de\n timbre):\n Défenseur des droits\n Libre réponse 71120 75342 Paris CEDEX 07",
"end": "Cette déclaration daccessibilité a été créé le\n 18 septembre 2023 grâce au",
"generator_betagouv": "Générateur de Déclaration dAccessibilité de BetaGouv"
}
}
}

View File

@@ -4,12 +4,12 @@ import App from './App.vue'
import router from './router'
import axios from 'axios'
import VueAxios from 'vue-axios'
import { globalCookiesConfig } from 'vue3-cookies'
import { createMetaManager } from 'vue-meta'
import { VueDraggableResizable } from 'vue-draggable-resizable-vue3'
import { pinia } from './store'
import fr from './locales/fr.json'
import en from './locales/en.json'
import hu from './locales/hu.json'
import './assets/main.scss'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-icons/font/bootstrap-icons.css'
@@ -26,10 +26,12 @@ const i18n = createI18n({
legacy: false,
messages: {
fr,
en,
hu
en
}
})
globalCookiesConfig({
expireTimes: '7d'
})
const app = createApp(App)

View File

@@ -1,4 +1,5 @@
import { createRouter, createWebHistory } from 'vue-router'
import { useCookies } from 'vue3-cookies'
import type {
RouteRecordRaw,
NavigationGuardNext,
@@ -14,6 +15,7 @@ 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: '/',

View File

@@ -30,13 +30,7 @@ describe('Template', () => {
})
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 uploadErrors = [{ message: 'my message', name: 'my name' }]
const wrapper = shallowMount(Modal, {
global: {
plugins: [i18n],
@@ -51,7 +45,7 @@ describe('Template', () => {
expect(wrapper.vm.uploadErrors).toEqual(uploadErrors)
expect(wrapper.html()).contains('my name - ')
expect(wrapper.html()).contains('my error')
expect(wrapper.html()).contains('my message')
})
})
})

View File

@@ -429,7 +429,7 @@ function fullImagesToDelete(): ResponseUserPhotoInterface[] {
return pictures.value.filter((el) => picturesToDelete.value.includes(el.id))
}
async function goToNextPage(value: string): Promise<void> {
async function goToNextPage(value: string) {
isLoading.value = true
const { data } = await fetchCollectionItemsWithFullUrl(value)
selfLink.value = data.links.filter((el) => el.rel === 'self')

View File

@@ -26,52 +26,61 @@
/>
</vue-draggable-resizable>
</section>
<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">
<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>
<li
v-if="userSequences.length"
v-for="item in userSequences"
v-for="(item, i) in userSequences"
:id="`el-list${i}`"
:class="[
'sequence-item',
item.id === seqId ? 'button-item-hover' : ''
@@ -170,29 +179,17 @@
<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, nextTick } from 'vue'
import { onMounted, ref, watchEffect, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useSequenceStore } from '@/store/sequence'
import { storeToRefs } from 'pinia'
import {
scrollIntoSelected,
formatPaginationItems
} from '@/views/utils/sequence/index'
import { scrollIntoSelected } from '@/views/utils/sequence/index'
import { useCookies } from 'vue3-cookies'
import axios from 'axios'
import Viewer from '@/components/Viewer.vue'
@@ -200,33 +197,23 @@ 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 Pagination from '@/components/Pagination.vue'
import type { SequenceLinkInterface } from './interfaces/MySequencesView'
import type { ResponseUserPhotoLinksInterface } from './interfaces/MySequenceView'
import type {
SequenceLinkInterface,
ExtentSequenceLinkInterface
} from './interfaces/MySequencesView'
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 isLoading = ref<boolean>(false)
let isSorted = ref<boolean>(false)
let isLoading = ref<boolean>(false)
let seqId = ref<string>('')
let width = ref<number>(0)
let mapWidth = ref<number>(window.innerWidth / 3)
@@ -234,10 +221,6 @@ 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')
@@ -271,110 +254,63 @@ function sequenceStatus(status: string): string {
if (status === 'hidden') return t('pages.sequences.sequence_hidden')
return t('pages.sequences.sequence_waiting')
}
function onResizeMap(width: any): void {
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) {
if (width) {
width.value = width
mapWidth.value = width.value.width
listWidth.value = window.innerWidth - width.value.width
}
}
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()
}
}
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)}`
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
}
)
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
userSequences.value = sorted
}
function bboxIsInsideOther(mainBbox: number[], bboxInside: number[]): boolean {
return (
bboxInside[0] <= mainBbox[0] &&
bboxInside[1] <= mainBbox[1] &&
bboxInside[2] >= mainBbox[2] &&
bboxInside[3] >= mainBbox[3]
)
}
function goToSequence(sequence: SequenceLinkInterface): void {
function goToSequence(sequence: SequenceLinkInterface) {
seqId.value = sequence.id
viewerRef.value.viewer.select(seqId.value)
const currentBbox = [
viewerRef.value.viewer._map.getBounds()._ne.lng,
viewerRef.value.viewer._map.getBounds()._ne.lat,
viewerRef.value.viewer._map.getBounds()._sw.lng,
viewerRef.value.viewer._map.getBounds()._sw.lat
]
if (bboxIsInsideOther(currentBbox, sequence.extent.spatial.bbox[0])) return
viewerRef.value.viewer._map.flyTo({
center: [
sequence.extent.spatial.bbox[0][0],
sequence.extent.spatial.bbox[0][1]
],
zoom: 10,
duration: 0
})
}
function getRelChild(
sequences: SequenceLinkInterface[]
): SequenceLinkInterface[] {
function getRelChild(sequences: 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>((): 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'
: ''
}
return ''
})
const getUserId = computed<string>(() => cookies.get('user_id'))
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'
)
paginationLinks.value = formatPaginationItems(data.links)
const { data } = await axios.get('api/users/me/collection')
collectionBbox.value = data.extent.spatial.bbox[0]
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]
]
isLoading.value = false
} catch (err) {
isLoading.value = false
@@ -451,21 +387,25 @@ 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(1);
bottom: 0;
right: toRem(2);
bottom: toRem(2);
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
height: 100%;
.button--white:first-child {
margin-bottom: toRem(1);
margin-right: toRem(1);
}
}
.button-item-hover {
@@ -480,21 +420,8 @@ 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: var(--white);
background-color: initial;
color: initial;
}
.wrapper-title {
@@ -570,6 +497,7 @@ watchEffect(() => {
}
.sequence-header-item {
width: calc(31% - #{toRem(4.75)});
margin-left: toRem(-1);
&:first-child {
margin-right: toRem(2);
}
@@ -605,13 +533,6 @@ 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%;
@@ -633,7 +554,7 @@ watchEffect(() => {
.section-sequence {
width: 100% !important;
}
.sequence-item-head {
.sequence-item:first-child {
display: none;
}
.button-item {
@@ -655,17 +576,11 @@ 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>

View File

@@ -41,8 +41,6 @@
</p>
<EditText
:default-text="newSequenceTitle || sequenceTitle"
:is-loading="isLoading"
:is-loaded="isLoaded"
@triggerNewText="setNewSequenceTitle"
/>
<form>
@@ -150,7 +148,7 @@ onUnmounted(() => {
window.onbeforeunload = null
})
onBeforeRouteLeave((to, from, next) => {
if (!isLoaded.value && isLoading.value) {
if (isLoading.value) {
const answer = window.confirm(t('pages.upload.leave_message'))
if (answer) return next()
return next(false)
@@ -240,7 +238,6 @@ 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 = [

View File

@@ -5,7 +5,14 @@ export interface SequenceLinkInterface {
title: string
type: string
created: Date
extent: { temporal: { interval: [Date[]] }; spatial: { bbox: [number[]] } }
extent: { temporal: { interval: [Date[]] } }
['stats:items']: { count: number }
['geovisio:status']: string
}
export interface ExtentSequenceLinkInterface {
extent: { temporal: { interval: [Date[]] } }
['stats:items']: { count: number }
title: string
created: Date
}

View File

@@ -8,6 +8,5 @@ export interface SequenceInterface {
}
export interface uploadErrorInterface {
message: string
details: { error: string }
name: string
}

856
yarn.lock

File diff suppressed because it is too large Load Diff