forked from Ivasoft/geovisio-website
Compare commits
33 Commits
2.5.0
...
tech-add-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0a9d0eb27 | ||
|
|
5488ac8521 | ||
|
|
80089126c4 | ||
|
|
4fed5f7938 | ||
|
|
5a6d144196 | ||
|
|
9c2ab2453c | ||
|
|
be2cdbb62b | ||
|
|
2e2f23de69 | ||
|
|
669f0528e5 | ||
|
|
6e0f19a5f1 | ||
|
|
661ba984fd | ||
|
|
aa3dbdaf03 | ||
|
|
aa257adbcf | ||
|
|
d1779da92d | ||
|
|
379c44c5ce | ||
|
|
8452b50ea0 | ||
|
|
30b49a8c7d | ||
|
|
482372fccb | ||
|
|
f37dc950cb | ||
|
|
02ba0efe8d | ||
|
|
a3bec701ea | ||
|
|
87da6714a2 | ||
|
|
1bf633b284 | ||
|
|
7b56fb5c33 | ||
|
|
7eb090492b | ||
|
|
e580887969 | ||
|
|
56e2908f70 | ||
|
|
2afabb82f4 | ||
|
|
8d4a0968ea | ||
|
|
a006811b44 | ||
|
|
387f84b1a7 | ||
|
|
86a54fed85 | ||
|
|
6f6e16c2c7 |
57
CHANGELOG.md
57
CHANGELOG.md
@@ -7,51 +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.5.0] - 2024-03-11
|
||||
|
||||
### Changed
|
||||
|
||||
- GeoVisio web viewer updated to [2.5.0](https://gitlab.com/geovisio/web-viewer/-/compare/2.4.0...2.5.0) to reduce tiles size.
|
||||
|
||||
## [2.4.1] - 2024-02-01
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix geovisio version yarn.lock
|
||||
|
||||
## [2.4.0] - 2024-01-31
|
||||
|
||||
### Added
|
||||
|
||||
- Possibility to edit a sequence title in the sequence page
|
||||
|
||||
### Changed
|
||||
|
||||
- GeoVisio web viewer updated to [2.4.0](https://gitlab.com/geovisio/web-viewer/-/compare/2.3.1...2.4.0) to manage sequence by user
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix filter reset button to include bbox filter
|
||||
- Fix fullscreen button added by the widget viewer
|
||||
- Some UI and UX fixes before user tests
|
||||
|
||||
## [2.3.1] - 2024-01-29
|
||||
|
||||
### Added
|
||||
|
||||
- Add the possibility to fullscreen the viewer in the homepage : https://gitlab.com/geovisio/website/-/issues/60
|
||||
- In the sequence list page add a filter by bbox in the map : https://gitlab.com/geovisio/website/-/issues/61
|
||||
- In the sequence list page add a filter by date in the list : https://gitlab.com/geovisio/website/-/issues/57
|
||||
- Add a cancel button to when a sequence is uploading to cancel the upload
|
||||
|
||||
### Changed
|
||||
|
||||
- GeoVisio web viewer updated to [2.3.1](https://gitlab.com/geovisio/web-viewer/-/compare/2.3.0...2.3.1)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Some UI and UX fixes before user tests
|
||||
|
||||
## [2.3.0] - 2023-12-06
|
||||
|
||||
### Added
|
||||
@@ -192,14 +147,10 @@ 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.5.0...develop
|
||||
[2.5.0]: https://gitlab.com/geovisio/website/-/compare/2.4.1...2.5.0
|
||||
[2.4.1]: https://gitlab.com/geovisio/website/-/compare/2.4.0...2.4.1
|
||||
[2.4.0]: https://gitlab.com/geovisio/website/-/compare/2.3.1...2.4.0
|
||||
[2.3.1]: https://gitlab.com/geovisio/website/-/compare/2.3.0...2.3.1
|
||||
[2.3.0]: https://gitlab.com/geovisio/website/-/compare/2.2.3...2.3.0
|
||||
[2.2.3]: https://gitlab.com/geovisio/website/-/compare/2.2.2...2.2.3
|
||||
[2.2.2]: https://gitlab.com/geovisio/website/-/compare/2.2.1...2.2.2
|
||||
[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
|
||||
[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
|
||||
[2.2.0]: https://gitlab.com/geovisio/website/-/compare/2.1.3...2.2.0
|
||||
[2.1.3]: https://gitlab.com/geovisio/website/-/compare/2.1.2...2.1.3
|
||||
|
||||
@@ -19,7 +19,6 @@ 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
|
||||
@@ -51,7 +50,6 @@ 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=""
|
||||
|
||||
|
||||
@@ -20,10 +20,6 @@ describe('In the contribute page', () => {
|
||||
})
|
||||
interface contributeInterface {
|
||||
textButtonContribute: string
|
||||
textButtonDocPython: string
|
||||
textButtonCli: string
|
||||
textButtonDocCli: string
|
||||
textButtonTiles: string
|
||||
textButtonDoc: string
|
||||
}
|
||||
export {}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
describe('In the login page', () => {
|
||||
it('login and go to the upload page to upload images', () => {
|
||||
cy.visit(`${Cypress.env('api_url')}api/auth/login`)
|
||||
cy.get('#username').type('Elysee')
|
||||
cy.get('#password').type('my password')
|
||||
cy.fixture('upload').then((uploadData: uploadInterface) => {
|
||||
cy.contains(uploadData.textLinkLogin).click()
|
||||
cy.visit('/envoyer')
|
||||
cy.get('.edit-button').click()
|
||||
cy.get('#upload-title').clear()
|
||||
cy.get('#upload-title').type(uploadData.textTitle)
|
||||
cy.contains(uploadData.textButtonTitle).click()
|
||||
cy.contains(uploadData.textButtonUpload).click()
|
||||
})
|
||||
cy.get('.input-file').selectFile(
|
||||
[
|
||||
'/src/cypress/fixtures/images/image1.jpg',
|
||||
'/src/cypress/fixtures/images/image2.jpg',
|
||||
'/src/cypress/fixtures/images/image3.jpg'
|
||||
],
|
||||
{ force: true }
|
||||
)
|
||||
})
|
||||
})
|
||||
interface uploadInterface {
|
||||
textLinkLogin: string
|
||||
textLinkUpload: string
|
||||
textButtonUpload: string
|
||||
textTitle: string
|
||||
textButtonTitle: string
|
||||
}
|
||||
export {}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"textLinkLogin": "Sign In",
|
||||
"textLinkUpload": "Mes photos",
|
||||
"textTitle": "My title",
|
||||
"textButtonTitle": "Valider",
|
||||
"textButtonUpload": "Glissez vos images ici ou cliquez sur"
|
||||
}
|
||||
@@ -14,7 +14,6 @@ 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
|
||||
|
||||
25
index.html
25
index.html
@@ -4,31 +4,6 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" href="/static/favicon.ico" />
|
||||
<title>
|
||||
Panoramax <%- instanceName %> : photo-cartographier les territoires
|
||||
</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="L'instance Panoramax <%- instanceName %> permet la publication de photo de terrain pour cartographier le territoire. Panoramax favorise la réutilisation des photos pour de nombreux cas d'usages."
|
||||
/>
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="Panoramax <%- instanceName %> : photo-cartographier les territoires"
|
||||
/>
|
||||
<meta
|
||||
name="og:title"
|
||||
content="Panoramax <%- instanceName %> : photo-cartographier les territoires"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="L'instance Panoramax <%- instanceName %> permet la publication de photo de terrain pour cartographier le territoire. Panoramax favorise la réutilisation des photos pour de nombreux cas d'usages."
|
||||
/>
|
||||
<meta
|
||||
name="og:description"
|
||||
content="L'instance Panoramax <%- instanceName %> permet la publication de photo de terrain pour cartographier le territoire. Panoramax favorise la réutilisation des photos pour de nombreux cas d'usages."
|
||||
/>
|
||||
<meta name="og:image" content="<%- frontUrl %>/static/meta-img.jpg" />
|
||||
<meta name="twitter:image" content="<%- frontUrl %>/static/meta-img.jpg" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "geovisio-website",
|
||||
"version": "2.5.0",
|
||||
"version": "2.3.0",
|
||||
"engines": {
|
||||
"node": "18.16.1"
|
||||
},
|
||||
@@ -25,17 +25,16 @@
|
||||
"axios": "^1.2.3",
|
||||
"bootstrap": "^5.2.3",
|
||||
"bootstrap-icons": "^1.10.3",
|
||||
"geovisio": "2.5.0",
|
||||
"geovisio": "2.3.0",
|
||||
"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",
|
||||
"vue-eslint-parser": "^9.1.0",
|
||||
"vue-i18n": "9.2.2",
|
||||
"vue-matomo": "^4.2.0",
|
||||
"vue-meta": "^3.0.0-alpha.10",
|
||||
"vue-router": "^4.1.6",
|
||||
"vue3-cookies": "^1.0.6",
|
||||
"vue3-smooth-scroll": "^0.8.1"
|
||||
@@ -70,7 +69,6 @@
|
||||
"typescript": "~4.7.4",
|
||||
"vite": "^3.2.4",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-html": "^3.2.2",
|
||||
"vitest": "^0.25.3",
|
||||
"vue-tsc": "^1.0.9"
|
||||
},
|
||||
|
||||
14
src/App.vue
14
src/App.vue
@@ -3,14 +3,28 @@ import { ref, computed } from 'vue'
|
||||
import Header from '@/components/Header.vue'
|
||||
import Footer from '@/components/Footer.vue'
|
||||
import { RouterView } from 'vue-router'
|
||||
import { useMeta } from 'vue-meta'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { hasASessionCookieDecoded } from '@/utils/auth'
|
||||
import { title } from '@/utils/index'
|
||||
import authConfig from './composables/auth'
|
||||
const { authConf } = authConfig()
|
||||
const { t } = useI18n()
|
||||
|
||||
let focusMap = ref<string>('focus-map')
|
||||
|
||||
useMeta({
|
||||
title: title(t('general.title')),
|
||||
og: {
|
||||
title: title(t('general.meta.title')),
|
||||
description: title(t('general.meta.description'))
|
||||
},
|
||||
twitter: {
|
||||
title: title(t('general.meta.title')),
|
||||
description: title(t('general.meta.description'))
|
||||
}
|
||||
})
|
||||
|
||||
function setFocusMap(value: string) {
|
||||
focusMap.value = value
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 33 KiB |
@@ -1,45 +1,3 @@
|
||||
export function createLink(href: string, text: string): string {
|
||||
return `<a href='mailto:signalement.ign@panoramax.fr${href}' target='_blank' title='${text}' class='gvs-btn gvs-widget-bg gvs-btn-large' style='font-size: 1.6em;display: block; margin-top: 0.5em;'><i class="bi bi-exclamation-triangle"></i></a>`
|
||||
}
|
||||
export function createSequenceLink(href: string, title: string): string {
|
||||
return `<a href='${href}' title='${title}' class='gvs-btn gvs-widget-bg gvs-btn-large' style='font-size: 1.6em;display: block; position: relative; margin-top: 0.5em;'>
|
||||
<i class="bi bi-images"></i>
|
||||
</a>`
|
||||
}
|
||||
export function createFullScreenButton(): string {
|
||||
return `<button type='button' onClick="
|
||||
const header = document.getElementById('navHeader')
|
||||
const footer = document.getElementById('navFooter')
|
||||
const icon = document.getElementById('iconScreen')
|
||||
const home = document.getElementById('homePage')
|
||||
if (header) {
|
||||
const isHiddenHeader = header.classList.contains('hidden')
|
||||
if (isHiddenHeader) header.classList.remove('hidden')
|
||||
else header.classList.add('hidden')
|
||||
}
|
||||
if (footer) {
|
||||
const isHiddenHeader = footer.classList.contains('hidden')
|
||||
if (isHiddenHeader) footer.classList.remove('hidden')
|
||||
else footer.classList.add('hidden')
|
||||
}
|
||||
if (icon) {
|
||||
const isIconNotFull = icon.classList.contains('bi-fullscreen')
|
||||
if(isIconNotFull) {
|
||||
icon.classList.remove('bi-fullscreen')
|
||||
icon.classList.add('bi-fullscreen-exit')
|
||||
} else {
|
||||
icon.classList.remove('bi-fullscreen-exit')
|
||||
icon.classList.add('bi-fullscreen')
|
||||
}
|
||||
}
|
||||
if (home) {
|
||||
const isHomeFull = home.classList.contains('full-viewer')
|
||||
if(isHomeFull) home.classList.remove('full-viewer')
|
||||
else home.classList.add('full-viewer')
|
||||
}
|
||||
"
|
||||
class='gvs-btn gvs-widget-bg gvs-btn-large'
|
||||
>
|
||||
<i id='iconScreen' class="bi bi-fullscreen"></i>
|
||||
</button>`
|
||||
return `<a href='mailto:signalement.ign@panoramax.fr${href}' target='_blank' title='${text}' class='gvs-btn gvs-widget-bg gvs-btn-large' style='font-size: 1.6em;display: block'><i class="bi bi-exclamation-triangle"></i></a>`
|
||||
}
|
||||
|
||||
@@ -55,30 +55,10 @@ defineProps({
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
.row-reverse {
|
||||
flex-direction: row-reverse;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
font-size: toRem(1.2);
|
||||
.icon {
|
||||
margin-left: toRem(0.5);
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
.button--black {
|
||||
color: var(--white);
|
||||
background-color: var(--black);
|
||||
}
|
||||
.button-border--black {
|
||||
font-size: toRem(1.4);
|
||||
background-color: var(--white);
|
||||
border: toRem(0.1) solid var(--black);
|
||||
.icon {
|
||||
font-size: toRem(1.4);
|
||||
color: var(--black);
|
||||
}
|
||||
}
|
||||
.button--blue {
|
||||
color: var(--white);
|
||||
background-color: var(--blue);
|
||||
@@ -95,37 +75,28 @@ defineProps({
|
||||
color: var(--white);
|
||||
}
|
||||
.button--red {
|
||||
color: var(--white);
|
||||
background-color: var(--red-pale);
|
||||
border: toRem(0.1) solid var(--red-pale);
|
||||
color: var(--red);
|
||||
background-color: transparent;
|
||||
border: toRem(0.1) solid var(--red);
|
||||
.icon {
|
||||
margin-right: 0;
|
||||
font-size: toRem(1.4);
|
||||
color: var(--white);
|
||||
color: var(--red);
|
||||
}
|
||||
.text {
|
||||
margin-left: toRem(1);
|
||||
}
|
||||
&.background-white {
|
||||
color: var(--red-pale);
|
||||
.icon {
|
||||
color: var(--red-pale);
|
||||
}
|
||||
&.disabled {
|
||||
.icon {
|
||||
color: var(--grey-pale);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.button--white {
|
||||
color: var(--blue);
|
||||
background-color: var(--white);
|
||||
border: toRem(0.1) solid var(--blue);
|
||||
.icon {
|
||||
font-size: toRem(1.4);
|
||||
color: var(--blue);
|
||||
margin-right: toRem(1);
|
||||
margin-right: 0;
|
||||
}
|
||||
.text {
|
||||
margin-left: toRem(1);
|
||||
}
|
||||
}
|
||||
.no-text {
|
||||
@@ -133,6 +104,7 @@ defineProps({
|
||||
width: toRem(3);
|
||||
padding: 0;
|
||||
.icon {
|
||||
color: var(--black);
|
||||
font-size: toRem(1.8);
|
||||
margin-right: 0;
|
||||
}
|
||||
@@ -141,27 +113,9 @@ defineProps({
|
||||
color: var(--white);
|
||||
margin-right: 0;
|
||||
}
|
||||
.no-text-blue-dark .icon {
|
||||
color: var(--blue-dark);
|
||||
margin-right: 0;
|
||||
font-size: toRem(1.4);
|
||||
}
|
||||
.no-text-green .icon {
|
||||
color: var(--blue);
|
||||
margin-right: 0;
|
||||
font-size: toRem(1.6);
|
||||
}
|
||||
.background-white {
|
||||
background-color: var(--white);
|
||||
}
|
||||
.link--blue {
|
||||
color: var(--blue);
|
||||
text-decoration: underline;
|
||||
.icon {
|
||||
font-size: toRem(1.4);
|
||||
color: var(--blue);
|
||||
}
|
||||
}
|
||||
.link--grey {
|
||||
color: var(--grey-semi-dark);
|
||||
.icon {
|
||||
@@ -169,19 +123,12 @@ 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-pale);
|
||||
text-decoration: underline;
|
||||
color: var(--red);
|
||||
background-color: var(--white);
|
||||
.icon {
|
||||
font-size: toRem(1.4);
|
||||
color: var(--red-pale);
|
||||
color: var(--red);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,10 +143,8 @@ defineProps({
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
padding: 0;
|
||||
height: toRem(4);
|
||||
width: toRem(4);
|
||||
background-color: var(--white);
|
||||
border: toRem(0.1) solid var(--grey-pale);
|
||||
height: toRem(2.5);
|
||||
width: toRem(2.5);
|
||||
.icon {
|
||||
color: var(---black);
|
||||
font-size: toRem(1.8);
|
||||
@@ -217,7 +162,6 @@ defineProps({
|
||||
visibility: hidden;
|
||||
width: toRem(20);
|
||||
right: 0;
|
||||
z-index: 9;
|
||||
@include text(xss-regular);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +1,15 @@
|
||||
<template>
|
||||
<div :class="['entry-edit', { 'edit-mode': isEditTitle }]">
|
||||
<span v-if="isEditTitle && formTitle" class="form-title">{{
|
||||
formTitle
|
||||
}}</span>
|
||||
<div class="entry-form">
|
||||
<form
|
||||
v-if="isEditTitle && !isDisabled"
|
||||
@submit.prevent="isEditTitle = false"
|
||||
class="edit-form"
|
||||
>
|
||||
<div class="wrapper-input">
|
||||
<Input
|
||||
id="upload-title"
|
||||
:text="text || ''"
|
||||
:placeholder="$t('pages.upload.edit_placeholder_input')"
|
||||
@input="changeTextValue"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
id="valid-button"
|
||||
:text="$t('pages.upload.ok_button')"
|
||||
type="submit"
|
||||
look="button--white"
|
||||
@trigger="validNewName"
|
||||
<div class="entry-edit">
|
||||
<form
|
||||
v-if="isEditTitle && !isDisabled"
|
||||
@submit.prevent="isEditTitle = false"
|
||||
class="edit-form"
|
||||
>
|
||||
<div class="wrapper-input">
|
||||
<Input
|
||||
:text="text || ''"
|
||||
:placeholder="$t('pages.upload.edit_placeholder_input')"
|
||||
@input="changeTextValue"
|
||||
/>
|
||||
<div class="close-button">
|
||||
<Button
|
||||
@@ -32,17 +19,24 @@
|
||||
@trigger="closeEdition"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<span v-else class="title">{{ text }}</span>
|
||||
<div v-if="!isEditTitle" class="edit-button">
|
||||
<Button
|
||||
look="no-text-blue-dark"
|
||||
icon="bi bi-pen"
|
||||
:tooltip="$t('pages.upload.edit_title_tooltip')"
|
||||
:disabled="isDisabled"
|
||||
@trigger="goToEditMode"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
id="valid-button"
|
||||
:text="$t('pages.upload.ok_button')"
|
||||
type="submit"
|
||||
look="button button--blue"
|
||||
@trigger="validNewName"
|
||||
/>
|
||||
</form>
|
||||
<span v-else class="title">{{ text }}</span>
|
||||
<div v-if="!isEditTitle" class="edit-button">
|
||||
<Button
|
||||
look="no-text"
|
||||
icon="bi bi-pen"
|
||||
:tooltip="$t('pages.upload.edit_title_tooltip')"
|
||||
:disabled="isDisabled"
|
||||
@trigger="goToEditMode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -58,8 +52,7 @@ const emit = defineEmits<{
|
||||
const props = defineProps({
|
||||
defaultText: { type: String, default: null },
|
||||
isLoading: { type: Boolean, default: false },
|
||||
isLoaded: { type: Boolean, default: false },
|
||||
formTitle: { type: String, default: null }
|
||||
isLoaded: { type: Boolean, default: false }
|
||||
})
|
||||
let titleToEdit = ref<string | null>(null)
|
||||
let isEditTitle = ref<boolean>(false)
|
||||
@@ -92,27 +85,13 @@ const isDisabled = computed<boolean>(() => props.isLoading && !props.isLoaded)
|
||||
<style scoped lang="scss">
|
||||
.title {
|
||||
color: var(--blue-dark);
|
||||
text-align: left;
|
||||
}
|
||||
.form-title {
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
@include text(xs-r-regular);
|
||||
margin-bottom: toRem(0.3);
|
||||
}
|
||||
.entry-edit {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
.entry-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.edit-mode {
|
||||
background-color: var(--blue-pale);
|
||||
padding: toRem(1);
|
||||
border-radius: toRem(0.4);
|
||||
margin-bottom: toRem(2);
|
||||
width: 100%;
|
||||
height: toRem(4.7);
|
||||
}
|
||||
.wrapper-edit {
|
||||
display: flex;
|
||||
@@ -123,14 +102,13 @@ const isDisabled = computed<boolean>(() => props.isLoading && !props.isLoaded)
|
||||
.edit-button {
|
||||
background-color: var(--grey);
|
||||
border-radius: 50%;
|
||||
height: toRem(2.5);
|
||||
width: toRem(2.5);
|
||||
height: toRem(3.5);
|
||||
width: toRem(3.5);
|
||||
padding: toRem(1);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-left: toRem(1.5);
|
||||
z-index: 2;
|
||||
}
|
||||
.wrapper-input {
|
||||
position: relative;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<footer id="navFooter" class="footer">
|
||||
<footer class="footer">
|
||||
<ul class="link-list">
|
||||
<li class="link-item">
|
||||
<div class="link">
|
||||
@@ -62,9 +62,6 @@ ul {
|
||||
padding: toRem(1.5) toRem(3);
|
||||
border-top: toRem(0.1) solid var(--grey);
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.link-list {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<header id="navHeader" class="header">
|
||||
<header class="header">
|
||||
<div class="responsive entry-instance">
|
||||
<InstanceName />
|
||||
</div>
|
||||
@@ -128,6 +128,7 @@ onClickOutside(list, () => closeModal())
|
||||
function closeModal(): void {
|
||||
menuIsClosed.value = true
|
||||
}
|
||||
|
||||
function toggleMenu(): void {
|
||||
menuIsClosed.value = !menuIsClosed.value
|
||||
}
|
||||
@@ -160,9 +161,6 @@ const userName = computed((): string => {
|
||||
background-color: var(--white);
|
||||
border-bottom: toRem(0.1) solid var(--grey-pale);
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.nav {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
@@ -24,11 +24,10 @@ function emitValue(event: Event): void {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.input {
|
||||
padding: toRem(0.5) toRem(1);
|
||||
padding: toRem(1);
|
||||
border-radius: toRem(0.5);
|
||||
border: toRem(0.1) solid var(--blue-dark);
|
||||
color: var(--blue-dark);
|
||||
width: 100%;
|
||||
@include text(s-r-regular);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -117,12 +117,6 @@ function triggerButton() {
|
||||
color: var(--grey-semi-dark);
|
||||
text-decoration: underline;
|
||||
font-weight: inherit;
|
||||
font-size: toRem(1.4);
|
||||
.icon {
|
||||
color: var(--grey-semi-dark);
|
||||
font-size: toRem(1.4);
|
||||
margin-right: toRem(0.5);
|
||||
}
|
||||
}
|
||||
.link--blue-dark {
|
||||
color: var(--blue-dark);
|
||||
|
||||
@@ -13,10 +13,15 @@
|
||||
<i class="bi bi-x-circle-fill"></i>
|
||||
</button>
|
||||
<div class="modal-header">
|
||||
<h5>{{ title }}</h5>
|
||||
<h5>{{ $t('pages.upload.modal_error_title') }}</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<slot name="body"></slot>
|
||||
<ul>
|
||||
<li v-for="item in uploadErrors" class="error-item">
|
||||
<span>{{ item.name }} - </span>
|
||||
<span>{{ item.details.error }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -25,13 +30,15 @@
|
||||
|
||||
<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({
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
uploadErrors: {
|
||||
type: Array as PropType<uploadErrorInterface[]>,
|
||||
default: []
|
||||
}
|
||||
})
|
||||
|
||||
@@ -55,6 +62,12 @@ 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);
|
||||
}
|
||||
|
||||
@@ -3,21 +3,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import axios from 'axios'
|
||||
import { onMounted, onUnmounted, ref, computed } from 'vue'
|
||||
import { useSequenceStore } from '@/store/sequence'
|
||||
import { Viewer, StandaloneMap } from 'geovisio'
|
||||
import { getIgnTiles } from '@/utils/mapAndViewer'
|
||||
import { createUrlLink } from '@/utils'
|
||||
import {
|
||||
createLink,
|
||||
createSequenceLink,
|
||||
createFullScreenButton
|
||||
} from '@/components-viewer/reportLink'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { hasASessionCookieDecoded } from '@/utils/auth'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import type { ViewerInterface, MapInterface } from '@/views/interfaces/common'
|
||||
const sequenceStore = useSequenceStore()
|
||||
import { getIgnTiles } from '@/utils/mapAndViewer'
|
||||
import { Viewer, StandaloneMap } from 'geovisio'
|
||||
import { createUrlLink } from '@/utils'
|
||||
import { createLink } from '@/components-viewer/reportLink'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const { t } = useI18n()
|
||||
let mapIsLoaded = ref<boolean>(false)
|
||||
let viewer = ref()
|
||||
@@ -28,170 +20,119 @@ const props = defineProps({
|
||||
bbox: { type: Array, default: null },
|
||||
userId: { type: String, default: '' }
|
||||
})
|
||||
const isLogged = computed((): boolean => {
|
||||
const cookie = hasASessionCookieDecoded()
|
||||
return !!(cookie && cookie.account)
|
||||
})
|
||||
const userName = computed((): string => {
|
||||
const cookie = hasASessionCookieDecoded()
|
||||
if (cookie && cookie.account) return cookie.account.name
|
||||
return ''
|
||||
})
|
||||
defineExpose({
|
||||
viewer
|
||||
})
|
||||
async function getSequenceId(imgId: string): Promise<{
|
||||
sequenceId: string
|
||||
username: string
|
||||
}> {
|
||||
const { data } = await axios.get(`api/search?ids=${imgId}`)
|
||||
return {
|
||||
sequenceId: data.features[0].collection,
|
||||
username: data.features[0].properties['geovisio:producer']
|
||||
}
|
||||
}
|
||||
function createViewerButton(link: HTMLDivElement): void {
|
||||
link.innerHTML = `<div>${createFullScreenButton()}</div>`
|
||||
viewer.value.addEventListener(
|
||||
'picture-loaded',
|
||||
async (e: { detail: { picId: string } }): Promise<void> => {
|
||||
const sequenceInformation = await getSequenceId(e.detail.picId)
|
||||
let href: string
|
||||
if (isLogged.value && sequenceInformation.username === userName.value) {
|
||||
href = `${window.location.origin}/sequence/${sequenceInformation.sequenceId}?currentPic=${e.detail.picId}`
|
||||
link.innerHTML = `<div>
|
||||
${createFullScreenButton()}
|
||||
${createSequenceLink(
|
||||
href,
|
||||
t('pages.home.sequence_title')
|
||||
)}
|
||||
</div>`
|
||||
sequenceStore.addSequence(e.detail.picId)
|
||||
} else {
|
||||
href = t('pages.home.report_mail', {
|
||||
picId: e.detail.picId,
|
||||
link: createUrlLink(e.detail.picId)
|
||||
})
|
||||
link.innerHTML = `<div>
|
||||
${createFullScreenButton()}
|
||||
${createLink(
|
||||
href,
|
||||
t('pages.home.report_button_text')
|
||||
)}
|
||||
</div>`
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
async function setupViewerMap(tiles: string): Promise<void> {
|
||||
onMounted(async () => {
|
||||
const tiles = import.meta.env.VITE_TILES
|
||||
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 = {
|
||||
map: {
|
||||
...paramsViewer.map,
|
||||
center: centerMap
|
||||
}
|
||||
}
|
||||
}
|
||||
if (zoom && zoom.length) {
|
||||
paramsViewer = {
|
||||
map: {
|
||||
...paramsViewer.map,
|
||||
zoom: parseFloat(zoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (maxZoom && maxZoom.length) {
|
||||
paramsViewer = {
|
||||
map: {
|
||||
...paramsViewer.map,
|
||||
maxZoom: parseInt(maxZoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tiles && tiles.length) {
|
||||
const style = tiles.includes('wxs.ign.fr') ? await getIgnTiles() : tiles
|
||||
paramsViewer = {
|
||||
map: {
|
||||
...paramsViewer.map,
|
||||
style
|
||||
}
|
||||
}
|
||||
}
|
||||
if (props.fetchOptions) {
|
||||
paramsViewer = {
|
||||
...paramsViewer,
|
||||
...props.fetchOptions
|
||||
}
|
||||
}
|
||||
const reportLink = document.createElement('div')
|
||||
reportLink.className = 'gvs-group gvs-group-large gvs-group-btnpanel'
|
||||
viewer.value = new Viewer(
|
||||
'viewer', // Div ID
|
||||
`${import.meta.env.VITE_API_URL}/api/search`,
|
||||
{
|
||||
...paramsViewer,
|
||||
widgets: { customWidget: reportLink }
|
||||
}
|
||||
)
|
||||
if (viewer.value && viewer.value.addEventListener) {
|
||||
createViewerButton(reportLink)
|
||||
}
|
||||
mapIsLoaded.value = true
|
||||
}
|
||||
async function setupMap(tiles: string): Promise<void> {
|
||||
let paramsViewer: ViewerInterface
|
||||
let paramsMap: MapInterface
|
||||
paramsMap = { users: [props.userId], minZoom: 7 }
|
||||
if (tiles && tiles.length) {
|
||||
const style = tiles.includes('wxs.ign.fr') ? await getIgnTiles() : tiles
|
||||
paramsMap = {
|
||||
...paramsMap,
|
||||
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
|
||||
}
|
||||
)
|
||||
viewer.value.addEventListener('ready', () => {
|
||||
viewer.value.fitBounds(bbox, {
|
||||
padding: { top: 70, bottom: 70, left: 70, right: 70 },
|
||||
maxZoom: 14,
|
||||
speed: 10
|
||||
})
|
||||
})
|
||||
mapIsLoaded.value = true
|
||||
}
|
||||
onMounted(async (): Promise<void> => {
|
||||
const tiles = import.meta.env.VITE_TILES
|
||||
|
||||
try {
|
||||
if (props.geovisioViewer) return await setupViewerMap(tiles)
|
||||
return await setupMap(tiles)
|
||||
} catch (err) {
|
||||
if (props.geovisioViewer) {
|
||||
paramsViewer = { map: { startWide: true } }
|
||||
if (center && center !== '') {
|
||||
const centerMap = center.split(',').map((el: string) => parseInt(el))
|
||||
paramsViewer = {
|
||||
map: {
|
||||
...paramsViewer.map,
|
||||
center: centerMap
|
||||
}
|
||||
}
|
||||
}
|
||||
if (zoom && zoom !== '') {
|
||||
paramsViewer = {
|
||||
map: {
|
||||
...paramsViewer.map,
|
||||
zoom: parseFloat(zoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (maxZoom && maxZoom !== '') {
|
||||
paramsViewer = {
|
||||
map: {
|
||||
...paramsViewer.map,
|
||||
maxZoom: parseInt(maxZoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tiles) {
|
||||
const style = tiles.includes('wxs.ign.fr') ? await getIgnTiles() : tiles
|
||||
paramsViewer = {
|
||||
map: {
|
||||
...paramsViewer.map,
|
||||
style
|
||||
}
|
||||
}
|
||||
}
|
||||
if (props.fetchOptions) {
|
||||
paramsViewer = {
|
||||
...paramsViewer,
|
||||
...props.fetchOptions
|
||||
}
|
||||
}
|
||||
const reportLink = document.createElement('div')
|
||||
reportLink.className = 'gvs-group gvs-group-large gvs-group-btnpanel'
|
||||
viewer.value = new Viewer(
|
||||
'viewer', // Div ID
|
||||
`${import.meta.env.VITE_API_URL}/api/search`,
|
||||
{
|
||||
...paramsViewer,
|
||||
widgets: { customWidget: reportLink }
|
||||
}
|
||||
)
|
||||
if (viewer.value && viewer.value.addEventListener) {
|
||||
viewer.value.addEventListener(
|
||||
'picture-loaded',
|
||||
async (e: { detail: { picId: string } }): Promise<void> => {
|
||||
const href = t('pages.home.report_mail', {
|
||||
picId: e.detail.picId,
|
||||
link: createUrlLink(e.detail.picId)
|
||||
})
|
||||
reportLink.innerHTML = createLink(
|
||||
href,
|
||||
t('pages.home.report_button_text')
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
paramsMap = { minZoom: 7 }
|
||||
if (tiles) {
|
||||
const style = tiles.includes('wxs.ign.fr') ? await getIgnTiles() : tiles
|
||||
paramsMap = {
|
||||
...paramsMap,
|
||||
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
|
||||
}
|
||||
)
|
||||
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
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
onUnmounted((): void => {
|
||||
onUnmounted(() => {
|
||||
if (viewer.value && props.geovisioViewer) viewer.value.destroy()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
<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>
|
||||
@@ -37,10 +37,7 @@
|
||||
"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": "Report this picture",
|
||||
"sequence_title": "See the séquence",
|
||||
"open_fullscreen": "Fullscreen mode",
|
||||
"close_fullscreen": "Normal mode"
|
||||
"report_button_text": "Report this picture"
|
||||
},
|
||||
"settings": {
|
||||
"title": "My tokens",
|
||||
@@ -50,9 +47,7 @@
|
||||
"sequence_published": "Published",
|
||||
"sequence_waiting": "Still processing",
|
||||
"sequence_hidden": "Hidden",
|
||||
"sequence_form_title": "Edit the title",
|
||||
"hide_sequence_tooltip": "Hide this sequences",
|
||||
"back_button": "Back to my sequence list",
|
||||
"delete_sequence_tooltip": "Permanently delete this sequence",
|
||||
"hide_photo_tooltip": "Hide selected pictures",
|
||||
"delete_photo_tooltip": "Permanently delete selected pictures",
|
||||
@@ -80,24 +75,10 @@
|
||||
},
|
||||
"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",
|
||||
"hide_button": "Hide",
|
||||
"show_button": "Show",
|
||||
"delete_button": "Delete",
|
||||
"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",
|
||||
"filter_bbox_button": "Search on this area",
|
||||
"sequence_status": "Status",
|
||||
"sequence_published": "Published",
|
||||
"sequence_waiting": "Still processing",
|
||||
@@ -157,8 +138,6 @@
|
||||
"text_import": "Upload your jpg files here. Each picture or series of pictures constitutes a \"sequence\". You can then find them in the \"my pictures\" section and choose to hide, show or delete them.",
|
||||
"subtitle_process": "Upload processing",
|
||||
"uploading_process": "Upload in progress...",
|
||||
"uploading_cancel": "Cancel sending photos",
|
||||
"cancel_message": "⚠️ Please note, the download will be interrupted if you validate and the sequence will be deleted.",
|
||||
"sequence_title": "Sequence ",
|
||||
"import": "Uploads",
|
||||
"upload_pending": "Upload in progress...",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"general": {
|
||||
"title": "DESC Panoramax {instanceName}: photo-cartographier les territoires",
|
||||
"description": "DESC L'instance Panoramax {instanceName} permet la publication de photo de terrain pour cartographier le territoire. Panoramax favorise la réutilisation des photos pour de nombreux cas d'usages.",
|
||||
"title": "Instance Panoramax",
|
||||
"meta": {
|
||||
"title": "Instance Panoramax",
|
||||
"description": "Panoramax, l’alternative libre pour photo-cartographier les territoires"
|
||||
@@ -38,10 +37,7 @@
|
||||
"pages": {
|
||||
"home": {
|
||||
"report_mail": "?subject=⚠️ Signalement sur l`image {picId}&body=Bonjour, %0D%0A%0D%0A Problème sur l`image (garder le type de problème signalé) : %0D%0A%0D%0A contenu inapproprié / absence de floutage sur un élément à anonymiser ou flouter pour des raisons de sécurité /surfloutage (floutage en trop) %0D%0A%0D%0A Lien vers la photo concernée : {link} %0D%0A%0D%0A Précision sur les éléments concernés (en particulier pour les problèmes de floutage - que faut-il flouter ou déflouter?) :",
|
||||
"report_button_text": "Signaler la photo",
|
||||
"sequence_title": "Voir la séquence",
|
||||
"open_fullscreen": "Mode plein écran",
|
||||
"close_fullscreen": "Mode normal"
|
||||
"report_button_text": "Signaler la photo"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Mes Tokens",
|
||||
@@ -51,9 +47,7 @@
|
||||
"sequence_published": "Publiée",
|
||||
"sequence_waiting": "En cours de publication",
|
||||
"sequence_hidden": "Masquée",
|
||||
"sequence_form_title": "Modifier le titre",
|
||||
"hide_sequence_tooltip": "Masque la séquence sur la carte",
|
||||
"back_button": "Retourner à la liste de mes séquences",
|
||||
"delete_sequence_tooltip": "Supprime définitivement la séquence",
|
||||
"hide_photo_tooltip": "Masque les photos sur la carte",
|
||||
"delete_photo_tooltip": "Supprime définitivement les photos",
|
||||
@@ -81,24 +75,10 @@
|
||||
},
|
||||
"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",
|
||||
"hide_button": "Masquer",
|
||||
"show_button": "Afficher",
|
||||
"delete_button": "Supprimer",
|
||||
"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 les filtres",
|
||||
"filter_bbox_button": "Chercher dans cette zone",
|
||||
"sequence_status": "Statut",
|
||||
"sequence_published": "Publiée",
|
||||
"sequence_waiting": "En cours de publication",
|
||||
@@ -158,8 +138,6 @@
|
||||
"text_import": "Déposez ici vos fichiers jpg. Chaque image ou série d’images 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_cancel": "Annuler l'envoi des photos",
|
||||
"cancel_message": "⚠️ Attention, le téléchargement sera interrompu si vous validez et la séquence sera supprimée.",
|
||||
"sequence_title": "Séquence du ",
|
||||
"import": "Imports",
|
||||
"upload_pending": "Transfert en cours...",
|
||||
|
||||
@@ -37,10 +37,7 @@
|
||||
"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",
|
||||
"sequence_title": "lásd a sorrendet",
|
||||
"open_fullscreen": "Teljes képernyős mód",
|
||||
"close_fullscreen": "Normál mód"
|
||||
"report_button_text": "Fénykép jelentése"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Saját tokenek",
|
||||
@@ -50,9 +47,7 @@
|
||||
"sequence_published": "Közzétéve",
|
||||
"sequence_waiting": "Feldolgozás alatt",
|
||||
"sequence_hidden": "Rejtett",
|
||||
"sequence_form_title": "Szerkessze a címet",
|
||||
"hide_sequence_tooltip": "A sorozat elrejtése",
|
||||
"back_button": "Vissza a sorozatlistámhoz",
|
||||
"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",
|
||||
@@ -80,24 +75,10 @@
|
||||
},
|
||||
"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",
|
||||
"hide_button": "Elrejt",
|
||||
"show_button": "Előadás",
|
||||
"delete_button": "Töröl",
|
||||
"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",
|
||||
"filter_bbox_button": "Keresés ezen a területen",
|
||||
"sequence_status": "Állapot",
|
||||
"sequence_published": "Közzétéve",
|
||||
"sequence_waiting": "Feldolgozás alatt",
|
||||
@@ -157,8 +138,6 @@
|
||||
"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…",
|
||||
"uploading_cancel": "Fényképek küldésének megszakítása",
|
||||
"cancel_message": "⚠️ Felhívjuk figyelmét, hogy a letöltés megszakad, ha érvényesíti, és a sorozat törlődik.",
|
||||
"sequence_title": "Sorozat ",
|
||||
"import": "Feltöltések",
|
||||
"upload_pending": "Feltöltés folyamatban…",
|
||||
|
||||
25
src/main.ts
25
src/main.ts
@@ -1,13 +1,11 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import VueMatomo from 'vue-matomo'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
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'
|
||||
@@ -17,17 +15,8 @@ import 'bootstrap/dist/css/bootstrap.css'
|
||||
import 'bootstrap-icons/font/bootstrap-icons.css'
|
||||
import 'geovisio/build/index.css'
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
_paq: any[]
|
||||
}
|
||||
}
|
||||
|
||||
axios.defaults.baseURL = import.meta.env.VITE_API_URL
|
||||
axios.defaults.withCredentials = true
|
||||
const matomoHost = import.meta.env.VITE_MATOMO_HOST
|
||||
const matomoSiteId = import.meta.env.VITE_MATOMO_SITE_ID
|
||||
const matomoExist = matomoHost && matomoSiteId
|
||||
|
||||
const i18n = createI18n({
|
||||
locale: navigator.language.split('-')[0],
|
||||
@@ -43,20 +32,12 @@ const i18n = createI18n({
|
||||
})
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(pinia)
|
||||
app.use(i18n)
|
||||
app.use(router)
|
||||
app.use(VueAxios, axios)
|
||||
app.provide('axios', app.config.globalProperties.axios)
|
||||
app.use(createMetaManager())
|
||||
app.use(VueDraggableResizable)
|
||||
app.use(VCalendar)
|
||||
if (matomoExist) {
|
||||
app.use(VueMatomo, {
|
||||
host: matomoHost,
|
||||
siteId: matomoExist
|
||||
})
|
||||
}
|
||||
app.mount('#app')
|
||||
if (matomoExist) {
|
||||
window._paq.push(['trackPageView']) // Pour suivre les visites sur vos pages
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@ import { defineStore } from 'pinia'
|
||||
export const useSequenceStore = defineStore('sequence', {
|
||||
state: () => ({
|
||||
toastText: <string>'',
|
||||
toastLook: <string>'',
|
||||
picId: <string>''
|
||||
toastLook: <string>''
|
||||
}),
|
||||
actions: {
|
||||
addToastText(text: string, look: string): void {
|
||||
@@ -13,9 +12,6 @@ export const useSequenceStore = defineStore('sequence', {
|
||||
setTimeout(() => {
|
||||
this.toastText = ''
|
||||
}, 3000)
|
||||
},
|
||||
addSequence(id: string): void {
|
||||
this.picId = id
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -26,10 +26,17 @@ describe('Template', () => {
|
||||
}
|
||||
}
|
||||
})
|
||||
expect(wrapper.vm.title).toEqual('')
|
||||
expect(wrapper.vm.uploadErrors).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],
|
||||
@@ -38,12 +45,13 @@ describe('Template', () => {
|
||||
}
|
||||
},
|
||||
props: {
|
||||
title: 'My title'
|
||||
uploadErrors
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.vm.title).toEqual('My title')
|
||||
expect(wrapper.html()).contains('My title')
|
||||
expect(wrapper.vm.uploadErrors).toEqual(uploadErrors)
|
||||
expect(wrapper.html()).contains('my name - ')
|
||||
expect(wrapper.html()).contains('my error')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
hasASessionCookieDecoded
|
||||
} from '../../utils/auth'
|
||||
import { img } from '../../utils/image'
|
||||
import { title } from '../../utils/index'
|
||||
import { useCookies } from 'vue3-cookies'
|
||||
vi.mock('vue3-cookies', () => {
|
||||
const mockCookies = {
|
||||
@@ -184,6 +185,19 @@ describe('img', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('title', () => {
|
||||
it('should return the formated title with instance name', () => {
|
||||
import.meta.env.VITE_INSTANCE_NAME = 'my instance'
|
||||
const myTitle = 'my title'
|
||||
expect(title(myTitle)).toEqual('my title my instance')
|
||||
})
|
||||
it('should return the formated title without instance name', () => {
|
||||
import.meta.env.VITE_INSTANCE_NAME = ''
|
||||
const myTitle = 'my title'
|
||||
expect(title(myTitle)).toEqual('my title')
|
||||
})
|
||||
})
|
||||
|
||||
describe('sortByName', () => {
|
||||
it('should return the the list sorted by name', () => {
|
||||
const list1 = [
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import moment from 'moment'
|
||||
import 'moment/dist/locale/fr'
|
||||
|
||||
function formatDate(date: Date | null | string, formatType: string): string {
|
||||
function formatDate(date: Date, formatType: string): string {
|
||||
const formatDate = moment(date)
|
||||
return formatDate.locale(window.navigator.language).format(formatType)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
export function title(title: string): string {
|
||||
const instanceName = import.meta.env.VITE_INSTANCE_NAME
|
||||
if (instanceName) return `${title} ${instanceName}`
|
||||
return title
|
||||
}
|
||||
|
||||
export function createUrlLink(picId: string): string {
|
||||
return encodeURIComponent(`${window.location.origin}/#focus=pic&pic=${picId}`)
|
||||
}
|
||||
|
||||
@@ -1,29 +1,21 @@
|
||||
import axios from 'axios'
|
||||
|
||||
async function getIgnTiles(): Promise<object | string> {
|
||||
try {
|
||||
const { data } = await axios.get(
|
||||
'https://wxs.ign.fr/essentiels/static/vectorTiles/styles/PLAN.IGN/attenue.json'
|
||||
)
|
||||
data.sources.plan_ign.scheme = 'xyz'
|
||||
data.sources.plan_ign.attribution = 'Données cartographiques : © IGN'
|
||||
const objIndex = data.layers.findIndex(
|
||||
(el: { id: string }) => el.id === 'toponyme - parcellaire - adresse'
|
||||
)
|
||||
data.layers[objIndex].layout = {
|
||||
...data.layers[objIndex].layout,
|
||||
'text-field': [
|
||||
'concat',
|
||||
['get', 'numero'],
|
||||
['get', 'indice_de_repetition']
|
||||
]
|
||||
}
|
||||
// Patch tms scheme to xyz to make it compatible for Maplibre GL JS / Mapbox GL JS
|
||||
// Patch num_repetition
|
||||
return data
|
||||
} catch (error) {
|
||||
return 'https://tile-vect.openstreetmap.fr/styles/basic/style.json'
|
||||
async function getIgnTiles(): Promise<object> {
|
||||
const { data } = await axios.get(
|
||||
'https://wxs.ign.fr/essentiels/static/vectorTiles/styles/PLAN.IGN/attenue.json'
|
||||
)
|
||||
data.sources.plan_ign.scheme = 'xyz'
|
||||
data.sources.plan_ign.attribution = 'Données cartographiques : © IGN'
|
||||
const objIndex = data.layers.findIndex(
|
||||
(el: any) => el.id === 'toponyme - parcellaire - adresse'
|
||||
)
|
||||
data.layers[objIndex].layout = {
|
||||
...data.layers[objIndex].layout,
|
||||
'text-field': ['concat', ['get', 'numero'], ['get', 'indice_de_repetition']]
|
||||
}
|
||||
// Patch tms scheme to xyz to make it compatible for Maplibre GL JS / Mapbox GL JS
|
||||
// Patch num_repetition
|
||||
return data
|
||||
}
|
||||
|
||||
export { getIgnTiles }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<main id="homePage" class="entry-page">
|
||||
<main class="entry-page">
|
||||
<section class="entry-section">
|
||||
<Viewer ref="viewerRef"> </Viewer>
|
||||
<Viewer ref="viewerRef" />
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
@@ -9,8 +9,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import Viewer from '@/components/Viewer.vue'
|
||||
import type ViewerType from '@/components/Viewer.vue'
|
||||
const viewerRef = ref<InstanceType<typeof ViewerType>>()
|
||||
|
||||
const viewerRef = ref<unknown>(null)
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.entry-page {
|
||||
@@ -20,18 +20,10 @@ const viewerRef = ref<InstanceType<typeof ViewerType>>()
|
||||
width: 100%;
|
||||
height: calc(100vh - #{toRem(13.2)});
|
||||
}
|
||||
.mobile {
|
||||
display: none;
|
||||
}
|
||||
.focus-pic,
|
||||
.logged .entry-section {
|
||||
height: calc(100vh - #{toRem(8)});
|
||||
}
|
||||
.full-viewer {
|
||||
.entry-section {
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
.gvs-focus-map .entry-report-button {
|
||||
display: none;
|
||||
}
|
||||
@@ -43,17 +35,5 @@ const viewerRef = ref<InstanceType<typeof ViewerType>>()
|
||||
.entry-section {
|
||||
height: calc(100dvh - #{toRem(18)});
|
||||
}
|
||||
.full-viewer {
|
||||
height: 100vh;
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
@media (max-width: toRem(50)) {
|
||||
.mobile {
|
||||
display: block;
|
||||
}
|
||||
.desktop {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<main :class="['entry-page', { 'menu-is-open': menuIsOpen }]">
|
||||
<div class="button-close">
|
||||
<Button
|
||||
look="no-text-blue-dark"
|
||||
look="no-text"
|
||||
:icon="menuIsOpen ? 'bi bi-chevron-right' : 'bi bi-chevron-left'"
|
||||
@trigger="menuIsOpen = !menuIsOpen"
|
||||
/>
|
||||
@@ -14,107 +14,95 @@
|
||||
/>
|
||||
</section>
|
||||
<div v-if="sequence && !isLoading" class="menu-right">
|
||||
<div ref="collapseMenu">
|
||||
<div class="back-button">
|
||||
<Link
|
||||
icon="bi bi-arrow-left"
|
||||
:text="$t('pages.sequence.back_button')"
|
||||
:route="{ name: 'my-sequences' }"
|
||||
look="link--grey"
|
||||
/>
|
||||
</div>
|
||||
<div class="menu-top">
|
||||
<div class="header-menu">
|
||||
<div class="menu-top" ref="collapseMenu">
|
||||
<div class="header-menu">
|
||||
<button
|
||||
data-bs-target="#collapseTarget"
|
||||
data-bs-toggle="collapse"
|
||||
class="button-collapse"
|
||||
@click="toggleMenu"
|
||||
>
|
||||
<div class="wrapper-title">
|
||||
<span :class="[sequence.status, 'sequence-status']">{{
|
||||
sequenceStatus
|
||||
}}</span>
|
||||
<div @click.stop class="entry-edit-text">
|
||||
<EditText
|
||||
:default-text="sequence.title"
|
||||
:is-loading="isLoadingTitle"
|
||||
:form-title="$t('pages.sequence.sequence_form_title')"
|
||||
@triggerNewText="setNewSequenceTitle"
|
||||
/>
|
||||
</div>
|
||||
<h1 class="title desktop">
|
||||
{{ sequence.title }}
|
||||
</h1>
|
||||
</div>
|
||||
<button
|
||||
data-bs-target="#collapseTarget"
|
||||
data-bs-toggle="collapse"
|
||||
class="button-collapse"
|
||||
@click="toggleMenu"
|
||||
>
|
||||
<i :class="headerPanelIsOpen ? 'bi bi-dash' : 'bi bi-plus'"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="isSequenceOwner" class="wrapper-button">
|
||||
<Button
|
||||
:tooltip="$t('pages.sequence.hide_sequence_tooltip')"
|
||||
:text="
|
||||
sequence.status === 'ready'
|
||||
? $t('pages.sequence.button_disable')
|
||||
: $t('pages.sequence.button_enable')
|
||||
"
|
||||
look="button--white background-white"
|
||||
:icon="
|
||||
sequence.status === 'ready' ? 'bi bi-eye-slash' : 'bi bi-eye'
|
||||
"
|
||||
class="disable-button"
|
||||
@trigger="patchCollection"
|
||||
/>
|
||||
<Button
|
||||
:tooltip="$t('pages.sequence.delete_sequence_tooltip')"
|
||||
:text="$t('pages.sequence.button_delete')"
|
||||
look="button--red background-white"
|
||||
icon="bi bi-trash"
|
||||
@trigger="deleteCollection"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
:class="[sequence.status, 'collapse py-2 show']"
|
||||
id="collapseTarget"
|
||||
>
|
||||
<div class="block-collapse">
|
||||
<div class="wrapper-info-top">
|
||||
<span v-if="sequence.created"
|
||||
>{{ $t('pages.sequence.created') }}
|
||||
{{
|
||||
formatDate(new Date(sequence.created), 'Do MMMM YYYY')
|
||||
}}</span
|
||||
>
|
||||
<span v-if="sequence.duration"
|
||||
>{{ $t('pages.sequence.duration') }}
|
||||
{{ sequence.duration }}</span
|
||||
>
|
||||
<span v-if="sequence.taken"
|
||||
>{{ $t('pages.sequence.taken') }}
|
||||
{{ formatDate(sequence.taken, 'Do MMMM YYYY') }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="wrapper-info-top">
|
||||
<span v-if="sequence.extent.temporal.interval[0][0]"
|
||||
>{{ $t('pages.sequence.duration_begin') }}
|
||||
{{
|
||||
formatDate(
|
||||
sequence.extent.temporal.interval[0][0],
|
||||
'Do MMMM YYYY, HH:mm:ss'
|
||||
)
|
||||
}}</span
|
||||
>
|
||||
<span v-if="sequence.extent.temporal.interval[0][1]"
|
||||
>{{ $t('pages.sequence.duration_end') }}
|
||||
{{
|
||||
formatDate(
|
||||
sequence.extent.temporal.interval[0][1],
|
||||
'Do MMMM YYYY, HH:mm:ss'
|
||||
)
|
||||
}}</span
|
||||
>
|
||||
<span v-if="sequence.camera"
|
||||
>{{ $t('pages.sequence.camera') }} {{ sequence.camera }} -
|
||||
{{ sequence.cameraModel }}</span
|
||||
>
|
||||
</div>
|
||||
<i :class="headerPanelIsOpen ? 'bi bi-dash' : 'bi bi-plus'"></i>
|
||||
</button>
|
||||
<h1 class="title responsive">
|
||||
{{ sequence.title }}
|
||||
</h1>
|
||||
</div>
|
||||
<div v-if="isSequenceOwner" class="wrapper-button">
|
||||
<Button
|
||||
:tooltip="$t('pages.sequence.hide_sequence_tooltip')"
|
||||
:text="
|
||||
sequence.status === 'ready'
|
||||
? $t('pages.sequence.button_disable')
|
||||
: $t('pages.sequence.button_enable')
|
||||
"
|
||||
look="button--white background-white"
|
||||
:icon="
|
||||
sequence.status === 'ready' ? 'bi bi-eye-slash' : 'bi bi-eye'
|
||||
"
|
||||
class="disable-button"
|
||||
@trigger="patchCollection"
|
||||
/>
|
||||
<Button
|
||||
:tooltip="$t('pages.sequence.delete_sequence_tooltip')"
|
||||
:text="$t('pages.sequence.button_delete')"
|
||||
look="button--red background-white"
|
||||
icon="bi bi-trash"
|
||||
@trigger="deleteCollection"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
:class="[sequence.status, 'collapse py-2 show']"
|
||||
id="collapseTarget"
|
||||
>
|
||||
<div class="block-collapse">
|
||||
<div class="wrapper-info-top">
|
||||
<span v-if="sequence.created"
|
||||
>{{ $t('pages.sequence.created') }}
|
||||
{{
|
||||
formatDate(new Date(sequence.created), 'Do MMMM YYYY')
|
||||
}}</span
|
||||
>
|
||||
<span v-if="sequence.duration"
|
||||
>{{ $t('pages.sequence.duration') }}
|
||||
{{ sequence.duration }}</span
|
||||
>
|
||||
<span v-if="sequence.taken"
|
||||
>{{ $t('pages.sequence.taken') }}
|
||||
{{ formatDate(sequence.taken, 'Do MMMM YYYY') }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="wrapper-info-top">
|
||||
<span v-if="sequence.extent.temporal.interval[0][0]"
|
||||
>{{ $t('pages.sequence.duration_begin') }}
|
||||
{{
|
||||
formatDate(
|
||||
sequence.extent.temporal.interval[0][0],
|
||||
'Do MMMM YYYY, HH:mm:ss'
|
||||
)
|
||||
}}</span
|
||||
>
|
||||
<span v-if="sequence.extent.temporal.interval[0][1]"
|
||||
>{{ $t('pages.sequence.duration_end') }}
|
||||
{{
|
||||
formatDate(
|
||||
sequence.extent.temporal.interval[0][1],
|
||||
'Do MMMM YYYY, HH:mm:ss'
|
||||
)
|
||||
}}</span
|
||||
>
|
||||
<span v-if="sequence.camera"
|
||||
>{{ $t('pages.sequence.camera') }} {{ sequence.camera }} -
|
||||
{{ sequence.cameraModel }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -137,7 +125,7 @@
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<Button
|
||||
look="button--white background-white no-text"
|
||||
look="button--white background-white"
|
||||
:icon="
|
||||
picturesToDeleteStatus === 'hidden' ||
|
||||
imagesSelectedHaveDifferentStatus
|
||||
@@ -152,7 +140,7 @@
|
||||
/>
|
||||
<div class="button-hidde">
|
||||
<Button
|
||||
look="button--red background-white no-text"
|
||||
look="button--red background-white"
|
||||
icon="bi bi-trash"
|
||||
:tooltip="$t('pages.sequence.delete_photo_tooltip')"
|
||||
:disabled="!picturesToDelete.length"
|
||||
@@ -207,15 +195,12 @@ import { useSequenceStore } from '@/store/sequence'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useCookies } from 'vue3-cookies'
|
||||
import Button from '@/components/Button.vue'
|
||||
import Link from '@/components/Link.vue'
|
||||
import Toast from '@/components/Toast.vue'
|
||||
import Pagination from '@/components/Pagination.vue'
|
||||
import InputCheckbox from '@/components/InputCheckbox.vue'
|
||||
import Loader from '@/components/Loader.vue'
|
||||
import ImageItem from '@/components/ImageItem.vue'
|
||||
import EditText from '@/components/EditText.vue'
|
||||
import Viewer from '@/components/Viewer.vue'
|
||||
import type ViewerType from '@/components/Viewer.vue'
|
||||
import { durationCalc, formatDate } from '@/utils/dates'
|
||||
import {
|
||||
deleteACollectionItem,
|
||||
@@ -257,11 +242,10 @@ let headerPanelIsOpen = ref<boolean>(true)
|
||||
let isShiftPressed = ref<boolean>(false)
|
||||
let itemSelected = ref<string>('')
|
||||
let isLoading = ref<boolean>(false)
|
||||
let isLoadingTitle = ref<boolean>(false)
|
||||
const collapseMenu = ref<HTMLDivElement>()
|
||||
const deleteAll = ref<HTMLDivElement>()
|
||||
const menuHeight = ref<string>()
|
||||
const viewerRef = ref<InstanceType<typeof ViewerType>>()
|
||||
const viewerRef = ref()
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
@@ -282,62 +266,37 @@ onMounted(async () => {
|
||||
)
|
||||
pictures.value = collectionItems
|
||||
setHeightValue()
|
||||
if (
|
||||
itemSelected.value.length ||
|
||||
!getCurrentPicId(collectionItemsReady[0].id)
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (!viewerRef.value) return
|
||||
if (itemSelected.value.length || !collectionItemsReady[0]) return
|
||||
viewerRef.value.viewer._api.onceReady().then(() => {
|
||||
if (!viewerRef.value) return
|
||||
viewerRef.value.viewer.goToPicture(
|
||||
getCurrentPicId(collectionItemsReady[0].id),
|
||||
collectionItemsReady[0].id,
|
||||
sequence.value?.id
|
||||
)
|
||||
})
|
||||
itemSelected.value = getCurrentPicId(collectionItemsReady[0].id)
|
||||
if (!pictureExistInList(getCurrentPicId(collectionItemsReady[0].id))) {
|
||||
await goToTheGoodPage(getCurrentPicId(collectionItemsReady[0].id))
|
||||
}
|
||||
scrollIntoSelected(
|
||||
itemSelected.value,
|
||||
pictures.value.map((e) => e.id)
|
||||
)
|
||||
itemSelected.value = collectionItemsReady[0].id
|
||||
scrollIntoSelected(collectionItemsReady[0].id, pictures.value)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
|
||||
watchEffect(async () => {
|
||||
if (!viewerRef || !viewerRef.value || !viewerRef.value.viewer) return
|
||||
if (!viewerExist(viewerRef)) return
|
||||
viewerRef.value.viewer.addEventListener(
|
||||
'picture-loaded',
|
||||
async (e: { detail: { picId: string } }): Promise<void> => {
|
||||
if (!pictureExistInList(getCurrentPicId(e.detail.picId))) {
|
||||
await goToTheGoodPage(getCurrentPicId(e.detail.picId))
|
||||
if (itemSelected.value === e.detail.picId) return
|
||||
if (!pictureExistInList(e.detail.picId)) {
|
||||
await goToTheGoodPage(e.detail.picId)
|
||||
}
|
||||
itemSelected.value = getCurrentPicId(e.detail.picId)
|
||||
scrollIntoSelected(
|
||||
getCurrentPicId(e.detail.picId),
|
||||
pictures.value.map((e) => e.id)
|
||||
)
|
||||
itemSelected.value = e.detail.picId
|
||||
scrollIntoSelected(e.detail.picId, pictures.value)
|
||||
}
|
||||
)
|
||||
})
|
||||
async function setNewSequenceTitle(value: string | null): Promise<void> {
|
||||
isLoadingTitle.value = true
|
||||
if (value && value.length > 0) {
|
||||
await patchACollection(route.params.id, { title: value })
|
||||
const fetchCollectionInfo = await fetchCollection(route.params.id)
|
||||
formatSequenceFetched(fetchCollectionInfo.data)
|
||||
isLoadingTitle.value = false
|
||||
}
|
||||
}
|
||||
function getCurrentPicId(id: string): string {
|
||||
const parseParams = new URLSearchParams(window.location.search)
|
||||
const pict = parseParams.get('currentPic')
|
||||
return pict && pict.length ? pict : id
|
||||
|
||||
function viewerExist(viewerRef: any): boolean {
|
||||
return !!(viewerRef && viewerRef.value && viewerRef.value.viewer)
|
||||
}
|
||||
|
||||
const isSequenceOwner = computed((): boolean => {
|
||||
@@ -454,7 +413,7 @@ async function patchCollection(): Promise<void> {
|
||||
let visible
|
||||
if (sequence.value?.status === 'ready') visible = 'false'
|
||||
else visible = 'true'
|
||||
await patchACollection(route.params.id, { visible: visible })
|
||||
await patchACollection(route.params.id, visible)
|
||||
const fetchCollectionInfo = await fetchCollection(route.params.id)
|
||||
formatSequenceFetched(fetchCollectionInfo.data)
|
||||
if (visible === 'false') hiddeAllPictures()
|
||||
@@ -462,7 +421,7 @@ async function patchCollection(): Promise<void> {
|
||||
const { data } = await fetchCollectionItems(route.params.id, '?limit=100')
|
||||
pictures.value = data.features
|
||||
}
|
||||
if (viewerRef.value) viewerRef.value.viewer.reloadVectorTiles()
|
||||
viewerRef.value.viewer.reloadVectorTiles()
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
@@ -476,10 +435,7 @@ async function goToNextPage(value: string): Promise<void> {
|
||||
selfLink.value = data.links.filter((el) => el.rel === 'self')
|
||||
paginationLinks.value = formatPaginationItems(data.links)
|
||||
pictures.value = data.features
|
||||
scrollIntoSelected(
|
||||
pictures.value[0].id,
|
||||
pictures.value.map((e) => e.id)
|
||||
)
|
||||
scrollIntoSelected(pictures.value[0].id, pictures.value)
|
||||
picturesToDelete.value = []
|
||||
isLoading.value = false
|
||||
setHeightValue()
|
||||
@@ -528,26 +484,16 @@ function selectPhotoToDeleteOrPatch(
|
||||
async function selectImageAndMove(
|
||||
item: ResponseUserPhotoInterface
|
||||
): Promise<void> {
|
||||
const parseParams = new URLSearchParams(window.location.search)
|
||||
const pict = parseParams.get('currentPic')
|
||||
if (pict && pict.length) {
|
||||
await router.push({ name: 'sequence', params: { id: route.params.id } })
|
||||
}
|
||||
selectPhotoToDeleteOrPatch(item)
|
||||
if (
|
||||
picturesToDelete.value.length < 2 &&
|
||||
item.properties['geovisio:status'] === 'ready'
|
||||
) {
|
||||
if (viewerRef.value) {
|
||||
const viewerMap = await viewerRef.value.viewer
|
||||
viewerMap.goToPicture(item.id, sequence.value?.id)
|
||||
}
|
||||
const viewerMap = await viewerRef.value.viewer
|
||||
viewerMap.goToPicture(item.id, sequence.value?.id)
|
||||
itemSelected.value = item.id
|
||||
await goToTheGoodPage(item.id)
|
||||
scrollIntoSelected(
|
||||
item.id,
|
||||
pictures.value.map((e) => e.id)
|
||||
)
|
||||
scrollIntoSelected(item.id, pictures.value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -607,14 +553,9 @@ async function patchOrDeleteCollectionItems(
|
||||
const { data } = await fetchCollectionItems(route.params.id, '?limit=100')
|
||||
pictures.value = data.features
|
||||
isLoading.value = false
|
||||
if (viewerRef.value) {
|
||||
viewerRef.value.viewer.reloadVectorTiles()
|
||||
viewerRef.value.viewer.goToPicture(pictures.value[0].id, route.params.id)
|
||||
}
|
||||
scrollIntoSelected(
|
||||
picturesToDelete.value[0],
|
||||
pictures.value.map((e) => e.id)
|
||||
)
|
||||
viewerRef.value.viewer.reloadVectorTiles()
|
||||
viewerRef.value.viewer.goToPicture(pictures.value[0].id, route.params.id)
|
||||
scrollIntoSelected(picturesToDelete.value[0], pictures.value)
|
||||
picturesToDelete.value = []
|
||||
sequenceStore.addToastText(t('general.success_text'), 'success')
|
||||
} catch (e) {
|
||||
@@ -640,12 +581,7 @@ async function patchOrDeleteCollectionItems(
|
||||
.menu-right {
|
||||
width: 50vw;
|
||||
height: calc(100vh - #{toRem(8)});
|
||||
overflow: auto;
|
||||
}
|
||||
.back-button {
|
||||
width: fit-content;
|
||||
margin-left: toRem(2);
|
||||
margin-top: toRem(2);
|
||||
overflow: hidden;
|
||||
}
|
||||
.wrapper-loader {
|
||||
display: flex;
|
||||
@@ -653,7 +589,8 @@ async function patchOrDeleteCollectionItems(
|
||||
align-items: center;
|
||||
}
|
||||
.wrapper-title {
|
||||
min-width: 60%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.wrapper-button {
|
||||
display: flex;
|
||||
@@ -693,7 +630,6 @@ async function patchOrDeleteCollectionItems(
|
||||
}
|
||||
.title {
|
||||
@include text(h4);
|
||||
text-align: left;
|
||||
color: var(--grey-dark);
|
||||
margin-right: toRem(1);
|
||||
margin-bottom: 0;
|
||||
@@ -702,9 +638,8 @@ async function patchOrDeleteCollectionItems(
|
||||
display: none;
|
||||
}
|
||||
.menu-top {
|
||||
position: relative;
|
||||
margin: toRem(2) toRem(2) 0;
|
||||
padding: toRem(2.5) toRem(2) toRem(1);
|
||||
padding: toRem(1) toRem(2);
|
||||
border: toRem(0.1) solid var(--grey);
|
||||
border-radius: toRem(0.5);
|
||||
background-color: var(--blue-semi);
|
||||
@@ -712,37 +647,35 @@ async function patchOrDeleteCollectionItems(
|
||||
.header-menu {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: toRem(1);
|
||||
}
|
||||
.sequence-status {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
@include text(xss-regular);
|
||||
border-radius: toRem(0.4);
|
||||
padding: toRem(0.3) toRem(0.8);
|
||||
@include text(xs-r-regular);
|
||||
border-radius: toRem(3);
|
||||
padding: toRem(0.5) toRem(1);
|
||||
margin-right: toRem(1);
|
||||
color: var(--white);
|
||||
&.ready {
|
||||
background-color: var(--green);
|
||||
border: toRem(0.1) solid var(--green);
|
||||
background-color: var(--orange);
|
||||
border: toRem(0.1) solid var(--orange);
|
||||
}
|
||||
&.waiting-for-process {
|
||||
background-color: var(--yellow);
|
||||
border: toRem(0.1) solid var(--yellow);
|
||||
}
|
||||
&.hidden {
|
||||
background-color: var(--red-pale);
|
||||
border: toRem(0.1) solid var(--red-pale);
|
||||
background-color: var(--blue-geovisio);
|
||||
border: toRem(0.1) solid var(--blue-geovisio);
|
||||
}
|
||||
}
|
||||
.button-collapse {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: toRem(2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
margin-bottom: toRem(1);
|
||||
}
|
||||
.bi-plus,
|
||||
.bi-dash {
|
||||
@@ -810,8 +743,9 @@ async function patchOrDeleteCollectionItems(
|
||||
justify-content: center;
|
||||
}
|
||||
@media (max-width: toRem(102.4)) {
|
||||
.wrapper-title {
|
||||
width: 90%;
|
||||
.header-menu {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.block-collapse {
|
||||
flex-direction: column;
|
||||
@@ -860,10 +794,9 @@ async function patchOrDeleteCollectionItems(
|
||||
margin-bottom: toRem(1);
|
||||
}
|
||||
.button-collapse {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0;
|
||||
top: toRem(-1);
|
||||
right: 0;
|
||||
}
|
||||
.photo-item {
|
||||
width: 100%;
|
||||
@@ -920,7 +853,6 @@ async function patchOrDeleteCollectionItems(
|
||||
}
|
||||
.menu-top {
|
||||
width: 0vw;
|
||||
padding: toRem(2.5) toRem(1);
|
||||
}
|
||||
.button-close {
|
||||
position: absolute;
|
||||
@@ -936,6 +868,9 @@ async function patchOrDeleteCollectionItems(
|
||||
background-color: var(--white);
|
||||
border: toRem(0.1) solid var(--black);
|
||||
}
|
||||
.menu-top {
|
||||
padding: toRem(1);
|
||||
}
|
||||
.menu-is-open {
|
||||
.menu-right {
|
||||
z-index: 3;
|
||||
|
||||
@@ -24,49 +24,16 @@
|
||||
: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>
|
||||
<div class="bbox-filter-button">
|
||||
<Button
|
||||
look="button--white background-white"
|
||||
icon="bi bi-search"
|
||||
:text="$t('pages.sequences.filter_bbox_button')"
|
||||
@trigger="triggerBboxFilter"
|
||||
/>
|
||||
<div class="reset-bbox">
|
||||
<Button
|
||||
v-if="filterBbox"
|
||||
look="no-text-blue-dark"
|
||||
icon="bi bi-x-circle-fill"
|
||||
@trigger="triggerResetBbox"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section
|
||||
:style="{ width: `${listWidth}px` }"
|
||||
class="section-sequence"
|
||||
@scroll="handleScroll"
|
||||
>
|
||||
<div class="header-title">
|
||||
<h1 id="sequenceTitle" class="sequences-title">
|
||||
{{ $t('pages.sequences.title') }}
|
||||
</h1>
|
||||
<Button
|
||||
v-if="
|
||||
filterDate.start || filterDate.end || sortDate.sortBy || filterBbox
|
||||
"
|
||||
look="button-border--black"
|
||||
:text="$t('pages.sequences.reset_filter_button')"
|
||||
@trigger="resetAllFilter"
|
||||
/>
|
||||
</div>
|
||||
<h1 id="sequenceTitle" class="sequences-title">
|
||||
{{ $t('pages.sequences.title') }}
|
||||
</h1>
|
||||
<div
|
||||
ref="headerList"
|
||||
:class="['sequence-item sequence-item-head', headerListClass]"
|
||||
@@ -81,39 +48,21 @@
|
||||
</div>
|
||||
<div class="sequence-header-item">
|
||||
<Button
|
||||
look="link--black no-text"
|
||||
:icon="iconButtonSort('datetime')"
|
||||
:text="$t('pages.sequences.sequence_date')"
|
||||
look="link--grey"
|
||||
icon="bi bi-arrow-down-up"
|
||||
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
|
||||
look="link--black no-text"
|
||||
:icon="iconButtonSort('created')"
|
||||
:text="$t('pages.sequences.sequence_creation')"
|
||||
look="link--grey"
|
||||
icon="bi bi-arrow-down-up"
|
||||
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>
|
||||
@@ -123,7 +72,10 @@
|
||||
<li
|
||||
v-if="userSequences.length"
|
||||
v-for="item in userSequences"
|
||||
:class="['sequence-item', { 'sequence-selected': item.id === seqId }]"
|
||||
:class="[
|
||||
'sequence-item',
|
||||
item.id === seqId ? 'button-item-hover' : ''
|
||||
]"
|
||||
@mouseover="goToSequence(item)"
|
||||
>
|
||||
<router-link
|
||||
@@ -132,6 +84,7 @@
|
||||
name: 'sequence',
|
||||
params: { id: item.id }
|
||||
}"
|
||||
@mouseover.stop
|
||||
>
|
||||
<div class="wrapper-thumb">
|
||||
<img
|
||||
@@ -151,7 +104,7 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sequence-title">
|
||||
<div>
|
||||
<span>
|
||||
{{ item.title }}
|
||||
</span>
|
||||
@@ -167,14 +120,14 @@
|
||||
{{
|
||||
formatDate(
|
||||
item.extent.temporal.interval[0][0],
|
||||
'Do MMM YYYY HH:mm:ss'
|
||||
'Do MMMM YYYY HH:mm:ss'
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>
|
||||
{{ formatDate(item.created, 'Do MMM YYYY HH:mm:ss') }}
|
||||
{{ formatDate(item.created, 'Do MMMM YYYY HH:mm:ss') }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
@@ -185,8 +138,8 @@
|
||||
</router-link>
|
||||
<div class="wrapper-button">
|
||||
<Button
|
||||
:text="sequenceButtonText(item['geovisio:status'])"
|
||||
look="link--blue row-reverse"
|
||||
:tooltip="$t('pages.sequence.hide_sequence_tooltip')"
|
||||
look="button--white background-white"
|
||||
:icon="
|
||||
item['geovisio:status'] === 'ready'
|
||||
? 'bi bi-eye-slash'
|
||||
@@ -196,18 +149,13 @@
|
||||
@trigger="patchCollection(item)"
|
||||
/>
|
||||
<Button
|
||||
:text="$t('pages.sequences.delete_button')"
|
||||
look="link--red row-reverse"
|
||||
:tooltip="$t('pages.sequence.delete_sequence_tooltip')"
|
||||
look="button--red background-white"
|
||||
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') }}
|
||||
@@ -233,28 +181,6 @@
|
||||
</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>
|
||||
|
||||
@@ -270,13 +196,10 @@ 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'
|
||||
@@ -303,86 +226,23 @@ 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 filterBbox = ref<number[] | null>(null)
|
||||
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 ViewerType>>()
|
||||
const headerList = ref<HTMLDivElement | null>(null)
|
||||
const list = ref<HTMLDListElement | null>(null)
|
||||
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 resetAllFilter(): Promise<void> {
|
||||
filterDate.value = { start: null, end: null, type: '' }
|
||||
sortDate.value = { sortBy: null }
|
||||
filterBbox.value = null
|
||||
formatUri()
|
||||
await updateSequence(uri.value)
|
||||
}
|
||||
async function triggerResetBbox(): Promise<void> {
|
||||
filterBbox.value = null
|
||||
formatUri()
|
||||
await updateSequence(uri.value)
|
||||
}
|
||||
function triggerBboxFilter(): void {
|
||||
if (viewerRef.value) {
|
||||
isLoading.value = true
|
||||
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
|
||||
]
|
||||
filterBbox.value = currentBbox
|
||||
formatUri()
|
||||
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?limit=50')
|
||||
const { data } = await axios.get('api/users/me/collection')
|
||||
collectionBbox.value = data.extent.spatial.bbox[0]
|
||||
userSequences.value = getLinkByRel(data.links, 'child')
|
||||
userSequences.value = getRelChild(data.links)
|
||||
}
|
||||
|
||||
async function patchCollection(sequence: SequenceLinkInterface): Promise<void> {
|
||||
@@ -390,9 +250,9 @@ async function patchCollection(sequence: SequenceLinkInterface): Promise<void> {
|
||||
let visible
|
||||
if (sequence['geovisio:status'] === 'ready') visible = 'false'
|
||||
else visible = 'true'
|
||||
await patchACollection(sequence.id, { visible: visible })
|
||||
await patchACollection(sequence.id, visible)
|
||||
await fetchAndFormatSequence()
|
||||
if (viewerRef.value) viewerRef.value.viewer.reloadVectorTiles()
|
||||
viewerRef.value.viewer.reloadVectorTiles()
|
||||
isLoading.value = false
|
||||
}
|
||||
async function deleteCollection(
|
||||
@@ -402,7 +262,7 @@ async function deleteCollection(
|
||||
if (confirm(t('pages.sequence.confirm_sequence_dialog'))) {
|
||||
await deleteACollection(sequence.id)
|
||||
await fetchAndFormatSequence()
|
||||
if (viewerRef.value) viewerRef.value.viewer.reloadVectorTiles()
|
||||
viewerRef.value.viewer.reloadVectorTiles()
|
||||
}
|
||||
isLoading.value = false
|
||||
}
|
||||
@@ -411,12 +271,8 @@ function sequenceStatus(status: string): string {
|
||||
if (status === 'hidden') return t('pages.sequences.sequence_hidden')
|
||||
return t('pages.sequences.sequence_waiting')
|
||||
}
|
||||
function sequenceButtonText(status: string): string {
|
||||
if (status === 'hidden') return t('pages.sequences.show_button')
|
||||
return t('pages.sequences.hide_button')
|
||||
}
|
||||
function onResizeMap(width: any): void {
|
||||
if (width && collectionBbox.value.length) {
|
||||
if (width) {
|
||||
width.value = width
|
||||
mapWidth.value = width.value.width
|
||||
listWidth.value = window.innerWidth - width.value.width
|
||||
@@ -431,30 +287,21 @@ 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
|
||||
if (!userSequences.value.length) noSequencesFound.value = true
|
||||
} 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}`
|
||||
sortDate.value.sortBy = encodeURIComponent(sortBy)
|
||||
formatUri()
|
||||
await updateSequence(uri.value)
|
||||
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 bboxIsInsideOther(mainBbox: number[], bboxInside: number[]): boolean {
|
||||
@@ -466,34 +313,28 @@ function bboxIsInsideOther(mainBbox: number[], bboxInside: number[]): boolean {
|
||||
)
|
||||
}
|
||||
function goToSequence(sequence: SequenceLinkInterface): void {
|
||||
if (!viewerRef.value) return
|
||||
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 (
|
||||
seqId.value === sequence.id &&
|
||||
bboxIsInsideOther(currentBbox, sequence.extent.spatial.bbox[0])
|
||||
) {
|
||||
return
|
||||
}
|
||||
seqId.value = sequence.id
|
||||
viewerRef.value.viewer.select(seqId.value)
|
||||
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 getLinkByRel(
|
||||
sequences: SequenceLinkInterface[],
|
||||
rel: string
|
||||
function getRelChild(
|
||||
sequences: SequenceLinkInterface[]
|
||||
): SequenceLinkInterface[] {
|
||||
return sequences.filter((el: SequenceLinkInterface) => el.rel === rel)
|
||||
return sequences.filter((el: SequenceLinkInterface) => el.rel === 'child')
|
||||
}
|
||||
function scrollToElement(): void {
|
||||
const elementTarget = document.querySelector('#sequenceTitle')
|
||||
@@ -501,22 +342,21 @@ function scrollToElement(): void {
|
||||
}
|
||||
async function goToNextPage(value: string): Promise<void> {
|
||||
isLoading.value = true
|
||||
await updateSequence(value)
|
||||
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 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) {
|
||||
const classCondition = headerLisPos.value.y != 0 && listPos.value.top < 174
|
||||
return classCondition ? 'item-head-fixed' : ''
|
||||
return headerLisPos.value.y != 0 && listPos.value.top < 180
|
||||
? 'item-head-fixed'
|
||||
: ''
|
||||
}
|
||||
return ''
|
||||
})
|
||||
@@ -524,66 +364,31 @@ onMounted(async () => {
|
||||
isLoading.value = true
|
||||
try {
|
||||
const { data } = await axios.get('api/users/me/collection?limit=50')
|
||||
selfLink.value = getLinkByRel(data.links, 'self')
|
||||
selfLink.value = data.links.filter(
|
||||
(el: SequenceLinkInterface) => el.rel === 'self'
|
||||
)
|
||||
paginationLinks.value = formatPaginationItems(data.links)
|
||||
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]
|
||||
]
|
||||
}
|
||||
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
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
function formatUri(): void {
|
||||
let params: string[] = []
|
||||
if (filterBbox && filterBbox.value) {
|
||||
const bboxFilter = `${filterBbox.value[0]},${filterBbox.value[1]},${filterBbox.value[2]},${filterBbox.value[3]}`
|
||||
params = [...params, `&bbox=${bboxFilter}`]
|
||||
}
|
||||
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
|
||||
filterDate.value = { start: value.start, end: value.end, type: value.type }
|
||||
formatUri()
|
||||
await updateSequence(uri.value)
|
||||
}
|
||||
watchEffect(() => {
|
||||
if (viewerRef.value && viewerRef.value.viewer) {
|
||||
viewerRef.value.viewer.addEventListener(
|
||||
'select',
|
||||
'hover',
|
||||
(e: { detail: { seqId: string } }) => {
|
||||
if (seqId.value === e.detail.seqId) return
|
||||
seqId.value = e.detail.seqId
|
||||
scrollIntoSelected(
|
||||
e.detail.seqId,
|
||||
userSequences.value.map((e) => e.id)
|
||||
)
|
||||
if (viewerRef.value) viewerRef.value.viewer.select(e.detail.seqId)
|
||||
scrollIntoSelected(e.detail.seqId, userSequences.value)
|
||||
viewerRef.value.viewer.select(e.detail.seqId)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -619,49 +424,18 @@ watchEffect(() => {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.bbox-filter-button {
|
||||
padding: toRem(2);
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
position: relative;
|
||||
}
|
||||
.reset-bbox {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: toRem(0.5);
|
||||
}
|
||||
.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);
|
||||
}
|
||||
.date-filter-title {
|
||||
@include text(s-r-regular);
|
||||
}
|
||||
.wrapper-date-filter-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: toRem(5);
|
||||
margin-top: toRem(3);
|
||||
margin-left: toRem(3);
|
||||
}
|
||||
.sequence-list {
|
||||
box-shadow: 0px 2px 30px 0px #0000000f;
|
||||
@@ -676,15 +450,10 @@ watchEffect(() => {
|
||||
align-items: center;
|
||||
margin: auto;
|
||||
background-color: var(--blue-pale);
|
||||
padding-right: toRem(2);
|
||||
padding-left: toRem(2);
|
||||
padding: toRem(2);
|
||||
&:nth-child(2n) {
|
||||
background-color: var(--white);
|
||||
}
|
||||
&:hover,
|
||||
&.sequence-selected {
|
||||
background-color: var(--blue-semi);
|
||||
}
|
||||
}
|
||||
.wrapper-button {
|
||||
position: absolute;
|
||||
@@ -699,6 +468,18 @@ watchEffect(() => {
|
||||
margin-bottom: toRem(1);
|
||||
}
|
||||
}
|
||||
.button-item-hover {
|
||||
background-color: var(--blue);
|
||||
&:nth-child(2n) {
|
||||
background-color: var(--blue);
|
||||
}
|
||||
.button-item {
|
||||
& > *,
|
||||
& > :nth-child(2) {
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
}
|
||||
.item-head-fixed {
|
||||
position: fixed;
|
||||
top: toRem(8);
|
||||
@@ -732,9 +513,6 @@ watchEffect(() => {
|
||||
height: toRem(15);
|
||||
z-index: 1;
|
||||
}
|
||||
.sequence-title {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.ready {
|
||||
color: var(--green);
|
||||
}
|
||||
@@ -791,10 +569,7 @@ 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);
|
||||
}
|
||||
@@ -804,15 +579,6 @@ 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);
|
||||
@@ -828,10 +594,6 @@ watchEffect(() => {
|
||||
.no-sequence-text {
|
||||
margin-bottom: toRem(4);
|
||||
}
|
||||
.no-sequence-found {
|
||||
text-align: center;
|
||||
padding: toRem(2);
|
||||
}
|
||||
.loader {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -872,20 +634,7 @@ watchEffect(() => {
|
||||
width: 100% !important;
|
||||
}
|
||||
.sequence-item-head {
|
||||
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;
|
||||
}
|
||||
display: none;
|
||||
}
|
||||
.button-item {
|
||||
flex-direction: column;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<template v-slot:cross>
|
||||
<Button
|
||||
icon="bi bi-x-lg"
|
||||
look="no-text-blue-dark"
|
||||
look="no-text"
|
||||
@trigger="informationCardDisplayed = false"
|
||||
/>
|
||||
</template>
|
||||
@@ -39,14 +39,12 @@
|
||||
<p class="import-text">
|
||||
{{ $t('pages.upload.description_title_sequence') }}
|
||||
</p>
|
||||
<div class="wrapper-edit-text">
|
||||
<EditText
|
||||
:default-text="newSequenceTitle || sequenceTitle"
|
||||
:is-loading="isLoading"
|
||||
:is-loaded="isLoaded"
|
||||
@triggerNewText="setNewSequenceTitle"
|
||||
/>
|
||||
</div>
|
||||
<EditText
|
||||
:default-text="newSequenceTitle || sequenceTitle"
|
||||
:is-loading="isLoading"
|
||||
:is-loaded="isLoaded"
|
||||
@triggerNewText="setNewSequenceTitle"
|
||||
/>
|
||||
<form>
|
||||
<div class="wrapper-form">
|
||||
<InputUpload
|
||||
@@ -65,13 +63,6 @@
|
||||
class="uploading-img"
|
||||
/>
|
||||
<span>{{ $t('pages.upload.uploading_process') }}</span>
|
||||
<div class="cancel-button">
|
||||
<Button
|
||||
:text="$t('pages.upload.uploading_cancel')"
|
||||
look="button--red"
|
||||
@trigger="cancelUpload"
|
||||
/>
|
||||
</div>
|
||||
<span class="loader-text-warning">{{
|
||||
$t('pages.upload.leave_message')
|
||||
}}</span>
|
||||
@@ -99,20 +90,7 @@
|
||||
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>
|
||||
|
||||
@@ -136,7 +114,6 @@ import {
|
||||
} from '@/views/utils/upload/request'
|
||||
import { sortByName } from '@/views/utils/upload/index'
|
||||
import authConfig from '../composables/auth'
|
||||
import { deleteACollection } from '@/views/utils/sequence/request'
|
||||
|
||||
const { authConf } = authConfig()
|
||||
const { t } = useI18n()
|
||||
@@ -192,7 +169,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 {
|
||||
@@ -223,16 +200,6 @@ function addPictures(value: FileList): void {
|
||||
loadPercentage.value = '0%'
|
||||
uploadPicture()
|
||||
}
|
||||
async function cancelUpload(): Promise<void> {
|
||||
const answer = window.confirm(t('pages.upload.cancel_message'))
|
||||
if (answer) {
|
||||
pictures.value = []
|
||||
isLoading.value = false
|
||||
if (uploadedSequence.value) {
|
||||
await deleteACollection(uploadedSequence.value.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
async function uploadPicture(): Promise<void> {
|
||||
if (!pictures.value || !pictures.value.length) {
|
||||
return
|
||||
@@ -256,16 +223,13 @@ async function uploadPicture(): Promise<void> {
|
||||
}
|
||||
let i = 0
|
||||
for (let el of picturesToUpload) {
|
||||
if (pictures.value.length === 0) {
|
||||
break
|
||||
}
|
||||
const body = new FormData()
|
||||
i++
|
||||
body.append('position', i.toString())
|
||||
body.append('picture', el)
|
||||
try {
|
||||
const pictureUploaded = await createAPictureToASequence(data.id, body)
|
||||
const pictures = { ...pictureUploaded, name: el.name }
|
||||
const pictures = { ...pictureUploaded.data, name: el.name }
|
||||
picturesUploadingSize.value = picturesUploadingSize.value + el.size
|
||||
uploadedSequence.value.pictures = [
|
||||
...uploadedSequence.value.pictures,
|
||||
@@ -303,6 +267,7 @@ h3 {
|
||||
.entry-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: calc(100vh - #{toRem(13.82)});
|
||||
background-color: var(--grey);
|
||||
@@ -335,12 +300,6 @@ 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);
|
||||
@@ -348,9 +307,6 @@ h3 {
|
||||
.import-text {
|
||||
@include text(m-r-regular);
|
||||
}
|
||||
.wrapper-edit-text {
|
||||
margin-bottom: toRem(2);
|
||||
}
|
||||
.entry-license {
|
||||
margin-bottom: toRem(1);
|
||||
}
|
||||
@@ -369,13 +325,10 @@ h3 {
|
||||
.uploading-img {
|
||||
height: toRem(20);
|
||||
}
|
||||
.cancel-button {
|
||||
margin-top: toRem(1);
|
||||
}
|
||||
.loader-text-warning {
|
||||
text-align: center;
|
||||
@include text(s-r-regular);
|
||||
color: var(--red-pale);
|
||||
color: var(--orange);
|
||||
margin-top: toRem(1);
|
||||
width: toRem(31);
|
||||
}
|
||||
@@ -387,9 +340,6 @@ h3 {
|
||||
align-items: center;
|
||||
}
|
||||
@media (max-width: toRem(76.8)) {
|
||||
.section {
|
||||
display: none;
|
||||
}
|
||||
.entry-page {
|
||||
padding-top: toRem(15);
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export interface ExtendedHtmlEl extends HTMLElement {
|
||||
mozRequestFullScreen?: () => void
|
||||
webkitRequestFullScreen?: () => void
|
||||
msRequestFullscreen?: () => void
|
||||
}
|
||||
@@ -39,5 +39,5 @@ export interface ResponseUserSequenceInterface extends UserSequenceInterface {
|
||||
|
||||
export interface CheckboxInterface {
|
||||
isChecked: boolean
|
||||
isIndeterminate?: boolean
|
||||
isIndeterminate: boolean
|
||||
}
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
export interface MapInterface {
|
||||
startWide?: boolean
|
||||
users?: string[]
|
||||
maxZoom?: number
|
||||
minZoom?: number
|
||||
style?: object | string
|
||||
zoom?: number
|
||||
center?: number[]
|
||||
bounds?: number[]
|
||||
raster?: {
|
||||
type: string
|
||||
tiles: string[]
|
||||
attribution: string
|
||||
tileSize: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface ViewerInterface {
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import type { ExtendedHtmlEl } from '@/views/interfaces/HomeView'
|
||||
interface ExtendedDocument extends Document {
|
||||
webkitFullscreenElement?: Element
|
||||
mozFullScreenElement?: Element
|
||||
msFullscreenElement?: Element
|
||||
mozCancelFullScreen?: () => void
|
||||
webkitExitFullscreen?: () => void
|
||||
msExitFullscreen?: () => void
|
||||
}
|
||||
export function toggleFullscreen(element: ExtendedHtmlEl) {
|
||||
const documentWithExtensions = document as ExtendedDocument
|
||||
const isInFullScreen =
|
||||
(documentWithExtensions.fullscreenElement &&
|
||||
documentWithExtensions.fullscreenElement !== null) ||
|
||||
(documentWithExtensions.webkitFullscreenElement &&
|
||||
documentWithExtensions.webkitFullscreenElement !== null) ||
|
||||
(documentWithExtensions.mozFullScreenElement &&
|
||||
documentWithExtensions.mozFullScreenElement !== null) ||
|
||||
(documentWithExtensions.msFullscreenElement &&
|
||||
documentWithExtensions.msFullscreenElement !== null)
|
||||
|
||||
if (!isInFullScreen) {
|
||||
if (element.requestFullscreen) {
|
||||
element.requestFullscreen()
|
||||
} else if (element.mozRequestFullScreen) {
|
||||
element.mozRequestFullScreen()
|
||||
} else if (element.webkitRequestFullScreen) {
|
||||
element.webkitRequestFullScreen()
|
||||
} else if (element.msRequestFullscreen) {
|
||||
element.msRequestFullscreen()
|
||||
}
|
||||
} else {
|
||||
if (documentWithExtensions.exitFullscreen) {
|
||||
documentWithExtensions.exitFullscreen()
|
||||
} else if (documentWithExtensions.webkitExitFullscreen) {
|
||||
documentWithExtensions.webkitExitFullscreen()
|
||||
} else if (documentWithExtensions.mozCancelFullScreen) {
|
||||
documentWithExtensions.mozCancelFullScreen()
|
||||
} else if (documentWithExtensions.msExitFullscreen) {
|
||||
documentWithExtensions.msExitFullscreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,9 @@ function imageStatus(imageStatus: string, sequenceStatus: string): string {
|
||||
if (sequenceStatus === 'hidden') return sequenceStatus
|
||||
return imageStatus
|
||||
}
|
||||
function scrollIntoSelected(id: string, userPhotos: string[]): void {
|
||||
const itemPosition = userPhotos.map((el: string) => el).indexOf(id)
|
||||
//TODO REMOVE ANY
|
||||
function scrollIntoSelected(id: string, userPhotos: any): void {
|
||||
const itemPosition = userPhotos.map((el: any) => el.id).indexOf(id)
|
||||
const elementTarget = document.querySelector(`#el-list${itemPosition}`)
|
||||
if (elementTarget) elementTarget.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ function deleteACollection(collectionId: string | string[]): Promise<unknown> {
|
||||
|
||||
function patchACollection(
|
||||
collectionId: string | string[],
|
||||
fieldObject: object
|
||||
visible: string
|
||||
): Promise<unknown> {
|
||||
return axios.patch(`api/collections/${collectionId}`, fieldObject)
|
||||
return axios.patch(`api/collections/${collectionId}`, { visible: visible })
|
||||
}
|
||||
|
||||
function deleteACollectionItem(
|
||||
|
||||
@@ -19,32 +19,12 @@ interface SequenceCreatedInterface {
|
||||
}
|
||||
}
|
||||
|
||||
interface PictureCreatedInterface {
|
||||
data: {
|
||||
assets: object
|
||||
bbox: number[]
|
||||
collection: string
|
||||
geometry: object
|
||||
id: string
|
||||
links: object[]
|
||||
properties: object
|
||||
providers: object[]
|
||||
stac_extensions: string[]
|
||||
stac_version: string
|
||||
type: string
|
||||
}
|
||||
}
|
||||
|
||||
function createASequence(title: string): Promise<SequenceCreatedInterface> {
|
||||
return axios.post('api/collections', { title: title })
|
||||
}
|
||||
|
||||
async function createAPictureToASequence(
|
||||
id: string,
|
||||
body: FormData
|
||||
): Promise<PictureCreatedInterface> {
|
||||
const { data } = await axios.post(`api/collections/${id}/items`, body)
|
||||
return data
|
||||
async function createAPictureToASequence(id: string, body: any): Promise<any> {
|
||||
return await axios.post(`api/collections/${id}/items`, body)
|
||||
}
|
||||
|
||||
export { createASequence, createAPictureToASequence }
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 59 KiB |
1
v-calendar.d.ts
vendored
1
v-calendar.d.ts
vendored
@@ -1 +0,0 @@
|
||||
declare module 'v-calendar'
|
||||
@@ -1,62 +1,33 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import eslintPlugin from 'vite-plugin-eslint'
|
||||
import { createHtmlPlugin } from 'vite-plugin-html'
|
||||
// https://vitejs.dev/config/
|
||||
export default ({ mode }) => {
|
||||
process.env = { ...process.env, ...loadEnv(mode, process.cwd()) }
|
||||
return defineConfig({
|
||||
server: {
|
||||
host: true,
|
||||
port: 5173,
|
||||
strictPort: true,
|
||||
hmr: {
|
||||
clientPort: 5173,
|
||||
overlay: false
|
||||
}
|
||||
},
|
||||
base: '/',
|
||||
plugins: [
|
||||
vue(),
|
||||
eslintPlugin(),
|
||||
createHtmlPlugin({
|
||||
minify: true,
|
||||
/**
|
||||
* After writing entry here, you will not need to add script tags in `index.html`, the original tags need to be deleted
|
||||
* @default src/main.ts
|
||||
*/
|
||||
entry: 'src/main.ts',
|
||||
/**
|
||||
* If you want to store `index.html` in the specified folder, you can modify it, otherwise no configuration is required
|
||||
* @default index.html
|
||||
*/
|
||||
template: '/index.html',
|
||||
|
||||
/**
|
||||
* Data that needs to be injected into the index.html ejs template
|
||||
*/
|
||||
inject: {
|
||||
data: {
|
||||
instanceName: process.env.VITE_INSTANCE_NAME,
|
||||
frontUrl: process.env.VITE_FRONT_URL
|
||||
}
|
||||
}
|
||||
})
|
||||
],
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
additionalData:
|
||||
'@import "@/assets/font-size.scss"; @import "@/assets/rem-calc.scss";'
|
||||
}
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js'
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
server: {
|
||||
host: true,
|
||||
port: 5173,
|
||||
strictPort: true,
|
||||
hmr: {
|
||||
clientPort: 5173,
|
||||
overlay: false
|
||||
}
|
||||
},
|
||||
base: '/',
|
||||
plugins: [vue(), eslintPlugin()],
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
additionalData:
|
||||
'@import "@/assets/font-size.scss"; @import "@/assets/rem-calc.scss";'
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
1
vue-matomo.d.ts
vendored
1
vue-matomo.d.ts
vendored
@@ -1 +0,0 @@
|
||||
declare module 'vue-matomo'
|
||||
Reference in New Issue
Block a user