forked from Ivasoft/geovisio-website
Compare commits
10 Commits
cquest-dev
...
fix/cookie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02e145456e | ||
|
|
7341883c14 | ||
|
|
230f978ffa | ||
|
|
08eec76104 | ||
|
|
316e1880b0 | ||
|
|
542eefac03 | ||
|
|
edd5d207a1 | ||
|
|
f99d19a13a | ||
|
|
448cd1af41 | ||
|
|
81cbe90d01 |
21
CHANGELOG.md
21
CHANGELOG.md
@@ -7,6 +7,24 @@ 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.
|
Before _0.1.0_, website development was on rolling release, meaning there are no version tags.
|
||||||
|
|
||||||
|
## [2.2.3] - 2023-11-03
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add translation based on the browser language (only trad for FR and EN for now)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Page My Sequences, add the possibility to select a sequence in the list with the map :
|
||||||
|
- the user can only see his sequences on the list
|
||||||
|
- display the selected sequence in blue in the map
|
||||||
|
- display a thumbnail the hovered sequence
|
||||||
|
- hover the sequence selected in the map on the list
|
||||||
|
|
||||||
|
- add some test e2e
|
||||||
|
- maj viewer Geovisio version to 2.2.1
|
||||||
|
- fix some CSS
|
||||||
|
|
||||||
## [2.2.2] - 2023-10-16
|
## [2.2.2] - 2023-10-16
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
@@ -107,7 +125,8 @@ 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
|
- 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
|
- 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.2.2...develop
|
[unreleased]: https://gitlab.com/geovisio/website/-/compare/2.2.3...develop
|
||||||
|
[2.2.1]: https://gitlab.com/geovisio/website/-/compare/2.2.2...2.2.3
|
||||||
[2.2.1]: https://gitlab.com/geovisio/website/-/compare/2.2.1...2.2.2
|
[2.2.1]: https://gitlab.com/geovisio/website/-/compare/2.2.1...2.2.2
|
||||||
[2.2.1]: https://gitlab.com/geovisio/website/-/compare/2.2.0...2.2.1
|
[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.2.0]: https://gitlab.com/geovisio/website/-/compare/2.1.3...2.2.0
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
describe('In the login page', () => {
|
describe('In the login page', () => {
|
||||||
it('type in the form to login', () => {
|
it('type in the form to login', () => {
|
||||||
cy.visit('https://geovisio-proxy-dev.osc-fr1.scalingo.io/api/auth/login')
|
cy.visit('/')
|
||||||
cy.get('#password').type('coucouc')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "geovisio-website",
|
"name": "geovisio-website",
|
||||||
"version": "2.2.2",
|
"version": "2.2.3",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "18.16.0"
|
"node": "18.16.0"
|
||||||
},
|
},
|
||||||
@@ -25,11 +25,13 @@
|
|||||||
"axios": "^1.2.3",
|
"axios": "^1.2.3",
|
||||||
"bootstrap": "^5.2.3",
|
"bootstrap": "^5.2.3",
|
||||||
"bootstrap-icons": "^1.10.3",
|
"bootstrap-icons": "^1.10.3",
|
||||||
"geovisio": "2.2.0",
|
"geovisio": "2.2.1-develop-acb9989e",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
|
"pako": "^2.1.0",
|
||||||
"pinia": "^2.1.4",
|
"pinia": "^2.1.4",
|
||||||
"vue": "^3.2.45",
|
"vue": "^3.2.45",
|
||||||
"vue-axios": "^3.5.2",
|
"vue-axios": "^3.5.2",
|
||||||
|
"vue-draggable-resizable-vue3": "^2.3.1-beta.13",
|
||||||
"vue-eslint-parser": "^9.1.0",
|
"vue-eslint-parser": "^9.1.0",
|
||||||
"vue-i18n": "9.2.2",
|
"vue-i18n": "9.2.2",
|
||||||
"vue-meta": "^3.0.0-alpha.10",
|
"vue-meta": "^3.0.0-alpha.10",
|
||||||
@@ -57,6 +59,8 @@
|
|||||||
"eslint-plugin-import": "^2.27.5",
|
"eslint-plugin-import": "^2.27.5",
|
||||||
"eslint-plugin-vue": "^9.8.0",
|
"eslint-plugin-vue": "^9.8.0",
|
||||||
"jsdom": "^20.0.3",
|
"jsdom": "^20.0.3",
|
||||||
|
"less": "^4.2.0",
|
||||||
|
"less-loader": "^11.1.3",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "2.8.1",
|
"prettier": "2.8.1",
|
||||||
"sass": "^1.62.0",
|
"sass": "^1.62.0",
|
||||||
|
|||||||
12
src/App.vue
12
src/App.vue
@@ -5,14 +5,13 @@ import Footer from '@/components/Footer.vue'
|
|||||||
import { RouterView } from 'vue-router'
|
import { RouterView } from 'vue-router'
|
||||||
import { useMeta } from 'vue-meta'
|
import { useMeta } from 'vue-meta'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRoute } from 'vue-router'
|
import { hasASessionCookieDecoded } from '@/utils/auth'
|
||||||
import { useCookies } from 'vue3-cookies'
|
import { useCookies } from 'vue3-cookies'
|
||||||
import { title } from '@/utils/index'
|
import { title } from '@/utils/index'
|
||||||
import authConfig from './composables/auth'
|
import authConfig from './composables/auth'
|
||||||
const { authConf } = authConfig()
|
const { authConf } = authConfig()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { cookies } = useCookies()
|
const { cookies } = useCookies()
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
let focusMap = ref<string>('focus-map')
|
let focusMap = ref<string>('focus-map')
|
||||||
|
|
||||||
@@ -31,7 +30,10 @@ useMeta({
|
|||||||
function setFocusMap(value: string) {
|
function setFocusMap(value: string) {
|
||||||
focusMap.value = value
|
focusMap.value = value
|
||||||
}
|
}
|
||||||
const getUserId = computed<string>(() => cookies.get('user_id'))
|
const isLogged = computed((): boolean => {
|
||||||
|
const cookie = hasASessionCookieDecoded()
|
||||||
|
return !!(cookie && cookie.account)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -53,7 +55,7 @@ const getUserId = computed<string>(() => cookies.get('user_id'))
|
|||||||
: ''
|
: ''
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<RouterView @trigger="setFocusMap" :class="{ logged: getUserId }" />
|
<RouterView @trigger="setFocusMap" :class="{ logged: isLogged }" />
|
||||||
<Footer v-if="!getUserId" />
|
<Footer v-if="!isLogged" />
|
||||||
</template>
|
</template>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ h5 {
|
|||||||
--grey-dark: #3e3e3e;
|
--grey-dark: #3e3e3e;
|
||||||
--blue: #2954e9;
|
--blue: #2954e9;
|
||||||
--blue-dark: #0a1f69;
|
--blue-dark: #0a1f69;
|
||||||
--blue-geovisio: #34495e;
|
|
||||||
--blue-semi: #d7dffc;
|
--blue-semi: #d7dffc;
|
||||||
--blue-pale: #f2f5ff;
|
--blue-pale: #f2f5ff;
|
||||||
|
--blue-geovisio: #34495e;
|
||||||
--beige: #f5f3ec;
|
--beige: #f5f3ec;
|
||||||
--yellow: #fec868;
|
--yellow: #fec868;
|
||||||
--orange: #ff6f00;
|
--orange: #ff6f00;
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ defineProps({
|
|||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
border-radius: toRem(1);
|
border-radius: toRem(1);
|
||||||
padding: toRem(1.3) toRem(2) toRem(1.3);
|
padding: toRem(1.3);
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
font-size: toRem(2);
|
font-size: toRem(2);
|
||||||
}
|
}
|
||||||
@@ -103,11 +104,15 @@ defineProps({
|
|||||||
width: toRem(3);
|
width: toRem(3);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
.icon {
|
.icon {
|
||||||
color: var(---black);
|
color: var(--black);
|
||||||
font-size: toRem(1.8);
|
font-size: toRem(1.8);
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.no-text-white .icon {
|
||||||
|
color: var(--white);
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
.background-white {
|
.background-white {
|
||||||
background-color: var(--white);
|
background-color: var(--white);
|
||||||
}
|
}
|
||||||
@@ -155,7 +160,7 @@ defineProps({
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: -100%;
|
bottom: -100%;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
width: toRem(18);
|
width: toRem(20);
|
||||||
right: 0;
|
right: 0;
|
||||||
@include text(xss-regular);
|
@include text(xss-regular);
|
||||||
}
|
}
|
||||||
|
|||||||
131
src/components/EditText.vue
Normal file
131
src/components/EditText.vue
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
<template>
|
||||||
|
<div class="entry-edit">
|
||||||
|
<form
|
||||||
|
v-if="isEditTitle"
|
||||||
|
@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
|
||||||
|
id="close-button"
|
||||||
|
look="no-text-white"
|
||||||
|
icon="bi bi-x"
|
||||||
|
@trigger="closeEdition"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</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')"
|
||||||
|
@trigger="goToEditMode"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import Button from '@/components/Button.vue'
|
||||||
|
import Input from '@/components/Input.vue'
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'triggerNewText', value: string | null): void
|
||||||
|
}>()
|
||||||
|
const props = defineProps({
|
||||||
|
defaultText: { type: String, default: null }
|
||||||
|
})
|
||||||
|
let titleToEdit = ref<string | null>(null)
|
||||||
|
let isEditTitle = ref<boolean>(false)
|
||||||
|
|
||||||
|
function changeTextValue(value: string): void {
|
||||||
|
titleToEdit.value = value
|
||||||
|
}
|
||||||
|
function closeEdition(): void {
|
||||||
|
isEditTitle.value = false
|
||||||
|
titleToEdit.value = null
|
||||||
|
}
|
||||||
|
function validNewName(): void {
|
||||||
|
emit('triggerNewText', titleToEdit.value)
|
||||||
|
isEditTitle.value = false
|
||||||
|
}
|
||||||
|
function goToEditMode(): void {
|
||||||
|
isEditTitle.value = true
|
||||||
|
titleToEdit.value = props.defaultText
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = computed<string | null>(() => {
|
||||||
|
if (isEditTitle.value) return titleToEdit.value
|
||||||
|
if (props.defaultText) return props.defaultText
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.title {
|
||||||
|
color: var(--blue-dark);
|
||||||
|
}
|
||||||
|
.entry-edit {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: toRem(2);
|
||||||
|
width: 100%;
|
||||||
|
height: toRem(4.7);
|
||||||
|
}
|
||||||
|
.wrapper-edit {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: toRem(1);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.edit-button {
|
||||||
|
background-color: var(--grey);
|
||||||
|
border-radius: 50%;
|
||||||
|
height: toRem(3.5);
|
||||||
|
width: toRem(3.5);
|
||||||
|
padding: toRem(1);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: toRem(1.5);
|
||||||
|
}
|
||||||
|
.wrapper-input {
|
||||||
|
position: relative;
|
||||||
|
margin-right: toRem(1.5);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.edit-form {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.close-button {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: toRem(2);
|
||||||
|
width: toRem(2);
|
||||||
|
top: toRem(-1);
|
||||||
|
right: toRem(-1);
|
||||||
|
background-color: var(--blue-dark);
|
||||||
|
color: var(--white);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -89,12 +89,31 @@ ul {
|
|||||||
margin-right: toRem(2);
|
margin-right: toRem(2);
|
||||||
height: toRem(2);
|
height: toRem(2);
|
||||||
}
|
}
|
||||||
|
@media (max-width: toRem(76.8)) {
|
||||||
|
.footer {
|
||||||
|
padding: toRem(1);
|
||||||
|
height: toRem(6.5);
|
||||||
|
}
|
||||||
|
.link-list {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
margin-right: toRem(0.5);
|
||||||
|
height: toRem(1.5);
|
||||||
|
}
|
||||||
|
.link-item {
|
||||||
|
margin-right: toRem(1);
|
||||||
|
margin-bottom: toRem(1);
|
||||||
|
}
|
||||||
|
.link {
|
||||||
|
height: toRem(1.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
@media (max-width: toRem(50)) {
|
@media (max-width: toRem(50)) {
|
||||||
.title {
|
.title {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
.footer {
|
|
||||||
padding: toRem(2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -107,16 +107,13 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { onClickOutside } from '@vueuse/core'
|
import { onClickOutside } from '@vueuse/core'
|
||||||
import { useCookies } from 'vue3-cookies'
|
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { getAuthRoute } from '@/utils/auth'
|
import { getAuthRoute, hasASessionCookieDecoded } from '@/utils/auth'
|
||||||
import Link from '@/components/Link.vue'
|
import Link from '@/components/Link.vue'
|
||||||
import InstanceName from '@/components/InstanceName.vue'
|
import InstanceName from '@/components/InstanceName.vue'
|
||||||
import HeaderOpen from '@/components/header/HeaderOpen.vue'
|
import HeaderOpen from '@/components/header/HeaderOpen.vue'
|
||||||
import AccountButton from '@/components/header/AccountButton.vue'
|
import AccountButton from '@/components/header/AccountButton.vue'
|
||||||
|
|
||||||
const { cookies } = useCookies()
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
defineProps({
|
defineProps({
|
||||||
@@ -128,26 +125,32 @@ let menuIsClosed = ref<boolean>(true)
|
|||||||
|
|
||||||
onClickOutside(list, () => closeModal())
|
onClickOutside(list, () => closeModal())
|
||||||
|
|
||||||
function closeModal() {
|
function closeModal(): void {
|
||||||
menuIsClosed.value = true
|
menuIsClosed.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleMenu(): void {
|
function toggleMenu(): void {
|
||||||
menuIsClosed.value = !menuIsClosed.value
|
menuIsClosed.value = !menuIsClosed.value
|
||||||
}
|
}
|
||||||
const isLogged = computed((): boolean => !!cookies.get('user_id'))
|
const isLogged = computed((): boolean => {
|
||||||
|
const cookie = hasASessionCookieDecoded()
|
||||||
|
return !!(cookie && cookie.account)
|
||||||
|
})
|
||||||
const ariaLabel = computed((): string =>
|
const ariaLabel = computed((): string =>
|
||||||
menuIsClosed.value
|
menuIsClosed.value
|
||||||
? t('general.header.burger_menu_aria_label_open')
|
? t('general.header.burger_menu_aria_label_open')
|
||||||
: t('general.header.burger_menu_aria_label_closed')
|
: t('general.header.burger_menu_aria_label_closed')
|
||||||
)
|
)
|
||||||
const userName = computed((): string =>
|
const userName = computed((): string => {
|
||||||
cookies!
|
const cookie = hasASessionCookieDecoded()
|
||||||
.get('user_name')
|
if (cookie && cookie.account) {
|
||||||
.match(/\b(\w)/g)!
|
return cookie.account.name
|
||||||
.join('')
|
.match(/\b(\w)/g)!
|
||||||
.toUpperCase()
|
.join('')
|
||||||
)
|
.toUpperCase()
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
<i v-if="status === 'hidden'" class="bi bi-eye-slash icon-hidden"></i>
|
<i v-if="status === 'hidden'" class="bi bi-eye-slash icon-hidden"></i>
|
||||||
<img
|
<img
|
||||||
v-if="href"
|
v-if="href"
|
||||||
|
loading="lazy"
|
||||||
:src="href"
|
:src="href"
|
||||||
alt=""
|
alt=""
|
||||||
loading="lazy"
|
|
||||||
class="photo-img"
|
class="photo-img"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -141,7 +141,6 @@ defineProps({
|
|||||||
}
|
}
|
||||||
.waiting-info {
|
.waiting-info {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 100%;
|
|
||||||
color: var(--black);
|
color: var(--black);
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
}
|
}
|
||||||
|
|||||||
33
src/components/Input.vue
Normal file
33
src/components/Input.vue
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<template>
|
||||||
|
<input
|
||||||
|
:value="text"
|
||||||
|
:required="true"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
class="input"
|
||||||
|
type="text"
|
||||||
|
@input="emitValue"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
const emit = defineEmits<{ (e: 'input', value: string): void }>()
|
||||||
|
const props = defineProps({
|
||||||
|
text: { type: String, default: null },
|
||||||
|
placeholder: { type: String, default: '' }
|
||||||
|
})
|
||||||
|
|
||||||
|
function emitValue(event: Event): void {
|
||||||
|
const inputValue = (event.target as HTMLInputElement).value
|
||||||
|
emit('input', inputValue)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.input {
|
||||||
|
padding: toRem(1);
|
||||||
|
border-radius: toRem(0.5);
|
||||||
|
border: toRem(0.1) solid var(--blue-dark);
|
||||||
|
color: var(--blue-dark);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -10,8 +10,8 @@
|
|||||||
type="file"
|
type="file"
|
||||||
multiple
|
multiple
|
||||||
:accept="accept"
|
:accept="accept"
|
||||||
class="input-file"
|
|
||||||
@change="changeFile"
|
@change="changeFile"
|
||||||
|
class="input-file"
|
||||||
/>
|
/>
|
||||||
<i class="bi bi-cloud-upload-fill"></i>
|
<i class="bi bi-cloud-upload-fill"></i>
|
||||||
<span v-if="text" class="input-text">
|
<span v-if="text" class="input-text">
|
||||||
|
|||||||
@@ -80,16 +80,6 @@ function triggerButton() {
|
|||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.icon {
|
|
||||||
color: var(--black);
|
|
||||||
font-size: toRem(2.4);
|
|
||||||
margin-right: toRem(1);
|
|
||||||
}
|
|
||||||
.logo {
|
|
||||||
height: inherit;
|
|
||||||
border-radius: toRem(0.5);
|
|
||||||
margin-right: toRem(1);
|
|
||||||
}
|
|
||||||
.button {
|
.button {
|
||||||
height: toRem(4);
|
height: toRem(4);
|
||||||
border-radius: toRem(1);
|
border-radius: toRem(1);
|
||||||
@@ -105,6 +95,16 @@ function triggerButton() {
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.icon {
|
||||||
|
color: var(--black);
|
||||||
|
font-size: toRem(2.4);
|
||||||
|
margin-right: toRem(1);
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
height: inherit;
|
||||||
|
border-radius: toRem(0.5);
|
||||||
|
margin-right: toRem(1);
|
||||||
|
}
|
||||||
.text {
|
.text {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
white-space: break-spaces;
|
white-space: break-spaces;
|
||||||
@@ -200,10 +200,6 @@ function triggerButton() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: toRem(50)) {
|
@media (max-width: toRem(50)) {
|
||||||
.default {
|
|
||||||
min-height: toRem(4);
|
|
||||||
min-width: toRem(4);
|
|
||||||
}
|
|
||||||
.icon {
|
.icon {
|
||||||
margin-right: toRem(0.5);
|
margin-right: toRem(0.5);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, onUnmounted, ref } from 'vue'
|
import { onMounted, onUnmounted, ref } from 'vue'
|
||||||
import type { ViewerInterface } from '@/views/interfaces/common'
|
import type { ViewerInterface, MapInterface } from '@/views/interfaces/common'
|
||||||
import { getIgnTiles } from '@/utils/mapAndViewer'
|
import { getIgnTiles } from '@/utils/mapAndViewer'
|
||||||
import { Viewer, StandaloneMap } from 'geovisio'
|
import { Viewer, StandaloneMap } from 'geovisio'
|
||||||
import { createUrlLink } from '@/utils'
|
import { createUrlLink } from '@/utils'
|
||||||
@@ -17,7 +17,8 @@ let viewer = ref()
|
|||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
fetchOptions: { type: Object, default: {} },
|
fetchOptions: { type: Object, default: {} },
|
||||||
geovisioViewer: { type: Boolean, default: true },
|
geovisioViewer: { type: Boolean, default: true },
|
||||||
bbox: { type: Array, default: null }
|
bbox: { type: Array, default: null },
|
||||||
|
userId: { type: String, default: '' }
|
||||||
})
|
})
|
||||||
defineExpose({
|
defineExpose({
|
||||||
viewer
|
viewer
|
||||||
@@ -28,6 +29,7 @@ onMounted(async () => {
|
|||||||
const zoom = import.meta.env.VITE_ZOOM
|
const zoom = import.meta.env.VITE_ZOOM
|
||||||
const center = import.meta.env.VITE_CENTER
|
const center = import.meta.env.VITE_CENTER
|
||||||
let paramsViewer: ViewerInterface
|
let paramsViewer: ViewerInterface
|
||||||
|
let paramsMap: MapInterface
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (props.geovisioViewer) {
|
if (props.geovisioViewer) {
|
||||||
@@ -98,7 +100,7 @@ onMounted(async () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let paramsMap = {}
|
paramsMap = { minZoom: 7 }
|
||||||
if (tiles) {
|
if (tiles) {
|
||||||
const style = tiles.includes('wxs.ign.fr') ? await getIgnTiles() : tiles
|
const style = tiles.includes('wxs.ign.fr') ? await getIgnTiles() : tiles
|
||||||
paramsMap = {
|
paramsMap = {
|
||||||
@@ -110,9 +112,14 @@ onMounted(async () => {
|
|||||||
'viewer', // Div ID
|
'viewer', // Div ID
|
||||||
`${import.meta.env.VITE_API_URL}/api/search`,
|
`${import.meta.env.VITE_API_URL}/api/search`,
|
||||||
{
|
{
|
||||||
...paramsMap
|
...paramsMap,
|
||||||
|
bounds: [props.bbox[0], props.bbox[1], props.bbox[2], props.bbox[3]],
|
||||||
|
zoom: 9
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
viewer.value.addEventListener('ready', () => {
|
||||||
|
viewer.value.setFilters({ user: props.userId }, true)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
mapIsLoaded.value = true
|
mapIsLoaded.value = true
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ ul {
|
|||||||
}
|
}
|
||||||
.logged-link {
|
.logged-link {
|
||||||
padding: toRem(0);
|
padding: toRem(0);
|
||||||
|
margin-bottom: toRem(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ defineProps({
|
|||||||
margin-bottom: toRem(1);
|
margin-bottom: toRem(1);
|
||||||
}
|
}
|
||||||
.sequence-button {
|
.sequence-button {
|
||||||
width: fit-content;
|
width: toRem(22);
|
||||||
}
|
}
|
||||||
.text-information {
|
.text-information {
|
||||||
@include text(s-r-regular);
|
@include text(s-r-regular);
|
||||||
@@ -167,7 +167,6 @@ defineProps({
|
|||||||
margin-bottom: toRem(2);
|
margin-bottom: toRem(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: toRem(102.4)) {
|
@media (max-width: toRem(102.4)) {
|
||||||
.loader-title {
|
.loader-title {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
184
src/locales/en.json
Normal file
184
src/locales/en.json
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
{
|
||||||
|
"general": {
|
||||||
|
"title": "Panoramax instance",
|
||||||
|
"meta": {
|
||||||
|
"title": "Panoramax instance",
|
||||||
|
"description": "Panoramax, the free alternative to photo-mapping territories"
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"login_text": "Connect",
|
||||||
|
"register_text": "Register",
|
||||||
|
"contribute_text": "Why contribute ?",
|
||||||
|
"my_account": "My account",
|
||||||
|
"upload_text": "+ Share pictures",
|
||||||
|
"sequences_text": "My pictures",
|
||||||
|
"alt_logo": "Instance logo",
|
||||||
|
"alt_photos": "Pictures icon",
|
||||||
|
"alt_information": "User icon",
|
||||||
|
"alt_settings": "Parameters icon",
|
||||||
|
"alt_logout": "Logout icon",
|
||||||
|
"title": "Panoramax",
|
||||||
|
"beta_text": "Beta version",
|
||||||
|
"logout_text": "Logout",
|
||||||
|
"my_information_text": "My details",
|
||||||
|
"my_settings_text": "My parameters",
|
||||||
|
"burger_menu_aria_label_open": "Show menu",
|
||||||
|
"burger_menu_aria_label_closed": "Hide menu"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"panoramax_site": "Discover Panoramax",
|
||||||
|
"information_gitlab": "Show source code",
|
||||||
|
"gitlab_logo": "Gitlab logo",
|
||||||
|
"ay11_text": "Accessibility: not compliant"
|
||||||
|
},
|
||||||
|
"error_text": "An error occured",
|
||||||
|
"success_text": "Update done"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "My tokens",
|
||||||
|
"setting_tooltip": "Show or hide token"
|
||||||
|
},
|
||||||
|
"sequence": {
|
||||||
|
"sequence_published": "Published",
|
||||||
|
"sequence_waiting": "Still processing",
|
||||||
|
"sequence_hidden": "Hidden",
|
||||||
|
"hide_sequence_tooltip": "Hide this sequences",
|
||||||
|
"delete_sequence_tooltip": "Permanently delete this sequence",
|
||||||
|
"hide_photo_tooltip": "Hide selected pictures",
|
||||||
|
"delete_photo_tooltip": "Permanently delete selected pictures",
|
||||||
|
"confirm_pictures_dialog": "⚠️ Selected photos will be permanently deleted",
|
||||||
|
"confirm_sequence_dialog": "⚠️ This sequence will be permanently deleted",
|
||||||
|
"created": "Uploaded :",
|
||||||
|
"taken": "Shot on :",
|
||||||
|
"duration": "Duration :",
|
||||||
|
"duration_begin": "Start :",
|
||||||
|
"duration_end": "End :",
|
||||||
|
"camera": "Camera :",
|
||||||
|
"button_delete": "Delete",
|
||||||
|
"button_disable": "Hide",
|
||||||
|
"button_enable": "Show",
|
||||||
|
"picture_selected": "{count} picture selected| {count} pictures selected",
|
||||||
|
"hours": "{count} hour| {count} hours",
|
||||||
|
"minutes": "{count} minute| {count} minutes",
|
||||||
|
"seconds": "{count} second| {count} seconds",
|
||||||
|
"select_text": "Select all",
|
||||||
|
"unselect_text": "Deselect all",
|
||||||
|
"select_shift_text": "Select multiple photos with shift",
|
||||||
|
"waiting_process": "Photo in process",
|
||||||
|
"broken": "Photo error processing",
|
||||||
|
"no_image": "No picture in this sequence"
|
||||||
|
},
|
||||||
|
"sequences": {
|
||||||
|
"title": "My sequences",
|
||||||
|
"sequence_name": "Name",
|
||||||
|
"sequence_photos": "Photos",
|
||||||
|
"sequence_date": "Shot on",
|
||||||
|
"sequence_creation": "Upload",
|
||||||
|
"sequence_status": "Status",
|
||||||
|
"sequence_published": "Published",
|
||||||
|
"sequence_waiting": "Still processing",
|
||||||
|
"sequence_hidden": "Hidden",
|
||||||
|
"no_sequences_text": "You have no photos published yet \uD83D\uDE22",
|
||||||
|
"button_upload": "Upload pictures",
|
||||||
|
"sequence_deleted": "The sequence has been deleted"
|
||||||
|
},
|
||||||
|
"share_pictures": {
|
||||||
|
"title": "Why contribute to Panoramax?",
|
||||||
|
"description": "Contributing to Panoramax means participating in the development of a geo-common, a sovereign, free and reusable digital resource. Each geolocalized photo published on Panoramax can be used by anyone for a variety of purposes, for example by a local authority needing to observe the status of its roads, or by a telecoms operator to prepare an intervention.\n\nEach contributor can send his or her image sequences, modify them and consult them, as well as all the views - 360° or not - contributed by the community. The compulsory blurring of faces and license plates is automated on the platform.",
|
||||||
|
"alt_img_map": "Illustration of a woman looking at a map with her geolocated smartphone",
|
||||||
|
"card_photo1": "Places visible from the public highway",
|
||||||
|
"card_photo2": "Photos published in 360° format or not",
|
||||||
|
"card_photo3": "Easily reusable photos",
|
||||||
|
"card_photo4": "A quick and easy image contribution",
|
||||||
|
"card_alt_photo1": "Image of a building",
|
||||||
|
"card_alt_photo2": "Image showing 360-degree ",
|
||||||
|
"card_alt_photo3": "Image showing a map with a pointer",
|
||||||
|
"card_alt_photo4": "Image representing a pointer",
|
||||||
|
"card_description1": "All photos taken from the public highway are accepted, as long as they are geolocated and viewed from the ground.",
|
||||||
|
"card_description2": "360° pictures are not mandatory: photos taken with a smartphone are all that's needed. Dates, locations and jpg format are the only prerequisites.",
|
||||||
|
"card_description3": "All photos easily accessible and reusable without an account: via the website or a standard API (STAC standard).",
|
||||||
|
"card_description4": "Several tools are available to facilitate contributions, including a command line and a web interface.",
|
||||||
|
"upload_subtitle": "Simply upload your images online",
|
||||||
|
"upload_illustration_alt": "Illustration showing online photo uploading",
|
||||||
|
"upload_description": "Panoramax's web application lets you upload all your field photos in JPEG format at the click of a button. No programming skills are required. For larger numbers, however, we recommend using the command-line tool",
|
||||||
|
"upload_button": "+ Upload pictures",
|
||||||
|
"command_line_subtitle": "Command line tool",
|
||||||
|
"comment_install": "Install the geovisio command-line tool",
|
||||||
|
"comment_upload": "LanceStart the image upload command on the chosen folder",
|
||||||
|
"description_terminal": "<a href='https://gitlab.com/geovisio/cli' target='_blank' style='color:black'>The CLI</a> lets you share large volumes of photos. The procedure is simple and requires <a target='_blank' href='https://www.python.org/downloads/' style='color:black'>python (version 3.8 or above)</a>.\n\nThe tool will ask for your login details before importing. Once the pictures have been uploaded, a processing time is required before publication.",
|
||||||
|
"terminal_install": "pip install geovisio_cli",
|
||||||
|
"terminal_text": "geovisio upload --api-url {url} <DOSSIER_PHOTOS>",
|
||||||
|
"button_copy": "Copy",
|
||||||
|
"information_subtitle": "Here, your photos are accessible to all : ",
|
||||||
|
"information_text1": "Automatically blurred in compliance with legislation.",
|
||||||
|
"information_text2": "The uploaded pictures will be published under {word}",
|
||||||
|
"information_text3": "In its original format and resolution for various reuse.",
|
||||||
|
"information_about_title": "Need to access pictures ?",
|
||||||
|
"information_about_description": "An API is available to retrieve all metadata and pictures. <a href='{docLink}' target='_blank' style='color:#0a1f69'>\nFind out more here</a>\nData is also displayed in the form <a href='{docTiles}' target='_blank' style='color:#0a1f69'>of vector tiles</a>",
|
||||||
|
"doc_subtitle": "Need help contributing to Panoramax?",
|
||||||
|
"doc_description": "Panoramax documentation is available from us, and you can access tutorials on the geo-commons forum.",
|
||||||
|
"doc_button": "See the documentation",
|
||||||
|
"doc_illustration_alt": "Illustration of a character with a sheet of documents"
|
||||||
|
},
|
||||||
|
"upload": {
|
||||||
|
"title": "Contribute to the Panoramax project",
|
||||||
|
"description": "For large volumes of pictures, the command line tool is more suitable.",
|
||||||
|
"know_more_button": "Read more",
|
||||||
|
"input_label": "Drag your pictures here or click on ",
|
||||||
|
"import_word": "upload",
|
||||||
|
"import_type": "JPEG format only",
|
||||||
|
"subtitle_import": "Picture upload",
|
||||||
|
"title_sequence": "Séquence title",
|
||||||
|
"description_title_sequence": "By default, the sequence title will be the date of the day. You can, if you want, edit the title here.",
|
||||||
|
"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...",
|
||||||
|
"sequence_title": "Sequence ",
|
||||||
|
"import": "Uploads",
|
||||||
|
"upload_pending": "Upload in progress...",
|
||||||
|
"images_count_text": "Pictures uploaded",
|
||||||
|
"no_img_text": "no picture upload so far",
|
||||||
|
"upload_done": "Sequence upload done",
|
||||||
|
"sequence_link": "Show this sequence",
|
||||||
|
"edit_title_tooltip": "Edit the sequence's title",
|
||||||
|
"edit_placeholder_input": "Edit the sequence's title",
|
||||||
|
"ok_button": "OK",
|
||||||
|
"pictures_error": "{count} picture could not be uploaded| {count} pictures could not be uploaded",
|
||||||
|
"sequence_loading_information": "Once uploaded, the sequence will be processed then published on Panoramax (usually within a couple of minutes).",
|
||||||
|
"sequence_loaded_information": "The sequences has been uploaded and is under processing. It should be publicly available on Panoramax within a couple of minutes.",
|
||||||
|
"leave_message": "⚠️ WARNING, the download will be interrupted if you leave the page before the end.",
|
||||||
|
"error_button": "Show errors",
|
||||||
|
"modal_error_title": "Pictures in error"
|
||||||
|
},
|
||||||
|
"ay11": {
|
||||||
|
"title": "Déclaration d’accessibilité",
|
||||||
|
"date": "Établie le 18 septembre 2023.",
|
||||||
|
"introduction": "IGN s’engage à rendre son service accessible, conformément à l’article 47 de la loi n° 2005-102 du 11 février 2005.\n Cette déclaration d’accessibilité s’applique à Panoramax Instance IGN : https://panoramax.ign.fr",
|
||||||
|
"subtitle_conformity": "État de conformité",
|
||||||
|
"conformity_text": "Panoramax Instance IGN est non conforme avec le ",
|
||||||
|
"conformity_text2": "Le site n’a encore pas été audité.",
|
||||||
|
"subtitle_conformity2": "Contenus non accessibles",
|
||||||
|
"subtitle_increase": "Amélioration et contact",
|
||||||
|
"increase_text": "Si vous n’arrivez pas à accéder à un contenu ou à un service, vous pouvez\n contacter le responsable de Panoramax Instance IGN pour être orienté vers une alternative accessible ou obtenir le contenu sous une autre forme.",
|
||||||
|
"phone": "Téléphone : +33 14 398 84 61",
|
||||||
|
"email_text": "E-mail :",
|
||||||
|
"email": "signalement.ign@panoramax.fr",
|
||||||
|
"address": "Adresse : IGN, Saint-Mandé",
|
||||||
|
"increase_info": "Nous essayons de répondre dans les 5 jours ouvrés.",
|
||||||
|
"subtitle_to_do": "Voie de recours",
|
||||||
|
"to_do_text": "Cette procédure est à utiliser dans le cas suivant : vous avez signalé au responsable du site internet un défaut d’accessibilité qui vous\n empêche d’accéder à un contenu ou à un des services du portail et vous n’avez pas obtenu de réponse satisfaisante. \n vous pouvez :",
|
||||||
|
"write_message": "Écrire un message au",
|
||||||
|
"defenseur_droits": "Défenseur des droits",
|
||||||
|
"contact": "Contacter",
|
||||||
|
"contact_text": "le délégué du Défenseur des droits dans votre région",
|
||||||
|
"send_letter": "Envoyer un courrier par la poste (gratuit, ne pas mettre de\n timbre):\n Défenseur des droits\n Libre réponse 71120 75342 Paris CEDEX 07",
|
||||||
|
"end": "Cette déclaration d’accessibilité a été créé le\n 18 septembre 2023 grâce au",
|
||||||
|
"generator_betagouv": "Générateur de Déclaration d’Accessibilité de BetaGouv"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -133,7 +133,9 @@
|
|||||||
"import_word": "importer",
|
"import_word": "importer",
|
||||||
"import_type": "Format JPEG uniquement",
|
"import_type": "Format JPEG uniquement",
|
||||||
"subtitle_import": "Dépôt des images",
|
"subtitle_import": "Dépôt des images",
|
||||||
"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.",
|
"title_sequence": "Titre de ma séquence",
|
||||||
|
"description_title_sequence": "Le titre d'une séquence est par défaut la date du jour. Vous pouvez, si vous le souhaitez le modifier ci-dessous.",
|
||||||
|
"text_import": "Déposez ici vos fichiers jpg. Chaque image ou série 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",
|
"subtitle_process": "Traitements de l'import",
|
||||||
"uploading_process": "Téléchargement en cours...",
|
"uploading_process": "Téléchargement en cours...",
|
||||||
"sequence_title": "Séquence du ",
|
"sequence_title": "Séquence du ",
|
||||||
@@ -143,6 +145,9 @@
|
|||||||
"no_img_text": "aucune image chargée actuellement",
|
"no_img_text": "aucune image chargée actuellement",
|
||||||
"upload_done": "Le chargement de la séquence est terminé",
|
"upload_done": "Le chargement de la séquence est terminé",
|
||||||
"sequence_link": "Accéder à cette séquence",
|
"sequence_link": "Accéder à cette séquence",
|
||||||
|
"edit_title_tooltip": "Modifier le titre de la séquence",
|
||||||
|
"edit_placeholder_input": "Modifier le titre de la séquence",
|
||||||
|
"ok_button": "Valider",
|
||||||
"pictures_error": "{count} image n'a pas pu être chargée| {count} image n'ont pas pu être chargées",
|
"pictures_error": "{count} image n'a pas pu être chargée| {count} image n'ont pas pu être chargées",
|
||||||
"sequence_loading_information": "Une fois chargée, la séquence sera en traitement et accessible sur Panoramax dans les prochaines minutes.",
|
"sequence_loading_information": "Une fois chargée, la séquence sera en traitement et accessible sur Panoramax dans les prochaines minutes.",
|
||||||
"sequence_loaded_information": "La séquence est chargée et est en cours de traitement. Elle sera accessible sur Panoramax dans quelques minutes.",
|
"sequence_loaded_information": "La séquence est chargée et est en cours de traitement. Elle sera accessible sur Panoramax dans quelques minutes.",
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import axios from 'axios'
|
|||||||
import VueAxios from 'vue-axios'
|
import VueAxios from 'vue-axios'
|
||||||
import { globalCookiesConfig } from 'vue3-cookies'
|
import { globalCookiesConfig } from 'vue3-cookies'
|
||||||
import { createMetaManager } from 'vue-meta'
|
import { createMetaManager } from 'vue-meta'
|
||||||
|
import { VueDraggableResizable } from 'vue-draggable-resizable-vue3'
|
||||||
import { pinia } from './store'
|
import { pinia } from './store'
|
||||||
import fr from './locales/fr.json'
|
import fr from './locales/fr.json'
|
||||||
|
import en from './locales/en.json'
|
||||||
import './assets/main.scss'
|
import './assets/main.scss'
|
||||||
import 'bootstrap/dist/css/bootstrap.css'
|
import 'bootstrap/dist/css/bootstrap.css'
|
||||||
import 'bootstrap-icons/font/bootstrap-icons.css'
|
import 'bootstrap-icons/font/bootstrap-icons.css'
|
||||||
@@ -17,13 +19,14 @@ axios.defaults.baseURL = import.meta.env.VITE_API_URL
|
|||||||
axios.defaults.withCredentials = true
|
axios.defaults.withCredentials = true
|
||||||
|
|
||||||
const i18n = createI18n({
|
const i18n = createI18n({
|
||||||
locale: 'fr',
|
locale: navigator.language.split('-')[0],
|
||||||
fallbackLocale: 'fr',
|
fallbackLocale: 'fr',
|
||||||
warnHtmlMessage: false,
|
warnHtmlMessage: false,
|
||||||
globalInjection: true,
|
globalInjection: true,
|
||||||
legacy: false,
|
legacy: false,
|
||||||
messages: {
|
messages: {
|
||||||
fr
|
fr,
|
||||||
|
en
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
globalCookiesConfig({
|
globalCookiesConfig({
|
||||||
@@ -38,4 +41,5 @@ app.use(router)
|
|||||||
app.use(VueAxios, axios)
|
app.use(VueAxios, axios)
|
||||||
app.provide('axios', app.config.globalProperties.axios)
|
app.provide('axios', app.config.globalProperties.axios)
|
||||||
app.use(createMetaManager())
|
app.use(createMetaManager())
|
||||||
|
app.use(VueDraggableResizable)
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import type {
|
|||||||
RouteLocationNormalized
|
RouteLocationNormalized
|
||||||
} from 'vue-router'
|
} from 'vue-router'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { getAuthRoute } from '@/utils/auth'
|
import { getAuthRoute, hasASessionCookieDecoded } from '@/utils/auth'
|
||||||
import HomeView from '../views/HomeView.vue'
|
import HomeView from '../views/HomeView.vue'
|
||||||
import MyInformationView from '../views/MyInformationView.vue'
|
import MyInformationView from '../views/MyInformationView.vue'
|
||||||
import MySettingsView from '../views/MySettingsView.vue'
|
import MySettingsView from '../views/MySettingsView.vue'
|
||||||
@@ -77,8 +77,9 @@ router.beforeResolve(
|
|||||||
to.name === 'upload-pictures'
|
to.name === 'upload-pictures'
|
||||||
|
|
||||||
if (siteLoggedRoutes) {
|
if (siteLoggedRoutes) {
|
||||||
if (!isSiteLogged()) goToLoginPage(to.path)
|
if (!isSiteLogged()) {
|
||||||
else return next()
|
goToLoginPage(to.path)
|
||||||
|
} else return next()
|
||||||
}
|
}
|
||||||
if (to.name === 'my-information') {
|
if (to.name === 'my-information') {
|
||||||
try {
|
try {
|
||||||
@@ -95,7 +96,8 @@ router.beforeResolve(
|
|||||||
)
|
)
|
||||||
|
|
||||||
function isSiteLogged(): boolean {
|
function isSiteLogged(): boolean {
|
||||||
return !!cookies.get('user_id')
|
const cookie = hasASessionCookieDecoded()
|
||||||
|
return !!(cookie && cookie.account)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function isKeycloakLogout(): Promise<{ status: number }> {
|
async function isKeycloakLogout(): Promise<{ status: number }> {
|
||||||
|
|||||||
88
src/tests/unit/components/EditText.spec.js
Normal file
88
src/tests/unit/components/EditText.spec.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { test, describe, expect, vi } from 'vitest'
|
||||||
|
import { shallowMount } from '@vue/test-utils'
|
||||||
|
import EditText from '../../../components/EditText.vue'
|
||||||
|
import Button from '../../../components/Button.vue'
|
||||||
|
import i18n from '../config'
|
||||||
|
describe('Template', () => {
|
||||||
|
describe('Props', () => {
|
||||||
|
test('should have default props', () => {
|
||||||
|
const wrapper = shallowMount(EditText, {
|
||||||
|
global: {
|
||||||
|
plugins: [i18n]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(wrapper.vm.defaultText).toBe(null)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('When the props are filled', () => {
|
||||||
|
test('should render the component with the defaultText', () => {
|
||||||
|
const wrapper = shallowMount(EditText, {
|
||||||
|
global: {
|
||||||
|
plugins: [i18n]
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
defaultText: 'My default text'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(wrapper.html()).contains('class="title">My default text</span>')
|
||||||
|
expect(wrapper.html()).contains('icon="bi bi-pen"')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('When the component is in editable mode', () => {
|
||||||
|
test('should render the component with the element to edit the title', async () => {
|
||||||
|
const wrapper = shallowMount(EditText, {
|
||||||
|
global: {
|
||||||
|
plugins: [i18n]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
wrapper.vm.isEditTitle = true
|
||||||
|
wrapper.vm.titleToEdit = 'title to edit'
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
|
expect(wrapper.html()).contains('text="title to edit"')
|
||||||
|
expect(wrapper.html()).contains('icon="bi bi-x"')
|
||||||
|
expect(wrapper.html()).contains('text="Valider"')
|
||||||
|
})
|
||||||
|
test('should valid the name and emit', async () => {
|
||||||
|
const wrapper = shallowMount(EditText, {
|
||||||
|
global: {
|
||||||
|
plugins: [i18n],
|
||||||
|
components: {
|
||||||
|
Button
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
wrapper.vm.isEditTitle = true
|
||||||
|
wrapper.vm.titleToEdit = 'title to edit'
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
|
const validationButton = wrapper.findComponent('#valid-button')
|
||||||
|
await validationButton.vm.$emit('trigger')
|
||||||
|
|
||||||
|
expect(wrapper.emitted()).toHaveProperty('triggerNewText')
|
||||||
|
expect(wrapper.emitted().triggerNewText[0][0]).toEqual(
|
||||||
|
wrapper.vm.titleToEdit
|
||||||
|
)
|
||||||
|
})
|
||||||
|
test('should close the edit mode', async () => {
|
||||||
|
const wrapper = shallowMount(EditText, {
|
||||||
|
global: {
|
||||||
|
plugins: [i18n],
|
||||||
|
components: {
|
||||||
|
Button
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
wrapper.vm.isEditTitle = true
|
||||||
|
wrapper.vm.titleToEdit = 'title to edit'
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
|
const closeButton = wrapper.findComponent('#close-button')
|
||||||
|
await closeButton.vm.$emit('trigger')
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
|
expect(wrapper.vm.isEditTitle).toEqual(false)
|
||||||
|
expect(wrapper.vm.titleToEdit).toEqual(null)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
39
src/tests/unit/components/Footer.spec.js
Normal file
39
src/tests/unit/components/Footer.spec.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { test, describe, expect } from 'vitest'
|
||||||
|
import { shallowMount } from '@vue/test-utils'
|
||||||
|
import Footer from '../../../components/Footer.vue'
|
||||||
|
import Link from '../../../components/Link.vue'
|
||||||
|
import i18n from '../config'
|
||||||
|
describe('Template', () => {
|
||||||
|
test('should have the links without the ay11 link', () => {
|
||||||
|
const wrapper = shallowMount(Footer, {
|
||||||
|
global: {
|
||||||
|
stubs: { Link },
|
||||||
|
plugins: [i18n]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(wrapper.html()).contains('href="https://panoramax.fr/"')
|
||||||
|
expect(wrapper.html()).contains('logo.jpeg')
|
||||||
|
expect(wrapper.html()).contains('Découvrir Panoramax')
|
||||||
|
|
||||||
|
expect(wrapper.html()).contains('href="https://gitlab.com/geovisio"')
|
||||||
|
expect(wrapper.html()).contains('gitlab-logo.svg')
|
||||||
|
expect(wrapper.html()).contains('Voir le code')
|
||||||
|
})
|
||||||
|
test('should have the ay11 link', () => {
|
||||||
|
Object.create(window)
|
||||||
|
const url = 'http://test.ign.fr'
|
||||||
|
Object.defineProperty(window, 'location', {
|
||||||
|
value: {
|
||||||
|
href: url
|
||||||
|
},
|
||||||
|
writable: true // possibility to override
|
||||||
|
})
|
||||||
|
const wrapper = shallowMount(Footer, {
|
||||||
|
global: {
|
||||||
|
stubs: { Link },
|
||||||
|
plugins: [i18n]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(wrapper.html()).contains('Accessibilité : non conforme')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -28,7 +28,7 @@ const i18n = createI18n({
|
|||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
routes: []
|
routes: [{ path: '/', component: { template: '<div></div>' } }]
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Template', () => {
|
describe('Template', () => {
|
||||||
@@ -96,7 +96,9 @@ describe('Template', () => {
|
|||||||
})
|
})
|
||||||
describe('When the user is logged', () => {
|
describe('When the user is logged', () => {
|
||||||
it('should render the component with good wording keys', async () => {
|
it('should render the component with good wording keys', async () => {
|
||||||
vi.spyOn(useCookies().cookies, 'get').mockReturnValue('user_id=id')
|
vi.spyOn(useCookies().cookies, 'get').mockReturnValue(
|
||||||
|
'.eJw9y0EKgzAQQNG7zLoDJpmYxMuUySRDra0pooUi3r3SRZcf_tuBRdo2rzDsMBYYgFxRytljkeyQrK0YVT16m6OhUlIihgvM_Kznfa88n9V4W2_XnzcuiqgmDBQMUtYec00WO3XqAndd7OUvXkt7j6Uup5vqRx6NJziOL8SPLNU.ZVy19Q.4DkVxu-LSF11uREkn6YIwHbn_0U'
|
||||||
|
)
|
||||||
|
|
||||||
import.meta.env.VITE_API_URL = 'api-url/'
|
import.meta.env.VITE_API_URL = 'api-url/'
|
||||||
const wrapper = shallowMount(Header, {
|
const wrapper = shallowMount(Header, {
|
||||||
@@ -116,7 +118,7 @@ describe('Template', () => {
|
|||||||
expect(wrapper.html()).contains('general.header.upload_text')
|
expect(wrapper.html()).contains('general.header.upload_text')
|
||||||
expect(wrapper.html()).contains('data-test="link-logged-upload"')
|
expect(wrapper.html()).contains('data-test="link-logged-upload"')
|
||||||
expect(wrapper.html()).contains('<account-button')
|
expect(wrapper.html()).contains('<account-button')
|
||||||
expect(wrapper.html()).contains('username="UI"')
|
expect(wrapper.html()).contains('username="J"')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
33
src/tests/unit/components/InformationCard.spec.js
Normal file
33
src/tests/unit/components/InformationCard.spec.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { test, describe, expect } from 'vitest'
|
||||||
|
import { shallowMount } from '@vue/test-utils'
|
||||||
|
import InformationCard from '../../../components/InformationCard.vue'
|
||||||
|
import i18n from '../config'
|
||||||
|
describe('Template', () => {
|
||||||
|
describe('Props', () => {
|
||||||
|
test('should have default props', () => {
|
||||||
|
const wrapper = shallowMount(InformationCard, {
|
||||||
|
global: {
|
||||||
|
plugins: [i18n]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(wrapper.vm.text).toBe(null)
|
||||||
|
expect(wrapper.vm.title).toBe(null)
|
||||||
|
expect(wrapper.vm.look).toBe('')
|
||||||
|
})
|
||||||
|
test('should have all the props filled', () => {
|
||||||
|
const wrapper = shallowMount(InformationCard, {
|
||||||
|
global: {
|
||||||
|
plugins: [i18n]
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
text: 'my text',
|
||||||
|
title: 'my title',
|
||||||
|
look: 'my-look'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(wrapper.html()).contains('my title</h3>')
|
||||||
|
expect(wrapper.html()).contains('my text</p>')
|
||||||
|
expect(wrapper.html()).contains('class="information-block my-look"')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
43
src/tests/unit/components/Input.spec.js
Normal file
43
src/tests/unit/components/Input.spec.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { test, describe, expect } from 'vitest'
|
||||||
|
import { shallowMount } from '@vue/test-utils'
|
||||||
|
import Input from '../../../components/Input.vue'
|
||||||
|
import i18n from '../config'
|
||||||
|
describe('Template', () => {
|
||||||
|
describe('Props', () => {
|
||||||
|
test('should have default props', () => {
|
||||||
|
const wrapper = shallowMount(Input, {
|
||||||
|
global: {
|
||||||
|
plugins: [i18n]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(wrapper.vm.text).toBe(null)
|
||||||
|
expect(wrapper.vm.placeholder).toBe('')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('When the props are filled', () => {
|
||||||
|
test('should render the button with the placeholder', () => {
|
||||||
|
const wrapper = shallowMount(Input, {
|
||||||
|
global: {
|
||||||
|
plugins: [i18n]
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
placeholder: 'My placeholder'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(wrapper.html()).contains('placeholder="My placeholder"')
|
||||||
|
})
|
||||||
|
test('should emit an input event with the prop value', async () => {
|
||||||
|
const wrapper = shallowMount(Input, {
|
||||||
|
global: {
|
||||||
|
plugins: [i18n]
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
text: 'My text'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await wrapper.trigger('input')
|
||||||
|
expect(wrapper.emitted()).toHaveProperty('input')
|
||||||
|
expect(wrapper.emitted().input[0][0]).toEqual(wrapper.vm.text)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
80
src/tests/unit/components/InputUpload.spec.js
Normal file
80
src/tests/unit/components/InputUpload.spec.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { test, describe, expect } from 'vitest'
|
||||||
|
import { shallowMount } from '@vue/test-utils'
|
||||||
|
import InputUpload from '../../../components/InputUpload.vue'
|
||||||
|
import i18n from '../config'
|
||||||
|
describe('Template', () => {
|
||||||
|
describe('Props', () => {
|
||||||
|
test('should have default props', () => {
|
||||||
|
const wrapper = shallowMount(InputUpload, {
|
||||||
|
global: {
|
||||||
|
plugins: [i18n]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(wrapper.vm.text).toBe(null)
|
||||||
|
expect(wrapper.vm.textPictureType).toBe(null)
|
||||||
|
expect(wrapper.vm.textSecondPart).toBe(null)
|
||||||
|
expect(wrapper.vm.accept).toBe('')
|
||||||
|
})
|
||||||
|
test('should have all the props filled', () => {
|
||||||
|
const wrapper = shallowMount(InputUpload, {
|
||||||
|
global: {
|
||||||
|
plugins: [i18n]
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
text: 'my text',
|
||||||
|
textPictureType: 'my textPictureType',
|
||||||
|
textSecondPart: 'my textSecondPart',
|
||||||
|
accept: 'accept'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(wrapper.html()).contains('accept="accept"')
|
||||||
|
expect(wrapper.html()).contains('my text')
|
||||||
|
expect(wrapper.html()).contains('my textSecondPart')
|
||||||
|
expect(wrapper.html()).contains('my textPictureType')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('When the input is dragover', () => {
|
||||||
|
test('should set the component dragging state', async () => {
|
||||||
|
const wrapper = shallowMount(InputUpload, {
|
||||||
|
global: {
|
||||||
|
plugins: [i18n]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await wrapper.find('label').trigger('dragover')
|
||||||
|
expect(wrapper.vm.isDragging).toBe(true)
|
||||||
|
expect(wrapper.html()).contains('class="file-upload dragging"')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('When the input is dragleave', () => {
|
||||||
|
test('should set the component dragging state', async () => {
|
||||||
|
const wrapper = shallowMount(InputUpload, {
|
||||||
|
global: {
|
||||||
|
plugins: [i18n]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await wrapper.find('label').trigger('dragover')
|
||||||
|
await wrapper.find('label').trigger('dragleave')
|
||||||
|
|
||||||
|
expect(wrapper.vm.isDragging).toBe(false)
|
||||||
|
expect(wrapper.html()).contains('class="file-upload"')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('When the input is dropped', () => {
|
||||||
|
test('should emit with value', async () => {
|
||||||
|
const wrapper = shallowMount(InputUpload, {
|
||||||
|
global: {
|
||||||
|
plugins: [i18n]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const dataTransfer = {
|
||||||
|
files: [{ type: 'image/jpeg' }]
|
||||||
|
}
|
||||||
|
const label = await wrapper.find('label')
|
||||||
|
await label.trigger('drop', { dataTransfer })
|
||||||
|
|
||||||
|
expect(wrapper.emitted()).toHaveProperty('trigger')
|
||||||
|
expect(wrapper.emitted().trigger[0][0]).toEqual(dataTransfer.files)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// TODO TEST -> When the input is changed should emit with value
|
||||||
|
})
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { it, describe, expect } from 'vitest'
|
import { it, describe, expect } from 'vitest'
|
||||||
import { shallowMount } from '@vue/test-utils'
|
import { shallowMount } from '@vue/test-utils'
|
||||||
import BetaText from '../../../components/InstanceName.vue'
|
import InstanceName from '../../../components/InstanceName.vue'
|
||||||
import { createI18n } from 'vue-i18n'
|
import { createI18n } from 'vue-i18n'
|
||||||
import fr from '../../../locales/fr.json'
|
import fr from '../../../locales/fr.json'
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ const i18n = createI18n({
|
|||||||
|
|
||||||
describe('Template', () => {
|
describe('Template', () => {
|
||||||
it('should render the component with good wording keys', async () => {
|
it('should render the component with good wording keys', async () => {
|
||||||
const wrapper = shallowMount(BetaText, {
|
const wrapper = shallowMount(InstanceName, {
|
||||||
global: {
|
global: {
|
||||||
plugins: [i18n],
|
plugins: [i18n],
|
||||||
mocks: {
|
mocks: {
|
||||||
@@ -17,7 +17,7 @@ const i18n = createI18n({
|
|||||||
})
|
})
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
routes: []
|
routes: [{ path: '/', component: { template: '<div></div>' } }]
|
||||||
})
|
})
|
||||||
const stubs = {
|
const stubs = {
|
||||||
'router-link': {
|
'router-link': {
|
||||||
|
|||||||
51
src/tests/unit/components/Modal.spec.js
Normal file
51
src/tests/unit/components/Modal.spec.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { it, describe, expect } from 'vitest'
|
||||||
|
import { shallowMount } from '@vue/test-utils'
|
||||||
|
import Modal from '../../../components/Modal.vue'
|
||||||
|
import { createI18n } from 'vue-i18n'
|
||||||
|
import fr from '../../../locales/fr.json'
|
||||||
|
|
||||||
|
const i18n = createI18n({
|
||||||
|
locale: 'fr',
|
||||||
|
fallbackLocale: 'fr',
|
||||||
|
globalInjection: true,
|
||||||
|
legacy: false,
|
||||||
|
messages: {
|
||||||
|
fr
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Template', () => {
|
||||||
|
describe('Props', () => {
|
||||||
|
it('should render the default props', async () => {
|
||||||
|
document.body.innerHTML = '<div id="bs-modal"></div>'
|
||||||
|
const wrapper = shallowMount(Modal, {
|
||||||
|
global: {
|
||||||
|
plugins: [i18n],
|
||||||
|
mocks: {
|
||||||
|
$t: (msg) => msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(wrapper.vm.uploadErrors).toEqual([])
|
||||||
|
})
|
||||||
|
it('should render the props filled', async () => {
|
||||||
|
document.body.innerHTML = '<div id="bs-modal"></div>'
|
||||||
|
const uploadErrors = [{ message: 'my message', name: 'my name' }]
|
||||||
|
const wrapper = shallowMount(Modal, {
|
||||||
|
global: {
|
||||||
|
plugins: [i18n],
|
||||||
|
mocks: {
|
||||||
|
$t: (msg) => msg
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
uploadErrors
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(wrapper.vm.uploadErrors).toEqual(uploadErrors)
|
||||||
|
expect(wrapper.html()).contains('my name - ')
|
||||||
|
expect(wrapper.html()).contains('my message')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -7,7 +7,7 @@ vi.mock('vue-router')
|
|||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
routes: []
|
routes: [{ path: '/', component: { template: '<div></div>' } }]
|
||||||
})
|
})
|
||||||
describe('Template', () => {
|
describe('Template', () => {
|
||||||
describe('Props', () => {
|
describe('Props', () => {
|
||||||
|
|||||||
36
src/tests/unit/components/share-pictures/Card.spec.js
Normal file
36
src/tests/unit/components/share-pictures/Card.spec.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { test, describe, expect } from 'vitest'
|
||||||
|
import { shallowMount } from '@vue/test-utils'
|
||||||
|
import Card from '../../../../components/share-pictures/Card.vue'
|
||||||
|
import i18n from '../../config'
|
||||||
|
describe('Template', () => {
|
||||||
|
describe('Props', () => {
|
||||||
|
test('should have default props', () => {
|
||||||
|
const wrapper = shallowMount(Card, {
|
||||||
|
global: {
|
||||||
|
plugins: [i18n]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(wrapper.vm.title).toBe(null)
|
||||||
|
expect(wrapper.vm.description).toBe(null)
|
||||||
|
expect(wrapper.vm.imgSrc).toBe(null)
|
||||||
|
expect(wrapper.vm.imgAlt).toBe(null)
|
||||||
|
})
|
||||||
|
test('should have all the props filled', () => {
|
||||||
|
const wrapper = shallowMount(Card, {
|
||||||
|
global: {
|
||||||
|
plugins: [i18n]
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
title: 'my title',
|
||||||
|
description: 'my description',
|
||||||
|
imgSrc: 'my-imgSrc.jpg',
|
||||||
|
imgAlt: 'my imgAlt'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(wrapper.html()).contains('my title</h2>')
|
||||||
|
expect(wrapper.html()).contains('my description</p>')
|
||||||
|
expect(wrapper.html()).contains('/my-imgSrc.jpg')
|
||||||
|
expect(wrapper.html()).contains('alt="my imgAlt"')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { it, describe, expect } from 'vitest'
|
import { it, describe, expect, vi, beforeEach } from 'vitest'
|
||||||
import {
|
import {
|
||||||
imageStatus,
|
imageStatus,
|
||||||
photoToDeleteOrPatchSelected,
|
photoToDeleteOrPatchSelected,
|
||||||
@@ -6,10 +6,26 @@ import {
|
|||||||
formatPaginationItems
|
formatPaginationItems
|
||||||
} from '../../views/utils/sequence/index'
|
} from '../../views/utils/sequence/index'
|
||||||
import { sortByName } from '../../views/utils/upload/index'
|
import { sortByName } from '../../views/utils/upload/index'
|
||||||
import { getAuthRoute } from '../../utils/auth'
|
import {
|
||||||
|
getAuthRoute,
|
||||||
|
decodingFlaskCookie,
|
||||||
|
hasASessionCookieDecoded
|
||||||
|
} from '../../utils/auth'
|
||||||
import { img } from '../../utils/image'
|
import { img } from '../../utils/image'
|
||||||
import { title } from '../../utils/index'
|
import { title } from '../../utils/index'
|
||||||
|
import { useCookies } from 'vue3-cookies'
|
||||||
|
vi.mock('vue3-cookies', () => {
|
||||||
|
const mockCookies = {
|
||||||
|
get: vi.fn(),
|
||||||
|
remove: vi.fn(),
|
||||||
|
keys: vi.fn()
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
useCookies: () => ({
|
||||||
|
cookies: mockCookies
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
describe('imageStatus', () => {
|
describe('imageStatus', () => {
|
||||||
it('should render the "status" value', () => {
|
it('should render the "status" value', () => {
|
||||||
const sequenceStatus = 'hidden'
|
const sequenceStatus = 'hidden'
|
||||||
@@ -126,6 +142,42 @@ describe('getAuthRoute', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('decodingFlaskCookie', () => {
|
||||||
|
it('should return a decoded cookie', () => {
|
||||||
|
const cookie =
|
||||||
|
'.eJw9y0EKgzAQQNG7zLoDJpmYxMuUySRDra0pooUi3r3SRZcf_tuBRdo2rzDsMBYYgFxRytljkeyQrK0YVT16m6OhUlIihgvM_Kznfa88n9V4W2_XnzcuiqgmDBQMUtYec00WO3XqAndd7OUvXkt7j6Uup5vqRx6NJziOL8SPLNU.ZVy19Q.4DkVxu-LSF11uREkn6YIwHbn_0U'
|
||||||
|
expect(decodingFlaskCookie(cookie)).toEqual(
|
||||||
|
JSON.stringify({
|
||||||
|
account: {
|
||||||
|
id: '43df4bb5-dcb3-422e-8ff5-52b814dd994a',
|
||||||
|
name: 'jean',
|
||||||
|
oauth_id: '138ccff9-7471-4bf6-be92-0f3f37a0086c',
|
||||||
|
oauth_provider: 'keycloak'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('hasASessionCookieDecoded', () => {
|
||||||
|
it('should return a cookie parsed', () => {
|
||||||
|
vi.spyOn(useCookies().cookies, 'get').mockReturnValue(
|
||||||
|
'.eJw9y0EKgzAQQNG7zLoDJpmYxMuUySRDra0pooUi3r3SRZcf_tuBRdo2rzDsMBYYgFxRytljkeyQrK0YVT16m6OhUlIihgvM_Kznfa88n9V4W2_XnzcuiqgmDBQMUtYec00WO3XqAndd7OUvXkt7j6Uup5vqRx6NJziOL8SPLNU.ZVy19Q.4DkVxu-LSF11uREkn6YIwHbn_0U'
|
||||||
|
)
|
||||||
|
expect(hasASessionCookieDecoded()).toEqual({
|
||||||
|
account: {
|
||||||
|
id: '43df4bb5-dcb3-422e-8ff5-52b814dd994a',
|
||||||
|
name: 'jean',
|
||||||
|
oauth_id: '138ccff9-7471-4bf6-be92-0f3f37a0086c',
|
||||||
|
oauth_provider: 'keycloak'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('should return null', () => {
|
||||||
|
vi.spyOn(useCookies().cookies, 'get').mockReturnValue(null)
|
||||||
|
expect(hasASessionCookieDecoded()).toBe(null)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('img', () => {
|
describe('img', () => {
|
||||||
it('should render the formated img path', () => {
|
it('should render the formated img path', () => {
|
||||||
const name = 'my-img'
|
const name = 'my-img'
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { createRouter, createWebHistory } from 'vue-router'
|
|||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
routes: []
|
routes: [{ path: '/', component: { template: '<div></div>' } }]
|
||||||
})
|
})
|
||||||
describe('Template', () => {
|
describe('Template', () => {
|
||||||
it('should render the view with the button link', async () => {
|
it('should render the view with the button link', async () => {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import * as createAPictureToASequence from '@/views/utils/upload/request'
|
|||||||
import * as createASequence from '@/views/utils/upload/request'
|
import * as createASequence from '@/views/utils/upload/request'
|
||||||
import { formatDate } from '../../../utils/dates'
|
import { formatDate } from '../../../utils/dates'
|
||||||
import * as sortByName from '../../../views/utils/upload/index'
|
import * as sortByName from '../../../views/utils/upload/index'
|
||||||
|
|
||||||
describe('Template', () => {
|
describe('Template', () => {
|
||||||
it('should render the view with the input upload and the good wordings', () => {
|
it('should render the view with the input upload and the good wordings', () => {
|
||||||
const wrapper = shallowMount(UploadPicturesView, {
|
const wrapper = shallowMount(UploadPicturesView, {
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
import pako from 'pako'
|
||||||
|
import { useCookies } from 'vue3-cookies'
|
||||||
|
const { cookies } = useCookies()
|
||||||
|
|
||||||
function getAuthRoute(authRoute: string, nextRoute: string): string {
|
function getAuthRoute(authRoute: string, nextRoute: string): string {
|
||||||
const next = `${location.protocol}//${location.host}${nextRoute}`
|
const next = `${location.protocol}//${location.host}${nextRoute}`
|
||||||
return `${
|
return `${
|
||||||
@@ -5,4 +9,25 @@ function getAuthRoute(authRoute: string, nextRoute: string): string {
|
|||||||
}api/${authRoute}?next_url=${encodeURIComponent(`${next}`)}`
|
}api/${authRoute}?next_url=${encodeURIComponent(`${next}`)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getAuthRoute }
|
// This function to decode the flask cookie and have the user information like username
|
||||||
|
function decodingFlaskCookie(cookie: string): string {
|
||||||
|
const cookieFormatted = cookie
|
||||||
|
.split('.')[1]
|
||||||
|
.replace(/_/g, '/')
|
||||||
|
.replace(/-/g, '+')
|
||||||
|
const binaryString = atob(cookieFormatted)
|
||||||
|
const bytes = new Uint8Array(binaryString.length)
|
||||||
|
for (let i = 0; i < binaryString.length; i++) {
|
||||||
|
bytes[i] = binaryString.charCodeAt(i)
|
||||||
|
}
|
||||||
|
return pako.inflate(bytes.buffer, { to: 'string' })
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasASessionCookieDecoded(): { account: { name: string } } | null {
|
||||||
|
if (cookies.get('session')) {
|
||||||
|
return JSON.parse(decodingFlaskCookie(cookies.get('session')))
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getAuthRoute, hasASessionCookieDecoded, decodingFlaskCookie }
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import Viewer from '@/components/Viewer.vue'
|
import Viewer from '@/components/Viewer.vue'
|
||||||
const emit = defineEmits<{ (e: 'trigger', value: string): void }>()
|
|
||||||
const viewerRef = ref<any>(null)
|
const viewerRef = ref<unknown>(null)
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.entry-page {
|
.entry-page {
|
||||||
@@ -29,11 +29,11 @@ const viewerRef = ref<any>(null)
|
|||||||
}
|
}
|
||||||
@media (max-width: toRem(76.8)) {
|
@media (max-width: toRem(76.8)) {
|
||||||
.entry-page {
|
.entry-page {
|
||||||
padding-top: toRem(11);
|
padding-top: toRem(11.5);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.entry-section {
|
.entry-section {
|
||||||
height: calc(100vh - #{toRem(11)});
|
height: calc(100dvh - #{toRem(18)});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -227,7 +227,6 @@ import type {
|
|||||||
ResponseUserSequenceInterface
|
ResponseUserSequenceInterface
|
||||||
} from './interfaces/MySequenceView'
|
} from './interfaces/MySequenceView'
|
||||||
const { cookies } = useCookies()
|
const { cookies } = useCookies()
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|||||||
@@ -1,21 +1,35 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="entry-page">
|
<main class="entry-page">
|
||||||
<section class="section-viewer">
|
<section :style="{ width: `${mapWidth}px` }" class="section-viewer">
|
||||||
<Viewer
|
<vue-draggable-resizable
|
||||||
:fetch-options="{
|
:style="{ width: `${mapWidth}px` }"
|
||||||
fetchOptions: {
|
:w="mapWidth"
|
||||||
credentials: 'include'
|
:h="windowHeight"
|
||||||
}
|
:max-width="windowWidth"
|
||||||
}"
|
:handles="['mr']"
|
||||||
:sequence-id="seqId"
|
:draggable="false"
|
||||||
:geovisio-viewer="false"
|
class-name-active="resize-active-map"
|
||||||
ref="viewerRef"
|
class-name-handle="resize-handle-map"
|
||||||
/>
|
@resizing="onResizeMap"
|
||||||
|
>
|
||||||
|
<Viewer
|
||||||
|
v-if="collectionBbox.length"
|
||||||
|
:fetch-options="{
|
||||||
|
fetchOptions: {
|
||||||
|
credentials: 'include'
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
:geovisio-viewer="false"
|
||||||
|
:user-id="getUserId"
|
||||||
|
:bbox="collectionBbox"
|
||||||
|
ref="viewerRef"
|
||||||
|
/>
|
||||||
|
</vue-draggable-resizable>
|
||||||
</section>
|
</section>
|
||||||
<section class="section-sequence">
|
<section :style="{ width: `${listWidth}px` }" class="section-sequence">
|
||||||
<h1 class="sequences-title">{{ $t('pages.sequences.title') }}</h1>
|
<h1 class="sequences-title">{{ $t('pages.sequences.title') }}</h1>
|
||||||
<ul v-if="!isLoading" class="sequence-list">
|
<ul v-if="!isLoading" class="sequence-list">
|
||||||
<li class="sequence-item">
|
<li class="sequence-item sequence-item-head">
|
||||||
<div class="sequence-header-item"></div>
|
<div class="sequence-header-item"></div>
|
||||||
<div class="sequence-header-item">
|
<div class="sequence-header-item">
|
||||||
<Button
|
<Button
|
||||||
@@ -67,32 +81,33 @@
|
|||||||
v-if="userSequences.length"
|
v-if="userSequences.length"
|
||||||
v-for="(item, i) in userSequences"
|
v-for="(item, i) in userSequences"
|
||||||
:id="`el-list${i}`"
|
:id="`el-list${i}`"
|
||||||
class="sequence-item"
|
:class="[
|
||||||
|
'sequence-item',
|
||||||
|
item.id === seqId ? 'button-item-hover' : ''
|
||||||
|
]"
|
||||||
@mouseover="goToSequence(item)"
|
@mouseover="goToSequence(item)"
|
||||||
>
|
>
|
||||||
<router-link
|
<router-link
|
||||||
:class="[
|
class="button-item"
|
||||||
'button-item',
|
|
||||||
item.id === seqId ? 'button-item-hover' : ''
|
|
||||||
]"
|
|
||||||
:to="{
|
:to="{
|
||||||
name: 'sequence',
|
name: 'sequence',
|
||||||
params: { id: item.id }
|
params: { id: item.id }
|
||||||
}"
|
}"
|
||||||
|
@mouseover.stop
|
||||||
>
|
>
|
||||||
<div class="wrapper-thumb">
|
<div class="wrapper-thumb">
|
||||||
<img
|
<img
|
||||||
v-if="item['stats:items'].count > 0"
|
v-if="item['stats:items'].count > 0"
|
||||||
:src="`${item.href}/thumb.jpg`"
|
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
|
:src="`${item.href}/thumb.jpg`"
|
||||||
alt=""
|
alt=""
|
||||||
class="thumb"
|
class="thumb"
|
||||||
/>
|
/>
|
||||||
<div class="wrapper-thumb-hover">
|
<div class="wrapper-thumb-hover">
|
||||||
<img
|
<img
|
||||||
v-if="item['stats:items'].count > 0"
|
v-if="item['stats:items'].count > 0"
|
||||||
:src="`${item.href}/thumb.jpg`"
|
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
|
:src="`${item.href}/thumb.jpg`"
|
||||||
alt=""
|
alt=""
|
||||||
class="thumb-hover"
|
class="thumb-hover"
|
||||||
/>
|
/>
|
||||||
@@ -130,6 +145,25 @@
|
|||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
<div class="wrapper-button">
|
||||||
|
<Button
|
||||||
|
:tooltip="$t('pages.sequence.hide_sequence_tooltip')"
|
||||||
|
look="button--white background-white"
|
||||||
|
:icon="
|
||||||
|
item['geovisio:status'] === 'ready'
|
||||||
|
? 'bi bi-eye-slash'
|
||||||
|
: 'bi bi-eye'
|
||||||
|
"
|
||||||
|
class="disable-button"
|
||||||
|
@trigger="patchCollection(item)"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
:tooltip="$t('pages.sequence.delete_sequence_tooltip')"
|
||||||
|
look="button--red background-white"
|
||||||
|
icon="bi bi-trash"
|
||||||
|
@trigger="deleteCollection(item)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<div v-else class="no-sequence">
|
<div v-else class="no-sequence">
|
||||||
<p class="no-sequence-text">
|
<p class="no-sequence-text">
|
||||||
@@ -145,8 +179,8 @@
|
|||||||
<div v-else class="loader">
|
<div v-else class="loader">
|
||||||
<Loader look="sm" :is-loaded="false" />
|
<Loader look="sm" :is-loaded="false" />
|
||||||
</div>
|
</div>
|
||||||
|
<Toast :text="toastText" :look="toastLook" @trigger="toastText = ''" />
|
||||||
</section>
|
</section>
|
||||||
<Toast :text="toastText" :look="toastLook" @trigger="toastText = ''" />
|
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -156,6 +190,7 @@ import { useI18n } from 'vue-i18n'
|
|||||||
import { useSequenceStore } from '@/store/sequence'
|
import { useSequenceStore } from '@/store/sequence'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { scrollIntoSelected } from '@/views/utils/sequence/index'
|
import { scrollIntoSelected } from '@/views/utils/sequence/index'
|
||||||
|
import { useCookies } from 'vue3-cookies'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import Viewer from '@/components/Viewer.vue'
|
import Viewer from '@/components/Viewer.vue'
|
||||||
import Button from '@/components/Button.vue'
|
import Button from '@/components/Button.vue'
|
||||||
@@ -167,17 +202,53 @@ import type {
|
|||||||
ExtentSequenceLinkInterface
|
ExtentSequenceLinkInterface
|
||||||
} from './interfaces/MySequencesView'
|
} from './interfaces/MySequencesView'
|
||||||
import { formatDate } from '@/utils/dates'
|
import { formatDate } from '@/utils/dates'
|
||||||
|
import {
|
||||||
|
deleteACollection,
|
||||||
|
patchACollection
|
||||||
|
} from '@/views/utils/sequence/request'
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
const { cookies } = useCookies()
|
||||||
const sequenceStore = useSequenceStore()
|
const sequenceStore = useSequenceStore()
|
||||||
const { toastText, toastLook } = storeToRefs(sequenceStore)
|
const { toastText, toastLook } = storeToRefs(sequenceStore)
|
||||||
|
|
||||||
let userSequences = ref<SequenceLinkInterface[]>([])
|
let userSequences = ref<SequenceLinkInterface[]>([])
|
||||||
let collectionBbox = ref<number[]>([])
|
let collectionBbox = ref<number[]>([])
|
||||||
let isSorted = ref<boolean>(false)
|
let isSorted = ref<boolean>(false)
|
||||||
let isLoading = ref<boolean>(false)
|
let isLoading = ref<boolean>(false)
|
||||||
let seqId = ref<string>('')
|
let seqId = ref<string>('')
|
||||||
|
let width = ref<number>(0)
|
||||||
|
let mapWidth = ref<number>(window.innerWidth / 3)
|
||||||
|
let listWidth = ref<number>(window.innerWidth / 1.5)
|
||||||
|
const windowWidth = ref<number>(window.innerWidth)
|
||||||
|
const windowHeight = ref<number>(window.innerHeight - 80)
|
||||||
const viewerRef = ref<any>(null)
|
const viewerRef = ref<any>(null)
|
||||||
|
|
||||||
|
async function fetchAndFormatSequence(): Promise<void> {
|
||||||
|
const { data } = await axios.get('api/users/me/collection')
|
||||||
|
collectionBbox.value = data.extent.spatial.bbox[0]
|
||||||
|
userSequences.value = getRelChild(data.links)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function patchCollection(sequence: SequenceLinkInterface): Promise<void> {
|
||||||
|
isLoading.value = true
|
||||||
|
let visible
|
||||||
|
if (sequence['geovisio:status'] === 'ready') visible = 'false'
|
||||||
|
else visible = 'true'
|
||||||
|
await patchACollection(sequence.id, visible)
|
||||||
|
await fetchAndFormatSequence()
|
||||||
|
viewerRef.value.viewer.reloadVectorTiles()
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
async function deleteCollection(
|
||||||
|
sequence: SequenceLinkInterface
|
||||||
|
): Promise<void> {
|
||||||
|
isLoading.value = true
|
||||||
|
if (confirm(t('pages.sequence.confirm_sequence_dialog'))) {
|
||||||
|
await deleteACollection(sequence.id)
|
||||||
|
await fetchAndFormatSequence()
|
||||||
|
viewerRef.value.viewer.reloadVectorTiles()
|
||||||
|
}
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
function sequenceStatus(status: string): string {
|
function sequenceStatus(status: string): string {
|
||||||
if (status === 'ready') return t('pages.sequences.sequence_published')
|
if (status === 'ready') return t('pages.sequences.sequence_published')
|
||||||
if (status === 'hidden') return t('pages.sequences.sequence_hidden')
|
if (status === 'hidden') return t('pages.sequences.sequence_hidden')
|
||||||
@@ -197,6 +268,13 @@ function sortAlpha<TKey extends keyof SequenceLinkInterface>(key: TKey): void {
|
|||||||
isSorted.value = !isSorted.value
|
isSorted.value = !isSorted.value
|
||||||
userSequences.value = sorted
|
userSequences.value = sorted
|
||||||
}
|
}
|
||||||
|
function onResizeMap(width: any) {
|
||||||
|
if (width) {
|
||||||
|
width.value = width
|
||||||
|
mapWidth.value = width.value.width
|
||||||
|
listWidth.value = window.innerWidth - width.value.width
|
||||||
|
}
|
||||||
|
}
|
||||||
function sortNum(type: string, dateToSort?: string): void {
|
function sortNum(type: string, dateToSort?: string): void {
|
||||||
let aa, bb: number
|
let aa, bb: number
|
||||||
const sorted = userSequences.value.sort(
|
const sorted = userSequences.value.sort(
|
||||||
@@ -226,23 +304,13 @@ function goToSequence(sequence: SequenceLinkInterface) {
|
|||||||
function getRelChild(sequences: SequenceLinkInterface[]) {
|
function getRelChild(sequences: SequenceLinkInterface[]) {
|
||||||
return sequences.filter((el: SequenceLinkInterface) => el.rel === 'child')
|
return sequences.filter((el: SequenceLinkInterface) => el.rel === 'child')
|
||||||
}
|
}
|
||||||
|
const getUserId = computed<string>(() => cookies.get('user_id'))
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.get('api/users/me/catalog')
|
const { data } = await axios.get('api/users/me/collection')
|
||||||
const collectionData = await axios.get('api/users/me/collection')
|
collectionBbox.value = data.extent.spatial.bbox[0]
|
||||||
collectionBbox.value = collectionData.data.extent.spatial.bbox[0]
|
userSequences.value = getRelChild(data.links)
|
||||||
viewerRef.value.viewer.fitBounds(collectionBbox.value)
|
|
||||||
const sequences = getRelChild(data.links)
|
|
||||||
const sequencesCollection = getRelChild(collectionData.data.links)
|
|
||||||
sequencesCollection.map((el: SequenceLinkInterface) => {
|
|
||||||
let index = sequences.findIndex(
|
|
||||||
(elem: SequenceLinkInterface) => elem.id === el.id
|
|
||||||
)
|
|
||||||
sequences[index] = el
|
|
||||||
})
|
|
||||||
userSequences.value = sequencesCollection
|
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
@@ -256,26 +324,47 @@ watchEffect(() => {
|
|||||||
(e: { detail: { seqId: string } }) => {
|
(e: { detail: { seqId: string } }) => {
|
||||||
seqId.value = e.detail.seqId
|
seqId.value = e.detail.seqId
|
||||||
scrollIntoSelected(e.detail.seqId, userSequences.value)
|
scrollIntoSelected(e.detail.seqId, userSequences.value)
|
||||||
|
viewerRef.value.viewer.select(e.detail.seqId)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
.resize-handle-map-mr {
|
||||||
|
top: 0;
|
||||||
|
right: toRem(0);
|
||||||
|
cursor: e-resize;
|
||||||
|
background-color: var(--black);
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
.resize-handle-map {
|
||||||
|
z-index: 999999;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: toRem(0.5);
|
||||||
|
&:hover {
|
||||||
|
cursor: col-resize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.entry-page {
|
.entry-page {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: calc(100vh - #{toRem(8)});
|
height: calc(100vh - #{toRem(8)});
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
.section-viewer {
|
.section-viewer {
|
||||||
width: 40%;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
.section-sequence {
|
.section-sequence {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
width: 60%;
|
overflow-x: hidden;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
.sequences-title {
|
.sequences-title {
|
||||||
@include text(h1);
|
@include text(h1);
|
||||||
@@ -290,11 +379,14 @@ watchEffect(() => {
|
|||||||
}
|
}
|
||||||
.sequence-item {
|
.sequence-item {
|
||||||
@include text(s-regular);
|
@include text(s-regular);
|
||||||
|
position: relative;
|
||||||
border: none;
|
border: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
background-color: var(--blue-pale);
|
background-color: var(--blue-pale);
|
||||||
|
padding: toRem(2);
|
||||||
&:first-child {
|
&:first-child {
|
||||||
margin-bottom: toRem(1);
|
margin-bottom: toRem(1);
|
||||||
padding: toRem(1) toRem(2);
|
padding: toRem(1) toRem(2);
|
||||||
@@ -306,6 +398,32 @@ watchEffect(() => {
|
|||||||
background-color: var(--white);
|
background-color: var(--white);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.wrapper-button {
|
||||||
|
position: absolute;
|
||||||
|
right: toRem(2);
|
||||||
|
bottom: toRem(2);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.button--white:first-child {
|
||||||
|
margin-right: toRem(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.button-item-hover {
|
||||||
|
background-color: var(--blue);
|
||||||
|
&:nth-child(2n) {
|
||||||
|
background-color: var(--blue);
|
||||||
|
}
|
||||||
|
.button-item {
|
||||||
|
& > *,
|
||||||
|
& > :nth-child(2) {
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sequence-item-head:hover {
|
||||||
|
background-color: initial;
|
||||||
|
color: initial;
|
||||||
|
}
|
||||||
.wrapper-title {
|
.wrapper-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -350,7 +468,6 @@ watchEffect(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: toRem(2);
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@@ -374,19 +491,6 @@ watchEffect(() => {
|
|||||||
& > :nth-child(2) {
|
& > :nth-child(2) {
|
||||||
color: var(--blue-dark);
|
color: var(--blue-dark);
|
||||||
}
|
}
|
||||||
&:hover {
|
|
||||||
background-color: var(--blue);
|
|
||||||
& > * {
|
|
||||||
color: var(--white);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.button-item-hover {
|
|
||||||
background-color: var(--blue);
|
|
||||||
& > *,
|
|
||||||
& > :nth-child(2) {
|
|
||||||
color: var(--white);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.bi-images {
|
.bi-images {
|
||||||
margin-right: toRem(0.5);
|
margin-right: toRem(0.5);
|
||||||
@@ -423,7 +527,6 @@ watchEffect(() => {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-top: toRem(20);
|
|
||||||
}
|
}
|
||||||
.ay11-link {
|
.ay11-link {
|
||||||
padding: toRem(2);
|
padding: toRem(2);
|
||||||
@@ -449,18 +552,16 @@ watchEffect(() => {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.section-sequence {
|
.section-sequence {
|
||||||
width: 100%;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
.button-item,
|
|
||||||
.sequence-item:first-child {
|
.sequence-item:first-child {
|
||||||
padding-right: toRem(1);
|
display: none;
|
||||||
padding-left: toRem(1);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
@media (max-width: toRem(50)) {
|
|
||||||
.button-item {
|
.button-item {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
padding-right: toRem(1);
|
||||||
|
padding-left: toRem(1);
|
||||||
& > * {
|
& > * {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -469,12 +570,17 @@ watchEffect(() => {
|
|||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sequence-item:first-child {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.sequence-item {
|
.sequence-item {
|
||||||
border-top-right-radius: toRem(1);
|
border-top-right-radius: toRem(1);
|
||||||
border-top-left-radius: toRem(1);
|
border-top-left-radius: toRem(1);
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.wrapper-button {
|
||||||
|
position: relative;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: toRem(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -35,6 +35,14 @@
|
|||||||
:text="authConf.license.id"
|
:text="authConf.license.id"
|
||||||
class="entry-license"
|
class="entry-license"
|
||||||
/>
|
/>
|
||||||
|
<h2 class="subtitle">{{ $t('pages.upload.title_sequence') }}</h2>
|
||||||
|
<p class="import-text">
|
||||||
|
{{ $t('pages.upload.description_title_sequence') }}
|
||||||
|
</p>
|
||||||
|
<EditText
|
||||||
|
:default-text="newSequenceTitle || sequenceTitle"
|
||||||
|
@triggerNewText="setNewSequenceTitle"
|
||||||
|
/>
|
||||||
<form>
|
<form>
|
||||||
<div class="wrapper-form">
|
<div class="wrapper-form">
|
||||||
<InputUpload
|
<InputUpload
|
||||||
@@ -85,11 +93,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, watch, onUnmounted } from 'vue'
|
import { ref, computed, watch, onUnmounted, onMounted } from 'vue'
|
||||||
import { onBeforeRouteLeave } from 'vue-router'
|
import { onBeforeRouteLeave } from 'vue-router'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import InputUpload from '@/components/InputUpload.vue'
|
import InputUpload from '@/components/InputUpload.vue'
|
||||||
import Button from '@/components/Button.vue'
|
import Button from '@/components/Button.vue'
|
||||||
|
import Link from '@/components/Link.vue'
|
||||||
|
import EditText from '@/components/EditText.vue'
|
||||||
import Modal from '@/components/Modal.vue'
|
import Modal from '@/components/Modal.vue'
|
||||||
import InformationCard from '@/components/InformationCard.vue'
|
import InformationCard from '@/components/InformationCard.vue'
|
||||||
import UploadLoader from '@/components/upload/UploadLoader.vue'
|
import UploadLoader from '@/components/upload/UploadLoader.vue'
|
||||||
@@ -102,7 +112,6 @@ import {
|
|||||||
} from '@/views/utils/upload/request'
|
} from '@/views/utils/upload/request'
|
||||||
import { sortByName } from '@/views/utils/upload/index'
|
import { sortByName } from '@/views/utils/upload/index'
|
||||||
import authConfig from '../composables/auth'
|
import authConfig from '../composables/auth'
|
||||||
import Link from '@/components/Link.vue'
|
|
||||||
|
|
||||||
const { authConf } = authConfig()
|
const { authConf } = authConfig()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@@ -115,6 +124,8 @@ let uploadedSequence = ref<SequenceInterface | null>(null)
|
|||||||
let picturesUploadingSize = ref<number>(0)
|
let picturesUploadingSize = ref<number>(0)
|
||||||
let picturesToUploadSize = ref<number>(0)
|
let picturesToUploadSize = ref<number>(0)
|
||||||
let loadPercentage = ref<string>('0%')
|
let loadPercentage = ref<string>('0%')
|
||||||
|
let sequenceTitle = ref<string>(formatSequenceTitle())
|
||||||
|
let newSequenceTitle = ref<string | null>(null)
|
||||||
let modal = ref()
|
let modal = ref()
|
||||||
|
|
||||||
watch(isLoading, () => {
|
watch(isLoading, () => {
|
||||||
@@ -128,6 +139,11 @@ watch(isLoading, () => {
|
|||||||
window.onbeforeunload = null
|
window.onbeforeunload = null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
onMounted(() => {
|
||||||
|
setInterval(() => {
|
||||||
|
sequenceTitle.value = formatSequenceTitle()
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.onbeforeunload = null
|
window.onbeforeunload = null
|
||||||
})
|
})
|
||||||
@@ -145,7 +161,15 @@ const inputIsDisplayed = computed<boolean | null>(
|
|||||||
isLoaded.value ||
|
isLoaded.value ||
|
||||||
(uploadedSequence.value && !uploadedSequence.value.pictures)
|
(uploadedSequence.value && !uploadedSequence.value.pictures)
|
||||||
)
|
)
|
||||||
|
function setNewSequenceTitle(value: string | null): void {
|
||||||
|
newSequenceTitle.value = value
|
||||||
|
}
|
||||||
|
function formatSequenceTitle(): string {
|
||||||
|
return `${t('pages.upload.sequence_title')}${formatDate(
|
||||||
|
new Date(),
|
||||||
|
'Do MMMM YYYY, hh:mm:ss'
|
||||||
|
)}`
|
||||||
|
}
|
||||||
function picturesToUploadSizeText(): void {
|
function picturesToUploadSizeText(): void {
|
||||||
let fullSize = 0
|
let fullSize = 0
|
||||||
for (let i = 0; i < pictures.value.length; i++) {
|
for (let i = 0; i < pictures.value.length; i++) {
|
||||||
@@ -183,14 +207,12 @@ async function uploadPicture(): Promise<void> {
|
|||||||
picturesToUploadSizeText()
|
picturesToUploadSizeText()
|
||||||
uploadedSequence.value = null
|
uploadedSequence.value = null
|
||||||
const picturesToUpload = [...pictures.value]
|
const picturesToUpload = [...pictures.value]
|
||||||
|
const title = newSequenceTitle.value
|
||||||
const sequenceTitle = `${t('pages.upload.sequence_title')}${formatDate(
|
? newSequenceTitle.value
|
||||||
new Date(),
|
: sequenceTitle.value
|
||||||
'Do MMMM YYYY, hh:mm:ss'
|
const { data } = await createASequence(title)
|
||||||
)}`
|
|
||||||
const { data } = await createASequence(sequenceTitle)
|
|
||||||
uploadedSequence.value = {
|
uploadedSequence.value = {
|
||||||
title: sequenceTitle,
|
title: title,
|
||||||
id: data.id,
|
id: data.id,
|
||||||
pictures: [],
|
pictures: [],
|
||||||
picturesOnError: [],
|
picturesOnError: [],
|
||||||
@@ -228,6 +250,8 @@ async function uploadPicture(): Promise<void> {
|
|||||||
isLoaded.value = true
|
isLoaded.value = true
|
||||||
pictures.value = []
|
pictures.value = []
|
||||||
picturesCount.value = 0
|
picturesCount.value = 0
|
||||||
|
newSequenceTitle.value = null
|
||||||
|
sequenceTitle.value = formatSequenceTitle()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -247,6 +271,9 @@ h3 {
|
|||||||
padding: toRem(2) toRem(5) toRem(5);
|
padding: toRem(2) toRem(5) toRem(5);
|
||||||
color: var(--grey-dark);
|
color: var(--grey-dark);
|
||||||
}
|
}
|
||||||
|
.logged {
|
||||||
|
min-height: calc(100vh - #{toRem(8)});
|
||||||
|
}
|
||||||
.section {
|
.section {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
export interface OptionalViewerInterface {
|
export interface MapInterface {
|
||||||
|
startWide?: boolean
|
||||||
|
maxZoom?: number
|
||||||
|
minZoom?: number
|
||||||
|
style?: object | string
|
||||||
|
zoom?: number
|
||||||
|
center?: number[]
|
||||||
|
bounds?: number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ViewerInterface {
|
||||||
fetchOptions?: {
|
fetchOptions?: {
|
||||||
credentials: string
|
credentials: string
|
||||||
}
|
}
|
||||||
|
hash?: boolean
|
||||||
picId?: string
|
picId?: string
|
||||||
widgets?: {
|
widgets?: {
|
||||||
customWidget: HTMLAnchorElement
|
customWidget: HTMLAnchorElement
|
||||||
}
|
}
|
||||||
}
|
map: MapInterface
|
||||||
|
|
||||||
export interface ViewerInterface extends OptionalViewerInterface {
|
|
||||||
map: {
|
|
||||||
startWide: boolean
|
|
||||||
style?: object | string
|
|
||||||
maxZoom?: number
|
|
||||||
zoom?: number
|
|
||||||
center?: number[]
|
|
||||||
}
|
|
||||||
bounds?: number[]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,25 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
function createASequence(title: string): Promise<any> {
|
interface SequenceCreatedInterface {
|
||||||
|
data: {
|
||||||
|
created: string
|
||||||
|
description: string
|
||||||
|
extent: { spatial: object; temporal: object }
|
||||||
|
['geovisio:status']: string
|
||||||
|
id: string
|
||||||
|
keywords: string[]
|
||||||
|
license: string
|
||||||
|
links: object[]
|
||||||
|
providers: object[]
|
||||||
|
stac_extensions: string[]
|
||||||
|
stac_version: string
|
||||||
|
['stats:items']: { count: number }
|
||||||
|
title: string
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createASequence(title: string): Promise<SequenceCreatedInterface> {
|
||||||
return axios.post('api/collections', { title: title })
|
return axios.post('api/collections', { title: title })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1
vue-draggable-resizable-vue3.d.ts
vendored
Normal file
1
vue-draggable-resizable-vue3.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
declare module 'vue-draggable-resizable-vue3'
|
||||||
Reference in New Issue
Block a user