1 Commits

Author SHA1 Message Date
Christian Quest
4966b3a761 English translation (except accessibility part) 2023-10-20 08:32:11 +00:00
21 changed files with 77 additions and 388 deletions

View File

@@ -7,24 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Before _0.1.0_, website development was on rolling release, meaning there are no version tags.
## [2.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
### Changed
@@ -125,8 +107,7 @@ Before _0.1.0_, website development was on rolling release, meaning there are no
- Header have now a new entry `Mes photos` when the user is logged to access to the sequence list
- The router guard for logged pages has been changed to not call the api to check the token
[unreleased]: https://gitlab.com/geovisio/website/-/compare/2.2.3...develop
[2.2.1]: https://gitlab.com/geovisio/website/-/compare/2.2.2...2.2.3
[unreleased]: https://gitlab.com/geovisio/website/-/compare/2.2.2...develop
[2.2.1]: https://gitlab.com/geovisio/website/-/compare/2.2.1...2.2.2
[2.2.1]: https://gitlab.com/geovisio/website/-/compare/2.2.0...2.2.1
[2.2.0]: https://gitlab.com/geovisio/website/-/compare/2.1.3...2.2.0

View File

@@ -1,6 +1,6 @@
{
"name": "geovisio-website",
"version": "2.2.3",
"version": "2.2.2",
"engines": {
"node": "18.16.0"
},
@@ -25,7 +25,7 @@
"axios": "^1.2.3",
"bootstrap": "^5.2.3",
"bootstrap-icons": "^1.10.3",
"geovisio": "2.2.1",
"geovisio": "2.2.0",
"moment": "^2.29.4",
"pinia": "^2.1.4",
"vue": "^3.2.45",

View File

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

View File

@@ -36,9 +36,9 @@ h5 {
--grey-dark: #3e3e3e;
--blue: #2954e9;
--blue-dark: #0a1f69;
--blue-geovisio: #34495e;
--blue-semi: #d7dffc;
--blue-pale: #f2f5ff;
--blue-geovisio: #34495e;
--beige: #f5f3ec;
--yellow: #fec868;
--orange: #ff6f00;

View File

@@ -89,31 +89,12 @@ ul {
margin-right: 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)) {
.title {
margin-bottom: 0;
}
.footer {
padding: toRem(2);
}
}
</style>

View File

@@ -80,6 +80,16 @@ function triggerButton() {
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 {
height: toRem(4);
border-radius: toRem(1);
@@ -95,16 +105,6 @@ function triggerButton() {
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 {
width: 100%;
white-space: break-spaces;
@@ -200,6 +200,10 @@ function triggerButton() {
}
}
@media (max-width: toRem(50)) {
.default {
min-height: toRem(4);
min-width: toRem(4);
}
.icon {
margin-right: toRem(0.5);
}

View File

@@ -4,7 +4,7 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue'
import type { ViewerInterface, MapInterface } from '@/views/interfaces/common'
import type { ViewerInterface } from '@/views/interfaces/common'
import { getIgnTiles } from '@/utils/mapAndViewer'
import { Viewer, StandaloneMap } from 'geovisio'
import { createUrlLink } from '@/utils'
@@ -17,8 +17,7 @@ let viewer = ref()
const props = defineProps({
fetchOptions: { type: Object, default: {} },
geovisioViewer: { type: Boolean, default: true },
bbox: { type: Array, default: null },
userId: { type: String, default: '' }
bbox: { type: Array, default: null }
})
defineExpose({
viewer
@@ -29,7 +28,6 @@ onMounted(async () => {
const zoom = import.meta.env.VITE_ZOOM
const center = import.meta.env.VITE_CENTER
let paramsViewer: ViewerInterface
let paramsMap: MapInterface
try {
if (props.geovisioViewer) {
@@ -100,7 +98,7 @@ onMounted(async () => {
)
}
} else {
paramsMap = { minZoom: 7 }
let paramsMap = {}
if (tiles) {
const style = tiles.includes('wxs.ign.fr') ? await getIgnTiles() : tiles
paramsMap = {
@@ -112,14 +110,9 @@ onMounted(async () => {
'viewer', // Div ID
`${import.meta.env.VITE_API_URL}/api/search`,
{
...paramsMap,
bounds: [props.bbox[0], props.bbox[1], props.bbox[2], props.bbox[3]],
zoom: 9
...paramsMap
}
)
viewer.value.addEventListener('ready', () => {
viewer.value.setFilters({ user: props.userId }, true)
})
}
mapIsLoaded.value = true
} catch (err) {

View File

@@ -8,7 +8,7 @@
"header": {
"login_text": "Connect",
"register_text": "Register",
"contribute_text": "Why contribute ?",
"contribute_text": "PouWhyrquoi contribute ?",
"my_account": "My account",
"upload_text": "+ Share pictures",
"sequences_text": "My pictures",
@@ -26,7 +26,7 @@
"burger_menu_aria_label_closed": "Hide menu"
},
"footer": {
"panoramax_site": "Discover Panoramax",
"panoramax_site": "Dicover Panoramax",
"information_gitlab": "Show source code",
"gitlab_logo": "Gitlab logo",
"ay11_text": "Accessibility: not compliant"
@@ -88,7 +88,7 @@
"sequence_deleted": "The sequence has been deleted"
},
"share_pictures": {
"title": "Why contribute to Panoramax?",
"title": "Why contribute to the Panoramax project ?",
"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",

View File

@@ -8,7 +8,6 @@ import { globalCookiesConfig } from 'vue3-cookies'
import { createMetaManager } from 'vue-meta'
import { pinia } from './store'
import fr from './locales/fr.json'
import en from './locales/en.json'
import './assets/main.scss'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-icons/font/bootstrap-icons.css'
@@ -18,14 +17,13 @@ axios.defaults.baseURL = import.meta.env.VITE_API_URL
axios.defaults.withCredentials = true
const i18n = createI18n({
locale: navigator.language.split('-')[0],
locale: 'fr',
fallbackLocale: 'fr',
warnHtmlMessage: false,
globalInjection: true,
legacy: false,
messages: {
fr,
en
fr
}
})
globalCookiesConfig({

View File

@@ -1,6 +1,6 @@
import { it, describe, expect } from 'vitest'
import { shallowMount } from '@vue/test-utils'
import InstanceName from '../../../components/InstanceName.vue'
import BetaText from '../../../components/InstanceName.vue'
import { createI18n } from 'vue-i18n'
import fr from '../../../locales/fr.json'
@@ -16,7 +16,7 @@ const i18n = createI18n({
describe('Template', () => {
it('should render the component with good wording keys', async () => {
const wrapper = shallowMount(InstanceName, {
const wrapper = shallowMount(BetaText, {
global: {
plugins: [i18n],
mocks: {

View File

@@ -1,39 +0,0 @@
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')
})
})

View File

@@ -1,33 +0,0 @@
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"')
})
})
})

View File

@@ -1,80 +0,0 @@
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
})

View File

@@ -1,51 +0,0 @@
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')
})
})
})

View File

@@ -1,36 +0,0 @@
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"')
})
})
})

View File

@@ -10,7 +10,7 @@
import { ref } from 'vue'
import Viewer from '@/components/Viewer.vue'
const emit = defineEmits<{ (e: 'trigger', value: string): void }>()
const viewerRef = ref<unknown>(null)
const viewerRef = ref<any>(null)
</script>
<style scoped lang="scss">
.entry-page {
@@ -29,11 +29,11 @@ const viewerRef = ref<unknown>(null)
}
@media (max-width: toRem(76.8)) {
.entry-page {
padding-top: toRem(11.5);
padding-top: toRem(11);
overflow: hidden;
}
.entry-section {
height: calc(100dvh - #{toRem(18)});
height: calc(100vh - #{toRem(11)});
}
}
</style>

View File

@@ -2,22 +2,20 @@
<main class="entry-page">
<section class="section-viewer">
<Viewer
v-if="collectionBbox.length"
:fetch-options="{
fetchOptions: {
credentials: 'include'
}
}"
:sequence-id="seqId"
:geovisio-viewer="false"
:user-id="getUserId"
:bbox="collectionBbox"
ref="viewerRef"
/>
</section>
<section class="section-sequence">
<h1 class="sequences-title">{{ $t('pages.sequences.title') }}</h1>
<ul v-if="!isLoading" class="sequence-list">
<li class="sequence-item sequence-item-head">
<li class="sequence-item">
<div class="sequence-header-item"></div>
<div class="sequence-header-item">
<Button
@@ -69,19 +67,18 @@
v-if="userSequences.length"
v-for="(item, i) in userSequences"
:id="`el-list${i}`"
:class="[
'sequence-item',
item.id === seqId ? 'button-item-hover' : ''
]"
class="sequence-item"
@mouseover="goToSequence(item)"
>
<router-link
class="button-item"
:class="[
'button-item',
item.id === seqId ? 'button-item-hover' : ''
]"
:to="{
name: 'sequence',
params: { id: item.id }
}"
@mouseover.stop
>
<div class="wrapper-thumb">
<img
@@ -159,7 +156,6 @@ import { useI18n } from 'vue-i18n'
import { useSequenceStore } from '@/store/sequence'
import { storeToRefs } from 'pinia'
import { scrollIntoSelected } from '@/views/utils/sequence/index'
import { useCookies } from 'vue3-cookies'
import axios from 'axios'
import Viewer from '@/components/Viewer.vue'
import Button from '@/components/Button.vue'
@@ -172,7 +168,6 @@ import type {
} from './interfaces/MySequencesView'
import { formatDate } from '@/utils/dates'
const { t } = useI18n()
const { cookies } = useCookies()
const sequenceStore = useSequenceStore()
const { toastText, toastLook } = storeToRefs(sequenceStore)
@@ -231,13 +226,14 @@ function goToSequence(sequence: SequenceLinkInterface) {
function getRelChild(sequences: SequenceLinkInterface[]) {
return sequences.filter((el: SequenceLinkInterface) => el.rel === 'child')
}
const getUserId = computed<string>(() => cookies.get('user_id'))
onMounted(async () => {
isLoading.value = true
try {
const { data } = await axios.get('api/users/me/catalog')
const collectionData = await axios.get('api/users/me/collection')
collectionBbox.value = collectionData.data.extent.spatial.bbox[0]
viewerRef.value.viewer.fitBounds(collectionBbox.value)
const sequences = getRelChild(data.links)
const sequencesCollection = getRelChild(collectionData.data.links)
sequencesCollection.map((el: SequenceLinkInterface) => {
@@ -260,7 +256,6 @@ watchEffect(() => {
(e: { detail: { seqId: string } }) => {
seqId.value = e.detail.seqId
scrollIntoSelected(e.detail.seqId, userSequences.value)
viewerRef.value.viewer.select(e.detail.seqId)
}
)
}
@@ -300,7 +295,6 @@ watchEffect(() => {
justify-content: center;
margin: auto;
background-color: var(--blue-pale);
padding: toRem(2);
&:first-child {
margin-bottom: toRem(1);
padding: toRem(1) toRem(2);
@@ -312,22 +306,6 @@ watchEffect(() => {
background-color: var(--white);
}
}
.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 {
display: flex;
align-items: center;
@@ -372,6 +350,7 @@ watchEffect(() => {
display: flex;
align-items: center;
width: 100%;
padding: toRem(2);
background-color: transparent;
border: none;
text-decoration: none;
@@ -395,6 +374,19 @@ watchEffect(() => {
& > :nth-child(2) {
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 {
margin-right: toRem(0.5);

View File

@@ -247,9 +247,6 @@ h3 {
padding: toRem(2) toRem(5) toRem(5);
color: var(--grey-dark);
}
.logged {
height: calc(100vh - #{toRem(8)});
}
.section {
width: 100%;
}

View File

@@ -1,21 +1,20 @@
export interface MapInterface {
startWide?: boolean
maxZoom?: number
minZoom?: number
style?: object | string
zoom?: number
center?: number[]
bounds?: number[]
}
export interface ViewerInterface {
export interface OptionalViewerInterface {
fetchOptions?: {
credentials: string
}
hash?: boolean
picId?: string
widgets?: {
customWidget: HTMLAnchorElement
}
map: MapInterface
}
export interface ViewerInterface extends OptionalViewerInterface {
map: {
startWide: boolean
style?: object | string
maxZoom?: number
zoom?: number
center?: number[]
}
bounds?: number[]
}

View File

@@ -1,25 +1,6 @@
import axios from 'axios'
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> {
function createASequence(title: string): Promise<any> {
return axios.post('api/collections', { title: title })
}

View File

@@ -3164,10 +3164,10 @@ geojson-vt@^3.2.1:
resolved "https://registry.yarnpkg.com/geojson-vt/-/geojson-vt-3.2.1.tgz#f8adb614d2c1d3f6ee7c4265cad4bbf3ad60c8b7"
integrity sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==
geovisio@2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/geovisio/-/geovisio-2.2.1.tgz#75ded2d10ca0c69464b5c734c0c2b3eeff8db8eb"
integrity sha512-E2s6dCXmMSRbtmZ7c4bMCDhgqT5xaKpGZxmbXMfB7+VgLQugiaIyRPKTB5Xtvw3f08FS7mC42WNqZqN5sFitKg==
geovisio@2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/geovisio/-/geovisio-2.2.0.tgz#8d67bf4e3fe04699d16d76bfcc0c5ee3e10e88f1"
integrity sha512-EGUvDNW4gyYdBSszU8fqgpQHWxeSS04pdSUK0oWvBLV24Fl0PYtDOI6uoUAa/69B1kQbGs5qp4wNCyzll7hIrg==
dependencies:
"@fortawesome/fontawesome-svg-core" "^6.4.0"
"@fortawesome/free-solid-svg-icons" "^6.4.0"