Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f047c30078 | |||
| bb660d0295 | |||
|
|
478b2f32ec | ||
|
|
7e936a1832 | ||
|
|
0e2b5629cf | ||
|
|
9f91b09919 | ||
|
|
c831f007da | ||
|
|
8cbf75161d | ||
|
|
a0cc0cd5f7 | ||
|
|
ee5e32d290 | ||
|
|
c07e6587c3 | ||
|
|
e5bc6323a3 | ||
|
|
16bfbfd4da | ||
|
|
2492b4cffa | ||
|
|
6c7e233d00 | ||
|
|
79aaf29aed | ||
|
|
43fed2fb13 | ||
|
|
71ed9d4f33 | ||
|
|
715e234298 | ||
|
|
77bd7604e4 | ||
|
|
783f079c95 | ||
|
|
47630098ee | ||
|
|
44d7e56dbc | ||
|
|
4cf6b3d7e2 | ||
|
|
58da8065e0 | ||
|
|
67ebf3dd80 | ||
|
|
46e64b3cc1 | ||
|
|
d814ba5855 | ||
|
|
4aa96cf243 | ||
|
|
6690eecb81 | ||
|
|
f15d6c29cb | ||
|
|
93389801e8 | ||
|
|
5568218ba4 | ||
|
|
fde2bfd302 | ||
|
|
70212f60cf | ||
|
|
38db40a2ed | ||
|
|
3e29065494 | ||
|
|
a9fb5957e9 | ||
|
|
e83b3cea4b | ||
|
|
6ede535f87 | ||
|
|
f315b42e74 | ||
|
|
f5176b5cae | ||
|
|
ea1b864a3b | ||
|
|
fe54cd78a2 | ||
|
|
08ce274a40 | ||
|
|
1a0f1bd60a | ||
|
|
6291f0567e | ||
|
|
d493af65c3 | ||
|
|
627d9d739f | ||
|
|
e2dac72ba9 | ||
| 5b6bfcdb3b |
6
.gitignore
vendored
@@ -93,4 +93,8 @@ sw.*
|
|||||||
cypress/downloads/*
|
cypress/downloads/*
|
||||||
cypress/screenshot/
|
cypress/screenshot/
|
||||||
cypress/videos/
|
cypress/videos/
|
||||||
*.cy.ts.mp4
|
*.cy.ts.mp4
|
||||||
|
|
||||||
|
# Mkdocs
|
||||||
|
env/
|
||||||
|
site/
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ variables:
|
|||||||
DOCKER_BUILDKIT: 1 # use buildkit for better performance
|
DOCKER_BUILDKIT: 1 # use buildkit for better performance
|
||||||
DOCKER_DRIVER: overlay2 # better docker driver to avoid copying too many files on each run
|
DOCKER_DRIVER: overlay2 # better docker driver to avoid copying too many files on each run
|
||||||
GITLAB_REGISTRY: registry.gitlab.com # We use docker.io for official images and gitlab's registry to store temporary images
|
GITLAB_REGISTRY: registry.gitlab.com # We use docker.io for official images and gitlab's registry to store temporary images
|
||||||
IMAGE_NAME: geovisio/website
|
REPO_NAME: panoramax/server/website
|
||||||
CI_IMAGE_CACHE: $GITLAB_REGISTRY/$IMAGE_NAME:build_cache
|
DOCKER_IMAGE_NAME: geovisio/website
|
||||||
|
CI_IMAGE_CACHE: $GITLAB_REGISTRY/$REPO_NAME:build_cache
|
||||||
DOCKER_TLS_CERTDIR: ''
|
DOCKER_TLS_CERTDIR: ''
|
||||||
DOCKER_HOST: tcp://docker:2375
|
DOCKER_HOST: tcp://docker:2375
|
||||||
|
|
||||||
@@ -23,21 +24,21 @@ cache:
|
|||||||
|
|
||||||
install:
|
install:
|
||||||
stage: Install
|
stage: Install
|
||||||
image: node:18.16.1
|
image: node:20.9.0
|
||||||
script:
|
script:
|
||||||
- yarn install
|
- yarn install
|
||||||
- ls node_modules/.bin/cypress
|
- ls node_modules/.bin/cypress
|
||||||
|
|
||||||
test:unit:
|
test:unit:
|
||||||
stage: Test
|
stage: Test
|
||||||
image: node:18.16.1
|
image: node:20.9.0-alpine
|
||||||
script:
|
script:
|
||||||
- yarn test:unit
|
- yarn test:unit
|
||||||
coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/
|
coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/
|
||||||
|
|
||||||
test:e2e:
|
test:e2e:
|
||||||
stage: Test
|
stage: Test
|
||||||
image: node:18.16.1-alpine
|
image: node:20.9.0-alpine
|
||||||
services:
|
services:
|
||||||
- docker:dind
|
- docker:dind
|
||||||
script:
|
script:
|
||||||
@@ -56,7 +57,7 @@ test:e2e:
|
|||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
stage: Deploy
|
stage: Deploy
|
||||||
image: node:18.16.1
|
image: node:20.9.0
|
||||||
cache:
|
cache:
|
||||||
paths:
|
paths:
|
||||||
- node_modules
|
- node_modules
|
||||||
@@ -88,7 +89,7 @@ deploy:develop:
|
|||||||
- docker buildx build
|
- docker buildx build
|
||||||
--cache-from "type=registry,ref=$CI_IMAGE_CACHE"
|
--cache-from "type=registry,ref=$CI_IMAGE_CACHE"
|
||||||
--cache-to "type=registry,mode=max,ref=$CI_IMAGE_CACHE"
|
--cache-to "type=registry,mode=max,ref=$CI_IMAGE_CACHE"
|
||||||
--tag "$CI_REGISTRY_IMAGE:develop"
|
--tag "$DOCKER_IMAGE_NAME:develop"
|
||||||
--label "org.opencontainers.image.title=$CI_PROJECT_TITLE"
|
--label "org.opencontainers.image.title=$CI_PROJECT_TITLE"
|
||||||
--label "org.opencontainers.image.url=$CI_PROJECT_URL"
|
--label "org.opencontainers.image.url=$CI_PROJECT_URL"
|
||||||
--label "org.opencontainers.image.created=$CI_JOB_STARTED_AT"
|
--label "org.opencontainers.image.created=$CI_JOB_STARTED_AT"
|
||||||
@@ -98,7 +99,7 @@ deploy:develop:
|
|||||||
.
|
.
|
||||||
|
|
||||||
# publish image to dockerhub with the develop tag
|
# publish image to dockerhub with the develop tag
|
||||||
- docker push "$CI_REGISTRY_IMAGE:develop"
|
- docker push "$DOCKER_IMAGE_NAME:develop"
|
||||||
|
|
||||||
deploy:latest:
|
deploy:latest:
|
||||||
# we consider that tag always land on main
|
# we consider that tag always land on main
|
||||||
@@ -120,8 +121,8 @@ deploy:latest:
|
|||||||
- docker buildx build
|
- docker buildx build
|
||||||
--cache-from "type=registry,ref=$CI_IMAGE_CACHE"
|
--cache-from "type=registry,ref=$CI_IMAGE_CACHE"
|
||||||
--cache-to "type=registry,mode=max,ref=$CI_IMAGE_CACHE"
|
--cache-to "type=registry,mode=max,ref=$CI_IMAGE_CACHE"
|
||||||
--tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME"
|
--tag "$DOCKER_IMAGE_NAME:$CI_COMMIT_REF_NAME"
|
||||||
--tag "$CI_REGISTRY_IMAGE:latest"
|
--tag "$DOCKER_IMAGE_NAME:latest"
|
||||||
--label "org.opencontainers.image.title=$CI_PROJECT_TITLE"
|
--label "org.opencontainers.image.title=$CI_PROJECT_TITLE"
|
||||||
--label "org.opencontainers.image.url=$CI_PROJECT_URL"
|
--label "org.opencontainers.image.url=$CI_PROJECT_URL"
|
||||||
--label "org.opencontainers.image.created=$CI_JOB_STARTED_AT"
|
--label "org.opencontainers.image.created=$CI_JOB_STARTED_AT"
|
||||||
@@ -131,5 +132,5 @@ deploy:latest:
|
|||||||
.
|
.
|
||||||
|
|
||||||
# publish image to dockerhub
|
# publish image to dockerhub
|
||||||
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
|
- docker push $DOCKER_IMAGE_NAME:$CI_COMMIT_REF_NAME
|
||||||
- docker push $CI_REGISTRY_IMAGE:latest
|
- docker push $DOCKER_IMAGE_NAME:latest
|
||||||
|
|||||||
114
CHANGELOG.md
@@ -7,6 +7,45 @@ 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.6.2] - 2024-15-03
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Change Nodejs version to LTS 20.9.0
|
||||||
|
- Update dependencies (not prettier/typescript/eslint)
|
||||||
|
- Add tutorial on Android mobile for the picture upload
|
||||||
|
- Add tutorial on Android mobile when there is error on exif geoloc tag for the picture upload
|
||||||
|
- Add a server.js to serve statics files on deploy
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix orientation viewer on mobile
|
||||||
|
- Fix Header css
|
||||||
|
- Fix tests unit
|
||||||
|
|
||||||
|
## [2.6.1] - 2024-05-03
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix warning in browser console
|
||||||
|
- Fix lang switcher using cookie to set the locale
|
||||||
|
|
||||||
|
## [2.6.0] - 2024-05-02
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Update Geovisio Viewer to 3.0.1
|
||||||
|
- Adapt the code for the new viewer version
|
||||||
|
- Change some features after user testing:
|
||||||
|
- Upload page tooltip
|
||||||
|
- Add an upload cancel button
|
||||||
|
- New mobile sequence list version
|
||||||
|
- Add a return home button in the header
|
||||||
|
- Improve the interaction in the Sequence page
|
||||||
|
- Improve the performance of the code
|
||||||
|
- Some refactoring
|
||||||
|
- Some bug fixes
|
||||||
|
|
||||||
## [2.5.1] - 2024-03-19
|
## [2.5.1] - 2024-03-19
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@@ -20,7 +59,7 @@ Before _0.1.0_, website development was on rolling release, meaning there are no
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- GeoVisio web viewer updated to [2.5.0](https://gitlab.com/geovisio/web-viewer/-/compare/2.4.0...2.5.0) to reduce tiles size.
|
- GeoVisio web viewer updated to [2.5.0](https://gitlab.com/panoramax/clients/web-viewer/-/compare/2.4.0...2.5.0) to reduce tiles size.
|
||||||
|
|
||||||
## [2.4.1] - 2024-02-01
|
## [2.4.1] - 2024-02-01
|
||||||
|
|
||||||
@@ -36,7 +75,7 @@ Before _0.1.0_, website development was on rolling release, meaning there are no
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- GeoVisio web viewer updated to [2.4.0](https://gitlab.com/geovisio/web-viewer/-/compare/2.3.1...2.4.0) to manage sequence by user
|
- GeoVisio web viewer updated to [2.4.0](https://gitlab.com/panoramax/clients/web-viewer/-/compare/2.3.1...2.4.0) to manage sequence by user
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
@@ -48,14 +87,14 @@ Before _0.1.0_, website development was on rolling release, meaning there are no
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Add the possibility to fullscreen the viewer in the homepage : https://gitlab.com/geovisio/website/-/issues/60
|
- Add the possibility to fullscreen the viewer in the homepage : https://gitlab.com/panoramax/server/website/-/issues/60
|
||||||
- In the sequence list page add a filter by bbox in the map : https://gitlab.com/geovisio/website/-/issues/61
|
- In the sequence list page add a filter by bbox in the map : https://gitlab.com/panoramax/server/website/-/issues/61
|
||||||
- In the sequence list page add a filter by date in the list : https://gitlab.com/geovisio/website/-/issues/57
|
- In the sequence list page add a filter by date in the list : https://gitlab.com/panoramax/server/website/-/issues/57
|
||||||
- Add a cancel button to when a sequence is uploading to cancel the upload
|
- Add a cancel button to when a sequence is uploading to cancel the upload
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- GeoVisio web viewer updated to [2.3.1](https://gitlab.com/geovisio/web-viewer/-/compare/2.3.0...2.3.1)
|
- GeoVisio web viewer updated to [2.3.1](https://gitlab.com/panoramax/clients/web-viewer/-/compare/2.3.0...2.3.1)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
@@ -65,22 +104,22 @@ Before _0.1.0_, website development was on rolling release, meaning there are no
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Add the possibility to an user to select a sequence in the list using the map : https://gitlab.com/geovisio/website/-/merge_requests/100
|
- Add the possibility to an user to select a sequence in the list using the map : https://gitlab.com/panoramax/server/website/-/merge_requests/100
|
||||||
- For a selected sequence in the list, if the sequence is not displayed in the map, fly to the sequence on the map : https://gitlab.com/geovisio/website/-/merge_requests/108
|
- For a selected sequence in the list, if the sequence is not displayed in the map, fly to the sequence on the map : https://gitlab.com/panoramax/server/website/-/merge_requests/108
|
||||||
- Add the pagination to the sequence with sort with API routes : https://gitlab.com/geovisio/website/-/merge_requests/107
|
- Add the pagination to the sequence with sort with API routes : https://gitlab.com/panoramax/server/website/-/merge_requests/107
|
||||||
- Add Hungarian translation : https://gitlab.com/geovisio/website/-/merge_requests/105
|
- Add Hungarian translation : https://gitlab.com/panoramax/server/website/-/merge_requests/105
|
||||||
- Add the possibility to hide/delete a sequence in the sequence list : https://gitlab.com/geovisio/website/-/merge_requests/101
|
- Add the possibility to hide/delete a sequence in the sequence list : https://gitlab.com/panoramax/server/website/-/merge_requests/101
|
||||||
- Add the possibility for the user to change the title of the sequence before upload the pictures : https://gitlab.com/geovisio/website/-/merge_requests/101
|
- Add the possibility for the user to change the title of the sequence before upload the pictures : https://gitlab.com/panoramax/server/website/-/merge_requests/101
|
||||||
- Add the possibility to resize the sequence page by dragging the blocs : https://gitlab.com/geovisio/website/-/merge_requests/101
|
- Add the possibility to resize the sequence page by dragging the blocs : https://gitlab.com/panoramax/server/website/-/merge_requests/101
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- GeoVisio web viewer updated to [2.3.0](https://gitlab.com/geovisio/web-viewer/-/compare/2.2.1...2.3.0)
|
- GeoVisio web viewer updated to [2.3.0](https://gitlab.com/panoramax/clients/web-viewer/-/compare/2.2.1...2.3.0)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Nginx server in Docker container was not recognizing routes other than `/` on first loading.
|
- Nginx server in Docker container was not recognizing routes other than `/` on first loading.
|
||||||
- Fix the cookie bug by decoding flask cookie : https://gitlab.com/geovisio/website/-/merge_requests/102
|
- Fix the cookie bug by decoding flask cookie : https://gitlab.com/panoramax/server/website/-/merge_requests/102
|
||||||
|
|
||||||
## [2.2.3] - 2023-11-03
|
## [2.2.3] - 2023-11-03
|
||||||
|
|
||||||
@@ -152,7 +191,7 @@ Before _0.1.0_, website development was on rolling release, meaning there are no
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- GeoVisio web viewer upgraded to 2.1.4, [with alls its changes embedded](https://gitlab.com/geovisio/web-viewer/-/blob/develop/CHANGELOG.md?ref_type=heads#213-2023-08-30).
|
- GeoVisio web viewer upgraded to 2.1.4, [with alls its changes embedded](https://gitlab.com/panoramax/clients/web-viewer/-/blob/develop/CHANGELOG.md?ref_type=heads#213-2023-08-30).
|
||||||
- Dockerfile creates smaller and faster containers, using pre-built website and Nginx for HTTP serving.
|
- Dockerfile creates smaller and faster containers, using pre-built website and Nginx for HTTP serving.
|
||||||
- In the upload input you can now choose between gallery and camera on mobile IOS
|
- In the upload input you can now choose between gallery and camera on mobile IOS
|
||||||
- Some CSS fix for the responsive of one Sequence
|
- Some CSS fix for the responsive of one Sequence
|
||||||
@@ -167,14 +206,14 @@ Before _0.1.0_, website development was on rolling release, meaning there are no
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- A new page `/envoyer` to upload picture with an interface ([#13](https://gitlab.com/geovisio/website/-/issues/13)) :
|
- A new page `/envoyer` to upload picture with an interface ([#13](https://gitlab.com/panoramax/server/website/-/issues/13)) :
|
||||||
- the user can upload multiples pictures with the interface
|
- the user can upload multiples pictures with the interface
|
||||||
- the pictures are sorted by name
|
- the pictures are sorted by name
|
||||||
- the user can see all the pictures uploaded and all the errors
|
- the user can see all the pictures uploaded and all the errors
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Website releases now follow the synced `MAJOR.MINOR` API version rule, meaning that any version >= 2.1 of the website will be compatible with corresponding [GeoVisio API](https://gitlab.com/geovisio/api) version.
|
- Website releases now follow the synced `MAJOR.MINOR` API version rule, meaning that any version >= 2.1 of the website will be compatible with corresponding [GeoVisio API](https://gitlab.com/panoramax/server/api) version.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
@@ -184,13 +223,13 @@ Before _0.1.0_, website development was on rolling release, meaning there are no
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- A new page `/mes-sequences` to access to a list of sequences for a logged user ([#14](https://gitlab.com/geovisio/website/-/issues/14)) :
|
- A new page `/mes-sequences` to access to a list of sequences for a logged user ([#14](https://gitlab.com/panoramax/server/website/-/issues/14)) :
|
||||||
|
|
||||||
- the user can see all his sequences
|
- the user can see all his sequences
|
||||||
- the user can filter sequences
|
- the user can filter sequences
|
||||||
- the user can enter to a specific sequence
|
- the user can enter to a specific sequence
|
||||||
|
|
||||||
- A new page `/sequence/:id` to access to a sequence of photos for a logged user ([#14](https://gitlab.com/geovisio/website/-/issues/14)) :
|
- A new page `/sequence/:id` to access to a sequence of photos for a logged user ([#14](https://gitlab.com/panoramax/server/website/-/issues/14)) :
|
||||||
- the user can see the sequence on the map and move on the map from photos to photos
|
- the user can see the sequence on the map and move on the map from photos to photos
|
||||||
- the user can see information about the sequence
|
- the user can see information about the sequence
|
||||||
- the user can see all the sequence's photos
|
- the user can see all the sequence's photos
|
||||||
@@ -201,19 +240,22 @@ 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.5.1...develop
|
[unreleased]: https://gitlab.com/panoramax/server/website/-/compare/2.6.2...develop
|
||||||
[2.5.1]: https://gitlab.com/geovisio/website/-/compare/2.5.0...2.5.1
|
[2.6.2]: https://gitlab.com/panoramax/server/website/-/compare/2.6.1...2.6.2
|
||||||
[2.5.0]: https://gitlab.com/geovisio/website/-/compare/2.4.1...2.5.0
|
[2.6.1]: https://gitlab.com/panoramax/server/website/-/compare/2.6.0...2.6.1
|
||||||
[2.4.1]: https://gitlab.com/geovisio/website/-/compare/2.4.0...2.4.1
|
[2.6.0]: https://gitlab.com/panoramax/server/website/-/compare/2.5.1...2.6.0
|
||||||
[2.4.0]: https://gitlab.com/geovisio/website/-/compare/2.3.1...2.4.0
|
[2.5.1]: https://gitlab.com/panoramax/server/website/-/compare/2.5.0...2.5.1
|
||||||
[2.3.1]: https://gitlab.com/geovisio/website/-/compare/2.3.0...2.3.1
|
[2.5.0]: https://gitlab.com/panoramax/server/website/-/compare/2.4.1...2.5.0
|
||||||
[2.3.0]: https://gitlab.com/geovisio/website/-/compare/2.2.3...2.3.0
|
[2.4.1]: https://gitlab.com/panoramax/server/website/-/compare/2.4.0...2.4.1
|
||||||
[2.2.3]: https://gitlab.com/geovisio/website/-/compare/2.2.2...2.2.3
|
[2.4.0]: https://gitlab.com/panoramax/server/website/-/compare/2.3.1...2.4.0
|
||||||
[2.2.2]: https://gitlab.com/geovisio/website/-/compare/2.2.1...2.2.2
|
[2.3.1]: https://gitlab.com/panoramax/server/website/-/compare/2.3.0...2.3.1
|
||||||
[2.2.1]: https://gitlab.com/geovisio/website/-/compare/2.2.0...2.2.1
|
[2.3.0]: https://gitlab.com/panoramax/server/website/-/compare/2.2.3...2.3.0
|
||||||
[2.2.0]: https://gitlab.com/geovisio/website/-/compare/2.1.3...2.2.0
|
[2.2.3]: https://gitlab.com/panoramax/server/website/-/compare/2.2.2...2.2.3
|
||||||
[2.1.3]: https://gitlab.com/geovisio/website/-/compare/2.1.2...2.1.3
|
[2.2.2]: https://gitlab.com/panoramax/server/website/-/compare/2.2.1...2.2.2
|
||||||
[2.1.2]: https://gitlab.com/geovisio/website/-/compare/2.1.1...2.1.2
|
[2.2.1]: https://gitlab.com/panoramax/server/website/-/compare/2.2.0...2.2.1
|
||||||
[2.1.1]: https://gitlab.com/geovisio/website/-/compare/2.1.0...2.1.1
|
[2.2.0]: https://gitlab.com/panoramax/server/website/-/compare/2.1.3...2.2.0
|
||||||
[2.1.0]: https://gitlab.com/geovisio/website/-/compare/0.1.0...2.1.0
|
[2.1.3]: https://gitlab.com/panoramax/server/website/-/compare/2.1.2...2.1.3
|
||||||
[0.1.0]: https://gitlab.com/geovisio/website/-/commits/0.1.0
|
[2.1.2]: https://gitlab.com/panoramax/server/website/-/compare/2.1.1...2.1.2
|
||||||
|
[2.1.1]: https://gitlab.com/panoramax/server/website/-/compare/2.1.0...2.1.1
|
||||||
|
[2.1.0]: https://gitlab.com/panoramax/server/website/-/compare/0.1.0...2.1.0
|
||||||
|
[0.1.0]: https://gitlab.com/panoramax/server/website/-/commits/0.1.0
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#- Build image
|
#- Build image
|
||||||
#-
|
#-
|
||||||
|
|
||||||
FROM node:18.16.1-alpine AS build
|
FROM node:20.9.0-alpine AS build
|
||||||
|
|
||||||
WORKDIR /opt/geovisio
|
WORKDIR /opt/geovisio
|
||||||
|
|
||||||
@@ -27,7 +27,6 @@ ENV VITE_ZOOM=DOCKER_VITE_ZOOM
|
|||||||
ENV VITE_CENTER=DOCKER_VITE_CENTER
|
ENV VITE_CENTER=DOCKER_VITE_CENTER
|
||||||
|
|
||||||
# Build code
|
# Build code
|
||||||
ENV PORT=3000
|
|
||||||
RUN yarn deploy
|
RUN yarn deploy
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
42
README.md
@@ -1,35 +1,19 @@
|
|||||||
# 
|
#  Panoramax
|
||||||
|
|
||||||
__GeoVisio__ is a complete solution for storing and __serving your own 📍📷 geolocated pictures__ (like [StreetView](https://www.google.com/streetview/) / [Mapillary](https://mapillary.com/)).
|
__Panoramax__ is a digital resource for sharing and exploiting 📍📷 field photos. Anyone can take photographs of places visible from the public streets and contribute them to the Panoramax database. This data is then freely accessible and reusable by all. More information available at [gitlab.com/panoramax](https://gitlab.com/panoramax) and [panoramax.fr](https://panoramax.fr/).
|
||||||
|
|
||||||
➡️ __Give it a try__ at [panoramax.ign.fr](https://panoramax.ign.fr/) or [geovisio.fr](https://geovisio.fr/viewer) !
|
|
||||||
|
|
||||||
## 📦 Components
|
|
||||||
|
|
||||||
GeoVisio is __modular__ and made of several components, each of them standardized and ♻️ replaceable.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
All of them are 📖 __open-source__ and available online:
|
|
||||||
|
|
||||||
| 🌐 Server | 💻 Client |
|
|
||||||
|:-----------------------------------------------------------------------:|:----------------------------------------------------:|
|
|
||||||
| [API](https://gitlab.com/geovisio/api) | [Website](https://gitlab.com/geovisio/website) |
|
|
||||||
| [Blur API](https://gitlab.com/geovisio/blurring) | [Web viewer](https://gitlab.com/geovisio/web-viewer) |
|
|
||||||
| [GeoPic Tag Reader](https://gitlab.com/geovisio/geo-picture-tag-reader) | [Command line](https://gitlab.com/geovisio/cli) |
|
|
||||||
|
|
||||||
|
|
||||||
# 💻 GeoVisio Website
|
# 💻 GeoVisio Website
|
||||||
|
|
||||||
This repository only contains __the web front-end of GeoVisio__.
|
This repository only contains __the web front-end of GeoVisio__.
|
||||||
|
|
||||||
Note that the 📷 __web viewer__ (component showing pictures and their location on a map) is in [a separate, dedicated repository](https://gitlab.com/geovisio/web-viewer).
|
Note that the 📷 __web viewer__ (component showing pictures and their location on a map) is in [a separate, dedicated repository](https://gitlab.com/panoramax/clients/web-viewer).
|
||||||
|
|
||||||
## ⚙️ Features
|
## ⚙️ Features
|
||||||
|
|
||||||
The website offers these functionalities:
|
The website offers these functionalities:
|
||||||
|
|
||||||
- Display of pictures and their location (using the embed [web viewer](https://gitlab.com/geovisio/web-viewer))
|
- Display of pictures and their location (using the embed [web viewer](https://gitlab.com/panoramax/clients/web-viewer))
|
||||||
- Handle user authentication and account management
|
- Handle user authentication and account management
|
||||||
- Show simple to read documentation
|
- Show simple to read documentation
|
||||||
|
|
||||||
@@ -39,22 +23,8 @@ The website offers these functionalities:
|
|||||||
|
|
||||||
## 💁 Contributing
|
## 💁 Contributing
|
||||||
|
|
||||||
Pull requests are welcome. For major changes, please open an [issue](https://gitlab.com/geovisio/website/-/issues) first to discuss what you would like to change.
|
Pull requests are welcome. For major changes, please open an [issue](https://gitlab.com/panoramax/server/website/-/issues) first to discuss what you would like to change.
|
||||||
|
|
||||||
## 🤗 Special thanks
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
GeoVisio was made possible thanks to a group of ✨ __amazing__ people ✨ :
|
|
||||||
|
|
||||||
- __[GéoVélo](https://geovelo.fr/)__ team, for 💶 funding initial development and for 🔍 testing/improving software
|
|
||||||
- __[Carto Cité](https://cartocite.fr/)__ team (in particular Antoine Riche), for 💶 funding improvements on viewer (map browser, flat pictures support)
|
|
||||||
- __[La Fabrique des Géocommuns (IGN)](https://www.ign.fr/institut/la-fabrique-des-geocommuns-incubateur-de-communs-lign)__ for offering long-term support and funding the [Panoramax](https://panoramax.fr/) initiative and core team (Camille Salou, Mathilde Ferrey, Christian Quest, Antoine Desbordes, Jean Andreani, Adrien Pavie)
|
|
||||||
- Many _many_ __wonderful people__ who worked on various parts of GeoVisio or core dependencies we use : 🧙 Stéphane Péneau, 🎚 Albin Calais & Cyrille Giquello, 📷 [Damien Sorel](https://www.strangeplanet.fr/), Pascal Rhod, Nick Whitelegg...
|
|
||||||
- __[Adrien Pavie](https://pavie.info/)__, for ⚙️ initial development of GeoVisio
|
|
||||||
- And you all ✨ __GeoVisio users__ for making this project useful !
|
|
||||||
|
|
||||||
|
|
||||||
## ⚖️ License
|
## ⚖️ License
|
||||||
|
|
||||||
Copyright (c) GeoVisio team 2022-2023, [released under MIT license](./LICENSE).
|
Copyright (c) Panoramax team 2022-2024, [released under MIT license](./LICENSE).
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# Docker-compose used in gitlab-ci to run a container having access to all the other containers
|
# Docker-compose used in gitlab-ci to run a container having access to all the other containers
|
||||||
services:
|
services:
|
||||||
web:
|
web:
|
||||||
image: node:18.16.1-alpine
|
image: node:20.9.0-alpine
|
||||||
volumes:
|
volumes:
|
||||||
- $PROJECT_DIR:/src
|
- $PROJECT_DIR:/src
|
||||||
working_dir: /src
|
working_dir: /src
|
||||||
command: >
|
command: >
|
||||||
sh -c "apk add --update --no-cache curl && yarn install && yarn start"
|
sh -c "apk add --update --no-cache curl && yarn install && yarn dev"
|
||||||
environment:
|
environment:
|
||||||
PORT: 5173
|
PORT: 5173
|
||||||
VITE_API_URL: http://api.localtest.me:5000
|
VITE_API_URL: http://api.localtest.me:5000
|
||||||
@@ -29,7 +29,7 @@ services:
|
|||||||
aliases:
|
aliases:
|
||||||
- front.localtest.me
|
- front.localtest.me
|
||||||
e2e:
|
e2e:
|
||||||
image: cypress/included:cypress-12.17.3-node-18.16.1-chrome-114.0.5735.133-1-ff-114.0.2-edge-114.0.1823.51-1
|
image: cypress/included:cypress-13.8.1-node-22.0.0-chrome-124.0.6367.60-1-ff-125.0.2-edge-124.0.2478.51-1
|
||||||
volumes:
|
volumes:
|
||||||
- $PROJECT_DIR:/src
|
- $PROJECT_DIR:/src
|
||||||
working_dir: /src
|
working_dir: /src
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ describe('In the login page', () => {
|
|||||||
})
|
})
|
||||||
cy.get('.input-file').selectFile(
|
cy.get('.input-file').selectFile(
|
||||||
[
|
[
|
||||||
'/src/cypress/fixtures/images/image1.jpg',
|
'cypress/fixtures/images/image1.jpg',
|
||||||
'/src/cypress/fixtures/images/image2.jpg',
|
'cypress/fixtures/images/image2.jpg',
|
||||||
'/src/cypress/fixtures/images/image3.jpg'
|
'cypress/fixtures/images/image3.jpg'
|
||||||
],
|
],
|
||||||
{ force: true }
|
{ force: true }
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"textButtonContribute": "Partager des images",
|
"textButtonContribute": "Partager des photos",
|
||||||
"textButtonDocCli": "Voir la documentation",
|
"textButtonDocCli": "Voir la documentation",
|
||||||
"textButtonDoc": "Retrouvez sa documentation ici",
|
"textButtonDoc": "Retrouvez sa documentation ici",
|
||||||
"textButtonDocPython": "de python (au moins la version 3.8)",
|
"textButtonDocPython": "de python (au moins la version 3.8)",
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
ROOT_DIR=/usr/share/nginx/html
|
ROOT_DIR=/usr/share/nginx/html
|
||||||
DOCKER_VARS=(VITE_INSTANCE_NAME VITE_API_URL VITE_TILES VITE_MAX_ZOOM VITE_ZOOM VITE_CENTER )
|
DOCKER_VARS=(VITE_INSTANCE_NAME VITE_API_URL VITE_TILES VITE_MAX_ZOOM VITE_ZOOM VITE_CENTER VITE_RASTER_TILE )
|
||||||
|
|
||||||
echo "Setting env variables in web files"
|
echo "Setting env variables in web files"
|
||||||
for file in $ROOT_DIR/assets/*.js $ROOT_DIR/index.html; do
|
for file in $ROOT_DIR/assets/*.js $ROOT_DIR/*.html; do
|
||||||
echo "Processing $file...";
|
echo "Processing $file...";
|
||||||
|
|
||||||
for i in ${!DOCKER_VARS[@]}; do
|
for i in ${!DOCKER_VARS[@]}; do
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
# GeoVisio Website hands-on guide
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Welcome to GeoVisio __Website__ documentation ! It will help you through all phases of setup, run and develop on GeoVisio Website.
|
|
||||||
|
|
||||||
__Note that__ this only covers the Website / front-end component, if you're looking for docs on another component, you may go to [this page](https://gitlab.com/geovisio) instead.
|
|
||||||
|
|
||||||
Also, if at some point you're lost or need help, you can contact us through [issues](https://gitlab.com/geovisio/website/-/issues) or by [email](mailto:panieravide@riseup.net).
|
|
||||||
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
The website relies on the following technologies and components:
|
|
||||||
|
|
||||||
- Frontend website made in [Vue 3](https://vuejs.org/guide/introduction.html)
|
|
||||||
- Project use [Vite](https://vitejs.dev/guide/) who offer a fast development server and an optimized compilation for production (like webpack)
|
|
||||||
- The style is made with CSS/SASS and the [bootstrap library](https://getbootstrap.com/)
|
|
||||||
- [Typescript](https://www.typescriptlang.org/) used to type
|
|
||||||
- [Jest](https://jestjs.io/fr/) used for unit testing
|
|
||||||
|
|
||||||
|
|
||||||
## All the docs
|
|
||||||
|
|
||||||
You might want to dive into docs :
|
|
||||||
|
|
||||||
- [Install and setup](./02_Setup.md)
|
|
||||||
- [Change the settings](./03_Settings.md)
|
|
||||||
- [Work on the code](./09_Develop.md)
|
|
||||||
120
docs/02_Setup.md
@@ -2,81 +2,63 @@
|
|||||||
|
|
||||||
GeoVisio website can be installed through classic method, or using Docker.
|
GeoVisio website can be installed through classic method, or using Docker.
|
||||||
|
|
||||||
__Contents__
|
=== ":gear: Classic"
|
||||||
|
|
||||||
[[_TOC_]]
|
You need to have installed on your system:
|
||||||
|
|
||||||
|
* [NodeJS](https://nodejs.org/en/download) >= 18.13.0
|
||||||
|
* [NPM](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) or [Yarn](https://yarnpkg.com/)
|
||||||
|
|
||||||
|
The website can be installed locally by retrieving this repository and installing dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Retrieve source code
|
||||||
|
git clone https://gitlab.com/panoramax/server/website.git
|
||||||
|
cd website/
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, you need to define some settings. You have to create a `.env` file and edit its content.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cp env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
See _Configuration_ for more details about available settings.
|
||||||
|
|
||||||
|
Then, building for production can be done with these commands:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
PORT=3000 npm run start
|
||||||
|
```
|
||||||
|
|
||||||
|
The website is now available at [localhost:3000](http://localhost:3000).
|
||||||
|
|
||||||
|
|
||||||
## Classic install
|
=== ":simple-docker: Docker"
|
||||||
|
|
||||||
### System requirements
|
The [Docker](https://docs.docker.com/get-docker/) deployment is a really convenient way to have a Geovisio website running in an easy and fast way. Note that this setup documentation only covers __GeoVisio front-end__ (website), if you also need an API running, please refer to [Docker API deployment](https://gitlab.com/panoramax/server/api/-/blob/develop/docs/14_Running_Docker.md).
|
||||||
|
|
||||||
**You need to have [Nodejs installed](https://nodejs.org/en/download)**
|
You can use the provided __Docker Hub__ `geovisio/website:latest` image directly:
|
||||||
Node version : >=18.13.0
|
|
||||||
|
|
||||||
**You need to have [Npm installed](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)**
|
```bash
|
||||||
|
docker run \
|
||||||
|
-e VITE_API_URL="https://your.geovisio.api/" \
|
||||||
|
-e VITE_INSTANCE_NAME="My Own GeoVisio" \
|
||||||
|
-e VITE_TILES="https://your.geovisio.api/vector/tiles/style.json" \
|
||||||
|
-p 3000:3000 \
|
||||||
|
--name geovisio-website \
|
||||||
|
-d \
|
||||||
|
geovisio/website:latest
|
||||||
|
```
|
||||||
|
|
||||||
You can use npm or [yarn](https://yarnpkg.com/) as package manager
|
This will run a container bound on [localhost:3000](http://localhost:3000).
|
||||||
|
|
||||||
### Install
|
You can also build the image from the local source with:
|
||||||
|
|
||||||
The website can be installed locally by retrieving this repository and installing dependencies:
|
```bash
|
||||||
|
docker build -t geovisio/website:latest .
|
||||||
```sh
|
```
|
||||||
# Retrieve source code
|
|
||||||
git clone https://gitlab.com/geovisio/website.git
|
|
||||||
cd website/
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
### Build for production
|
|
||||||
|
|
||||||
Before building, you need to define a bit of settings. At least, you have to create a `.env` file and edit its content.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cp env.example .env
|
|
||||||
```
|
|
||||||
|
|
||||||
More details about settings [can be found in docs here](./03_Settings.md).
|
|
||||||
|
|
||||||
Then, building for production can be done with these commands:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm run build
|
|
||||||
PORT=3000 npm run start
|
|
||||||
```
|
|
||||||
|
|
||||||
The website is now available at [localhost:3000](http://localhost:3000).
|
|
||||||
|
|
||||||
|
|
||||||
## Docker setup
|
|
||||||
|
|
||||||
The [Docker](https://docs.docker.com/get-docker/) deployment is a really convenient way to have a Geovisio website running in an easy and fast way. Note that this setup documentation only covers __GeoVisio front-end__ (website), if you also need an API running, please refer to [Docker API deployment](https://gitlab.com/geovisio/api/-/blob/develop/docs/14_Running_Docker.md).
|
|
||||||
|
|
||||||
You can use the provided __Docker Hub__ `geovisio/website:latest` image directly:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run \
|
|
||||||
-e VITE_API_URL="https://your.geovisio.api/" \
|
|
||||||
-e VITE_INSTANCE_NAME="My Own GeoVisio" \
|
|
||||||
-e VITE_TILES="https://your.geovisio.api/vector/tiles/style.json" \
|
|
||||||
-p 3000:3000 \
|
|
||||||
--name geovisio-website \
|
|
||||||
-d \
|
|
||||||
geovisio/website:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
This will run a container bound on [localhost:3000](http://localhost:3000).
|
|
||||||
|
|
||||||
You can also build the image from the local source with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker build -t geovisio/website:latest .
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Next steps
|
|
||||||
|
|
||||||
You can check out [the available settings for your instance](./03_Settings.md).
|
|
||||||
|
|||||||
@@ -8,17 +8,17 @@ Low-level settings can be changed through the `.env` file. An example is given i
|
|||||||
|
|
||||||
Available parameters are:
|
Available parameters are:
|
||||||
|
|
||||||
- `VITE_API_URL`: the URL to the GeoVisio API (with trailing `/`, example: `https://geovisio.fr/`)
|
- `VITE_API_URL`: the URL to the GeoVisio API (example: `https://panoramax.ign.fr/`)
|
||||||
- `VITE_INSTANCE_NAME`: the name of the instance (example: `IGN`)
|
- `VITE_INSTANCE_NAME`: the name of the instance (example: `IGN`)
|
||||||
- `VITE_TILES`: the URL of your tiles : default tiles are the Open Street Map Tiles (example: `https://wxs.ign.fr/essentiels/static/vectorTiles/styles/PLAN.IGN/attenue.json`)
|
- `VITE_TILES`: the URL of your tiles : default tiles are the OpenStreetMap Tiles (example: `https://data.geopf.fr/annexes/ressources/vectorTiles/styles/PLAN.IGN/standard.json`)
|
||||||
- `VITE_MAX_ZOOM`: the max zoom to use on the map (defaults to 24).
|
- `VITE_MAX_ZOOM`: the max zoom to use on the map (defaults to 24).
|
||||||
- `VITE_ZOOM`: the zoom to use at the initialization of the map (defaults to 0).
|
- `VITE_ZOOM`: the zoom to use at the initialization of the map (defaults to 0).
|
||||||
- `VITE_CENTER`: the center position to use at the initialization of the map (defaults to 0).
|
- `VITE_CENTER`: the center position to use at the initialization of the map (defaults to 0).
|
||||||
- `VITE_RASTER_TILE`: the raster tile. Example : `https://maplibre.org/maplibre-style-spec/sources/#raster`.
|
- `VITE_RASTER_TILE`: the raster tile. Example : `https://maplibre.org/maplibre-style-spec/sources/#raster`.
|
||||||
- Settings for the work environment:
|
- Settings for the work environment:
|
||||||
- `NPM_CONFIG_PRODUCTION`: is it production environment (`true`, `false`)
|
- `NPM_CONFIG_PRODUCTION`: is it production environment (`true`, `false`)
|
||||||
- `YARN_PRODUCTION`: same as below, but if you use Yarn instead of NPM
|
- `YARN_PRODUCTION`: same as below, but if you use Yarn instead of NPM
|
||||||
- `VITE_ENV`: `dev`
|
- `VITE_ENV`: `dev`
|
||||||
|
|
||||||
More settings are available [in official Vite documentation](https://vitejs.dev/guide/env-and-mode.html#env-files)
|
More settings are available [in official Vite documentation](https://vitejs.dev/guide/env-and-mode.html#env-files)
|
||||||
|
|
||||||
@@ -28,21 +28,17 @@ Note that you can also change the _Vite_ server configuration in the `vite.confi
|
|||||||
|
|
||||||
GeoVisio website can be customized to have wording reflecting your brand, licence and other elements.
|
GeoVisio website can be customized to have wording reflecting your brand, licence and other elements.
|
||||||
|
|
||||||
All the wordings of the website are on this [locale file](./src/locales/fr.json). In there, you might want to change:
|
All the wordings of the website are on this [locale file](https://gitlab.com/panoramax/server/website/-/blob/develop/src/locales/en.json). In there, you might want to change:
|
||||||
|
|
||||||
- The website title (properties `title` and `meta.title`)
|
- The website title (properties `title` and `meta.title`)
|
||||||
- The description (property `meta.description`)
|
- The description (property `meta.description`)
|
||||||
- Links to help pages:
|
- Links to help pages:
|
||||||
- `upload.description`
|
- `upload.description`
|
||||||
- `upload.footer_description_terminal`
|
- `upload.footer_description_terminal`
|
||||||
|
|
||||||
## Visuals
|
## Visuals
|
||||||
|
|
||||||
The following images can be changed to make the website more personal:
|
The following images can be changed to make the website more personal:
|
||||||
|
|
||||||
- Logo: [`src/assets/images/logo.jpeg`](../src/assets/images/logo.jpeg)
|
- Logo: [`src/assets/images/logo.jpeg`](https://gitlab.com/panoramax/server/website/-/blob/develop/src/assets/images/logo.jpeg)
|
||||||
- Favicon: [`static/favicon.ico`](../static/favicon.ico)
|
- Favicon: [`static/favicon.ico`](https://gitlab.com/panoramax/server/website/-/blob/develop/static/favicon.ico)
|
||||||
|
|
||||||
## Next steps
|
|
||||||
|
|
||||||
You may be interested [in developing on the website](./09_Develop.md).
|
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
# Work on the code
|
# Work on the code
|
||||||
|
|
||||||
## Available commands
|
## Architecture
|
||||||
|
|
||||||
Note that all the commands and packages used are available in the `package.json` file.
|
The website relies on the following technologies and components:
|
||||||
|
|
||||||
### Compile and Hot-Reload for Development
|
- Frontend website made in [Vue 3](https://vuejs.org/guide/introduction.html)
|
||||||
|
- Project use [Vite](https://vitejs.dev/guide/) who offer a fast development server and an optimized compilation for production (like webpack)
|
||||||
|
- The style is made with CSS/SASS and the [bootstrap library](https://getbootstrap.com/)
|
||||||
|
- [Typescript](https://www.typescriptlang.org/) used to type
|
||||||
|
- [Jest](https://jestjs.io/fr/) used for unit testing
|
||||||
|
|
||||||
|
## Compile and Hot-Reload for Development
|
||||||
|
|
||||||
Launch your dev server :
|
Launch your dev server :
|
||||||
|
|
||||||
@@ -18,7 +24,7 @@ or
|
|||||||
yarn dev
|
yarn dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### Run Unit Tests with [Vitest](https://vitest.dev/)
|
## Run Unit Tests with [Vitest](https://vitest.dev/)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm run test:unit
|
npm run test:unit
|
||||||
@@ -30,7 +36,7 @@ or
|
|||||||
yarn test:unit
|
yarn test:unit
|
||||||
```
|
```
|
||||||
|
|
||||||
### Lint with [ESLint](https://eslint.org/)
|
## Lint with [ESLint](https://eslint.org/)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm run lint
|
npm run lint
|
||||||
@@ -41,3 +47,14 @@ or
|
|||||||
```sh
|
```sh
|
||||||
yarn lint
|
yarn lint
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Documentation is located `docs` folder, and can be served with [Mkdocs](https://www.mkdocs.org/):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m venv env
|
||||||
|
source ./env/bin/activate
|
||||||
|
pip install mkdocs-material
|
||||||
|
mkdocs serve
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
# Make a release
|
# Make a release
|
||||||
|
|
||||||
The web site uses [semantic versioning](https://semver.org/) for its release numbers.
|
The website uses [semantic versioning](https://semver.org/) for its release numbers.
|
||||||
|
|
||||||
__Note__ : make sure that versions are in-sync with other Website components. Each component can have different `PATCH` versions, but compatibility __must__ be ensured between `MAJOR.MINOR` versions.
|
!!! note
|
||||||
|
Make sure that versions are in-sync with other GeoVisio components. Each component can have different `PATCH` versions, but compatibility __must__ be ensured between `MAJOR.MINOR` versions.
|
||||||
|
|
||||||
Run these commands in order to issue a new release:
|
Run these commands in order to issue a new release:
|
||||||
|
|
||||||
|
|||||||
15
docs/index.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Panoramax instance Website
|
||||||
|
|
||||||
|
Welcome to GeoVisio __Website__ documentation ! It will help you through all phases of setup, run and develop on GeoVisio Website.
|
||||||
|
|
||||||
|
The website offers these functionalities:
|
||||||
|
|
||||||
|
- Display of pictures and their location (using the embed [web viewer](https://gitlab.com/panoramax/clients/web-viewer))
|
||||||
|
- Handle user authentication and account management
|
||||||
|
- Show simple to read documentation
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
The 📷 __web viewer__ (component showing pictures and their location on a map) is in [a separate, dedicated repository](https://gitlab.com/panoramax/clients/web-viewer). If you're looking for docs on another component, you may go to [this page](https://gitlab.com/panoramax) instead.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
If at some point you're lost or need help, you can contact us through [issues](https://gitlab.com/panoramax/server/website/-/issues) or by [email](mailto:panieravide@riseup.net).
|
||||||
42
mkdocs.yml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
site_name: 'Panoramax - Website'
|
||||||
|
site_url: https://docs.panoramax.fr
|
||||||
|
|
||||||
|
theme:
|
||||||
|
name: material
|
||||||
|
logo: https://gitlab.com/panoramax/gitlab-profile/-/raw/main/images/panoramax_favicon.svg
|
||||||
|
features:
|
||||||
|
- navigation.footer
|
||||||
|
- navigation.tracking
|
||||||
|
- navigation.sections
|
||||||
|
- search.suggest
|
||||||
|
- search.share
|
||||||
|
- content.code.annotate
|
||||||
|
- content.code.copy
|
||||||
|
- content.code.select
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
- search
|
||||||
|
|
||||||
|
markdown_extensions:
|
||||||
|
- admonition
|
||||||
|
- pymdownx.superfences
|
||||||
|
- pymdownx.tabbed:
|
||||||
|
alternate_style: true
|
||||||
|
- pymdownx.highlight:
|
||||||
|
anchor_linenums: true
|
||||||
|
line_spans: __span
|
||||||
|
pygments_lang_class: true
|
||||||
|
- pymdownx.inlinehilite
|
||||||
|
- pymdownx.snippets
|
||||||
|
- attr_list
|
||||||
|
- pymdownx.emoji:
|
||||||
|
emoji_index: !!python/name:material.extensions.emoji.twemoji
|
||||||
|
emoji_generator: !!python/name:material.extensions.emoji.to_svg
|
||||||
|
|
||||||
|
nav:
|
||||||
|
- Home: 'index.md'
|
||||||
|
- Install: '02_Setup.md'
|
||||||
|
- Configuration: '03_Settings.md'
|
||||||
|
- Developping:
|
||||||
|
- 'Where to start': '09_Develop.md'
|
||||||
|
- 'Make a release': '90_Releases.md'
|
||||||
22711
package-lock.json
generated
Normal file
56
package.json
@@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "geovisio-website",
|
"name": "geovisio-website",
|
||||||
"version": "2.5.1",
|
"version": "2.6.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "18.16.1"
|
"node": "20.9.0"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"start": "vite --port $PORT",
|
"start": "node server.js",
|
||||||
"build": "run-p type-check build-only",
|
"build": "run-p build-only",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test:unit": "vitest --environment jsdom --root src/",
|
"test:unit": "vitest --environment jsdom --root src/",
|
||||||
"test:e2e": "yarn cypress run --browser chrome",
|
"test:e2e": "yarn cypress run --browser chrome",
|
||||||
@@ -20,59 +20,61 @@
|
|||||||
"format": "prettier . --write"
|
"format": "prettier . --write"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@popperjs/core": "^2.11.6",
|
"@popperjs/core": "^2.11.8",
|
||||||
"@vueuse/core": "^10.2.1",
|
"@vueuse/core": "^10.9.0",
|
||||||
"axios": "^1.2.3",
|
"axios": "^1.6.8",
|
||||||
"bootstrap": "^5.2.3",
|
"bootstrap": "^5.3.3",
|
||||||
"bootstrap-icons": "^1.10.3",
|
"bootstrap-icons": "^1.11.3",
|
||||||
"geovisio": "2.5.1",
|
"express": "4.19.2",
|
||||||
"moment": "^2.29.4",
|
"geovisio": "3.0.1",
|
||||||
|
"moment": "^2.30.1",
|
||||||
"pako": "^2.1.0",
|
"pako": "^2.1.0",
|
||||||
"pinia": "^2.1.4",
|
"pinia": "^2.1.7",
|
||||||
"v-calendar": "^3.1.2",
|
"v-calendar": "^3.1.2",
|
||||||
"vue": "^3.2.45",
|
"vue": "^3.4.21",
|
||||||
"vue-axios": "^3.5.2",
|
"vue-axios": "^3.5.2",
|
||||||
"vue-draggable-resizable-vue3": "^2.3.1-beta.13",
|
"vue-draggable-resizable-vue3": "^2.3.1-beta.13",
|
||||||
"vue-eslint-parser": "^9.1.0",
|
"vue-i18n": "9.13.1",
|
||||||
"vue-i18n": "9.2.2",
|
|
||||||
"vue-matomo": "^4.2.0",
|
"vue-matomo": "^4.2.0",
|
||||||
"vue-router": "^4.1.6",
|
"vue-router": "^4.3.2",
|
||||||
"vue3-cookies": "^1.0.6",
|
"vue3-cookies": "^1.0.6",
|
||||||
"vue3-smooth-scroll": "^0.8.1"
|
"vue3-smooth-scroll": "^0.8.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@pinia/testing": "^0.1.2",
|
"@pinia/testing": "^0.1.3",
|
||||||
"@rushstack/eslint-patch": "^1.1.4",
|
"@rushstack/eslint-patch": "^1.1.4",
|
||||||
"@types/jest": "^29.5.4",
|
"@types/jest": "^29.5.4",
|
||||||
"@types/jsdom": "^20.0.1",
|
"@types/jsdom": "^20.0.1",
|
||||||
"@types/node": "^18.11.18",
|
"@types/node": "^18.11.18",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.46.0",
|
"@typescript-eslint/eslint-plugin": "^5.46.0",
|
||||||
"@typescript-eslint/parser": "^5.4.0",
|
"@typescript-eslint/parser": "^5.4.0",
|
||||||
"@vitejs/plugin-vue": "^3.2.0",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"@vue/cli-plugin-typescript": "~5.0.0",
|
"@vue/cli-plugin-typescript": "~5.0.0",
|
||||||
"@vue/eslint-config-prettier": "^7.0.0",
|
"@vue/eslint-config-prettier": "^7.0.0",
|
||||||
"@vue/eslint-config-typescript": "^11.0.0",
|
"@vue/eslint-config-typescript": "^11.0.0",
|
||||||
"@vue/test-utils": "^2.2.4",
|
"@vue/test-utils": "^2.4.6",
|
||||||
"@vue/tsconfig": "^0.1.3",
|
"@vue/tsconfig": "^0.1.3",
|
||||||
"cypress": "^13.1.0",
|
"cypress": "^13.9.0",
|
||||||
"eslint": "^8.29.0",
|
"eslint": "^8.29.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-import-resolver-typescript": "^3.5.5",
|
"eslint-import-resolver-typescript": "^3.5.5",
|
||||||
"eslint-plugin-import": "^2.27.5",
|
"eslint-plugin-import": "^2.27.5",
|
||||||
"eslint-plugin-vue": "^9.8.0",
|
"eslint-plugin-vue": "^9.8.0",
|
||||||
"jest": "^29.6.4",
|
"jest": "^29.7.0",
|
||||||
"jsdom": "^20.0.3",
|
"jsdom": "^24.0.0",
|
||||||
"less": "^4.2.0",
|
"less": "^4.2.0",
|
||||||
"less-loader": "^11.1.3",
|
"less-loader": "^12.2.0",
|
||||||
"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.77.1",
|
||||||
|
"terser": "^5.30.4",
|
||||||
"typescript": "~4.7.4",
|
"typescript": "~4.7.4",
|
||||||
"vite": "^3.2.4",
|
"vite": "^5.2.11",
|
||||||
"vite-plugin-eslint": "^1.8.1",
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
"vite-plugin-html": "^3.2.2",
|
"vite-plugin-html": "^3.2.2",
|
||||||
"vitest": "^0.25.3",
|
"vitest": "^1.6.0",
|
||||||
"vue-tsc": "^1.0.9"
|
"vue-eslint-parser": "^9.4.2",
|
||||||
|
"vue-tsc": "^2.0.17"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
|
|||||||
13
server.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
const express = require('express')
|
||||||
|
|
||||||
|
const app = express()
|
||||||
|
const directory = '/' + (process.env.STATIC_DIR || 'dist')
|
||||||
|
app.use(express.static(__dirname + directory))
|
||||||
|
|
||||||
|
const port = process.env.PORT || 3003
|
||||||
|
app.listen(port, function () {
|
||||||
|
console.log('Listening on', port)
|
||||||
|
})
|
||||||
|
app.get('*', function (request, response) {
|
||||||
|
response.sendFile(__dirname + '/dist/index.html')
|
||||||
|
})
|
||||||
15
src/App.vue
@@ -1,21 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
import { RouterView } from 'vue-router'
|
||||||
|
import { authConfig, isAuth } from './composables/auth'
|
||||||
|
const { authConf } = authConfig()
|
||||||
|
const { isLogged } = isAuth()
|
||||||
import Header from '@/components/Header.vue'
|
import Header from '@/components/Header.vue'
|
||||||
import Footer from '@/components/Footer.vue'
|
import Footer from '@/components/Footer.vue'
|
||||||
import { RouterView } from 'vue-router'
|
|
||||||
import { hasASessionCookieDecoded } from '@/utils/auth'
|
|
||||||
import authConfig from './composables/auth'
|
|
||||||
const { authConf } = authConfig()
|
|
||||||
|
|
||||||
let focusMap = ref<string>('focus-map')
|
let focusMap = ref<string>('focus-map')
|
||||||
|
|
||||||
function setFocusMap(value: string) {
|
function setFocusMap(value: string) {
|
||||||
focusMap.value = value
|
focusMap.value = value
|
||||||
}
|
}
|
||||||
const isLogged = computed((): boolean => {
|
|
||||||
const cookie = hasASessionCookieDecoded()
|
|
||||||
return !!(cookie && cookie.account)
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -42,10 +42,6 @@
|
|||||||
font-size: toRem(1.4);
|
font-size: toRem(1.4);
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
@if $size == xss-regular {
|
|
||||||
font-size: toRem(0.9);
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@if $size == m-r-regular {
|
@if $size == m-r-regular {
|
||||||
font-size: toRem(1.6);
|
font-size: toRem(1.6);
|
||||||
@@ -54,13 +50,6 @@
|
|||||||
font-size: toRem(1.4);
|
font-size: toRem(1.4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@if $size == xs-r-regular {
|
|
||||||
font-size: toRem(1.2);
|
|
||||||
font-weight: normal;
|
|
||||||
@media (max-width: toRem(50)) {
|
|
||||||
font-size: toRem(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@if $size == s-r-regular {
|
@if $size == s-r-regular {
|
||||||
font-size: toRem(1.4);
|
font-size: toRem(1.4);
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
@@ -68,4 +57,15 @@
|
|||||||
font-size: toRem(1.2);
|
font-size: toRem(1.2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@if $size == xs-r-regular {
|
||||||
|
font-size: toRem(1.2);
|
||||||
|
font-weight: normal;
|
||||||
|
@media (max-width: toRem(50)) {
|
||||||
|
font-size: toRem(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@if $size == xss-regular {
|
||||||
|
font-size: toRem(1);
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
src/assets/images/android-upload-tutorial-files.jpg
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
src/assets/images/android-upload-tutorial.jpg
Normal file
|
After Width: | Height: | Size: 43 KiB |
1
src/assets/images/en.svg
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
1
src/assets/images/fr.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512" version="1.1"><path d="M 341 256 L 341 424.064 412.272 423.782 L 483.545 423.500 490.127 420.259 C 498.055 416.355, 505.078 409.201, 508.820 401.218 L 511.500 395.500 511.500 256 L 511.500 116.500 508.820 110.782 C 505.078 102.799, 498.055 95.645, 490.127 91.741 L 483.545 88.500 412.272 88.218 L 341 87.936 341 256" stroke="none" fill="#ff4b55" fill-rule="evenodd"/><path d="M 171 256 L 171 424 256 424 L 341 424 341 256 L 341 88 256 88 L 171 88 171 256" stroke="none" fill="#f4f4f4" fill-rule="evenodd"/><path d="M 27.929 89.367 C 17.637 92.142, 8.008 100.484, 3.160 110.825 L 0.500 116.500 0.500 256 L 0.500 395.500 3.180 401.218 C 6.922 409.201, 13.945 416.355, 21.873 420.259 L 28.455 423.500 99.728 423.782 L 171 424.064 171 256.032 L 171 88 101.750 88.067 C 50.201 88.118, 31.332 88.450, 27.929 89.367" stroke="none" fill="#44449c" fill-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 953 B |
1
src/assets/images/hu.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512" version="1.1"><path d="M 50 17.886 C 41.197 20.732, 35.982 23.858, 29.912 29.928 C 23.834 36.006, 20.197 42.216, 17.612 50.932 C 16.208 55.662, 16 64.083, 16 116.182 L 16 176 256 176 L 496 176 496 116.182 C 496 51.184, 496.061 51.984, 490.272 40.554 C 487.059 34.209, 477.791 24.941, 471.446 21.728 C 459.317 15.586, 474.887 15.995, 255.568 16.054 C 66.632 16.105, 55.194 16.206, 50 17.886" stroke="none" fill="#c0392b" fill-rule="evenodd"/><path d="M 16 256 L 16 336 256 336 L 496 336 496 256 L 496 176 256 176 L 16 176 16 256" stroke="none" fill="#fcfcfc" fill-rule="evenodd"/><path d="M 16 395.818 C 16 460.816, 15.939 460.016, 21.728 471.446 C 24.941 477.791, 34.209 487.059, 40.554 490.272 C 52.678 496.412, 37.187 496, 256 496 C 474.813 496, 459.322 496.412, 471.446 490.272 C 477.791 487.059, 487.059 477.791, 490.272 471.446 C 496.061 460.016, 496 460.816, 496 395.818 L 496 336 256 336 L 16 336 16 395.818" stroke="none" fill="#24ac64" fill-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
23
src/assets/images/logo-geovisio.svg
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
viewBox="0 0 12.7 12.7"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<path
|
||||||
|
d="M-3.007-.005a5.978 5.978 0 0 1-5.979 5.978V-.005z"
|
||||||
|
style="fill:#1a237e;fill-opacity:1;stroke:#fff;stroke-width:0.661458;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
transform="rotate(-135)"
|
||||||
|
id="path1" />
|
||||||
|
<circle
|
||||||
|
cx="6.35"
|
||||||
|
cy="6.545"
|
||||||
|
r="2.64"
|
||||||
|
style="fill:#1e88e5;fill-opacity:1;stroke:#fff;stroke-width:0.660027;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="circle1" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 721 B |
1
src/assets/images/pt.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512" version="1.1"><path d="M 170.377 245.461 C 170.056 246.296, 169.957 252.497, 170.157 259.240 C 170.443 268.882, 170.911 272.141, 172.345 274.500 C 178.068 283.914, 190.051 284.114, 195.500 274.887 C 197.234 271.950, 197.500 269.704, 197.500 258 L 197.500 244.500 184.230 244.221 C 173.107 243.987, 170.865 244.187, 170.377 245.461" stroke="none" fill="#004cba" fill-rule="evenodd"/><path d="M 153.189 250.776 L 153.500 276.551 156.787 283.223 C 164.622 299.127, 181.991 306.147, 195.922 299.040 C 202.361 295.755, 208.630 289.450, 211.728 283.143 L 214.500 277.500 214.824 251.250 L 215.147 225 184.012 225 L 152.877 225 153.189 250.776 M 170.377 245.461 C 170.056 246.296, 169.953 252.497, 170.147 259.240 C 170.453 269.872, 170.801 271.984, 172.766 275.144 C 176.832 281.682, 184.484 283.718, 190.656 279.903 C 196.539 276.268, 197.480 273.250, 197.491 258 L 197.500 244.500 184.230 244.221 C 173.107 243.987, 170.865 244.187, 170.377 245.461" stroke="none" fill="#ffffff" fill-rule="evenodd"/><path d="M 173.500 148.570 C 140.174 153.092, 115.707 166.576, 97.570 190.413 C 61.447 237.890, 70.878 305.962, 118.472 341.285 C 153.937 367.605, 199.632 369.859, 238.444 347.202 C 249.571 340.707, 267.651 322.620, 274.392 311.242 C 279.888 301.965, 285.403 288.090, 287.784 277.546 C 290.041 267.558, 290.033 243.939, 287.771 233.765 C 283.057 212.569, 274.214 196.340, 259 180.962 C 242.241 164.022, 226.242 155.179, 203.819 150.462 C 196.553 148.933, 178.928 147.834, 173.500 148.570 M 128.750 201.080 C 127.133 202.022, 127 204.912, 127 239 C 127 261.889, 127.417 277.914, 128.097 281.201 C 130.603 293.305, 140.064 308.220, 149.714 315.277 C 164.345 325.975, 179.016 329.755, 193.451 326.543 C 211.705 322.482, 226.548 310.919, 234.893 294.260 C 240.551 282.965, 240.969 279.203, 240.985 239.405 C 240.999 205.696, 240.879 203.201, 239.171 201.655 C 237.517 200.158, 232.254 200.003, 183.921 200.030 C 149.501 200.050, 129.878 200.423, 128.750 201.080" stroke="none" fill="#ffe600" fill-rule="evenodd"/><path d="M 75 1.553 C 40.842 8.877, 14.816 32.647, 3.340 67 L 0.500 75.500 0.236 253.351 C -0.024 429.355, -0.006 431.291, 2.013 439.791 C 9.839 472.741, 35.014 498.784, 69.067 509.155 C 76.325 511.366, 77.765 511.427, 130.250 511.758 L 184 512.097 184 437.765 L 184 363.434 172.750 362.225 C 158.801 360.727, 149.219 358.088, 137.148 352.421 C 81.316 326.207, 59.078 258.161, 88.643 204 C 106.061 172.094, 138.074 151.693, 175.750 148.493 L 184 147.792 184 73.896 L 184 0 132.750 0.079 C 92.950 0.141, 80.048 0.470, 75 1.553" stroke="none" fill="#04a206" fill-rule="evenodd"/><path d="M 184 73.898 L 184 147.841 190.750 148.526 C 210.176 150.497, 226.827 156.307, 242.910 166.727 C 251.981 172.603, 268.750 190.300, 274.476 200.037 C 280.234 209.831, 285.314 222.719, 287.771 233.765 C 290.033 243.939, 290.041 267.558, 287.784 277.546 C 283.274 297.513, 274.210 314.428, 260.101 329.208 C 241.790 348.391, 220.088 359.179, 193.793 362.169 L 184 363.282 184 437.641 L 184 512 306.542 512 C 414.592 512, 429.996 511.812, 436.792 510.412 C 473.570 502.834, 503.058 473.237, 510.450 436.484 C 511.895 429.301, 512.027 410.846, 511.773 252.500 L 511.490 76.500 509.204 69 C 499.478 37.085, 474.881 12.491, 443 2.803 L 435.500 0.523 309.750 0.239 L 184 -0.045 184 73.898 M 128.750 201.080 C 127.133 202.022, 127 204.912, 127 239 C 127 261.889, 127.417 277.914, 128.097 281.201 C 130.603 293.305, 140.064 308.220, 149.714 315.277 C 164.345 325.975, 179.016 329.755, 193.451 326.543 C 211.705 322.482, 226.548 310.919, 234.893 294.260 C 240.551 282.965, 240.969 279.203, 240.985 239.405 C 240.999 205.696, 240.879 203.201, 239.171 201.655 C 237.517 200.158, 232.254 200.003, 183.921 200.030 C 149.501 200.050, 129.878 200.423, 128.750 201.080 M 153.189 251.250 L 153.500 277.500 156.272 283.143 C 161.388 293.558, 171.743 300.889, 182.568 301.758 C 190.722 302.412, 199.009 298.836, 205.555 291.838 C 214.130 282.670, 214.456 281.242, 214.811 251.250 L 215.121 225 184 225 L 152.879 225 153.189 251.250" stroke="none" fill="#f3042b" fill-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
BIN
src/assets/images/tutorial-upload-loc-2.jpg
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
src/assets/images/tutorial-upload-loc.jpg
Normal file
|
After Width: | Height: | Size: 90 KiB |
@@ -148,7 +148,7 @@ defineProps({
|
|||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
font-size: toRem(1.4);
|
font-size: toRem(1.4);
|
||||||
}
|
}
|
||||||
.no-text-green .icon {
|
.no-text-blue .icon {
|
||||||
color: var(--blue);
|
color: var(--blue);
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
font-size: toRem(1.6);
|
font-size: toRem(1.6);
|
||||||
@@ -173,6 +173,7 @@ defineProps({
|
|||||||
}
|
}
|
||||||
.link--black {
|
.link--black {
|
||||||
color: var(--black);
|
color: var(--black);
|
||||||
|
text-decoration: underline;
|
||||||
.icon {
|
.icon {
|
||||||
font-size: toRem(1.4);
|
font-size: toRem(1.4);
|
||||||
color: var(--black);
|
color: var(--black);
|
||||||
|
|||||||
@@ -27,8 +27,8 @@
|
|||||||
<div class="close-button">
|
<div class="close-button">
|
||||||
<Button
|
<Button
|
||||||
id="close-button"
|
id="close-button"
|
||||||
look="no-text-white"
|
look="no-text-blue-dark"
|
||||||
icon="bi bi-x"
|
icon="bi bi-x-lg"
|
||||||
@trigger="closeEdition"
|
@trigger="closeEdition"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -36,9 +36,9 @@
|
|||||||
<span v-else class="title">{{ text }}</span>
|
<span v-else class="title">{{ text }}</span>
|
||||||
<div v-if="!isEditTitle" class="edit-button">
|
<div v-if="!isEditTitle" class="edit-button">
|
||||||
<Button
|
<Button
|
||||||
look="no-text-white"
|
:text="$t('pages.upload.edit_title')"
|
||||||
|
look="link--black row-reverse"
|
||||||
icon="bi bi-pen"
|
icon="bi bi-pen"
|
||||||
:tooltip="$t('pages.upload.edit_title_tooltip')"
|
|
||||||
:disabled="isDisabled"
|
:disabled="isDisabled"
|
||||||
@trigger="goToEditMode"
|
@trigger="goToEditMode"
|
||||||
/>
|
/>
|
||||||
@@ -122,15 +122,8 @@ const isDisabled = computed<boolean>(() => props.isLoading && !props.isLoaded)
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.edit-button {
|
.edit-button {
|
||||||
background-color: var(--blue);
|
min-width: toRem(11);
|
||||||
border-radius: 50%;
|
margin-left: toRem(1);
|
||||||
height: toRem(2.5);
|
|
||||||
width: toRem(2.5);
|
|
||||||
padding: toRem(1);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
margin-left: toRem(1.5);
|
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
.wrapper-input {
|
.wrapper-input {
|
||||||
@@ -150,10 +143,8 @@ const isDisabled = computed<boolean>(() => props.isLoading && !props.isLoaded)
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
height: toRem(2);
|
height: toRem(2);
|
||||||
width: toRem(2);
|
width: toRem(2);
|
||||||
top: toRem(-1);
|
top: toRem(0.5);
|
||||||
left: toRem(-1);
|
right: toRem(0.5);
|
||||||
background-color: var(--blue-dark);
|
border-radius: toRem(0.5);
|
||||||
color: var(--white);
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -31,6 +31,15 @@
|
|||||||
@triggerClose="closeModal"
|
@triggerClose="closeModal"
|
||||||
/>
|
/>
|
||||||
<div class="wrapper-right-entries">
|
<div class="wrapper-right-entries">
|
||||||
|
<div>
|
||||||
|
<Link
|
||||||
|
:text="$t('general.header.viewer')"
|
||||||
|
:route="{ name: 'home' }"
|
||||||
|
look="link--blue"
|
||||||
|
class="desktop"
|
||||||
|
@click.native="closeModal"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Link
|
<Link
|
||||||
v-if="isLogged"
|
v-if="isLogged"
|
||||||
@@ -73,7 +82,10 @@
|
|||||||
:menu-is-closed="menuIsClosed"
|
:menu-is-closed="menuIsClosed"
|
||||||
@triggerToggleMenu="toggleMenu"
|
@triggerToggleMenu="toggleMenu"
|
||||||
/>
|
/>
|
||||||
<div v-else-if="!isLogged && authEnabled">
|
<div
|
||||||
|
v-else-if="!isLogged && authEnabled"
|
||||||
|
class="button-login-responsive"
|
||||||
|
>
|
||||||
<div class="desktop" data-test="button-login-desktop">
|
<div class="desktop" data-test="button-login-desktop">
|
||||||
<Link
|
<Link
|
||||||
type="external"
|
type="external"
|
||||||
@@ -98,6 +110,9 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="entry-lang-switcher">
|
||||||
|
<LangSwitcher />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -110,10 +125,13 @@ import { onClickOutside } from '@vueuse/core'
|
|||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { getAuthRoute, hasASessionCookieDecoded } from '@/utils/auth'
|
import { getAuthRoute, hasASessionCookieDecoded } from '@/utils/auth'
|
||||||
|
import { isAuth } from '../composables/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'
|
||||||
|
import LangSwitcher from '@/components/header/LangSwitcher.vue'
|
||||||
|
const { isLogged } = isAuth()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
defineProps({
|
defineProps({
|
||||||
@@ -131,10 +149,6 @@ function closeModal(): void {
|
|||||||
function toggleMenu(): void {
|
function toggleMenu(): void {
|
||||||
menuIsClosed.value = !menuIsClosed.value
|
menuIsClosed.value = !menuIsClosed.value
|
||||||
}
|
}
|
||||||
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')
|
||||||
@@ -187,13 +201,11 @@ const userName = computed((): string => {
|
|||||||
.wrapper-right-entries {
|
.wrapper-right-entries {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
div:first-child {
|
div {
|
||||||
margin-right: toRem(2);
|
margin-right: toRem(2);
|
||||||
}
|
}
|
||||||
div:last-child {
|
div:last-child {
|
||||||
.desktop {
|
margin-right: 0;
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.link-upload {
|
.link-upload {
|
||||||
@@ -208,7 +220,21 @@ const userName = computed((): string => {
|
|||||||
padding-left: toRem(0.5);
|
padding-left: toRem(0.5);
|
||||||
color: var(--blue);
|
color: var(--blue);
|
||||||
}
|
}
|
||||||
@media (max-width: toRem(76.8)) {
|
.entry-lang-switcher {
|
||||||
|
margin-left: toRem(2);
|
||||||
|
}
|
||||||
|
@media (max-width: toRem(102.4)) {
|
||||||
|
.nav {
|
||||||
|
padding-right: toRem(2);
|
||||||
|
padding-left: toRem(2);
|
||||||
|
}
|
||||||
|
.wrapper-right-entries {
|
||||||
|
div {
|
||||||
|
margin-right: toRem(1.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: toRem(84.8)) {
|
||||||
.nav {
|
.nav {
|
||||||
padding-right: toRem(2);
|
padding-right: toRem(2);
|
||||||
padding-left: toRem(2);
|
padding-left: toRem(2);
|
||||||
@@ -234,5 +260,8 @@ const userName = computed((): string => {
|
|||||||
.responsive {
|
.responsive {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
.wrapper-right-entries .button-login-responsive {
|
||||||
|
margin-right: toRem(-1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div :class="status">
|
<div :class="status">
|
||||||
<div class="wrapper-image">
|
<div class="wrapper-image">
|
||||||
<button
|
<button
|
||||||
:class="[{ selected }, 'button-image-item']"
|
:class="[{ selected: selectedOnMap }, 'button-image-item']"
|
||||||
:disabled="status === 'waiting-for-process'"
|
:disabled="status === 'waiting-for-process'"
|
||||||
type="button"
|
type="button"
|
||||||
@click="$emit('trigger')"
|
@click="$emit('trigger')"
|
||||||
@@ -33,18 +33,8 @@
|
|||||||
$t('pages.sequence.waiting_process')
|
$t('pages.sequence.waiting_process')
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-if="selectedOnMap" class="pointer-map">
|
||||||
v-if="selectedOnMap && !selected"
|
<img src="@/assets/images/logo-geovisio.svg" alt="" />
|
||||||
class="icon-img pointer-map"
|
|
||||||
></div>
|
|
||||||
<div v-if="selected && !selectedOnMap" class="icon-img button-check">
|
|
||||||
<i class="bi bi-check-lg" />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="selected && selectedOnMap"
|
|
||||||
class="icon-img button-check-pointer"
|
|
||||||
>
|
|
||||||
<i class="bi bi-check-lg" />
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="status.length && (status === 'ready' || status === 'hidden')"
|
v-if="status.length && (status === 'ready' || status === 'hidden')"
|
||||||
@@ -64,6 +54,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
<div class="geovisio-pointer">
|
||||||
|
<slot name="checkbox"></slot>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -75,7 +68,6 @@ defineProps({
|
|||||||
created: { type: String, default: null },
|
created: { type: String, default: null },
|
||||||
href: { type: String, default: null },
|
href: { type: String, default: null },
|
||||||
hrefHd: { type: String, default: null },
|
hrefHd: { type: String, default: null },
|
||||||
selected: { type: Boolean, default: false },
|
|
||||||
selectedOnMap: { type: Boolean, default: false },
|
selectedOnMap: { type: Boolean, default: false },
|
||||||
status: {
|
status: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -95,12 +87,16 @@ defineProps({
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.selected {
|
.selected {
|
||||||
border: toRem(0.1) solid var(--blue);
|
border: toRem(0.2) solid var(--blue);
|
||||||
border-radius: toRem(0.5);
|
border-radius: toRem(1.5);
|
||||||
box-shadow: 0px 4px 4px 0px #00000040;
|
box-shadow: 0px 4px 4px 0px #00000040;
|
||||||
|
.photo-img-wrapper {
|
||||||
|
padding: toRem(0.2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.wrapper-image {
|
.wrapper-image {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
.photo-img-wrapper {
|
.photo-img-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -115,7 +111,7 @@ defineProps({
|
|||||||
.photo-img {
|
.photo-img {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: toRem(0.5);
|
border-radius: toRem(1.5);
|
||||||
}
|
}
|
||||||
.icon-hidden {
|
.icon-hidden {
|
||||||
color: var(--grey-dark);
|
color: var(--grey-dark);
|
||||||
@@ -146,7 +142,7 @@ defineProps({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
@include text(xs-r-regular);
|
@include text(xss-regular);
|
||||||
padding: toRem(0.5) toRem(0.8);
|
padding: toRem(0.5) toRem(0.8);
|
||||||
background-color: var(--white);
|
background-color: var(--white);
|
||||||
border-radius: toRem(0.5);
|
border-radius: toRem(0.5);
|
||||||
@@ -164,9 +160,15 @@ defineProps({
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: toRem(1.3);
|
font-size: toRem(1.3);
|
||||||
}
|
}
|
||||||
.pointer-map,
|
.geovisio-pointer {
|
||||||
.button-check-pointer {
|
position: absolute;
|
||||||
background-color: var(--orange);
|
top: toRem(1.5);
|
||||||
|
right: toRem(1.5);
|
||||||
|
}
|
||||||
|
.pointer-map {
|
||||||
|
position: absolute;
|
||||||
|
top: toRem(1);
|
||||||
|
left: 0;
|
||||||
}
|
}
|
||||||
.delete-checked {
|
.delete-checked {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ h3 {
|
|||||||
}
|
}
|
||||||
.icon-block-img {
|
.icon-block-img {
|
||||||
margin-right: toRem(0.5);
|
margin-right: toRem(0.5);
|
||||||
height: toRem(2.2);
|
height: toRem(2);
|
||||||
}
|
}
|
||||||
.information-text {
|
.information-text {
|
||||||
margin-top: toRem(1);
|
margin-top: toRem(1);
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
ref="upload"
|
ref="upload"
|
||||||
type="file"
|
type="file"
|
||||||
multiple
|
multiple
|
||||||
:accept="accept"
|
|
||||||
@change="changeFile"
|
@change="changeFile"
|
||||||
|
@cancel="cancelUpload"
|
||||||
class="input-file"
|
class="input-file"
|
||||||
/>
|
/>
|
||||||
<i class="bi bi-cloud-upload-fill"></i>
|
<i class="bi bi-cloud-upload-fill"></i>
|
||||||
@@ -26,19 +26,24 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
const emit = defineEmits<{ (e: 'trigger', value: FileList): void }>()
|
const emit = defineEmits<{
|
||||||
|
(e: 'trigger', value: FileList): void
|
||||||
|
(e: 'triggerCancel'): void
|
||||||
|
}>()
|
||||||
let isDragging = ref<boolean>(false)
|
let isDragging = ref<boolean>(false)
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
text: { type: String, default: null },
|
text: { type: String, default: null },
|
||||||
textPictureType: { type: String, default: null },
|
textPictureType: { type: String, default: null },
|
||||||
textSecondPart: { type: String, default: null },
|
textSecondPart: { type: String, default: null }
|
||||||
accept: { type: String, default: '' }
|
|
||||||
})
|
})
|
||||||
|
|
||||||
interface HTMLInputChangeEvent extends Event {
|
interface HTMLInputChangeEvent extends Event {
|
||||||
target: HTMLInputElement & EventTarget
|
target: HTMLInputElement & EventTarget
|
||||||
}
|
}
|
||||||
|
function cancelUpload(): void {
|
||||||
|
if (navigator.userAgent.includes('Android')) emit('triggerCancel')
|
||||||
|
}
|
||||||
function changeFile(event: Event): void {
|
function changeFile(event: Event): void {
|
||||||
const { target } = event as HTMLInputChangeEvent
|
const { target } = event as HTMLInputChangeEvent
|
||||||
if (target && target.files) {
|
if (target && target.files) {
|
||||||
@@ -59,8 +64,9 @@ function drop(event: DragEvent): void | boolean {
|
|||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
const { dataTransfer } = event
|
const { dataTransfer } = event
|
||||||
if (dataTransfer && dataTransfer.files) {
|
if (dataTransfer && dataTransfer.files) {
|
||||||
if (!checkPicturesType(dataTransfer.files))
|
if (!checkPicturesType(dataTransfer.files)) {
|
||||||
return (isDragging.value = false)
|
return (isDragging.value = false)
|
||||||
|
}
|
||||||
emit('trigger', dataTransfer.files)
|
emit('trigger', dataTransfer.files)
|
||||||
isDragging.value = false
|
isDragging.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,9 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
import { getEnv } from '@/utils'
|
||||||
const instanceName = computed((): string | null => {
|
const instanceName = computed((): string | null => {
|
||||||
const instanceName = import.meta.env.VITE_INSTANCE_NAME
|
const instanceName = getEnv('VITE_INSTANCE_NAME')
|
||||||
if (instanceName) return instanceName
|
if (instanceName) return instanceName
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
@@ -43,7 +44,7 @@ const instanceName = computed((): string | null => {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: toRem(0.3);
|
top: toRem(0.3);
|
||||||
margin-left: toRem(0.5);
|
margin-left: toRem(0.5);
|
||||||
width: toRem(7);
|
width: toRem(7.3);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -79,6 +79,10 @@ function triggerButton() {
|
|||||||
&:hover {
|
&:hover {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
&.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.button {
|
.button {
|
||||||
height: toRem(4);
|
height: toRem(4);
|
||||||
|
|||||||
@@ -2,18 +2,23 @@
|
|||||||
<div class="tab">
|
<div class="tab">
|
||||||
<button
|
<button
|
||||||
:class="['tablinks', { selected: panelSelected === 'photos' }]"
|
:class="['tablinks', { selected: panelSelected === 'photos' }]"
|
||||||
|
:disabled="isLoading"
|
||||||
@click="$emit('trigger', 'photos')"
|
@click="$emit('trigger', 'photos')"
|
||||||
>
|
>
|
||||||
{{ $t('pages.sequence.button_panel_photos') }}
|
{{ $t('pages.sequence.button_panel_photos') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
v-if="isSequenceOwner"
|
||||||
:class="['tablinks', { selected: panelSelected === 'orientation' }]"
|
:class="['tablinks', { selected: panelSelected === 'orientation' }]"
|
||||||
|
:disabled="isLoading"
|
||||||
@click="$emit('trigger', 'orientation')"
|
@click="$emit('trigger', 'orientation')"
|
||||||
>
|
>
|
||||||
{{ $t('pages.sequence.button_panel_orientation') }}
|
{{ $t('pages.sequence.button_panel_orientation') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
v-if="isSequenceOwner"
|
||||||
:class="['tablinks', { selected: panelSelected === 'sort' }]"
|
:class="['tablinks', { selected: panelSelected === 'sort' }]"
|
||||||
|
:disabled="isLoading"
|
||||||
@click="$emit('trigger', 'sort')"
|
@click="$emit('trigger', 'sort')"
|
||||||
>
|
>
|
||||||
{{ $t('pages.sequence.button_panel_sort') }}
|
{{ $t('pages.sequence.button_panel_sort') }}
|
||||||
@@ -23,7 +28,9 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
defineProps({
|
defineProps({
|
||||||
panelSelected: { type: String, default: '' }
|
panelSelected: { type: String, default: '' },
|
||||||
|
isLoading: { type: Boolean, default: false },
|
||||||
|
isSequenceOwner: { type: Boolean, default: false }
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ defineProps({
|
|||||||
min-width: toRem(10);
|
min-width: toRem(10);
|
||||||
padding-right: toRem(1);
|
padding-right: toRem(1);
|
||||||
padding-left: toRem(1);
|
padding-left: toRem(1);
|
||||||
z-index: 1;
|
z-index: 3;
|
||||||
}
|
}
|
||||||
.button-close {
|
.button-close {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -6,8 +6,9 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { onMounted, onUnmounted, ref, computed } from 'vue'
|
import { onMounted, onUnmounted, ref, computed } from 'vue'
|
||||||
import { useSequenceStore } from '@/store/sequence'
|
import { useSequenceStore } from '@/store/sequence'
|
||||||
import { Viewer, StandaloneMap } from 'geovisio'
|
import { Viewer, StandaloneMap, Editor } from 'geovisio'
|
||||||
import { createUrlLink } from '@/utils'
|
import { createUrlLink, manageSlashUrl, getEnv } from '@/utils'
|
||||||
|
import { isAuth } from '../composables/auth'
|
||||||
import {
|
import {
|
||||||
createLink,
|
createLink,
|
||||||
createSequenceLink,
|
createSequenceLink,
|
||||||
@@ -15,23 +16,28 @@ import {
|
|||||||
} from '@/components-viewer/reportLink'
|
} from '@/components-viewer/reportLink'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { hasASessionCookieDecoded } from '@/utils/auth'
|
import { hasASessionCookieDecoded } from '@/utils/auth'
|
||||||
import type { ViewerInterface, MapInterface } from '@/views/interfaces/common'
|
import type {
|
||||||
|
ViewerInterface,
|
||||||
|
EditorInterface,
|
||||||
|
StandAloneInterface,
|
||||||
|
ParamsViewerInterface,
|
||||||
|
ParamsEditorStandaloneInterface
|
||||||
|
} from '@/views/interfaces/common'
|
||||||
const sequenceStore = useSequenceStore()
|
const sequenceStore = useSequenceStore()
|
||||||
const { t } = useI18n()
|
const { isLogged } = isAuth()
|
||||||
const emit = defineEmits<{ (e: 'triggerReady', value: boolean): void }>()
|
const { t, locale } = useI18n()
|
||||||
let mapIsLoaded = ref<boolean>(false)
|
let mapIsLoaded = ref<boolean>(false)
|
||||||
let viewer = ref()
|
let viewer = ref()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
id: { type: String, default: 'viewer' },
|
id: { type: String, default: 'viewer' },
|
||||||
fetchOptions: { type: Object, default: {} },
|
fetchOptions: { type: Object, default: {} },
|
||||||
geovisioViewer: { type: Boolean, default: true },
|
viewerType: { type: String, default: 'viewer' },
|
||||||
bbox: { type: Array, default: null },
|
bbox: { type: Array, default: null },
|
||||||
userId: { type: String, default: '' }
|
picId: { type: String, default: null },
|
||||||
})
|
seqId: { type: String, default: null },
|
||||||
const isLogged = computed((): boolean => {
|
userId: { type: String, default: '' },
|
||||||
const cookie = hasASessionCookieDecoded()
|
seqBruteDeg: { type: Number, default: 0 },
|
||||||
return !!(cookie && cookie.account)
|
roadDegrees: { type: Number, default: 0 }
|
||||||
})
|
})
|
||||||
const userName = computed((): string => {
|
const userName = computed((): string => {
|
||||||
const cookie = hasASessionCookieDecoded()
|
const cookie = hasASessionCookieDecoded()
|
||||||
@@ -52,15 +58,15 @@ async function getSequenceId(imgId: string): Promise<{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function createViewerButton(link: HTMLDivElement): void {
|
function createViewerButton(link: HTMLDivElement): void {
|
||||||
link.innerHTML = `<div>${createFullScreenButton()}</div>`
|
link.innerHTML = `<div id='instanceBtn'>${createFullScreenButton()}</div>`
|
||||||
viewer.value.addEventListener(
|
viewer.value.addEventListener(
|
||||||
'picture-loaded',
|
'psv:picture-loaded',
|
||||||
async (e: { detail: { picId: string } }): Promise<void> => {
|
async (e: { detail: { picId: string } }): Promise<void> => {
|
||||||
const sequenceInformation = await getSequenceId(e.detail.picId)
|
const sequenceInformation = await getSequenceId(e.detail.picId)
|
||||||
let href: string
|
let href: string
|
||||||
if (isLogged.value && sequenceInformation.username === userName.value) {
|
if (isLogged.value && sequenceInformation.username === userName.value) {
|
||||||
href = `${window.location.origin}/sequence/${sequenceInformation.sequenceId}?currentPic=${e.detail.picId}`
|
href = `${window.location.origin}/sequence/${sequenceInformation.sequenceId}?currentPic=${e.detail.picId}`
|
||||||
link.innerHTML = `<div>
|
link.innerHTML = `<div id='instanceBtn'>
|
||||||
${createFullScreenButton()}
|
${createFullScreenButton()}
|
||||||
${createSequenceLink(
|
${createSequenceLink(
|
||||||
href,
|
href,
|
||||||
@@ -73,7 +79,7 @@ function createViewerButton(link: HTMLDivElement): void {
|
|||||||
picId: e.detail.picId,
|
picId: e.detail.picId,
|
||||||
link: createUrlLink(e.detail.picId)
|
link: createUrlLink(e.detail.picId)
|
||||||
})
|
})
|
||||||
link.innerHTML = `<div>
|
link.innerHTML = `<div id='instanceBtn'>
|
||||||
${createFullScreenButton()}
|
${createFullScreenButton()}
|
||||||
${createLink(
|
${createLink(
|
||||||
href,
|
href,
|
||||||
@@ -84,115 +90,146 @@ function createViewerButton(link: HTMLDivElement): void {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
async function setupViewerMap(tiles: string): Promise<void> {
|
function setupViewer(params: ParamsViewerInterface): void {
|
||||||
const maxZoom = import.meta.env.VITE_MAX_ZOOM
|
const maxZoom = getEnv('VITE_MAX_ZOOM')
|
||||||
const zoom = import.meta.env.VITE_ZOOM
|
const zoom = getEnv('VITE_ZOOM')
|
||||||
const center = import.meta.env.VITE_CENTER
|
const center = getEnv('VITE_CENTER')
|
||||||
const raster = import.meta.env.VITE_RASTER_TILE
|
const reportLink = document.createElement('div')
|
||||||
let paramsViewer: ViewerInterface = { map: { startWide: true } }
|
reportLink.className = 'gvs-group gvs-group-large gvs-group-btnpanel'
|
||||||
if (raster && raster !== '') {
|
let paramsViewer: ViewerInterface = {
|
||||||
|
...params,
|
||||||
|
widgets: { customWidget: reportLink },
|
||||||
|
map: { startWide: true }
|
||||||
|
}
|
||||||
|
if (params.map && params.map.raster) {
|
||||||
paramsViewer = {
|
paramsViewer = {
|
||||||
|
...paramsViewer,
|
||||||
map: {
|
map: {
|
||||||
...paramsViewer.map,
|
...paramsViewer.map,
|
||||||
raster: JSON.parse(raster)
|
raster: params.map.raster
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (center && center !== '') {
|
if (center && center.length > 0) {
|
||||||
const centerMap = center.split(',').map((el: string) => parseInt(el))
|
const centerMap = center.split(',').map((el: string) => parseInt(el))
|
||||||
paramsViewer = {
|
paramsViewer = {
|
||||||
|
...paramsViewer,
|
||||||
map: {
|
map: {
|
||||||
...paramsViewer.map,
|
...paramsViewer.map,
|
||||||
center: centerMap
|
center: centerMap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (zoom && zoom.length) {
|
if (zoom && zoom.length > 0) {
|
||||||
paramsViewer = {
|
paramsViewer = {
|
||||||
|
...paramsViewer,
|
||||||
map: {
|
map: {
|
||||||
...paramsViewer.map,
|
...paramsViewer.map,
|
||||||
zoom: parseFloat(zoom)
|
zoom: parseFloat(zoom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (maxZoom && maxZoom.length) {
|
if (maxZoom && maxZoom.length > 0) {
|
||||||
paramsViewer = {
|
paramsViewer = {
|
||||||
|
...paramsViewer,
|
||||||
map: {
|
map: {
|
||||||
...paramsViewer.map,
|
...paramsViewer.map,
|
||||||
maxZoom: parseInt(maxZoom)
|
maxZoom: parseInt(maxZoom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (tiles && tiles.length) {
|
|
||||||
paramsViewer = {
|
|
||||||
map: {
|
|
||||||
...paramsViewer.map,
|
|
||||||
style: tiles
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (props.fetchOptions) {
|
|
||||||
paramsViewer = {
|
|
||||||
...paramsViewer,
|
|
||||||
...props.fetchOptions
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const reportLink = document.createElement('div')
|
|
||||||
reportLink.className = 'gvs-group gvs-group-large gvs-group-btnpanel'
|
|
||||||
viewer.value = new Viewer(
|
viewer.value = new Viewer(
|
||||||
'viewer', // Div ID
|
'viewer', // Div ID
|
||||||
`${import.meta.env.VITE_API_URL}/api/search`,
|
`${manageSlashUrl()}api`,
|
||||||
{
|
{
|
||||||
...paramsViewer,
|
...paramsViewer
|
||||||
widgets: { customWidget: reportLink }
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if (viewer.value && viewer.value.addEventListener) {
|
if (viewer.value && viewer.value.addEventListener) {
|
||||||
createViewerButton(reportLink)
|
createViewerButton(reportLink)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function setupMap(tiles: string): Promise<void> {
|
function setupStandAlone(params: ParamsViewerInterface): void {
|
||||||
let paramsMap: MapInterface
|
let paramsMap: StandAloneInterface = {
|
||||||
paramsMap = { users: [props.userId], minZoom: 7 }
|
...params,
|
||||||
if (tiles && tiles.length) {
|
padding: { top: 70, bottom: 70, left: 70, right: 70 },
|
||||||
paramsMap = {
|
minZoom: 7,
|
||||||
...paramsMap,
|
maxZoom: 14,
|
||||||
style: tiles
|
speed: 10,
|
||||||
}
|
zoom: 14
|
||||||
}
|
}
|
||||||
|
if (props.userId) paramsMap = { ...paramsMap, users: [props.userId] }
|
||||||
const bbox = [props.bbox[0], props.bbox[1], props.bbox[2], props.bbox[3]]
|
const bbox = [props.bbox[0], props.bbox[1], props.bbox[2], props.bbox[3]]
|
||||||
viewer.value = new StandaloneMap(
|
viewer.value = new StandaloneMap(
|
||||||
props.id, // Div ID
|
props.id, // Div ID
|
||||||
`${import.meta.env.VITE_API_URL}/api/search`,
|
`${manageSlashUrl()}api`,
|
||||||
{
|
{
|
||||||
...paramsMap,
|
...paramsMap,
|
||||||
bounds: bbox,
|
bounds: bbox
|
||||||
zoom: 14
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
viewer.value.addEventListener('ready', () => {
|
|
||||||
viewer.value.fitBounds(bbox, {
|
|
||||||
padding: { top: 70, bottom: 70, left: 70, right: 70 },
|
|
||||||
maxZoom: 14,
|
|
||||||
speed: 10
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
onMounted(async (): Promise<void> => {
|
function setupEditor(params: ParamsEditorStandaloneInterface): void {
|
||||||
const tiles = import.meta.env.VITE_TILES
|
let paramsMap: EditorInterface = { ...params, minZoom: 7 }
|
||||||
|
if (props.seqId) paramsMap = { ...paramsMap, selectedSequence: props.seqId }
|
||||||
|
if (props.userId) paramsMap = { ...paramsMap, users: [props.userId] }
|
||||||
try {
|
try {
|
||||||
if (props.geovisioViewer) await setupViewerMap(tiles)
|
viewer.value = new Editor(
|
||||||
else await setupMap(tiles)
|
'viewer', // Div ID
|
||||||
|
`${manageSlashUrl()}api`,
|
||||||
|
{
|
||||||
|
...paramsMap
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function initViewer(): Promise<void> {
|
||||||
|
const tiles = getEnv('VITE_TILES')
|
||||||
|
const rasterTile = getEnv('VITE_RASTER_TILE')
|
||||||
|
let params: ParamsViewerInterface | ParamsEditorStandaloneInterface = {
|
||||||
|
lang: locale.value
|
||||||
|
}
|
||||||
|
if (rasterTile && rasterTile.length > 0) {
|
||||||
|
const raster = JSON.parse(rasterTile)
|
||||||
|
if (props.viewerType !== 'viewer') params = { ...params, raster }
|
||||||
|
else params = { ...params, map: { raster } }
|
||||||
|
}
|
||||||
|
if (tiles && tiles.length) {
|
||||||
|
params = { ...params, style: await fetchTiles(tiles) }
|
||||||
|
}
|
||||||
|
if (props.picId && props.picId.length) {
|
||||||
|
params = { ...params, selectedPicture: props.picId }
|
||||||
|
}
|
||||||
|
if (props.fetchOptions && props.fetchOptions.credentials) {
|
||||||
|
params = { ...params, fetchOptions: { ...props.fetchOptions } }
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (props.viewerType === 'standAlone') setupStandAlone(params)
|
||||||
|
else if (props.viewerType === 'editor') setupEditor(params)
|
||||||
|
else setupViewer(params)
|
||||||
mapIsLoaded.value = true
|
mapIsLoaded.value = true
|
||||||
emit('triggerReady', mapIsLoaded.value)
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
mapIsLoaded.value = true
|
mapIsLoaded.value = true
|
||||||
console.log(err)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
initViewer()
|
||||||
})
|
})
|
||||||
onUnmounted((): void => {
|
onUnmounted((): void => {
|
||||||
if (viewer.value && props.geovisioViewer) viewer.value.destroy()
|
if (viewer.value && props.viewerType) {
|
||||||
|
viewer.value.addEventListener('psv:picture-loaded', (): void => {
|
||||||
|
viewer.value.destroy()
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
async function fetchTiles(tiles: string): Promise<void> {
|
||||||
|
return fetch(tiles)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((style) => style)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
113
src/components/header/LangSwitcher.vue
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
<template>
|
||||||
|
<div class="dropdown">
|
||||||
|
<img :src="img(`${locale}.svg`)" loading="lazy" class="img-lang" />
|
||||||
|
<span class="desktop">{{ locale.toUpperCase() }}</span>
|
||||||
|
<div class="image-chevron desktop">
|
||||||
|
<i class="bi bi-chevron-down" />
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-block">
|
||||||
|
<button
|
||||||
|
v-for="lang in allLocales"
|
||||||
|
@click.native="changeLocale(lang)"
|
||||||
|
class="item-lang"
|
||||||
|
>
|
||||||
|
<img :src="formatLangListUrl(lang)" loading="lazy" class="img-lang" />
|
||||||
|
<span>{{ lang.toUpperCase() }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { img } from '../../utils/image'
|
||||||
|
import { useCookies } from 'vue3-cookies'
|
||||||
|
import i18n from '../../i18n'
|
||||||
|
const { locale, messages } = useI18n()
|
||||||
|
const { cookies } = useCookies()
|
||||||
|
const displayed = ref<boolean>(true)
|
||||||
|
const allLocales = ref<string[]>([])
|
||||||
|
onMounted(() => {
|
||||||
|
Object.keys(messages.value).forEach(function (key) {
|
||||||
|
if (locale.value !== key) allLocales.value = [...allLocales.value, key]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
function changeLocale(lang: string): void {
|
||||||
|
const index = allLocales.value.findIndex((el) => el === lang)
|
||||||
|
allLocales.value[index] = locale.value
|
||||||
|
cookies.set('lang', lang)
|
||||||
|
locale.value = cookies.get('lang')
|
||||||
|
const html = document.querySelector('html')
|
||||||
|
if (html) html.setAttribute('lang', locale.value)
|
||||||
|
localStorage.setItem('user-locale', locale.value)
|
||||||
|
i18n.global.locale.value = locale.value
|
||||||
|
displayed.value = false
|
||||||
|
location.reload()
|
||||||
|
}
|
||||||
|
function formatLangListUrl(lang: string): string {
|
||||||
|
return img(`${lang}.svg`)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.dropdown {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
height: toRem(2);
|
||||||
|
}
|
||||||
|
.dropdown:hover > .dropdown-block {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.dropdown:hover > .image-chevron {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
.dropdown-block {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
background-color: var(--white);
|
||||||
|
border-radius: toRem(0.5);
|
||||||
|
width: fit-content;
|
||||||
|
left: toRem(-1);
|
||||||
|
top: toRem(2);
|
||||||
|
padding: toRem(0.5);
|
||||||
|
}
|
||||||
|
.img-lang {
|
||||||
|
height: toRem(1.5);
|
||||||
|
margin-right: toRem(0.5);
|
||||||
|
}
|
||||||
|
.desktop {
|
||||||
|
@include text(xs-r-regular);
|
||||||
|
}
|
||||||
|
.image-chevron {
|
||||||
|
margin-left: toRem(0.5);
|
||||||
|
}
|
||||||
|
.item-lang {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
background-color: var(--white);
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
@include text(xs-r-regular);
|
||||||
|
margin-bottom: toRem(0.5);
|
||||||
|
}
|
||||||
|
@media (max-width: toRem(50)) {
|
||||||
|
.desktop {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.img-lang {
|
||||||
|
height: toRem(2);
|
||||||
|
}
|
||||||
|
.dropdown-block {
|
||||||
|
left: toRem(-2);
|
||||||
|
top: toRem(3);
|
||||||
|
padding: toRem(0.5);
|
||||||
|
}
|
||||||
|
.item-lang {
|
||||||
|
margin-bottom: toRem(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
:road-degrees="roadDegrees"
|
:road-degrees="roadDegrees"
|
||||||
:seq-brute-deg="angleValue"
|
:seq-brute-deg="angleValue"
|
||||||
@triggerAngle="captureAngle"
|
@triggerAngle="captureAngle"
|
||||||
|
@triggerMovingAngle="triggerMovingAngle"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="entry-button">
|
<div class="entry-button">
|
||||||
@@ -49,13 +50,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watchEffect } from 'vue'
|
import { ref, watchEffect, onMounted, onUnmounted } from 'vue'
|
||||||
import InformationCard from '@/components/InformationCard.vue'
|
import InformationCard from '@/components/InformationCard.vue'
|
||||||
import Button from '@/components/Button.vue'
|
import Button from '@/components/Button.vue'
|
||||||
import Input from '@/components/Input.vue'
|
import Input from '@/components/Input.vue'
|
||||||
import WidgetOrientation from '@/components/sequence/WidgetOrientation.vue'
|
import WidgetOrientation from '@/components/sequence/WidgetOrientation.vue'
|
||||||
|
import { modulo180 } from '@/utils/calc'
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'triggerAngle', value: number): void
|
(e: 'triggerAngle', value: number): void
|
||||||
|
(e: 'triggerMovingAngle', value: number): void
|
||||||
}>()
|
}>()
|
||||||
let angleValue = ref<number>(0)
|
let angleValue = ref<number>(0)
|
||||||
let angleInputValue = ref<number>(0)
|
let angleInputValue = ref<number>(0)
|
||||||
@@ -65,6 +69,13 @@ const props = defineProps({
|
|||||||
seqBruteDeg: { type: Number, default: 0 },
|
seqBruteDeg: { type: Number, default: 0 },
|
||||||
isLoading: { type: Boolean, default: false }
|
isLoading: { type: Boolean, default: false }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
triggerMovingAngle(angleInputValue.value)
|
||||||
|
})
|
||||||
|
onUnmounted(() => {
|
||||||
|
triggerMovingAngle(angleInputValue.value)
|
||||||
|
})
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
angleValue.value = props.seqBruteDeg
|
angleValue.value = props.seqBruteDeg
|
||||||
angleInputValue.value = Math.round(props.seqBruteDeg - props.roadDegrees)
|
angleInputValue.value = Math.round(props.seqBruteDeg - props.roadDegrees)
|
||||||
@@ -77,8 +88,13 @@ function captureAngle(value: number | string) {
|
|||||||
const valueNum = Number(value)
|
const valueNum = Number(value)
|
||||||
angleInputValue.value = valueNum
|
angleInputValue.value = valueNum
|
||||||
angleValue.value = valueNum + props.roadDegrees
|
angleValue.value = valueNum + props.roadDegrees
|
||||||
|
const movingAngle = modulo180(angleValue.value, Math.round(props.roadDegrees))
|
||||||
|
emit('triggerMovingAngle', movingAngle)
|
||||||
if (isDisabled(valueNum)) return (errorAngleValue.value = true)
|
if (isDisabled(valueNum)) return (errorAngleValue.value = true)
|
||||||
}
|
}
|
||||||
|
function triggerMovingAngle(value: number) {
|
||||||
|
emit('triggerMovingAngle', value)
|
||||||
|
}
|
||||||
function triggerAngle() {
|
function triggerAngle() {
|
||||||
const valueToSend = angleValue.value - Number(props.roadDegrees)
|
const valueToSend = angleValue.value - Number(props.roadDegrees)
|
||||||
if (isDisabled(valueToSend)) return
|
if (isDisabled(valueToSend)) return
|
||||||
|
|||||||
@@ -40,6 +40,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="informationCardDisplayed" class="entry-info-card">
|
||||||
|
<InformationCard look="blue">
|
||||||
|
<template v-slot:title>
|
||||||
|
<span class="msg-info">
|
||||||
|
{{ $t('pages.sequence.info_msg_maj') }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-slot:cross>
|
||||||
|
<Button
|
||||||
|
icon="bi bi-x-lg"
|
||||||
|
look="no-text-blue-dark"
|
||||||
|
@trigger="informationCardDisplayed = false"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</InformationCard>
|
||||||
|
</div>
|
||||||
<ul class="photo-list">
|
<ul class="photo-list">
|
||||||
<li v-for="(item, i) in pictures" :id="`el-list${i}`" class="photo-item">
|
<li v-for="(item, i) in pictures" :id="`el-list${i}`" class="photo-item">
|
||||||
<ImageItem
|
<ImageItem
|
||||||
@@ -52,7 +68,16 @@
|
|||||||
imageStatus(item.properties['geovisio:status'], sequence.status)
|
imageStatus(item.properties['geovisio:status'], sequence.status)
|
||||||
"
|
"
|
||||||
@trigger="triggerSelectImageAndMove(item)"
|
@trigger="triggerSelectImageAndMove(item)"
|
||||||
/>
|
>
|
||||||
|
<template v-slot:checkbox>
|
||||||
|
<InputCheckbox
|
||||||
|
:is-checked="photoToDeleteOrPatchSelected(item, picturesToDelete)"
|
||||||
|
:name="item.id"
|
||||||
|
:is-indeterminate="false"
|
||||||
|
@trigger.self="triggerInputCheckItem"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</ImageItem>
|
||||||
</li>
|
</li>
|
||||||
<div class="entry-pagination">
|
<div class="entry-pagination">
|
||||||
<Pagination
|
<Pagination
|
||||||
@@ -69,13 +94,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import type { PropType } from 'vue'
|
import type { PropType } from 'vue'
|
||||||
import Pagination from '@/components/Pagination.vue'
|
import Pagination from '@/components/Pagination.vue'
|
||||||
import InputCheckbox from '@/components/InputCheckbox.vue'
|
|
||||||
import ImageItem from '@/components/ImageItem.vue'
|
import ImageItem from '@/components/ImageItem.vue'
|
||||||
import Button from '@/components/Button.vue'
|
import Button from '@/components/Button.vue'
|
||||||
|
import InputCheckbox from '@/components/InputCheckbox.vue'
|
||||||
|
import InformationCard from '@/components/InformationCard.vue'
|
||||||
import { formatDate } from '@/utils/dates'
|
import { formatDate } from '@/utils/dates'
|
||||||
import {
|
import {
|
||||||
imageStatus,
|
imageStatus,
|
||||||
@@ -90,11 +116,12 @@ import type {
|
|||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'triggerInputCheck', value: CheckboxInterface): void
|
(e: 'triggerInputCheck', value: CheckboxInterface): void
|
||||||
|
(e: 'triggerInputCheckItem', value: string): void
|
||||||
(e: 'triggerGoToNextPage', value: string): void
|
(e: 'triggerGoToNextPage', value: string): void
|
||||||
(e: 'triggerSelectImageAndMove', value: ResponseUserPhotoInterface): void
|
(e: 'triggerSelectImageAndMove', value: ResponseUserPhotoInterface): void
|
||||||
(e: 'triggerPatchOrDeleteCollectionItems', value: string): void
|
(e: 'triggerPatchOrDeleteCollectionItems', value: string): void
|
||||||
}>()
|
}>()
|
||||||
|
let informationCardDisplayed = ref<boolean>(true)
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
pictures: {
|
pictures: {
|
||||||
type: Array as PropType<ResponseUserPhotoInterface[]>,
|
type: Array as PropType<ResponseUserPhotoInterface[]>,
|
||||||
@@ -159,6 +186,9 @@ function triggerSelectImageAndMove(value: ResponseUserPhotoInterface): void {
|
|||||||
function triggerPatchOrDeleteCollectionItems(value: string): void {
|
function triggerPatchOrDeleteCollectionItems(value: string): void {
|
||||||
emit('triggerPatchOrDeleteCollectionItems', value)
|
emit('triggerPatchOrDeleteCollectionItems', value)
|
||||||
}
|
}
|
||||||
|
function triggerInputCheckItem(value: CheckboxInterface): void {
|
||||||
|
emit('triggerInputCheckItem', value.name)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -190,6 +220,12 @@ function triggerPatchOrDeleteCollectionItems(value: string): void {
|
|||||||
margin-right: toRem(1);
|
margin-right: toRem(1);
|
||||||
margin-left: toRem(1);
|
margin-left: toRem(1);
|
||||||
}
|
}
|
||||||
|
.msg-info {
|
||||||
|
@include text(xs-r-regular);
|
||||||
|
}
|
||||||
|
.entry-info-card {
|
||||||
|
padding: toRem(1);
|
||||||
|
}
|
||||||
.photo-list {
|
.photo-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -202,7 +238,7 @@ function triggerPatchOrDeleteCollectionItems(value: string): void {
|
|||||||
width: calc(33% - #{toRem(2)});
|
width: calc(33% - #{toRem(2)});
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
margin: toRem(1);
|
margin: toRem(1);
|
||||||
border-radius: toRem(0.5);
|
border-radius: toRem(1.5);
|
||||||
background-color: var(--grey);
|
background-color: var(--grey);
|
||||||
}
|
}
|
||||||
.no-photo {
|
.no-photo {
|
||||||
@@ -213,6 +249,7 @@ function triggerPatchOrDeleteCollectionItems(value: string): void {
|
|||||||
}
|
}
|
||||||
.entry-pagination {
|
.entry-pagination {
|
||||||
margin-top: toRem(2);
|
margin-top: toRem(2);
|
||||||
|
margin-bottom: toRem(2);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -223,6 +260,9 @@ function triggerPatchOrDeleteCollectionItems(value: string): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: toRem(76.8)) {
|
@media (max-width: toRem(76.8)) {
|
||||||
|
.entry-info-card {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
.delete-all {
|
.delete-all {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
<Button
|
<Button
|
||||||
look="button--blue"
|
look="button--blue"
|
||||||
type="submit"
|
type="submit"
|
||||||
:text="$t('pages.sequence.orientation_panel_button')"
|
:text="$t('pages.sequence.sort_panel_button')"
|
||||||
:disabled="sortValue.length === 0"
|
:disabled="sortValue.length === 0"
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -30,7 +30,22 @@
|
|||||||
@mousedown="mousedown"
|
@mousedown="mousedown"
|
||||||
@mousemove="handleMouseMove"
|
@mousemove="handleMouseMove"
|
||||||
@mouseup="mouseup"
|
@mouseup="mouseup"
|
||||||
class="cursor-img"
|
@mouseout="mouseup"
|
||||||
|
class="cursor-img desktop"
|
||||||
|
>
|
||||||
|
<button class="arrow-img arrow-img-1" @click="clickAndMove(45)">
|
||||||
|
<img src="@/assets/images/icon/cursor-arrow.svg" alt="" />
|
||||||
|
</button>
|
||||||
|
<button class="arrow-img arrow-img-2" @click="clickAndMove(-45)">
|
||||||
|
<img src="@/assets/images/icon/cursor-arrow.svg" alt="" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="rotate"
|
||||||
|
@touchstart="touchdown"
|
||||||
|
@touchmove="handleTouchMove"
|
||||||
|
@touchend="mouseup"
|
||||||
|
class="cursor-img responsive"
|
||||||
>
|
>
|
||||||
<button class="arrow-img arrow-img-1" @click="clickAndMove(45)">
|
<button class="arrow-img arrow-img-1" @click="clickAndMove(45)">
|
||||||
<img src="@/assets/images/icon/cursor-arrow.svg" alt="" />
|
<img src="@/assets/images/icon/cursor-arrow.svg" alt="" />
|
||||||
@@ -48,6 +63,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref, watchEffect } from 'vue'
|
import { onMounted, ref, watchEffect } from 'vue'
|
||||||
|
import { modulo180 } from '@/utils/calc'
|
||||||
let angleValue = ref<number>(0)
|
let angleValue = ref<number>(0)
|
||||||
let angle = ref<number>(0)
|
let angle = ref<number>(0)
|
||||||
let prevRotation = ref<number>(0)
|
let prevRotation = ref<number>(0)
|
||||||
@@ -60,6 +76,7 @@ let center: { x: number; y: number } = { x: 0, y: 0 }
|
|||||||
const R2D: number = 180 / Math.PI
|
const R2D: number = 180 / Math.PI
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'triggerAngle', value: number): void
|
(e: 'triggerAngle', value: number): void
|
||||||
|
(e: 'triggerMovingAngle', value: number): void
|
||||||
}>()
|
}>()
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
roadDegrees: { type: Number, default: 0 },
|
roadDegrees: { type: Number, default: 0 },
|
||||||
@@ -74,6 +91,17 @@ onMounted(() => {
|
|||||||
rotate.value = document.getElementById('rotate')
|
rotate.value = document.getElementById('rotate')
|
||||||
if (app) app.addEventListener('mouseup', () => (active.value = false))
|
if (app) app.addEventListener('mouseup', () => (active.value = false))
|
||||||
})
|
})
|
||||||
|
function touchdown(e: TouchEvent): void {
|
||||||
|
e.preventDefault()
|
||||||
|
if (!rotateWrapper.value) return
|
||||||
|
const bb = rotateWrapper.value.getBoundingClientRect()
|
||||||
|
const { top: t, left: l, height: h, width: w } = bb
|
||||||
|
center = { x: l + w / 2, y: t + h / 2 }
|
||||||
|
let x = e.changedTouches[0].clientX - center.x
|
||||||
|
let y = e.changedTouches[0].clientY - center.y
|
||||||
|
startAngle.value = R2D * Math.atan2(y, x)
|
||||||
|
active.value = true
|
||||||
|
}
|
||||||
function mousedown(e: MouseEvent): void {
|
function mousedown(e: MouseEvent): void {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (!rotateWrapper.value) return
|
if (!rotateWrapper.value) return
|
||||||
@@ -85,8 +113,16 @@ function mousedown(e: MouseEvent): void {
|
|||||||
startAngle.value = R2D * Math.atan2(y, x)
|
startAngle.value = R2D * Math.atan2(y, x)
|
||||||
active.value = true
|
active.value = true
|
||||||
}
|
}
|
||||||
|
function handleTouchMove(e: TouchEvent): void {
|
||||||
|
if (!active.value || !rotateWrapper.value) return
|
||||||
|
const x = e.changedTouches[0].clientX - center.x
|
||||||
|
const y = e.changedTouches[0].clientY - center.y
|
||||||
|
const d = R2D * Math.atan2(y, x)
|
||||||
|
rotation.value = d - startAngle.value
|
||||||
|
const calc = angle.value + rotation.value
|
||||||
|
rotateWrapper.value.style.transform = `rotate(${calc}deg)`
|
||||||
|
}
|
||||||
function handleMouseMove(e: MouseEvent): void {
|
function handleMouseMove(e: MouseEvent): void {
|
||||||
e.preventDefault()
|
|
||||||
if (!active.value || !rotateWrapper.value) return
|
if (!active.value || !rotateWrapper.value) return
|
||||||
const x = e.clientX - center.x
|
const x = e.clientX - center.x
|
||||||
const y = e.clientY - center.y
|
const y = e.clientY - center.y
|
||||||
@@ -102,6 +138,7 @@ function mouseup(): void {
|
|||||||
prevRotation.value = rotation.value
|
prevRotation.value = rotation.value
|
||||||
angleValue.value = angle.value
|
angleValue.value = angle.value
|
||||||
if (angleValue.value !== 0) {
|
if (angleValue.value !== 0) {
|
||||||
|
emit('triggerMovingAngle', modulo180(angle.value, props.roadDegrees))
|
||||||
emit('triggerAngle', modulo180(angle.value, props.roadDegrees))
|
emit('triggerAngle', modulo180(angle.value, props.roadDegrees))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,18 +147,14 @@ function mouseup(): void {
|
|||||||
function clickAndMove(value: number): void {
|
function clickAndMove(value: number): void {
|
||||||
const moduloAngle = modulo180(angle.value, Math.round(props.roadDegrees))
|
const moduloAngle = modulo180(angle.value, Math.round(props.roadDegrees))
|
||||||
if (moduloAngle % 45 === 0) {
|
if (moduloAngle % 45 === 0) {
|
||||||
return emit('triggerAngle', moduloAngle + value)
|
let angleToEmit = moduloAngle + value
|
||||||
|
if (angleToEmit > 180) angleToEmit = -135
|
||||||
|
if (angleToEmit < -180) angleToEmit = 135
|
||||||
|
return emit('triggerAngle', angleToEmit)
|
||||||
}
|
}
|
||||||
let closestMultiple = Math.ceil(moduloAngle / 45) * value
|
let closestMultiple = Math.ceil(moduloAngle / 45) * value
|
||||||
return emit('triggerAngle', closestMultiple)
|
return emit('triggerAngle', closestMultiple)
|
||||||
}
|
}
|
||||||
|
|
||||||
function modulo180(value: number, roadDegrees: number): number {
|
|
||||||
let moduloAngle = (value - roadDegrees) % 360
|
|
||||||
if (moduloAngle < -180) moduloAngle += 360
|
|
||||||
if (moduloAngle > 180) moduloAngle -= 360
|
|
||||||
return Math.round(moduloAngle)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -192,6 +225,12 @@ function modulo180(value: number, roadDegrees: number): number {
|
|||||||
background-position: center;
|
background-position: center;
|
||||||
cursor: move;
|
cursor: move;
|
||||||
}
|
}
|
||||||
|
.desktop {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.responsive {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.arrow-img {
|
.arrow-img {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -229,4 +268,34 @@ function modulo180(value: number, roadDegrees: number): number {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: toRem(76.8)) {
|
||||||
|
.rotate-wrapper {
|
||||||
|
.desktop {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.responsive {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.rotate-wrapper {
|
||||||
|
width: 75%;
|
||||||
|
height: 75%;
|
||||||
|
}
|
||||||
|
.arrow-img {
|
||||||
|
height: toRem(4);
|
||||||
|
width: toRem(4);
|
||||||
|
}
|
||||||
|
.arrow-img-1 {
|
||||||
|
right: toRem(3);
|
||||||
|
top: toRem(4.5);
|
||||||
|
}
|
||||||
|
.arrow-img-2 {
|
||||||
|
left: toRem(2.4);
|
||||||
|
bottom: toRem(7.88);
|
||||||
|
}
|
||||||
|
.car-img {
|
||||||
|
height: 40%;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -34,33 +34,64 @@
|
|||||||
<span>{{ $t('pages.upload.images_count_text') }}</span>
|
<span>{{ $t('pages.upload.images_count_text') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="loaded-block">
|
<div v-else-if="uploadError" class="loaded-block error">
|
||||||
|
<div class="error-block">
|
||||||
|
<i class="bi bi-x-octagon"></i>
|
||||||
|
<p>{{ uploadError }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="loaded-block success">
|
||||||
<img src="@/assets/images/success.svg" alt="" />
|
<img src="@/assets/images/success.svg" alt="" />
|
||||||
<p>{{ $t('pages.upload.upload_done') }}</p>
|
<p>{{ $t('pages.upload.upload_done') }}</p>
|
||||||
|
<span
|
||||||
|
v-if="!uploadedSequence.picturesOnError.length && otherFilesCount > 0"
|
||||||
|
class="other-files"
|
||||||
|
>{{
|
||||||
|
$t('pages.upload.upload_other_files', { count: otherFilesCount })
|
||||||
|
}}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-if="uploadedSequence?.picturesOnError?.length" class="error-wrapper">
|
||||||
v-if="uploadedSequence && uploadedSequence.picturesOnError.length"
|
|
||||||
class="error-wrapper"
|
|
||||||
>
|
|
||||||
<div class="error-corpus">
|
<div class="error-corpus">
|
||||||
<i class="bi bi-exclamation-triangle"></i>
|
<div>
|
||||||
<span class="error-text">{{
|
<i class="bi bi-exclamation-triangle"></i>
|
||||||
$t('pages.upload.pictures_error', {
|
<span class="error-text">{{
|
||||||
count: uploadedSequence.picturesOnError.length
|
$t('pages.upload.pictures_error', {
|
||||||
})
|
count: uploadedSequence.picturesOnError.length
|
||||||
}}</span>
|
})
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="userAgentAndroid" class="entry-tutorial-error-exif">
|
||||||
|
<span class="other-files">{{
|
||||||
|
$t('pages.sequence.sequence_tutorial_exif_text')
|
||||||
|
}}</span>
|
||||||
|
<img
|
||||||
|
src="@/assets/images/tutorial-upload-loc.jpg"
|
||||||
|
alt=""
|
||||||
|
class="img-tutorial-error-exif"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src="@/assets/images/tutorial-upload-loc-2.jpg"
|
||||||
|
alt=""
|
||||||
|
class="img-tutorial-error-exif first-tutorial-img-error"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
:text="$t('pages.upload.error_button')"
|
:text="$t('pages.upload.error_button')"
|
||||||
look="button button--red"
|
look="button button--red"
|
||||||
@trigger="$emit('triggerModal')"
|
@trigger="$emit('triggerModal')"
|
||||||
/>
|
/>
|
||||||
|
<span v-if="otherFilesCount > 0" class="other-files">{{
|
||||||
|
$t('pages.upload.upload_other_files', { count: otherFilesCount })
|
||||||
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import Loader from '@/components/Loader.vue'
|
import Loader from '@/components/Loader.vue'
|
||||||
import Link from '@/components/Link.vue'
|
import Link from '@/components/Link.vue'
|
||||||
import Button from '@/components/Button.vue'
|
import Button from '@/components/Button.vue'
|
||||||
@@ -74,8 +105,14 @@ defineProps({
|
|||||||
type: Object as PropType<SequenceInterface | null>,
|
type: Object as PropType<SequenceInterface | null>,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
picturesCount: { type: Number, default: null }
|
picturesCount: { type: Number, default: null },
|
||||||
|
otherFilesCount: { type: Number, default: null },
|
||||||
|
uploadError: { type: String, default: null }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const userAgentAndroid = computed<boolean>(() =>
|
||||||
|
navigator.userAgent.includes('Android')
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -151,8 +188,12 @@ defineProps({
|
|||||||
margin-bottom: toRem(1);
|
margin-bottom: toRem(1);
|
||||||
}
|
}
|
||||||
.error-text {
|
.error-text {
|
||||||
|
font-weight: bold;
|
||||||
margin-left: toRem(0.5);
|
margin-left: toRem(0.5);
|
||||||
}
|
}
|
||||||
|
.entry-tutorial-error-exif {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
.loading-block,
|
.loading-block,
|
||||||
.loaded-block {
|
.loaded-block {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -161,12 +202,25 @@ defineProps({
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.loaded-block {
|
.success {
|
||||||
color: var(--green);
|
color: var(--green);
|
||||||
img {
|
img {
|
||||||
margin-bottom: toRem(2);
|
margin-bottom: toRem(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.other-files {
|
||||||
|
@include text(s-regular);
|
||||||
|
color: var(--red-pale);
|
||||||
|
}
|
||||||
|
.error-block {
|
||||||
|
display: flex;
|
||||||
|
i {
|
||||||
|
margin-right: toRem(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
@media (max-width: toRem(102.4)) {
|
@media (max-width: toRem(102.4)) {
|
||||||
.loader-title {
|
.loader-title {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -176,4 +230,33 @@ defineProps({
|
|||||||
margin-bottom: toRem(1.5);
|
margin-bottom: toRem(1.5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@media (max-width: toRem(76.8)) {
|
||||||
|
.entry-tutorial-error-exif {
|
||||||
|
display: block;
|
||||||
|
padding-top: toRem(1);
|
||||||
|
}
|
||||||
|
.other-files {
|
||||||
|
margin-top: toRem(1);
|
||||||
|
}
|
||||||
|
.img-tutorial-error-exif {
|
||||||
|
width: fit-content;
|
||||||
|
height: toRem(27);
|
||||||
|
border-radius: toRem(1);
|
||||||
|
margin-top: toRem(1);
|
||||||
|
margin-bottom: toRem(1);
|
||||||
|
}
|
||||||
|
.first-tutorial-img-error {
|
||||||
|
margin-right: toRem(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: toRem(50)) {
|
||||||
|
.first-tutorial-img-error {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.img-tutorial-error-exif {
|
||||||
|
width: 100%;
|
||||||
|
height: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, watchEffect, ref } from 'vue'
|
||||||
|
import { hasASessionCookieDecoded } from '@/utils/auth'
|
||||||
import type { AuthConfigInterface } from './interfaces/Auth'
|
import type { AuthConfigInterface } from './interfaces/Auth'
|
||||||
|
|
||||||
export default function authConfig() {
|
export function authConfig() {
|
||||||
const authConf = ref<AuthConfigInterface>()
|
const authConf = ref<AuthConfigInterface>()
|
||||||
|
|
||||||
async function getConfig(): Promise<AuthConfigInterface> {
|
async function getConfig(): Promise<AuthConfigInterface> {
|
||||||
@@ -15,3 +16,12 @@ export default function authConfig() {
|
|||||||
onMounted(async () => (authConf.value = await getConfig()))
|
onMounted(async () => (authConf.value = await getConfig()))
|
||||||
return { authConf }
|
return { authConf }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isAuth() {
|
||||||
|
const isLogged = ref<boolean>(false)
|
||||||
|
watchEffect(() => {
|
||||||
|
const cookie = hasASessionCookieDecoded()
|
||||||
|
isLogged.value = !!(cookie && cookie.account)
|
||||||
|
})
|
||||||
|
return { isLogged }
|
||||||
|
}
|
||||||
|
|||||||
28
src/i18n/index.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { createI18n } from 'vue-i18n'
|
||||||
|
import { useCookies } from 'vue3-cookies'
|
||||||
|
import fr from '../locales/fr.json'
|
||||||
|
import en from '../locales/en.json'
|
||||||
|
import hu from '../locales/hu.json'
|
||||||
|
import pt from '../locales/pt.json'
|
||||||
|
import cs from '../locales/cs.json'
|
||||||
|
const { cookies } = useCookies()
|
||||||
|
|
||||||
|
const locale = cookies.get('lang')
|
||||||
|
? cookies.get('lang')
|
||||||
|
: navigator.language.split('-')[0]
|
||||||
|
|
||||||
|
const i18n = createI18n({
|
||||||
|
locale,
|
||||||
|
fallbackLocale: 'en',
|
||||||
|
warnHtmlMessage: false,
|
||||||
|
globalInjection: true,
|
||||||
|
legacy: false,
|
||||||
|
messages: {
|
||||||
|
cs,
|
||||||
|
fr,
|
||||||
|
en,
|
||||||
|
hu,
|
||||||
|
pt
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export default i18n
|
||||||
1
src/locales/de.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@@ -1,61 +1,65 @@
|
|||||||
{
|
{
|
||||||
"general": {
|
"general": {
|
||||||
"title": "Panoramax instance",
|
"title": "Panoramax Instance",
|
||||||
"meta": {
|
"meta": {
|
||||||
"title": "Panoramax instance",
|
"title": "Panoramax Instance",
|
||||||
"description": "Panoramax, the free alternative to photo-mapping territories"
|
"description": "Panoramax, the free alternative to photo-mapping territories"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"login_text": "Connect",
|
"login_text": "Login",
|
||||||
"register_text": "Register",
|
"register_text": "Register",
|
||||||
"contribute_text": "Why contribute ?",
|
"contribute_text": "Why Contribute?",
|
||||||
"my_account": "My account",
|
"viewer": "Viewer",
|
||||||
"upload_text": "+ Share pictures",
|
"my_account": "My Account",
|
||||||
"sequences_text": "My pictures",
|
"upload_text": "+ Share Pictures",
|
||||||
|
"sequences_text": "My Pictures",
|
||||||
"alt_logo": "Instance logo",
|
"alt_logo": "Instance logo",
|
||||||
"alt_photos": "Pictures icon",
|
"alt_photos": "Pictures icon",
|
||||||
"alt_information": "User icon",
|
"alt_information": "User icon",
|
||||||
"alt_settings": "Parameters icon",
|
"alt_settings": "Settings icon",
|
||||||
"alt_logout": "Logout icon",
|
"alt_logout": "Logout icon",
|
||||||
"title": "Panoramax",
|
"title": "Panoramax",
|
||||||
"beta_text": "Beta version",
|
"beta_text": "Beta Version",
|
||||||
"logout_text": "Logout",
|
"logout_text": "Logout",
|
||||||
"my_information_text": "My details",
|
"my_information_text": "My details",
|
||||||
"my_settings_text": "My parameters",
|
"my_settings_text": "My Settings",
|
||||||
"burger_menu_aria_label_open": "Show menu",
|
"burger_menu_aria_label_open": "Show menu",
|
||||||
"burger_menu_aria_label_closed": "Hide menu"
|
"burger_menu_aria_label_closed": "Hide menu"
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"panoramax_site": "Discover Panoramax",
|
"panoramax_site": "Discover Panoramax",
|
||||||
"information_gitlab": "Show source code",
|
"information_gitlab": "Show source code",
|
||||||
"gitlab_logo": "Gitlab logo",
|
"gitlab_logo": "GitLab logo",
|
||||||
"ay11_text": "Accessibility: not compliant"
|
"ay11_text": "Accessibility: not compliant"
|
||||||
},
|
},
|
||||||
"error_text": "An error occured",
|
"error_text": "An error occured",
|
||||||
"success_text": "Update done"
|
"success_text": "Update complete"
|
||||||
},
|
},
|
||||||
"pages": {
|
"pages": {
|
||||||
"home": {
|
"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_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",
|
"report_button_text": "Report this picture",
|
||||||
"sequence_title": "See the séquence",
|
"sequence_title": "See the sequence",
|
||||||
"open_fullscreen": "Fullscreen mode",
|
"open_fullscreen": "Fullscreen mode",
|
||||||
"close_fullscreen": "Normal mode"
|
"close_fullscreen": "Normal mode"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "My tokens",
|
"title": "My Tokens",
|
||||||
"setting_tooltip": "Show or hide token"
|
"setting_tooltip": "Show or hide token"
|
||||||
},
|
},
|
||||||
"sequence": {
|
"sequence": {
|
||||||
"sequence_published": "Published",
|
"sequence_published": "Published",
|
||||||
"sequence_waiting": "Still processing",
|
"sequence_waiting": "Processing",
|
||||||
"sequence_hidden": "Hidden",
|
"sequence_hidden": "Hidden",
|
||||||
"sequence_form_title": "Edit the title",
|
"sequence_form_title": "Edit the title",
|
||||||
|
"sequence_tutorial_text": "⚠ File selection has been canceled. If this was not intentional, it is possible that your smartphone does not handle sending photos correctly.\nGoing through “My Files” (see image below) to select photos might resolve this issue.",
|
||||||
|
"sequence_tutorial_exif_text": "If the photo upload errors are related to geolocation, please make sure « Location Tags » is enabled on your phone's camera (see images below).",
|
||||||
"hide_sequence_tooltip": "Hide this sequences",
|
"hide_sequence_tooltip": "Hide this sequences",
|
||||||
"back_button": "Back to my sequence list",
|
"back_button": "Back to my sequence list",
|
||||||
"delete_sequence_tooltip": "Permanently delete this sequence",
|
"delete_sequence_tooltip": "Permanently delete this sequence",
|
||||||
"hide_photo_tooltip": "Hide selected pictures",
|
"hide_photo_tooltip": "Hide selected pictures",
|
||||||
"delete_photo_tooltip": "Permanently delete selected pictures",
|
"delete_photo_tooltip": "Permanently delete selected pictures",
|
||||||
|
"info_msg_maj": "Use the SHIFT key to select many pictures",
|
||||||
"conf_pic_msg": "⚠️ Selected photos will be permanently deleted",
|
"conf_pic_msg": "⚠️ Selected photos will be permanently deleted",
|
||||||
"conf_sequence_msg": "⚠️ This sequence will be permanently deleted",
|
"conf_sequence_msg": "⚠️ This sequence will be permanently deleted",
|
||||||
"button_panel_photos": "Manage pictures",
|
"button_panel_photos": "Manage pictures",
|
||||||
@@ -77,15 +81,16 @@
|
|||||||
"sort_panel_check_gps": "GPS Date",
|
"sort_panel_check_gps": "GPS Date",
|
||||||
"sort_panel_check_file": "File date",
|
"sort_panel_check_file": "File date",
|
||||||
"sort_panel_check_name": "File name",
|
"sort_panel_check_name": "File name",
|
||||||
|
"sort_panel_button": "Validate sort",
|
||||||
"created": "Uploaded :",
|
"created": "Uploaded :",
|
||||||
"taken": "Shot on :",
|
"taken": "Shot on :",
|
||||||
"duration": "Duration :",
|
"duration": "Duration :",
|
||||||
"duration_begin": "Start :",
|
"duration_begin": "Start :",
|
||||||
"duration_end": "End :",
|
"duration_end": "End :",
|
||||||
"camera": "Camera :",
|
"camera": "Camera :",
|
||||||
"button_delete": "Delete",
|
"button_delete": "Delete the sequence",
|
||||||
"button_disable": "Hide",
|
"button_disable": "Hide the sequence",
|
||||||
"button_enable": "Show",
|
"button_enable": "Show the sequence",
|
||||||
"picture_selected": "{count} picture selected| {count} pictures selected",
|
"picture_selected": "{count} picture selected| {count} pictures selected",
|
||||||
"hours": "{count} hour| {count} hours",
|
"hours": "{count} hour| {count} hours",
|
||||||
"minutes": "{count} minute| {count} minutes",
|
"minutes": "{count} minute| {count} minutes",
|
||||||
@@ -99,7 +104,7 @@
|
|||||||
},
|
},
|
||||||
"sequences": {
|
"sequences": {
|
||||||
"title": "My sequences",
|
"title": "My sequences",
|
||||||
"filter_date_upload_title": "Filter by upload date",
|
"filter_date_upload_title": "Filter by upload date :",
|
||||||
"filter_date_title": "Filter by shooting date :",
|
"filter_date_title": "Filter by shooting date :",
|
||||||
"radio_date_placeholder": "03/01/2024",
|
"radio_date_placeholder": "03/01/2024",
|
||||||
"radio_date": "date",
|
"radio_date": "date",
|
||||||
@@ -119,9 +124,9 @@
|
|||||||
"filter_bbox_button": "Search on this area",
|
"filter_bbox_button": "Search on this area",
|
||||||
"sequence_status": "Status",
|
"sequence_status": "Status",
|
||||||
"sequence_published": "Published",
|
"sequence_published": "Published",
|
||||||
"sequence_waiting": "Still processing",
|
"sequence_waiting": "Processing",
|
||||||
"sequence_hidden": "Hidden",
|
"sequence_hidden": "Hidden",
|
||||||
"no_sequences_text": "You have no photos published yet \uD83D\uDE22",
|
"no_sequences_text": "You have no photos published yet 😢",
|
||||||
"button_upload": "Upload pictures",
|
"button_upload": "Upload pictures",
|
||||||
"sequence_deleted": "The sequence has been deleted"
|
"sequence_deleted": "The sequence has been deleted"
|
||||||
},
|
},
|
||||||
@@ -134,7 +139,7 @@
|
|||||||
"card_photo3": "Easily reusable photos",
|
"card_photo3": "Easily reusable photos",
|
||||||
"card_photo4": "A quick and easy image contribution",
|
"card_photo4": "A quick and easy image contribution",
|
||||||
"card_alt_photo1": "Image of a building",
|
"card_alt_photo1": "Image of a building",
|
||||||
"card_alt_photo2": "Image showing 360-degree ",
|
"card_alt_photo2": "Image showing 360-degree",
|
||||||
"card_alt_photo3": "Image showing a map with a pointer",
|
"card_alt_photo3": "Image showing a map with a pointer",
|
||||||
"card_alt_photo4": "Image representing 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_description1": "All photos taken from the public highway are accepted, as long as they are geolocated and viewed from the ground.",
|
||||||
@@ -147,10 +152,10 @@
|
|||||||
"upload_button": "+ Upload pictures",
|
"upload_button": "+ Upload pictures",
|
||||||
"command_line_subtitle": "Command line tool",
|
"command_line_subtitle": "Command line tool",
|
||||||
"comment_install": "Install the geovisio command-line tool",
|
"comment_install": "Install the geovisio command-line tool",
|
||||||
"comment_upload": "LanceStart the image upload command on the chosen folder",
|
"comment_upload": "Start 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.",
|
"description_terminal": "<a href='https://gitlab.com/panoramax/clients/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_install": "pip install geovisio_cli",
|
||||||
"terminal_text": "geovisio upload --api-url {url} <DOSSIER_PHOTOS>",
|
"terminal_text": "geovisio upload --api-url {url} <PHOTOS_FOLDER>",
|
||||||
"button_copy": "Copy",
|
"button_copy": "Copy",
|
||||||
"information_subtitle": "Here, your photos are accessible to all : ",
|
"information_subtitle": "Here, your photos are accessible to all : ",
|
||||||
"information_text1": "Automatically blurred in compliance with legislation.",
|
"information_text1": "Automatically blurred in compliance with legislation.",
|
||||||
@@ -171,21 +176,24 @@
|
|||||||
"import_word": "upload",
|
"import_word": "upload",
|
||||||
"import_type": "JPEG format only",
|
"import_type": "JPEG format only",
|
||||||
"subtitle_import": "Picture upload",
|
"subtitle_import": "Picture upload",
|
||||||
"title_sequence": "Séquence title",
|
"title_sequence": "Sequence 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.",
|
"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.",
|
"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",
|
"subtitle_process": "Upload processing",
|
||||||
"uploading_process": "Upload in progress...",
|
"uploading_process": "Upload in progress…",
|
||||||
"uploading_cancel": "Cancel sending photos",
|
"uploading_cancel": "Cancel sending photos",
|
||||||
"cancel_message": "⚠️ Please note, the download will be interrupted if you validate and the sequence will be deleted.",
|
"cancel_message": "⚠️ Please note, the download will be interrupted if you validate and the sequence will be deleted.",
|
||||||
"sequence_title": "Sequence ",
|
"sequence_title": "Sequence ",
|
||||||
|
"error_upload": "Sequence creation error",
|
||||||
|
"error_upload_img": "An error has occurred",
|
||||||
"import": "Uploads",
|
"import": "Uploads",
|
||||||
"upload_pending": "Upload in progress...",
|
"upload_pending": "Upload in progress…",
|
||||||
"images_count_text": "Pictures uploaded",
|
"images_count_text": "Pictures uploaded",
|
||||||
"no_img_text": "no picture upload so far",
|
"no_img_text": "no picture upload so far",
|
||||||
"upload_done": "Sequence upload done",
|
"upload_done": "Sequence upload done",
|
||||||
|
"upload_other_files": "{count} photo in wrong format was removed from the list| {count} wrong format photos have been removed from the list",
|
||||||
"sequence_link": "Show this sequence",
|
"sequence_link": "Show this sequence",
|
||||||
"edit_title_tooltip": "Edit the sequence's title",
|
"edit_title": "Edit the title",
|
||||||
"edit_placeholder_input": "Edit the sequence's title",
|
"edit_placeholder_input": "Edit the sequence's title",
|
||||||
"ok_button": "OK",
|
"ok_button": "OK",
|
||||||
"pictures_error": "{count} picture could not be uploaded| {count} pictures could not be uploaded",
|
"pictures_error": "{count} picture could not be uploaded| {count} pictures could not be uploaded",
|
||||||
@@ -196,29 +204,29 @@
|
|||||||
"modal_error_title": "Pictures in error"
|
"modal_error_title": "Pictures in error"
|
||||||
},
|
},
|
||||||
"ay11": {
|
"ay11": {
|
||||||
"title": "Déclaration d’accessibilité",
|
"title": "Accessibility Statement",
|
||||||
"date": "Établie le 18 septembre 2023.",
|
"date": "Issued on September 18, 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",
|
"introduction": "IGN aims to make its services accessible, in accordance with article 47 of Act No. 2005-102 on 11 February 2005.\nThis accessibility statement applies to Panoramax Instance IGN: https://panoramax.ign.fr",
|
||||||
"subtitle_conformity": "État de conformité",
|
"subtitle_conformity": "Compliance status",
|
||||||
"conformity_text": "Panoramax Instance IGN est non conforme avec le ",
|
"conformity_text": "Panoramax Instance IGN is non-compliant with the ",
|
||||||
"conformity_text2": "Le site n’a encore pas été audité.",
|
"conformity_text2": "The site has not yet been audited.",
|
||||||
"subtitle_conformity2": "Contenus non accessibles",
|
"subtitle_conformity2": "Content not accessible",
|
||||||
"subtitle_increase": "Amélioration et contact",
|
"subtitle_increase": "Improvement and 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.",
|
"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",
|
"phone": "Telephone: +33 14 398 84 61",
|
||||||
"email_text": "E-mail :",
|
"email_text": "E-mail:",
|
||||||
"email": "signalement.ign@panoramax.fr",
|
"email": "signalement.ign@panoramax.fr",
|
||||||
"address": "Adresse : IGN, Saint-Mandé",
|
"address": "Address: IGN, Saint-Mandé",
|
||||||
"increase_info": "Nous essayons de répondre dans les 5 jours ouvrés.",
|
"increase_info": "We try to respond within 5 business days.",
|
||||||
"subtitle_to_do": "Voie de recours",
|
"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 :",
|
"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",
|
"write_message": "Write a message to",
|
||||||
"defenseur_droits": "Défenseur des droits",
|
"defenseur_droits": "Defender of Rights",
|
||||||
"contact": "Contacter",
|
"contact": "Contact",
|
||||||
"contact_text": "le délégué du Défenseur des droits dans votre région",
|
"contact_text": "the delegate of the Defender of Rights in your region",
|
||||||
"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",
|
"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",
|
"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"
|
"generator_betagouv": "BetaGouv Accessibility Statement Generator"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,224 +1,232 @@
|
|||||||
{
|
{
|
||||||
"general": {
|
"general": {
|
||||||
"title": "Instance Panoramax",
|
"title": "Instance Panoramax",
|
||||||
"meta": {
|
"meta": {
|
||||||
"title": "Instance Panoramax",
|
"title": "Instance Panoramax",
|
||||||
"description": "Panoramax, l’alternative libre pour photo-cartographier les territoires"
|
"description": "Panoramax, l’alternative libre pour photo-cartographier les territoires"
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"login_text": "Connexion",
|
||||||
|
"register_text": "Inscription",
|
||||||
|
"contribute_text": "Pourquoi contribuer ?",
|
||||||
|
"viewer": "Visionneuse",
|
||||||
|
"my_account": "Mon compte",
|
||||||
|
"upload_text": "+ Partager des photos",
|
||||||
|
"sequences_text": "Mes photos",
|
||||||
|
"alt_logo": "Logo de l'instance",
|
||||||
|
"alt_photos": "Icone représentant des photos",
|
||||||
|
"alt_information": "Icone représentant un utilisateur",
|
||||||
|
"alt_settings": "Icône des paramètres",
|
||||||
|
"alt_logout": "Icone représentant un bouton de déconnexion",
|
||||||
|
"title": "Panoramax",
|
||||||
|
"beta_text": "Version bêta",
|
||||||
|
"logout_text": "Déconnexion",
|
||||||
|
"my_information_text": "Mes informations",
|
||||||
|
"my_settings_text": "Mes paramètres",
|
||||||
|
"burger_menu_aria_label_open": "Afficher le menu",
|
||||||
|
"burger_menu_aria_label_closed": "Masquer le menu"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"panoramax_site": "Découvrir Panoramax",
|
||||||
|
"information_gitlab": "Voir le code",
|
||||||
|
"gitlab_logo": "Logo GitLab",
|
||||||
|
"ay11_text": "Accessibilité : non conforme"
|
||||||
|
},
|
||||||
|
"error_text": "Une erreur est survenue",
|
||||||
|
"success_text": "Mise à jour réussie"
|
||||||
},
|
},
|
||||||
"header": {
|
"pages": {
|
||||||
"login_text": "Connexion",
|
"home": {
|
||||||
"register_text": "Inscription",
|
"report_mail": "?subject=⚠️ Signalement sur l'image {picId}&body=Bonjour, %0D%0A%0D%0A Problème sur l'image (garder le type de problème signalé) : %0D%0A%0D%0A contenu inapproprié / absence de floutage sur un élément à anonymiser ou flouter pour des raisons de sécurité /surfloutage (floutage en trop) %0D%0A%0D%0A Lien vers la photo concernée : {link} %0D%0A%0D%0A Précision sur les éléments concernés (en particulier pour les problèmes de floutage - que faut-il flouter ou déflouter ?) :",
|
||||||
"contribute_text": "Pourquoi contribuer ?",
|
"report_button_text": "Signaler la photo",
|
||||||
"my_account": "Mon compte",
|
"sequence_title": "Voir la séquence",
|
||||||
"upload_text": "+ Partager des photos",
|
"open_fullscreen": "Mode plein écran",
|
||||||
"sequences_text": "Mes photos",
|
"close_fullscreen": "Mode normal"
|
||||||
"alt_logo": "Logo de l'instance",
|
},
|
||||||
"alt_photos": "Icone représentant des photos",
|
"settings": {
|
||||||
"alt_information": "Icone représentant un utilisateur",
|
"title": "Mes jetons",
|
||||||
"alt_settings": "Icone des paramètres",
|
"setting_tooltip": "Afficher ou masquer le token"
|
||||||
"alt_logout": "Icone représentant un bouton de déconnexion",
|
},
|
||||||
"title": "Panoramax",
|
"sequence": {
|
||||||
"beta_text": "Version beta",
|
"sequence_published": "Publiée",
|
||||||
"logout_text": "Déconnexion",
|
"sequence_waiting": "En cours de publication",
|
||||||
"my_information_text": "Mes informations",
|
"sequence_hidden": "Masquée",
|
||||||
"my_settings_text": "Mes paramètres",
|
"sequence_form_title": "Modifier le titre",
|
||||||
"burger_menu_aria_label_open": "Afficher le menu",
|
"sequence_tutorial_text": "⚠ La sélection des fichiers a été annulée. Si ce n'était pas intentionnel, il est possible que votre smartphone gère mal l'envoi de photos.\nPasser par « Mes fichiers » (voir l'image ci-dessous) pour sélectionner les photos pourrait résoudre ce problème.",
|
||||||
"burger_menu_aria_label_closed": "Masquer le menu"
|
"sequence_tutorial_exif_text": "Si les erreurs de téléchargement de photos concernent la géolocalisation, veuillez vous assurer que l'option « Tags de localisation » est bien activée sur l'appareil photo de votre téléphone (voir les images ci-dessous).",
|
||||||
},
|
"hide_sequence_tooltip": "Masque la séquence sur la carte",
|
||||||
"footer": {
|
"back_button": "Retourner à la liste de mes séquences",
|
||||||
"panoramax_site": "Découvrir Panoramax",
|
"delete_sequence_tooltip": "Supprime définitivement la séquence",
|
||||||
"information_gitlab": "Voir le code",
|
"hide_photo_tooltip": "Masque les photos sur la carte",
|
||||||
"gitlab_logo": "Logo Gitlab",
|
"delete_photo_tooltip": "Supprime définitivement les photos",
|
||||||
"ay11_text": "Accessibilité : non conforme"
|
"info_msg_maj": "Utilisez la touche MAJ pour sélectionner plusieurs photos",
|
||||||
},
|
"conf_pic_msg": "⚠️ Les photos sélectionnées vont être définitivement supprimées",
|
||||||
"error_text": "Une erreur est survenue",
|
"conf_sequence_msg": "⚠️ La séquence va être définitivement supprimée",
|
||||||
"success_text": "Mise à jour réussie"
|
"button_panel_photos": "Gérer les photos",
|
||||||
},
|
"button_panel_orientation": "Régler l'orientation",
|
||||||
"pages": {
|
"button_panel_sort": "Trier la séquence",
|
||||||
"home": {
|
"orientation_panel_title": "Définir l'orientation de la caméra sur le véhicule",
|
||||||
"report_mail": "?subject=⚠️ Signalement sur l`image {picId}&body=Bonjour, %0D%0A%0D%0A Problème sur l`image (garder le type de problème signalé) : %0D%0A%0D%0A contenu inapproprié / absence de floutage sur un élément à anonymiser ou flouter pour des raisons de sécurité /surfloutage (floutage en trop) %0D%0A%0D%0A Lien vers la photo concernée : {link} %0D%0A%0D%0A Précision sur les éléments concernés (en particulier pour les problèmes de floutage - que faut-il flouter ou déflouter?) :",
|
"orientation_panel_tooltip": "Faites glisser la zone bleu dans la direction souhaitée",
|
||||||
"report_button_text": "Signaler la photo",
|
"orientation_input_label": "ou modifiez l'angle ici",
|
||||||
"sequence_title": "Voir la séquence",
|
"orientation_input_placeholder": "Valeur entre -180 et 180",
|
||||||
"open_fullscreen": "Mode plein écran",
|
"orientation_input_error_value": "La valeur doit être entre -180 et 180",
|
||||||
"close_fullscreen": "Mode normal"
|
"orientation_panel_button": "Valider la position",
|
||||||
},
|
"orientation_updated": "L'orientation a bien été modifiée",
|
||||||
"settings": {
|
"sort_updated": "La séquence a bien triée",
|
||||||
"title": "Mes Tokens",
|
"sort_panel_title": "Réglage du tri de la séquence",
|
||||||
"setting_tooltip": "Afficher ou masquer le token"
|
"sort_panel_settings": "Trier la séquence par :",
|
||||||
},
|
"sort_panel_settings_order": "Ordre :",
|
||||||
"sequence": {
|
"sort_panel_settings_order_increase": "Croissant",
|
||||||
"sequence_published": "Publiée",
|
"sort_panel_settings_order_decrease": "Décroissant",
|
||||||
"sequence_waiting": "En cours de publication",
|
"sort_panel_check_gps": "Date du GPS",
|
||||||
"sequence_hidden": "Masquée",
|
"sort_panel_check_file": "Date de la caméra",
|
||||||
"sequence_form_title": "Modifier le titre",
|
"sort_panel_check_name": "Nom du fichier",
|
||||||
"hide_sequence_tooltip": "Masque la séquence sur la carte",
|
"sort_panel_button": "Valider le tri",
|
||||||
"back_button": "Retourner à la liste de mes séquences",
|
"created": "Versement :",
|
||||||
"delete_sequence_tooltip": "Supprime définitivement la séquence",
|
"taken": "Prise de vue :",
|
||||||
"hide_photo_tooltip": "Masque les photos sur la carte",
|
"duration": "Durée :",
|
||||||
"delete_photo_tooltip": "Supprime définitivement les photos",
|
"duration_begin": "Début :",
|
||||||
"conf_pic_msg": "⚠️ Les photos sélectionnées vont être définitivement supprimées",
|
"duration_end": "Fin :",
|
||||||
"conf_sequence_msg": "⚠️ La séquence va être définitivement supprimée",
|
"camera": "Matériel :",
|
||||||
"button_panel_photos": "Gérer les photos",
|
"button_delete": "Supprimer la séquence",
|
||||||
"button_panel_orientation": "Régler l'orientation",
|
"button_disable": "Masquer la séquence",
|
||||||
"button_panel_sort": "Trier la séquence",
|
"button_enable": "Afficher la séquence",
|
||||||
"orientation_panel_title": "Définir l'orientation de la caméra sur le véhicule",
|
"picture_selected": "{count} photo sélectionnée| {count} photos sélectionnées",
|
||||||
"orientation_panel_tooltip": "Faites glisser la zone bleu dans la direction souhaitée",
|
"hours": "{count} heure| {count} heures",
|
||||||
"orientation_input_label": "ou modifiez l'angle ici",
|
"minutes": "{count} minute| {count} minutes",
|
||||||
"orientation_input_placeholder": "Valeur entre -180 et 180",
|
"seconds": "{count} seconde| {count} secondes",
|
||||||
"orientation_input_error_value": "La valeur doit être entre -180 et 180",
|
"select_text": "Tout sélectionner",
|
||||||
"orientation_panel_button": "Valider la position",
|
"unselect_text": "Tout désélectionner",
|
||||||
"orientation_updated": "L'orientation a bien été modifiée",
|
"select_shift_text": "Sélectionnez plusieurs photos avec shift",
|
||||||
"sort_updated": "La séquence a bien triée",
|
"waiting_process": "Photo en cours de traitement",
|
||||||
"sort_panel_title": "Réglage du tri de la séquence",
|
"broken": "Traitement de la photo en erreur",
|
||||||
"sort_panel_settings": "Trier la séquence par :",
|
"no_image": "Aucune photo dans cette séquence"
|
||||||
"sort_panel_settings_order": "Ordre :",
|
},
|
||||||
"sort_panel_settings_order_increase": "Croissant",
|
"sequences": {
|
||||||
"sort_panel_settings_order_decrease": "Décroissant",
|
"title": "Mes séquences de photos",
|
||||||
"sort_panel_check_gps": "Date du GPS",
|
"filter_date_upload_title": "Filtrer par date de versement :",
|
||||||
"sort_panel_check_file": "Date de la caméra",
|
"filter_date_title": "Filtrer par date de prise de vue :",
|
||||||
"sort_panel_check_name": "Nom du fichier",
|
"radio_date_placeholder": "2024-01-03",
|
||||||
"created": "Versement :",
|
"radio_date": "date",
|
||||||
"taken": "Prise de vue :",
|
"hide_button": "Masquer",
|
||||||
"duration": "Durée :",
|
"show_button": "Afficher",
|
||||||
"duration_begin": "Début :",
|
"delete_button": "Supprimer",
|
||||||
"duration_end": "Fin :",
|
"filter_date_reset_button": "Réinitialiser",
|
||||||
"camera": "Matériel :",
|
"filter_date_close_button": "Fermer",
|
||||||
"button_delete": "Supprimer",
|
"no_sequence_found": "Aucune séquence trouvée",
|
||||||
"button_disable": "Masquer",
|
"sequence_name": "Nom",
|
||||||
"button_enable": "Afficher",
|
"sequence_photos": "Photos",
|
||||||
"picture_selected": "{count} photo sélectionnée| {count} photos sélectionnées",
|
"sequence_date": "Prise de vue",
|
||||||
"hours": "{count} heure| {count} heures",
|
"sequence_creation": "Versement",
|
||||||
"minutes": "{count} minute| {count} minutes",
|
"sequence_creation_tooltip": "Filtre par date de versement",
|
||||||
"seconds": "{count} seconde| {count} secondes",
|
"sequence_date_tooltip": "Filtre par date de prise de vue",
|
||||||
"select_text": "Tout sélectionner",
|
"reset_filter_button": "Réinitialiser les filtres",
|
||||||
"unselect_text": "Tout désélectionner",
|
"filter_bbox_button": "Chercher dans cette zone",
|
||||||
"select_shift_text": "Sélectionnez plusieurs photos avec shift",
|
"sequence_status": "Statut",
|
||||||
"waiting_process": "Photo en cours de traitement",
|
"sequence_published": "Publiée",
|
||||||
"broken": "Traitement de la photo en erreur",
|
"sequence_waiting": "En cours de publication",
|
||||||
"no_image": "Aucune photo dans cette séquence"
|
"sequence_hidden": "Masquée",
|
||||||
},
|
"no_sequences_text": "Vous n'avez pas encore de photos publiées 😢",
|
||||||
"sequences": {
|
"button_upload": "Partager vos photos",
|
||||||
"title": "Mes séquences de photos",
|
"sequence_deleted": "La séquence a bien été supprimée"
|
||||||
"filter_date_upload_title": "Filtrer par date de versement :",
|
},
|
||||||
"filter_date_title": "Filtrer par date de prise de vue :",
|
"share_pictures": {
|
||||||
"radio_date_placeholder": "2024-01-03",
|
"title": "Pourquoi contribuer à la base de photographies de Panoramax ?",
|
||||||
"radio_date": "date",
|
"description": "Contribuer à Panoramax, c'est participer au développement d'un géo-commun, une ressource numérique souveraine, libre et réutilisable. Chaque photo géolocalisée publiée sur Panoramax est utilisable par tous et pour des usages variés, par exemple par une collectivité territoriale qui a besoin d'observer l’état de la voirie ou par un opérateur de télécommunications pour préparer une intervention.\n\nChaque contributeur peut envoyer ses séquences d'images, les modifier et les consulter tout comme l'ensemble des vues - 360° ou non - versées par la communauté. Le floutage obligatoire des visages et plaques d'immatriculation est automatisé sur la plateforme.",
|
||||||
"hide_button": "Masquer",
|
"alt_img_map": "Illustration d'une femme qui regarde une carte avec son smartphone geolocalisé",
|
||||||
"show_button": "Afficher",
|
"card_photo1": "Des lieux visibles depuis la voie publique",
|
||||||
"delete_button": "Supprimer",
|
"card_photo2": "Des photos publiées au format 360° ou non",
|
||||||
"filter_date_reset_button": "Réinitialiser",
|
"card_photo3": "Des photos facilement réutilisables",
|
||||||
"filter_date_close_button": "Fermer",
|
"card_photo4": "Une contribution en images facile et rapide",
|
||||||
"no_sequence_found": "Aucune séquence trouvée",
|
"card_alt_photo1": "Image qui représente un immeuble",
|
||||||
"sequence_name": "Nom",
|
"card_alt_photo2": "Image qui représente des photos 360 degrés",
|
||||||
"sequence_photos": "Photos",
|
"card_alt_photo3": "Image qui représente une carte avec un pointeur",
|
||||||
"sequence_date": "Prise de vue",
|
"card_alt_photo4": "Image qui représente un pointeur",
|
||||||
"sequence_creation": "Versement",
|
"card_description1": "Toutes les photos prises depuis la voie publique sont concernées, dès lors qu'elles sont géolocalisées et vues du sol.",
|
||||||
"sequence_creation_tooltip": "Filtre par date de versement",
|
"card_description2": "Le format 360° n'est pas obligatoire : des photos prises via un smartphone suffisent, dates, lieux et format jpg sont les seuls pre-requis.",
|
||||||
"sequence_date_tooltip": "Filtre par date de prise de vue",
|
"card_description3": "Toutes les photos facilement accessible et réutilisables sans compte : via le site web ou une API standard (format STAC).",
|
||||||
"reset_filter_button": "Réinitialiser les filtres",
|
"card_description4": "Plusieurs outils sont mis à disposition pour faciliter les contributions dont une ligne de commande et une interface web.",
|
||||||
"filter_bbox_button": "Chercher dans cette zone",
|
"upload_subtitle": "Chargez vos images simplement en ligne",
|
||||||
"sequence_status": "Statut",
|
"upload_illustration_alt": "Illustration qui représente l'envoie de photo en ligne",
|
||||||
"sequence_published": "Publiée",
|
"upload_description": "L'application web de Panoramax vous permet de déposer toutes vos photos de terrain au format jpg d'un simple clic. Aucune notion de programmation n'est nécessaire. Pour les envois en grand nombre, il est toutefois conseillé de faire appel à l'outil en ligne de commande",
|
||||||
"sequence_waiting": "En cours de publication",
|
"upload_button": "+ Partager des photos",
|
||||||
"sequence_hidden": "Masquée",
|
"command_line_subtitle": "L'outil en ligne de commande",
|
||||||
"no_sequences_text": "Vous n'avez pas encore de photos publiées \uD83D\uDE22",
|
"comment_install": "Installer l’outil en ligne de commande geovisio",
|
||||||
"button_upload": "Partager vos photos",
|
"comment_upload": "Lancez la commande de versement d’images sur le dossier choisi",
|
||||||
"sequence_deleted": "La séquence a bien été supprimée"
|
"description_terminal": "<a href='https://gitlab.com/panoramax/clients/cli' target='_blank' style='color:black'>L'outil en ligne de commande</a> vous permet de partager de grands volumes de photos. La procédure est simple et vous devez disposer <a target='_blank' href='https://www.python.org/downloads/' style='color:black'>de python (au moins la version 3.8)</a>.\n\nL’outil demandera vos informations de connexion avant l'import. Une fois les données chargées, un temps de traitement est nécessaire pour les rendre disponibles.",
|
||||||
},
|
"terminal_install": "pip install geovisio_cli",
|
||||||
"share_pictures": {
|
"terminal_text": "geovisio upload --api-url {url} <DOSSIER_PHOTOS>",
|
||||||
"title": "Pourquoi contribuer à la base de photographies de Panoramax ?",
|
"button_copy": "Copier",
|
||||||
"description": "Contribuer à Panoramax, c'est participer au développement d'un géo-commun, une ressource numérique souveraine, libre et réutilisable. Chaque photo géolocalisée publiée sur Panoramax est utilisable par tous et pour des usages variés, par exemple par une collectivité territoriale qui a besoin d'observer l’état de la voirie ou par un opérateur de télécommunications pour préparer une intervention.\n\nChaque contributeur peut envoyer ses séquences d'images, les modifier et les consulter tout comme l'ensemble des vues - 360° ou non - versées par la communauté. Le floutage obligatoire des visages et plaques d'immatriculation est automatisé sur la plateforme.",
|
"information_subtitle": "Ici, vos photos sont accessibles à tous : ",
|
||||||
"alt_img_map": "Illustration d'une femme qui regarde une carte avec son smartphone geolocalisé",
|
"information_text1": "Automatiquement floutées dans le respect de la législation.",
|
||||||
"card_photo1": "Des lieux visibles depuis la voie publique",
|
"information_text2": "Les données déposées seront publiées sous {word}",
|
||||||
"card_photo2": "Des photos publiées au format 360° ou non",
|
"information_text3": "Sous forme «brute» pour des réutilisations variées (ex: préparation des chantiers).",
|
||||||
"card_photo3": "Des photos facilement réutilisables",
|
"information_about_title": "Besoin de récupérer les données ?",
|
||||||
"card_photo4": "Une contribution en images facile et rapide",
|
"information_about_description": "Une API est mise à disposition pour récupérer toutes les données. <a href='{docLink}' target='_blank' style='color:#0a1f69'>\nRetrouvez sa documentation ici</a>\nLes données sont aussi exposées sous forme <a href='{docTiles}' target='_blank' style='color:#0a1f69'>de tuiles vectorielles</a>",
|
||||||
"card_alt_photo1": "Image qui représente un immeuble",
|
"doc_subtitle": "Besoin d'aide pour contribuer à Panoramax ?",
|
||||||
"card_alt_photo2": "Image qui représente des photos 360 degrés",
|
"doc_description": "Nous mettons à disposition l’ensemble de la documentation relative à Panoramax et vous pouvez accéder aux tutoriels sur le forum des géo-communs.",
|
||||||
"card_alt_photo3": "Image qui représente une carte avec un pointeur",
|
"doc_button": "Voir la documentation",
|
||||||
"card_alt_photo4": "Image qui représente un pointeur",
|
"doc_illustration_alt": "Illustration représentant un personnage avec un feuillet de documents"
|
||||||
"card_description1": "Toutes les photos prises depuis la voie publique sont concernées, dès lors qu'elles sont géolocalisées et vues du sol.",
|
},
|
||||||
"card_description2": "Le format 360° n'est pas obligatoire : des photos prises via un smartphone suffisent, dates, lieux et format jpg sont les seuls pre-requis.",
|
"upload": {
|
||||||
"card_description3": "Toutes les photos facilement accessible et réutilisables sans compte : via le site web ou une API standard (format STAC).",
|
"title": "Contribuez à la base de photographies de Panoramax",
|
||||||
"card_description4": "Plusieurs outils sont mis à disposition pour faciliter les contributions dont une ligne de commande et une interface web.",
|
"description": "Pour le versement d'un grand volume de photographies, l'outil en ligne de commande est plus adapté.",
|
||||||
"upload_subtitle": "Chargez vos images simplement en ligne",
|
"know_more_button": "En savoir plus",
|
||||||
"upload_illustration_alt": "Illustration qui représente l'envoie de photo en ligne",
|
"input_label": "Glissez vos images ici ou cliquez sur ",
|
||||||
"upload_description": "L'application web de Panoramax vous permet de déposer toutes vos photos de terrain au format jpg d'un simple clic. Aucune notion de programmation n'est nécessaire. Pour les envois en grand nombre, il est toutefois conseillé de faire appel à l'outil en ligne de commande",
|
"import_word": "importer",
|
||||||
"upload_button": "+ Partager des images",
|
"import_type": "Format JPEG uniquement",
|
||||||
"command_line_subtitle": "L'outil en ligne de commande",
|
"subtitle_import": "Dépôt des images",
|
||||||
"comment_install": "Installer l’outil en ligne de commande geovisio",
|
"title_sequence": "Titre de ma séquence",
|
||||||
"comment_upload": "Lancez la commande de versement d’images sur le dossier choisi",
|
"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.",
|
||||||
"description_terminal": "<a href='https://gitlab.com/geovisio/cli' target='_blank' style='color:black'>L'outil en ligne de commande</a> vous permet de partager de grands volumes de photos. La procédure est simple et vous devez disposer <a target='_blank' href='https://www.python.org/downloads/' style='color:black'>de python (au moins la version 3.8)</a>.\n\nL’outil demandera vos informations de connexion avant l'import. Une fois les données chargées, un temps de traitement est nécessaire pour les rendre disponibles.",
|
"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.",
|
||||||
"terminal_install": "pip install geovisio_cli",
|
"subtitle_process": "Traitements de l'import",
|
||||||
"terminal_text": "geovisio upload --api-url {url} <DOSSIER_PHOTOS>",
|
"uploading_process": "Envoi en cours…",
|
||||||
"button_copy": "Copier",
|
"uploading_cancel": "Annuler l'envoi des photos",
|
||||||
"information_subtitle": "Ici, vos photos sont accessibles à tous : ",
|
"cancel_message": "⚠️ Attention, le téléchargement sera interrompu si vous validez et la séquence sera supprimée.",
|
||||||
"information_text1": "Automatiquement floutées dans le respect de la législation.",
|
"sequence_title": "Séquence du ",
|
||||||
"information_text2": "Les données déposées seront publiées sous {word}",
|
"error_upload": "Erreur de création de la séquence",
|
||||||
"information_text3": "Sous forme «brute» pour des réutilisations variées (ex: préparation des chantiers).",
|
"error_upload_img": "Une erreur est survenue",
|
||||||
"information_about_title": "Besoin de récupérer les données ?",
|
"import": "Imports",
|
||||||
"information_about_description": "Une API est mise à disposition pour récupérer toutes les données. <a href='{docLink}' target='_blank' style='color:#0a1f69'>\nRetrouvez sa documentation ici</a>\nLes données sont aussi exposées sous forme <a href='{docTiles}' target='_blank' style='color:#0a1f69'>de tuiles vectorielles</a>",
|
"upload_pending": "Transfert en cours…",
|
||||||
"doc_subtitle": "Besoin d'aide pour contribuer à Panoramax ?",
|
"images_count_text": "Images chargées",
|
||||||
"doc_description": "Nous mettons à disposition l’ensemble de la documentation relative à Panoramax et vous pouvez accéder aux tutoriels sur le forum des géo-communs.",
|
"no_img_text": "aucune image chargée actuellement",
|
||||||
"doc_button": "Voir la documentation",
|
"upload_done": "Le chargement de la séquence est terminé",
|
||||||
"doc_illustration_alt": "Illustration représentant un personnage avec un feuillet de documents"
|
"upload_other_files": "{count} photo au mauvais format a été retirée de la liste| {count} photos au mauvais format ont été retirées de la liste",
|
||||||
},
|
"sequence_link": "Accéder à cette séquence",
|
||||||
"upload": {
|
"edit_title": "Modifier le titre",
|
||||||
"title": "Contribuez à la base de photographies de Panoramax",
|
"edit_placeholder_input": "Modifier le titre de la séquence",
|
||||||
"description": "Pour le versement d'un grand volume de photographies, l'outil en ligne de commande est plus adapté.",
|
"ok_button": "Valider",
|
||||||
"know_more_button": "En savoir plus",
|
"pictures_error": "{count} photo n'a pas pu être chargée| {count} photos n'ont pas pu être chargées",
|
||||||
"input_label": "Glissez vos images ici ou cliquez sur ",
|
"sequence_loading_information": "Une fois chargée, la séquence sera en traitement et accessible sur Panoramax dans les prochaines minutes.",
|
||||||
"import_word": "importer",
|
"sequence_loaded_information": "La séquence est chargée et est en cours de traitement. Elle sera accessible sur Panoramax dans quelques minutes.",
|
||||||
"import_type": "Format JPEG uniquement",
|
"leave_message": "⚠️ Attention, le téléchargement sera interrompu si vous quittez la page avant la fin.",
|
||||||
"subtitle_import": "Dépôt des images",
|
"error_button": "Afficher les erreurs",
|
||||||
"title_sequence": "Titre de ma séquence",
|
"modal_error_title": "Liste des photos non chargés"
|
||||||
"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.",
|
"ay11": {
|
||||||
"subtitle_process": "Traitements de l'import",
|
"title": "Déclaration d’accessibilité",
|
||||||
"uploading_process": "Envoi en cours...",
|
"date": "Établie le 18 septembre 2023.",
|
||||||
"uploading_cancel": "Annuler l'envoi des photos",
|
"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",
|
||||||
"cancel_message": "⚠️ Attention, le téléchargement sera interrompu si vous validez et la séquence sera supprimée.",
|
"subtitle_conformity": "État de conformité",
|
||||||
"sequence_title": "Séquence du ",
|
"conformity_text": "Panoramax Instance IGN est non conforme avec le ",
|
||||||
"import": "Imports",
|
"conformity_text2": "Le site n’a encore pas été audité.",
|
||||||
"upload_pending": "Transfert en cours...",
|
"subtitle_conformity2": "Contenus non accessibles",
|
||||||
"images_count_text": "Images chargées",
|
"subtitle_increase": "Amélioration et contact",
|
||||||
"no_img_text": "aucune image chargée actuellement",
|
"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.",
|
||||||
"upload_done": "Le chargement de la séquence est terminé",
|
"phone": "Téléphone : +33 14 398 84 61",
|
||||||
"sequence_link": "Accéder à cette séquence",
|
"email_text": "E-mail :",
|
||||||
"edit_title_tooltip": "Modifier le titre de la séquence",
|
"email": "signalement.ign@panoramax.fr",
|
||||||
"edit_placeholder_input": "Modifier le titre de la séquence",
|
"address": "Adresse : IGN, Saint-Mandé",
|
||||||
"ok_button": "Valider",
|
"increase_info": "Nous essayons de répondre dans les 5 jours ouvrés.",
|
||||||
"pictures_error": "{count} image n'a pas pu être chargée| {count} images n'ont pas pu être chargées",
|
"subtitle_to_do": "Voie de recours",
|
||||||
"sequence_loading_information": "Une fois chargée, la séquence sera en traitement et accessible sur Panoramax dans les prochaines minutes.",
|
"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 :",
|
||||||
"sequence_loaded_information": "La séquence est chargée et est en cours de traitement. Elle sera accessible sur Panoramax dans quelques minutes.",
|
"write_message": "Écrire un message au",
|
||||||
"leave_message": "⚠️ Attention, le téléchargement sera interrompu si vous quittez la page avant la fin.",
|
"defenseur_droits": "Défenseur des droits",
|
||||||
"error_button": "Afficher les erreurs",
|
"contact": "Contacter",
|
||||||
"modal_error_title": "Liste des photos non chargés"
|
"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",
|
||||||
"ay11": {
|
"end": "Cette déclaration d’accessibilité a été créé le\n 18 septembre 2023 grâce au",
|
||||||
"title": "Déclaration d’accessibilité",
|
"generator_betagouv": "Générateur de Déclaration d’Accessibilité de BetaGouv"
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"login_text": "Kapcsolódás",
|
"login_text": "Kapcsolódás",
|
||||||
"register_text": "Regisztráció",
|
"register_text": "Regisztráció",
|
||||||
"contribute_text": "Miért működjön közre?",
|
"contribute_text": "Miért működjön közre?",
|
||||||
|
"viewer": "Néző",
|
||||||
"my_account": "Saját fiók",
|
"my_account": "Saját fiók",
|
||||||
"upload_text": "+ Fényképek megosztása",
|
"upload_text": "+ Fényképek megosztása",
|
||||||
"sequences_text": "Saját fényképek",
|
"sequences_text": "Saját fényképek",
|
||||||
@@ -51,11 +52,14 @@
|
|||||||
"sequence_waiting": "Feldolgozás alatt",
|
"sequence_waiting": "Feldolgozás alatt",
|
||||||
"sequence_hidden": "Rejtett",
|
"sequence_hidden": "Rejtett",
|
||||||
"sequence_form_title": "Szerkessze a címet",
|
"sequence_form_title": "Szerkessze a címet",
|
||||||
|
"sequence_tutorial_text": "⚠ A fájl kiválasztása megszakadt. Ha ez nem volt szándékos, akkor lehetséges, hogy az okostelefon nem kezeli megfelelően a fényképek küldését.\nA „Saját fájlok” (lásd az alábbi képet) segítségével kiválaszthatja a képeket, és megoldhatja ezt a problémát.",
|
||||||
|
"sequence_tutorial_exif_text": "Ha a fotófeltöltési hibák a földrajzi helyhez kapcsolódnak, kérjük, győződjön meg arról, hogy a „Helycímkék” engedélyezve van a telefon kameráján (lásd az alábbi képeket).",
|
||||||
"hide_sequence_tooltip": "A sorozat elrejtése",
|
"hide_sequence_tooltip": "A sorozat elrejtése",
|
||||||
"back_button": "Vissza a sorozatlistámhoz",
|
"back_button": "Vissza a sorozatlistámhoz",
|
||||||
"delete_sequence_tooltip": "A sorozat végleges törlése",
|
"delete_sequence_tooltip": "A sorozat végleges törlése",
|
||||||
"hide_photo_tooltip": "A kiválasztott fényképek elrejtése",
|
"hide_photo_tooltip": "A kiválasztott fényképek elrejtése",
|
||||||
"delete_photo_tooltip": "A kiválasztottt fényképek végleges törlése",
|
"delete_photo_tooltip": "A kiválasztottt fényképek végleges törlése",
|
||||||
|
"info_msg_maj": "Használja a SHIFT billentyűt sok kép kiválasztásához",
|
||||||
"conf_pic_msg": "⚠️ A kiválasztott fényképek véglegesen elvesznek",
|
"conf_pic_msg": "⚠️ A kiválasztott fényképek véglegesen elvesznek",
|
||||||
"conf_sequence_msg": "⚠️ A kiválasztott sorozat véglegesen elvész",
|
"conf_sequence_msg": "⚠️ A kiválasztott sorozat véglegesen elvész",
|
||||||
"button_panel_photos": "Fényképek kezelése",
|
"button_panel_photos": "Fényképek kezelése",
|
||||||
@@ -83,9 +87,9 @@
|
|||||||
"duration_begin": "Kezdet:",
|
"duration_begin": "Kezdet:",
|
||||||
"duration_end": "Vég:",
|
"duration_end": "Vég:",
|
||||||
"camera": "Kamera:",
|
"camera": "Kamera:",
|
||||||
"button_delete": "Törlés",
|
"button_delete": "Törlés szekvenciás",
|
||||||
"button_disable": "Elrejtés",
|
"button_disable": "Elrejtés szekvenciás",
|
||||||
"button_enable": "Megjelenítés",
|
"button_enable": "Megjelenítés szekvenciás",
|
||||||
"picture_selected": "{count} fénykép kiválasztva| {count} fénykép kiválasztva",
|
"picture_selected": "{count} fénykép kiválasztva| {count} fénykép kiválasztva",
|
||||||
"hours": "{count} óra| {count} óra",
|
"hours": "{count} óra| {count} óra",
|
||||||
"minutes": "{count} perc| {count} perc",
|
"minutes": "{count} perc| {count} perc",
|
||||||
@@ -121,7 +125,7 @@
|
|||||||
"sequence_published": "Közzétéve",
|
"sequence_published": "Közzétéve",
|
||||||
"sequence_waiting": "Feldolgozás alatt",
|
"sequence_waiting": "Feldolgozás alatt",
|
||||||
"sequence_hidden": "Rejtett",
|
"sequence_hidden": "Rejtett",
|
||||||
"no_sequences_text": "Még nincsenek közzétett fényképei \uD83D\uDE22",
|
"no_sequences_text": "Még nincsenek közzétett fényképei 😢",
|
||||||
"button_upload": "Fényképek feltöltése",
|
"button_upload": "Fényképek feltöltése",
|
||||||
"sequence_deleted": "A sorozat törlésre került"
|
"sequence_deleted": "A sorozat törlésre került"
|
||||||
},
|
},
|
||||||
@@ -148,7 +152,7 @@
|
|||||||
"command_line_subtitle": "Parancssoros eszköz",
|
"command_line_subtitle": "Parancssoros eszköz",
|
||||||
"comment_install": "A geovisio parancssoros eszköz telepítése",
|
"comment_install": "A geovisio parancssoros eszköz telepítése",
|
||||||
"comment_upload": "A képfeltöltési parancs indítása a kiválasztott mappán",
|
"comment_upload": "A képfeltöltési parancs indítása a kiválasztott mappán",
|
||||||
"description_terminal": "<a href='https://gitlab.com/geovisio/cli' target='_blank' style='color:black'>A parancssor</a> lehetővé teszi, hogy nagy számú képet osszon meg. A folyamat egyszerű, és <a target='_blank' href='https://www.python.org/downloads/' style='color:black'>Pythont (3.8-as vagy újabb verzió)</a> igényli.\n\nAz importálás előtt az eszköz bekéri a bejelentkezési adatait. Amint a képek felöltésre kerültek, azok közzététele előtt feldolgozási idő szükséges.",
|
"description_terminal": "<a href='https://gitlab.com/panoramax/clients/cli' target='_blank' style='color:black'>A parancssor</a> lehetővé teszi, hogy nagy számú képet osszon meg. A folyamat egyszerű, és <a target='_blank' href='https://www.python.org/downloads/' style='color:black'>Pythont (3.8-as vagy újabb verzió)</a> igényli.\n\nAz importálás előtt az eszköz bekéri a bejelentkezési adatait. Amint a képek felöltésre kerültek, azok közzététele előtt feldolgozási idő szükséges.",
|
||||||
"terminal_install": "pip install geovisio_cli",
|
"terminal_install": "pip install geovisio_cli",
|
||||||
"terminal_text": "geovisio upload --api-url {webcím} <FÉNYKÉPMAPPA>",
|
"terminal_text": "geovisio upload --api-url {webcím} <FÉNYKÉPMAPPA>",
|
||||||
"button_copy": "Másolás",
|
"button_copy": "Másolás",
|
||||||
@@ -179,13 +183,16 @@
|
|||||||
"uploading_cancel": "Fényképek küldésének megszakítása",
|
"uploading_cancel": "Fényképek küldésének megszakítása",
|
||||||
"cancel_message": "⚠️ Felhívjuk figyelmét, hogy a letöltés megszakad, ha érvényesíti, és a sorozat törlődik.",
|
"cancel_message": "⚠️ Felhívjuk figyelmét, hogy a letöltés megszakad, ha érvényesíti, és a sorozat törlődik.",
|
||||||
"sequence_title": "Sorozat ",
|
"sequence_title": "Sorozat ",
|
||||||
|
"error_upload": "Sorozat létrehozási hiba",
|
||||||
|
"error_upload_img": "Hiba történt",
|
||||||
"import": "Feltöltések",
|
"import": "Feltöltések",
|
||||||
"upload_pending": "Feltöltés folyamatban…",
|
"upload_pending": "Feltöltés folyamatban…",
|
||||||
"images_count_text": "Feltöltött képek",
|
"images_count_text": "Feltöltött képek",
|
||||||
"no_img_text": "még nem volt képfeltöltés",
|
"no_img_text": "még nem volt képfeltöltés",
|
||||||
"upload_done": "A sorozat feltöltése elkészült",
|
"upload_done": "A sorozat feltöltése elkészült",
|
||||||
|
"upload_other_files": "{count} rossz formátumú fényképet eltávolítottunk a listáról| {count} rossz formátumú fényképet eltávolítottunk a listáról",
|
||||||
"sequence_link": "A sorozat megjelenítése",
|
"sequence_link": "A sorozat megjelenítése",
|
||||||
"edit_title_tooltip": "A sorozat címének szerkesztése",
|
"edit_title": "A sorozat címének",
|
||||||
"edit_placeholder_input": "A sorozat címének szerkesztése",
|
"edit_placeholder_input": "A sorozat címének szerkesztése",
|
||||||
"ok_button": "OK",
|
"ok_button": "OK",
|
||||||
"pictures_error": "{count} kép feltöltése nem sikerült| {count} kép feltöltése nem sikerült",
|
"pictures_error": "{count} kép feltöltése nem sikerült| {count} kép feltöltése nem sikerült",
|
||||||
@@ -196,8 +203,8 @@
|
|||||||
"modal_error_title": "A hibában érintett képek"
|
"modal_error_title": "A hibában érintett képek"
|
||||||
},
|
},
|
||||||
"ay11": {
|
"ay11": {
|
||||||
"title": "Déclaration d’accessibilité",
|
"title": "Hozzáférhetőségi nyilatkozat",
|
||||||
"date": "Établie le 18 septembre 2023.",
|
"date": "Létrehozva 2023. szeptember 18-án.",
|
||||||
"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",
|
"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é",
|
"subtitle_conformity": "État de conformité",
|
||||||
"conformity_text": "Panoramax Instance IGN est non conforme avec le ",
|
"conformity_text": "Panoramax Instance IGN est non conforme avec le ",
|
||||||
|
|||||||
1
src/locales/nl.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
162
src/locales/pt.json
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
{
|
||||||
|
"general": {
|
||||||
|
"meta": {
|
||||||
|
"title": "Instância Panoramax",
|
||||||
|
"description": "Panoramax, a alternativa livre para foto-mapear território."
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"contribute_text": "Porquê Contribuir?",
|
||||||
|
"viewer": "Visualizador",
|
||||||
|
"my_account": "Minha Conta",
|
||||||
|
"upload_text": "+ Partilhar Imagens",
|
||||||
|
"alt_logo": "Logo da Instância",
|
||||||
|
"alt_photos": "Icon das Imagens",
|
||||||
|
"alt_information": "Icon do Utilizador",
|
||||||
|
"alt_settings": "Icon das Definições",
|
||||||
|
"alt_logout": "Icon de Terminar Sessão",
|
||||||
|
"title": "Panoramax",
|
||||||
|
"beta_text": "Versão Beta",
|
||||||
|
"logout_text": "Terminar Sessão",
|
||||||
|
"my_information_text": "Meus Detalhes",
|
||||||
|
"my_settings_text": "Minhas Definições",
|
||||||
|
"burger_menu_aria_label_open": "Mostrar Menu",
|
||||||
|
"burger_menu_aria_label_closed": "Esconder menu",
|
||||||
|
"sequences_text": "Minhas Imagens",
|
||||||
|
"login_text": "Iniciar sessão",
|
||||||
|
"register_text": "Registar"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"panoramax_site": "Descobrir Panoramax",
|
||||||
|
"information_gitlab": "Mostrar código fonte",
|
||||||
|
"gitlab_logo": "Logo do GitLab",
|
||||||
|
"ay11_text": "Acessibilidade: Não conforme"
|
||||||
|
},
|
||||||
|
"error_text": "Um erro ocorreu",
|
||||||
|
"success_text": "Atualização concluida",
|
||||||
|
"title": "Instância Panoramax"
|
||||||
|
},
|
||||||
|
"pages": {
|
||||||
|
"home": {
|
||||||
|
"report_button_text": "Reportar esta imagem",
|
||||||
|
"sequence_title": "Ver a sequencia",
|
||||||
|
"open_fullscreen": "Modo ecrã inteiro",
|
||||||
|
"close_fullscreen": "Modo normal",
|
||||||
|
"report_mail": "?subject=⚠️ Denuncia na imagem {picId}&body=Olá, %0D%0A%0D%0A Problema na imagem (manter tipo de problema reportado): %0D%0A%0D%0A %0D%0A%0D%0A conteúdo inapropriado / falta de desfoque num elemento a ser anonimizado ou desfocado por razões de segurançans / sobredesfocamento (demasiado desfocamento) %0D%0A%0D%0A Link para a imagem afetada: {link} %0D%0A%0D%0A Detalhes dos elementos afetados (especialmente para problemas de desfocamento - o que deveria ou não estar defocado?):"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "Meus tokens",
|
||||||
|
"setting_tooltip": "Mostrar ou esconder token"
|
||||||
|
},
|
||||||
|
"sequence": {
|
||||||
|
"sequence_published": "Publicado",
|
||||||
|
"sequence_waiting": "Ainda a processar",
|
||||||
|
"sequence_hidden": "Escondido",
|
||||||
|
"sequence_form_title": "Editar o titulo",
|
||||||
|
"back_button": "Voltar à lista das minhas sequencias",
|
||||||
|
"delete_sequence_tooltip": "Permanentemente apagar esta sequencia",
|
||||||
|
"hide_photo_tooltip": "Esconder imagens selecionadas",
|
||||||
|
"delete_photo_tooltip": "Apagar permanentemente as imagens selecionadas",
|
||||||
|
"conf_pic_msg": "⚠️ As imagens selecionadas irão ser apagadas permanentemente",
|
||||||
|
"conf_sequence_msg": "⚠️ Esta sequencia vai ser apagada permanentemente",
|
||||||
|
"button_panel_orientation": "Definir orientação",
|
||||||
|
"button_panel_sort": "Ordenar sequencia",
|
||||||
|
"orientation_panel_tooltip": "Arraste a caixa azul para a direção desejada\"",
|
||||||
|
"orientation_input_label": "ou altere o ângulo aqui",
|
||||||
|
"orientation_input_placeholder": "Valor entre -180 e 180",
|
||||||
|
"orientation_input_error_value": "O valor tem de estar entre -180 e 180",
|
||||||
|
"orientation_panel_button": "Validar posição",
|
||||||
|
"orientation_updated": "Orientação atualizada",
|
||||||
|
"sort_updated": "Sequencia ordenada",
|
||||||
|
"hide_sequence_tooltip": "Esconder estas sequencias",
|
||||||
|
"info_msg_maj": "Use a tecla SHIFT para selecionar várias imagens",
|
||||||
|
"button_panel_photos": "Gerir imagens",
|
||||||
|
"orientation_panel_title": "Ajustar a orientação de todas as fotos na sequencia",
|
||||||
|
"duration_end": "Fim :",
|
||||||
|
"camera": "Camera :",
|
||||||
|
"button_delete": "Apagar a sequencia",
|
||||||
|
"button_disable": "Ocultar a sequencia",
|
||||||
|
"button_enable": "Mostrar a sequencia",
|
||||||
|
"picture_selected": "{count} imagem selecionada| {count} imagens selecionadas",
|
||||||
|
"hours": "{count} hora| {count} horas",
|
||||||
|
"minutes": "{count} minuto| {count} minutos",
|
||||||
|
"seconds": "{count} segundo| {count} segundos",
|
||||||
|
"select_text": "Selecionar tudo",
|
||||||
|
"unselect_text": "Desselecionar tudo",
|
||||||
|
"select_shift_text": "Selecione múltiplas imagens com shift",
|
||||||
|
"waiting_process": "Foto em progresso",
|
||||||
|
"no_image": "Sem imagens na sequencia",
|
||||||
|
"sort_panel_title": "Definição de ordenamento da sequencia",
|
||||||
|
"sort_panel_settings": "Ordenar sequencia por:",
|
||||||
|
"sort_panel_settings_order": "Ordenar:",
|
||||||
|
"sort_panel_settings_order_increase": "Ascendente",
|
||||||
|
"sort_panel_settings_order_decrease": "Descendente",
|
||||||
|
"sort_panel_check_gps": "Data GPS",
|
||||||
|
"sort_panel_check_file": "Data do ficheiro",
|
||||||
|
"sort_panel_check_name": "Nome do ficheiro",
|
||||||
|
"created": "Enviada :",
|
||||||
|
"taken": "Tirada em :",
|
||||||
|
"duration": "Duração :",
|
||||||
|
"duration_begin": "Inicio :"
|
||||||
|
},
|
||||||
|
"sequences": {
|
||||||
|
"no_sequence_found": "Nenhuma sequencia encontrada",
|
||||||
|
"filter_bbox_button": "Procurar nesta área",
|
||||||
|
"filter_date_close_button": "Fechar",
|
||||||
|
"title": "Minhas sequencias",
|
||||||
|
"filter_date_upload_title": "Filtrar por data de envio:",
|
||||||
|
"radio_date_placeholder": "03/01/2024",
|
||||||
|
"radio_date": "data",
|
||||||
|
"hide_button": "Ocultar",
|
||||||
|
"show_button": "Mostrar",
|
||||||
|
"delete_button": "Apagar",
|
||||||
|
"filter_date_reset_button": "Restaurar",
|
||||||
|
"filter_date_title": "Filtrar por data da foto :",
|
||||||
|
"sequence_name": "Nome",
|
||||||
|
"sequence_photos": "Fotos",
|
||||||
|
"sequence_date": "Tirada em",
|
||||||
|
"sequence_creation": "Enviar",
|
||||||
|
"sequence_creation_tooltip": "Filtrar por data de envio",
|
||||||
|
"sequence_date_tooltip": "Filtrar por data da foto",
|
||||||
|
"reset_filter_button": "Restaurar filtros",
|
||||||
|
"sequence_status": "Estado",
|
||||||
|
"sequence_published": "Publicado",
|
||||||
|
"sequence_waiting": "Ainda a processar",
|
||||||
|
"sequence_hidden": "Oculto",
|
||||||
|
"no_sequences_text": "Ainda não tem fotos publicadas 😢",
|
||||||
|
"button_upload": "Enviar imagens",
|
||||||
|
"sequence_deleted": "A sequencia foi apagada"
|
||||||
|
},
|
||||||
|
"share_pictures": {
|
||||||
|
"title": "Porquê contribuir para o Panoramax?",
|
||||||
|
"upload_description": "A aplicação web Panoramax permite o envio de todas as fotos de campo em formato JPEG com apenas um clique. Não são necessárias habilidades de programação. Para grandes números de fotos, recomendamos o uso da ferramenta em linha comandos",
|
||||||
|
"information_about_description": "Uma API está disponivel para recolher todos os metadados e fotos. <a href='{docLink}' target='_blank' style='color:#0a1f69'>\nDescubra mais aqui </a>\nOs dados também são exibidos na forma de <a href='{docTiles}' target='_blank' style='color:#0a1f69'> vector tiles </a>",
|
||||||
|
"card_description2": "Fotos 360º não são obrigatórias: fotos tiradas com o smartphone são tudo o que é necessário. Datas, localização e formato jpg são os únicos pré-requisitos.",
|
||||||
|
"description": "Contribuir para o Panoramax significa participar no desenvolvimento de um recurso digital geo-comum, livre, soberano e reutilizavel. Cada foto geolocalizada publicada no panoramax pode ser usada por qualquer um para uma grande variedade de propósitos, por exemplo, por uma autoridade local que precise de observar o estado das estradas, ou por um operador de telecomunicações para preparar uma intrevenção.\n\nCada contribuidor pode enviar as suas sequencias de imagens, modificar e consulta-las, tal como ver todas as outras - 360º ou não - contribuidas pela comunidade. O desfocamento das caras e matriculas é automático na plataforma.",
|
||||||
|
"alt_img_map": "Ilustração de uma mulher a olhar para um mapa com o seu smartphone geolocalizado",
|
||||||
|
"card_photo1": "Locais visíveis de uma estrada publica",
|
||||||
|
"card_photo2": "Fotos publicadas em formato 360º ou não",
|
||||||
|
"card_photo3": "Fotos facilmente reutilizáveis",
|
||||||
|
"card_photo4": "Uma fácil e rápida contribuição de imagens",
|
||||||
|
"card_alt_photo1": "Imagem de um edificio",
|
||||||
|
"card_alt_photo2": "Imagem a mostrar 360-graus",
|
||||||
|
"card_alt_photo3": "Imagem a mostrar um mapa com um ponteiro",
|
||||||
|
"card_alt_photo4": "Imagem a representar um ponteiro",
|
||||||
|
"card_description1": "Todas as fotos tiradas de uma estrada publica são aceites, desde que sejam geolocalizadas e vistas desde o solo.",
|
||||||
|
"card_description3": "Todas as fotos são acessíveis e reutilizáveis sem uma conta: via website ou API standard (standard STAC).",
|
||||||
|
"card_description4": "Várias ferramentas estão disponíveis para facilitar contribuições, incluindo em linhas de comandos e no site.",
|
||||||
|
"upload_subtitle": "Envie as suas fotos facilmente online",
|
||||||
|
"upload_illustration_alt": "Ilustração a mostrar o envio de fotos online",
|
||||||
|
"upload_button": "+ Enviar fotos",
|
||||||
|
"command_line_subtitle": "Ferramenta de linha de comandos",
|
||||||
|
"comment_install": "Instalar a ferramenta de linha de comandos geovisio",
|
||||||
|
"comment_upload": "Iniciar o comando de envio de imagens na pasta escolhida",
|
||||||
|
"button_copy": "Copiar",
|
||||||
|
"information_subtitle": "Aqui, todas as suas fotos são acessíveis para todos: ",
|
||||||
|
"information_text1": "Automaticamente desfocado em conformidade com a legislação.",
|
||||||
|
"information_text3": "No seu formato e resolução original para diversas reutilizações.",
|
||||||
|
"information_about_title": "Precisa acesso a fotos?",
|
||||||
|
"terminal_text": "geovisio upload --api-url {url} <PASTA_FOTOS>",
|
||||||
|
"information_text2": "As fotos enviadas serão publicadas como {word}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/main.ts
@@ -1,5 +1,5 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { createI18n } from 'vue-i18n'
|
import i18n from './i18n'
|
||||||
import VueMatomo from 'vue-matomo'
|
import VueMatomo from 'vue-matomo'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
@@ -9,14 +9,11 @@ import { VueDraggableResizable } from 'vue-draggable-resizable-vue3'
|
|||||||
import VCalendar from 'v-calendar'
|
import VCalendar from 'v-calendar'
|
||||||
import 'v-calendar/style.css'
|
import 'v-calendar/style.css'
|
||||||
import { pinia } from './store'
|
import { pinia } from './store'
|
||||||
import fr from './locales/fr.json'
|
|
||||||
import en from './locales/en.json'
|
|
||||||
import hu from './locales/hu.json'
|
|
||||||
import cs from './locales/cs.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'
|
||||||
import 'geovisio/build/index.css'
|
import 'geovisio/build/index.css'
|
||||||
|
import { getEnv } from '@/utils'
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@@ -24,26 +21,12 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
axios.defaults.baseURL = import.meta.env.VITE_API_URL
|
axios.defaults.baseURL = getEnv('VITE_API_URL')
|
||||||
axios.defaults.withCredentials = true
|
axios.defaults.withCredentials = true
|
||||||
const matomoHost = import.meta.env.VITE_MATOMO_HOST
|
const matomoHost = getEnv('VITE_MATOMO_HOST')
|
||||||
const matomoSiteId = import.meta.env.VITE_MATOMO_SITE_ID
|
const matomoSiteId = getEnv('VITE_MATOMO_SITE_ID')
|
||||||
const matomoExist = matomoHost && matomoSiteId
|
const matomoExist = matomoHost && matomoSiteId
|
||||||
|
|
||||||
const i18n = createI18n({
|
|
||||||
locale: navigator.language.split('-')[0],
|
|
||||||
fallbackLocale: 'en',
|
|
||||||
warnHtmlMessage: false,
|
|
||||||
globalInjection: true,
|
|
||||||
legacy: false,
|
|
||||||
messages: {
|
|
||||||
fr,
|
|
||||||
en,
|
|
||||||
hu,
|
|
||||||
cs
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
app.use(pinia)
|
app.use(pinia)
|
||||||
app.use(i18n)
|
app.use(i18n)
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ describe('Template', () => {
|
|||||||
await wrapper.vm.$nextTick()
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
expect(wrapper.html()).contains('text="title to edit"')
|
expect(wrapper.html()).contains('text="title to edit"')
|
||||||
expect(wrapper.html()).contains('icon="bi bi-x"')
|
|
||||||
expect(wrapper.html()).contains('text="Valider"')
|
expect(wrapper.html()).contains('text="Valider"')
|
||||||
})
|
})
|
||||||
test('should valid the name and emit', async () => {
|
test('should valid the name and emit', async () => {
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ import { createRouter, createWebHistory } from 'vue-router'
|
|||||||
import { useCookies } from 'vue3-cookies'
|
import { useCookies } from 'vue3-cookies'
|
||||||
import fr from '../../../locales/fr.json'
|
import fr from '../../../locales/fr.json'
|
||||||
import Header from '../../../components/Header.vue'
|
import Header from '../../../components/Header.vue'
|
||||||
vi.mock('vue-router')
|
vi.mock('vue-router', () => ({
|
||||||
|
useRoute: () => ({
|
||||||
|
path: '/mock-path'
|
||||||
|
})
|
||||||
|
}))
|
||||||
vi.mock('vue3-cookies', () => {
|
vi.mock('vue3-cookies', () => {
|
||||||
const mockCookies = {
|
const mockCookies = {
|
||||||
get: vi.fn()
|
get: vi.fn()
|
||||||
@@ -26,11 +30,6 @@ const i18n = createI18n({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const router = createRouter({
|
|
||||||
history: createWebHistory(),
|
|
||||||
routes: [{ path: '/', component: { template: '<div></div>' } }]
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Template', () => {
|
describe('Template', () => {
|
||||||
describe('When the user is not logged', () => {
|
describe('When the user is not logged', () => {
|
||||||
describe('When the user is in desktop', () => {
|
describe('When the user is in desktop', () => {
|
||||||
@@ -38,7 +37,7 @@ describe('Template', () => {
|
|||||||
import.meta.env.VITE_API_URL = 'api-url/'
|
import.meta.env.VITE_API_URL = 'api-url/'
|
||||||
const wrapper = mount(Header, {
|
const wrapper = mount(Header, {
|
||||||
global: {
|
global: {
|
||||||
plugins: [i18n, router],
|
plugins: [i18n],
|
||||||
mocks: {
|
mocks: {
|
||||||
$t: (msg) => msg
|
$t: (msg) => msg
|
||||||
}
|
}
|
||||||
@@ -61,7 +60,7 @@ describe('Template', () => {
|
|||||||
import.meta.env.VITE_API_URL = 'api-url/'
|
import.meta.env.VITE_API_URL = 'api-url/'
|
||||||
const wrapper = mount(Header, {
|
const wrapper = mount(Header, {
|
||||||
global: {
|
global: {
|
||||||
plugins: [i18n, router],
|
plugins: [i18n],
|
||||||
mocks: {
|
mocks: {
|
||||||
$t: (msg) => msg
|
$t: (msg) => msg
|
||||||
}
|
}
|
||||||
@@ -84,7 +83,7 @@ describe('Template', () => {
|
|||||||
import.meta.env.VITE_API_URL = 'api-url/'
|
import.meta.env.VITE_API_URL = 'api-url/'
|
||||||
const wrapper = mount(Header, {
|
const wrapper = mount(Header, {
|
||||||
global: {
|
global: {
|
||||||
plugins: [i18n, router],
|
plugins: [i18n],
|
||||||
mocks: {
|
mocks: {
|
||||||
$t: (msg) => msg
|
$t: (msg) => msg
|
||||||
}
|
}
|
||||||
@@ -107,7 +106,7 @@ describe('Template', () => {
|
|||||||
userProfileUrl: 'profil'
|
userProfileUrl: 'profil'
|
||||||
},
|
},
|
||||||
global: {
|
global: {
|
||||||
plugins: [i18n, router],
|
plugins: [i18n],
|
||||||
mocks: {
|
mocks: {
|
||||||
$t: (msg) => msg
|
$t: (msg) => msg
|
||||||
}
|
}
|
||||||
@@ -125,17 +124,10 @@ describe('Template', () => {
|
|||||||
|
|
||||||
describe('Methods', () => {
|
describe('Methods', () => {
|
||||||
describe('toggleMenu', () => {
|
describe('toggleMenu', () => {
|
||||||
let wrapper
|
|
||||||
beforeEach(async () => {
|
|
||||||
const VueRouter = await import('vue-router')
|
|
||||||
VueRouter.useRoute.mockReturnValueOnce({
|
|
||||||
path: 'my-path'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
it('Menu should be closed by default', () => {
|
it('Menu should be closed by default', () => {
|
||||||
wrapper = shallowMount(Header, {
|
const wrapper = shallowMount(Header, {
|
||||||
global: {
|
global: {
|
||||||
plugins: [i18n, router],
|
plugins: [i18n],
|
||||||
mocks: {
|
mocks: {
|
||||||
$t: (msg) => msg
|
$t: (msg) => msg
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ describe('Template', () => {
|
|||||||
expect(wrapper.vm.created).toBe(null)
|
expect(wrapper.vm.created).toBe(null)
|
||||||
expect(wrapper.vm.href).toBe(null)
|
expect(wrapper.vm.href).toBe(null)
|
||||||
expect(wrapper.vm.hrefHd).toBe(null)
|
expect(wrapper.vm.hrefHd).toBe(null)
|
||||||
expect(wrapper.vm.selected).toBe(false)
|
|
||||||
expect(wrapper.vm.selectedOnMap).toBe(false)
|
expect(wrapper.vm.selectedOnMap).toBe(false)
|
||||||
expect(wrapper.vm.status).toBe('')
|
expect(wrapper.vm.status).toBe('')
|
||||||
})
|
})
|
||||||
@@ -42,21 +41,6 @@ describe('Template', () => {
|
|||||||
expect(wrapper.html()).contains('10 mars 2023')
|
expect(wrapper.html()).contains('10 mars 2023')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('When the component is selected', () => {
|
|
||||||
it('should render the component with the selected class', () => {
|
|
||||||
const wrapper = shallowMount(ImageItem, {
|
|
||||||
global: {
|
|
||||||
mocks: {
|
|
||||||
$t: (msg) => msg
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
selected: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
expect(wrapper.html()).contains('class="selected button-image-item"')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
describe('When the component is ready', () => {
|
describe('When the component is ready', () => {
|
||||||
it('should render the component with the status class', () => {
|
it('should render the component with the status class', () => {
|
||||||
const wrapper = shallowMount(ImageItem, {
|
const wrapper = shallowMount(ImageItem, {
|
||||||
@@ -93,7 +77,7 @@ describe('Template', () => {
|
|||||||
expect(wrapper.html()).contains('class="bi bi-card-image icon-waiting"')
|
expect(wrapper.html()).contains('class="bi bi-card-image icon-waiting"')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('When the component is selected on the map but not selected', () => {
|
describe('When the component is selected on the map', () => {
|
||||||
it('should render the component with the map pointer', () => {
|
it('should render the component with the map pointer', () => {
|
||||||
const wrapper = shallowMount(ImageItem, {
|
const wrapper = shallowMount(ImageItem, {
|
||||||
global: {
|
global: {
|
||||||
@@ -102,11 +86,11 @@ describe('Template', () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
selectedOnMap: true,
|
selectedOnMap: true
|
||||||
selected: false
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
expect(wrapper.html()).contains('class="icon-img pointer-map"')
|
expect(wrapper.html()).contains('class="pointer-map"')
|
||||||
|
expect(wrapper.html()).contains('class="selected button-image-item"')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('When the component is selected but not selected on the map', () => {
|
describe('When the component is selected but not selected on the map', () => {
|
||||||
@@ -118,29 +102,10 @@ describe('Template', () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
selectedOnMap: false,
|
selectedOnMap: false
|
||||||
selected: true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
expect(wrapper.html()).contains('class="bi bi-check-lg"')
|
expect(wrapper.html()).contains('class="button-image-item"')
|
||||||
expect(wrapper.html()).contains('class="icon-img button-check"')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
describe('When the component is selected and selected on the map', () => {
|
|
||||||
it('should render the component with the check icon', () => {
|
|
||||||
const wrapper = shallowMount(ImageItem, {
|
|
||||||
global: {
|
|
||||||
mocks: {
|
|
||||||
$t: (msg) => msg
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
selectedOnMap: true,
|
|
||||||
selected: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
expect(wrapper.html()).contains('class="icon-img button-check-pointer"')
|
|
||||||
expect(wrapper.html()).contains('class="bi bi-check-lg"')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('When the button is trigger', () => {
|
describe('When the button is trigger', () => {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ describe('Template', () => {
|
|||||||
expect(wrapper.vm.text).toBe(null)
|
expect(wrapper.vm.text).toBe(null)
|
||||||
expect(wrapper.vm.textPictureType).toBe(null)
|
expect(wrapper.vm.textPictureType).toBe(null)
|
||||||
expect(wrapper.vm.textSecondPart).toBe(null)
|
expect(wrapper.vm.textSecondPart).toBe(null)
|
||||||
expect(wrapper.vm.accept).toBe('')
|
|
||||||
})
|
})
|
||||||
test('should have all the props filled', () => {
|
test('should have all the props filled', () => {
|
||||||
const wrapper = shallowMount(InputUpload, {
|
const wrapper = shallowMount(InputUpload, {
|
||||||
@@ -23,11 +22,9 @@ describe('Template', () => {
|
|||||||
props: {
|
props: {
|
||||||
text: 'my text',
|
text: 'my text',
|
||||||
textPictureType: 'my textPictureType',
|
textPictureType: 'my textPictureType',
|
||||||
textSecondPart: 'my textSecondPart',
|
textSecondPart: 'my textSecondPart'
|
||||||
accept: 'accept'
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
expect(wrapper.html()).contains('accept="accept"')
|
|
||||||
expect(wrapper.html()).contains('my text')
|
expect(wrapper.html()).contains('my text')
|
||||||
expect(wrapper.html()).contains('my textSecondPart')
|
expect(wrapper.html()).contains('my textSecondPart')
|
||||||
expect(wrapper.html()).contains('my textPictureType')
|
expect(wrapper.html()).contains('my textPictureType')
|
||||||
|
|||||||
@@ -1,24 +1,13 @@
|
|||||||
import { test, describe, vi, expect } from 'vitest'
|
import { test, describe, vi, expect } from 'vitest'
|
||||||
import { shallowMount } from '@vue/test-utils'
|
import { shallowMount } from '@vue/test-utils'
|
||||||
import Link from '../../../components/Link.vue'
|
import Link from '../../../components/Link.vue'
|
||||||
import { createI18n } from 'vue-i18n'
|
import * as img from '../../../utils/image'
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
vi.mock('vue-i18n', () => ({
|
||||||
import fr from '../../../locales/fr.json'
|
useI18n: () => ({
|
||||||
vi.mock('vue-i18n')
|
t: (key) => key
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
const i18n = createI18n({
|
|
||||||
locale: 'fr',
|
|
||||||
fallbackLocale: 'fr',
|
|
||||||
globalInjection: true,
|
|
||||||
legacy: false,
|
|
||||||
messages: {
|
|
||||||
fr
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const router = createRouter({
|
|
||||||
history: createWebHistory(),
|
|
||||||
routes: [{ path: '/', component: { template: '<div></div>' } }]
|
|
||||||
})
|
|
||||||
const stubs = {
|
const stubs = {
|
||||||
'router-link': {
|
'router-link': {
|
||||||
template: '<router-link></router-link>'
|
template: '<router-link></router-link>'
|
||||||
@@ -28,9 +17,7 @@ describe('Template', () => {
|
|||||||
describe('Props', () => {
|
describe('Props', () => {
|
||||||
test('should have default props', () => {
|
test('should have default props', () => {
|
||||||
const wrapper = shallowMount(Link, {
|
const wrapper = shallowMount(Link, {
|
||||||
global: {
|
global: {}
|
||||||
plugins: [i18n, router]
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(wrapper.vm.text).toBe(null)
|
expect(wrapper.vm.text).toBe(null)
|
||||||
@@ -49,8 +36,7 @@ describe('Template', () => {
|
|||||||
test('should render the component as a <a>', () => {
|
test('should render the component as a <a>', () => {
|
||||||
const wrapper = shallowMount(Link, {
|
const wrapper = shallowMount(Link, {
|
||||||
global: {
|
global: {
|
||||||
stubs,
|
stubs
|
||||||
plugins: [i18n]
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
type: 'external'
|
type: 'external'
|
||||||
@@ -61,8 +47,7 @@ describe('Template', () => {
|
|||||||
test('should render an icon inside the link', () => {
|
test('should render an icon inside the link', () => {
|
||||||
const wrapper = shallowMount(Link, {
|
const wrapper = shallowMount(Link, {
|
||||||
global: {
|
global: {
|
||||||
stubs,
|
stubs
|
||||||
plugins: [i18n]
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
type: 'external',
|
type: 'external',
|
||||||
@@ -74,8 +59,7 @@ describe('Template', () => {
|
|||||||
test('should render the text inside the link', () => {
|
test('should render the text inside the link', () => {
|
||||||
const wrapper = shallowMount(Link, {
|
const wrapper = shallowMount(Link, {
|
||||||
global: {
|
global: {
|
||||||
stubs,
|
stubs
|
||||||
plugins: [i18n]
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
type: 'external',
|
type: 'external',
|
||||||
@@ -85,28 +69,29 @@ describe('Template', () => {
|
|||||||
expect(wrapper.html()).contains('my-text')
|
expect(wrapper.html()).contains('my-text')
|
||||||
})
|
})
|
||||||
test('should render an image inside the link', () => {
|
test('should render an image inside the link', () => {
|
||||||
|
const imgSrc = 'my-url.png'
|
||||||
|
const imgPath = vi.spyOn(img, 'img')
|
||||||
|
imgPath.mockReturnValue(imgSrc)
|
||||||
const wrapper = shallowMount(Link, {
|
const wrapper = shallowMount(Link, {
|
||||||
global: {
|
global: {
|
||||||
stubs,
|
stubs
|
||||||
plugins: [i18n]
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
type: 'external',
|
type: 'external',
|
||||||
image: {
|
image: {
|
||||||
url: 'my-url.png',
|
url: imgSrc,
|
||||||
alt: 'my-alt'
|
alt: 'my-alt'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
expect(wrapper.html()).contains('<img')
|
expect(wrapper.html()).contains('<img')
|
||||||
expect(wrapper.html()).contains('my-url.png')
|
expect(wrapper.html()).contains(imgSrc)
|
||||||
expect(wrapper.html()).contains('my-alt')
|
expect(wrapper.html()).contains('my-alt')
|
||||||
})
|
})
|
||||||
test('should render the text inside the link', () => {
|
test('should render the text inside the link', () => {
|
||||||
const wrapper = shallowMount(Link, {
|
const wrapper = shallowMount(Link, {
|
||||||
global: {
|
global: {
|
||||||
stubs,
|
stubs
|
||||||
plugins: [i18n]
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
type: 'external',
|
type: 'external',
|
||||||
@@ -120,17 +105,13 @@ describe('Template', () => {
|
|||||||
describe('When the component is an internal link', () => {
|
describe('When the component is an internal link', () => {
|
||||||
test('should render the component as an internal link', () => {
|
test('should render the component as an internal link', () => {
|
||||||
const wrapper = shallowMount(Link, {
|
const wrapper = shallowMount(Link, {
|
||||||
global: {
|
global: {}
|
||||||
plugins: [i18n, router]
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
expect(wrapper.html()).contains('<router-link')
|
expect(wrapper.html()).contains('<router-link')
|
||||||
})
|
})
|
||||||
test('should render an icon inside the link', () => {
|
test('should render an icon inside the link', () => {
|
||||||
const wrapper = shallowMount(Link, {
|
const wrapper = shallowMount(Link, {
|
||||||
global: {
|
global: {},
|
||||||
plugins: [i18n]
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
type: 'internal',
|
type: 'internal',
|
||||||
icon: 'my-icon'
|
icon: 'my-icon'
|
||||||
@@ -140,9 +121,7 @@ describe('Template', () => {
|
|||||||
})
|
})
|
||||||
test('should render the text inside the link', () => {
|
test('should render the text inside the link', () => {
|
||||||
const wrapper = shallowMount(Link, {
|
const wrapper = shallowMount(Link, {
|
||||||
global: {
|
global: {},
|
||||||
plugins: [i18n, router]
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
text: 'my-text'
|
text: 'my-text'
|
||||||
}
|
}
|
||||||
@@ -151,9 +130,7 @@ describe('Template', () => {
|
|||||||
})
|
})
|
||||||
test('should render the disabled attribute', () => {
|
test('should render the disabled attribute', () => {
|
||||||
const wrapper = shallowMount(Link, {
|
const wrapper = shallowMount(Link, {
|
||||||
global: {
|
global: {},
|
||||||
plugins: [i18n, router]
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
disabled: true
|
disabled: true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,19 @@ import { shallowMount } from '@vue/test-utils'
|
|||||||
import HeaderOpen from '../../../../components/header/HeaderOpen.vue'
|
import HeaderOpen from '../../../../components/header/HeaderOpen.vue'
|
||||||
import i18n from '../../config'
|
import i18n from '../../config'
|
||||||
import { createRouter, createWebHistory } from 'vue-router/dist/vue-router'
|
import { createRouter, createWebHistory } from 'vue-router/dist/vue-router'
|
||||||
vi.mock('vue-router')
|
vi.mock('vue-router', () => ({
|
||||||
|
useRoute: () => ({
|
||||||
|
path: '/mock-path'
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
import.meta.env.VITE_API_URL = 'api-url/'
|
||||||
|
|
||||||
const router = createRouter({
|
|
||||||
history: createWebHistory(),
|
|
||||||
routes: [{ path: '/', component: { template: '<div></div>' } }]
|
|
||||||
})
|
|
||||||
describe('Template', () => {
|
describe('Template', () => {
|
||||||
describe('Props', () => {
|
describe('Props', () => {
|
||||||
it('should have default props', () => {
|
it('should have default props', () => {
|
||||||
const wrapper = shallowMount(HeaderOpen, {
|
const wrapper = shallowMount(HeaderOpen, {
|
||||||
global: {
|
global: {
|
||||||
plugins: [i18n, router]
|
plugins: [i18n]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
expect(wrapper.vm.menuIsClosed).toBe(true)
|
expect(wrapper.vm.menuIsClosed).toBe(true)
|
||||||
@@ -25,7 +26,7 @@ describe('Template', () => {
|
|||||||
it('should render the desktop entries', () => {
|
it('should render the desktop entries', () => {
|
||||||
const wrapper = shallowMount(HeaderOpen, {
|
const wrapper = shallowMount(HeaderOpen, {
|
||||||
global: {
|
global: {
|
||||||
plugins: [i18n, router]
|
plugins: [i18n]
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
menuIsClosed: false,
|
menuIsClosed: false,
|
||||||
@@ -33,14 +34,14 @@ describe('Template', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
expect(wrapper.html()).contains('class="logged-link desktop"')
|
expect(wrapper.html()).contains('class="logged-link desktop"')
|
||||||
expect(wrapper.html()).contains('text="Pourquoi contribuer ?"')
|
expect(wrapper.html()).contains('text="Pourquoi contribuer ?"')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('When the user is in mobile', () => {
|
describe('When the user is in mobile', () => {
|
||||||
it('should render the responsive entries', () => {
|
it('should render the responsive entries', () => {
|
||||||
const wrapper = shallowMount(HeaderOpen, {
|
const wrapper = shallowMount(HeaderOpen, {
|
||||||
global: {
|
global: {
|
||||||
plugins: [i18n, router]
|
plugins: [i18n]
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
menuIsClosed: false,
|
menuIsClosed: false,
|
||||||
@@ -53,17 +54,16 @@ describe('Template', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
it('should render all the commons entries', () => {
|
it('should render all the commons entries', () => {
|
||||||
import.meta.env.VITE_API_URL = 'api-url/'
|
|
||||||
const wrapper = shallowMount(HeaderOpen, {
|
const wrapper = shallowMount(HeaderOpen, {
|
||||||
global: {
|
global: {
|
||||||
plugins: [i18n, router]
|
plugins: [i18n]
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
menuIsClosed: false,
|
menuIsClosed: false,
|
||||||
userProfileUrlLength: 5
|
userProfileUrlLength: 5
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
expect(wrapper.html()).contains('text="Pourquoi contribuer ?"')
|
expect(wrapper.html()).contains('text="Pourquoi contribuer ?"')
|
||||||
expect(wrapper.html()).contains('text="+ Partager des photos"')
|
expect(wrapper.html()).contains('text="+ Partager des photos"')
|
||||||
expect(wrapper.html()).contains('text="Mes informations"')
|
expect(wrapper.html()).contains('text="Mes informations"')
|
||||||
expect(wrapper.html()).contains('text="Mes paramètres"')
|
expect(wrapper.html()).contains('text="Mes paramètres"')
|
||||||
|
|||||||
@@ -269,7 +269,9 @@ describe('Template', () => {
|
|||||||
sequence
|
sequence
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
expect(wrapper.html()).contains('status="hidden"></image-item')
|
expect(wrapper.html()).contains(
|
||||||
|
'status="hidden" selected="false"></image-item'
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { test, describe, expect } from 'vitest'
|
import { vi, test, describe, expect } from 'vitest'
|
||||||
import { shallowMount } from '@vue/test-utils'
|
import { shallowMount } from '@vue/test-utils'
|
||||||
import Card from '../../../../components/share-pictures/Card.vue'
|
import Card from '../../../../components/share-pictures/Card.vue'
|
||||||
import i18n from '../../config'
|
import i18n from '../../config'
|
||||||
|
import * as img from '../../../../utils/image'
|
||||||
|
|
||||||
describe('Template', () => {
|
describe('Template', () => {
|
||||||
describe('Props', () => {
|
describe('Props', () => {
|
||||||
test('should have default props', () => {
|
test('should have default props', () => {
|
||||||
@@ -16,6 +18,10 @@ describe('Template', () => {
|
|||||||
expect(wrapper.vm.imgAlt).toBe(null)
|
expect(wrapper.vm.imgAlt).toBe(null)
|
||||||
})
|
})
|
||||||
test('should have all the props filled', () => {
|
test('should have all the props filled', () => {
|
||||||
|
const imgSrc = 'my-imgSrc.jpg'
|
||||||
|
const imgPath = vi.spyOn(img, 'img')
|
||||||
|
imgPath.mockReturnValue(imgSrc)
|
||||||
|
|
||||||
const wrapper = shallowMount(Card, {
|
const wrapper = shallowMount(Card, {
|
||||||
global: {
|
global: {
|
||||||
plugins: [i18n]
|
plugins: [i18n]
|
||||||
@@ -23,13 +29,14 @@ describe('Template', () => {
|
|||||||
props: {
|
props: {
|
||||||
title: 'my title',
|
title: 'my title',
|
||||||
description: 'my description',
|
description: 'my description',
|
||||||
imgSrc: 'my-imgSrc.jpg',
|
imgSrc: imgSrc,
|
||||||
imgAlt: 'my imgAlt'
|
imgAlt: 'my imgAlt'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(wrapper.html()).contains('my title</h2>')
|
expect(wrapper.html()).contains('my title</h2>')
|
||||||
expect(wrapper.html()).contains('my description</p>')
|
expect(wrapper.html()).contains('my description</p>')
|
||||||
expect(wrapper.html()).contains('/my-imgSrc.jpg')
|
expect(wrapper.html()).contains(imgSrc)
|
||||||
expect(wrapper.html()).contains('alt="my imgAlt"')
|
expect(wrapper.html()).contains('alt="my imgAlt"')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { it, describe, expect, vi, beforeEach } from 'vitest'
|
import { it, describe, expect, vi } from 'vitest'
|
||||||
import {
|
import {
|
||||||
imageStatus,
|
imageStatus,
|
||||||
photoToDeleteOrPatchSelected,
|
photoToDeleteOrPatchSelected,
|
||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
decodingFlaskCookie,
|
decodingFlaskCookie,
|
||||||
hasASessionCookieDecoded
|
hasASessionCookieDecoded
|
||||||
} from '../../utils/auth'
|
} from '../../utils/auth'
|
||||||
import { img } from '../../utils/image'
|
|
||||||
import { useCookies } from 'vue3-cookies'
|
import { useCookies } from 'vue3-cookies'
|
||||||
vi.mock('vue3-cookies', () => {
|
vi.mock('vue3-cookies', () => {
|
||||||
const mockCookies = {
|
const mockCookies = {
|
||||||
@@ -177,13 +176,6 @@ describe('hasASessionCookieDecoded', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('img', () => {
|
|
||||||
it('should render the formated img path', () => {
|
|
||||||
const name = 'my-img'
|
|
||||||
expect(img(name)).contains('src/assets/images')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('sortByName', () => {
|
describe('sortByName', () => {
|
||||||
it('should return the the list sorted by name', () => {
|
it('should return the the list sorted by name', () => {
|
||||||
const list1 = [
|
const list1 = [
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { it, describe, expect, vi } from 'vitest'
|
import { it, describe, expect, vi } from 'vitest'
|
||||||
import { shallowMount, mount } from '@vue/test-utils'
|
import { shallowMount, mount } from '@vue/test-utils'
|
||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import UploadPicturesView from '../../../views/UploadPicturesView.vue'
|
import UploadPicturesView from '../../../views/UploadPicturesView.vue'
|
||||||
import i18n from '../config'
|
import i18n from '../config'
|
||||||
import InputUpload from '../../../components/InputUpload.vue'
|
import InputUpload from '../../../components/InputUpload.vue'
|
||||||
@@ -8,11 +9,22 @@ 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'
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: { template: '<div></div>' },
|
||||||
|
onBeforeRouteLeave: vi.fn()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
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, {
|
||||||
global: {
|
global: {
|
||||||
plugins: [i18n],
|
plugins: [i18n, router],
|
||||||
mocks: {
|
mocks: {
|
||||||
$t: (msg) => msg
|
$t: (msg) => msg
|
||||||
}
|
}
|
||||||
@@ -28,7 +40,7 @@ describe('Template', () => {
|
|||||||
it('should trigger to uploadPictures', async () => {
|
it('should trigger to uploadPictures', async () => {
|
||||||
const wrapper = mount(UploadPicturesView, {
|
const wrapper = mount(UploadPicturesView, {
|
||||||
global: {
|
global: {
|
||||||
plugins: [i18n],
|
plugins: [i18n, router],
|
||||||
mocks: {
|
mocks: {
|
||||||
$t: (msg) => msg
|
$t: (msg) => msg
|
||||||
},
|
},
|
||||||
@@ -38,10 +50,16 @@ describe('Template', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
const sortByNameMock = vi.spyOn(sortByName, 'sortByName')
|
const sortByNameMock = vi.spyOn(sortByName, 'sortByName')
|
||||||
sortByNameMock.mockReturnValue([{}, {}])
|
sortByNameMock.mockReturnValue([
|
||||||
|
{ type: 'image/jpeg' },
|
||||||
|
{ type: 'image/jpeg' }
|
||||||
|
])
|
||||||
const wrapperInputUpload = wrapper.findComponent(InputUpload)
|
const wrapperInputUpload = wrapper.findComponent(InputUpload)
|
||||||
await wrapperInputUpload.trigger('trigger')
|
await wrapperInputUpload.trigger('trigger')
|
||||||
await wrapperInputUpload.vm.$emit('trigger', [{}, {}])
|
await wrapperInputUpload.vm.$emit('trigger', [
|
||||||
|
{ type: 'image/jpeg' },
|
||||||
|
{ type: 'image/jpeg' }
|
||||||
|
])
|
||||||
expect(wrapper.html()).contains('class="wrapper-uploading"')
|
expect(wrapper.html()).contains('class="wrapper-uploading"')
|
||||||
expect(wrapper.html()).contains('pages.upload.uploading_process')
|
expect(wrapper.html()).contains('pages.upload.uploading_process')
|
||||||
expect(wrapper.html()).contains('class="uploading-img"')
|
expect(wrapper.html()).contains('class="uploading-img"')
|
||||||
@@ -57,7 +75,7 @@ describe('Template', () => {
|
|||||||
template: '<div></div>'
|
template: '<div></div>'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugins: [i18n],
|
plugins: [i18n, router],
|
||||||
mocks: {
|
mocks: {
|
||||||
$t: (msg) => msg
|
$t: (msg) => msg
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import pako from 'pako'
|
import pako from 'pako'
|
||||||
import { useCookies } from 'vue3-cookies'
|
import { useCookies } from 'vue3-cookies'
|
||||||
|
import { manageSlashUrl } from '@/utils'
|
||||||
const { cookies } = useCookies()
|
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 `${manageSlashUrl()}api/${authRoute}?next_url=${encodeURIComponent(
|
||||||
import.meta.env.VITE_API_URL
|
`${next}`
|
||||||
}api/${authRoute}?next_url=${encodeURIComponent(`${next}`)}`
|
)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function to decode the flask cookie and have the user information like username
|
// This function to decode the flask cookie and have the user information like username
|
||||||
|
|||||||
10
src/utils/calc.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
function modulo180(value: number, roadDegrees: number): number {
|
||||||
|
let moduloAngle = (value - roadDegrees) % 360
|
||||||
|
while (moduloAngle > 180 || moduloAngle < -180) {
|
||||||
|
if (moduloAngle < -180) moduloAngle += 360
|
||||||
|
if (moduloAngle > 180) moduloAngle -= 360
|
||||||
|
}
|
||||||
|
return Math.round(moduloAngle)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { modulo180 }
|
||||||
@@ -1,3 +1,16 @@
|
|||||||
export function createUrlLink(picId: string): string {
|
function createUrlLink(picId: string): string {
|
||||||
return encodeURIComponent(`${window.location.origin}/#focus=pic&pic=${picId}`)
|
return encodeURIComponent(`${window.location.origin}/#focus=pic&pic=${picId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function manageSlashUrl(): string {
|
||||||
|
let apiUrl = getEnv('VITE_API_URL')
|
||||||
|
if (apiUrl.charAt(apiUrl.length - 1) !== '/') apiUrl += '/'
|
||||||
|
return apiUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEnv(value: string | undefined) {
|
||||||
|
if (value) return import.meta.env[value]
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export { createUrlLink, manageSlashUrl, getEnv }
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<main id="homePage" class="entry-page">
|
<main id="homePage" class="entry-page">
|
||||||
<section class="entry-section">
|
<section class="entry-section">
|
||||||
<Viewer ref="viewerRef"> </Viewer>
|
<WebsiteViewer ref="viewerRef" />
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import Viewer from '@/components/Viewer.vue'
|
import WebsiteViewer from '@/components/WebsiteViewer.vue'
|
||||||
import type ViewerType from '@/components/Viewer.vue'
|
import type ViewerType from '@/components/WebsiteViewer.vue'
|
||||||
const viewerRef = ref<InstanceType<typeof ViewerType>>()
|
const viewerRef = ref<InstanceType<typeof ViewerType>>()
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -27,24 +27,29 @@ const viewerRef = ref<InstanceType<typeof ViewerType>>()
|
|||||||
.logged .entry-section {
|
.logged .entry-section {
|
||||||
height: calc(100vh - #{toRem(8)});
|
height: calc(100vh - #{toRem(8)});
|
||||||
}
|
}
|
||||||
|
// dvh fix the bug the size of ths viewer depending on the device
|
||||||
|
// 2 times height because dvh not working on all browsers versions
|
||||||
.full-viewer {
|
.full-viewer {
|
||||||
.entry-section {
|
.entry-section {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
height: calc(100dvh - #{toRem(0)});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.gvs-focus-map .entry-report-button {
|
.gvs-focus-map .entry-report-button {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@media (max-width: toRem(76.8)) {
|
@media (max-width: toRem(84.8)) {
|
||||||
.entry-page {
|
.entry-page {
|
||||||
padding-top: toRem(11.5);
|
padding-top: toRem(11.5);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.entry-section {
|
.entry-section {
|
||||||
|
height: calc(100vh - #{toRem(18)});
|
||||||
height: calc(100dvh - #{toRem(18)});
|
height: calc(100dvh - #{toRem(18)});
|
||||||
}
|
}
|
||||||
.full-viewer {
|
.full-viewer {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
height: calc(100dvh - #{toRem(0)});
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,10 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
import { manageSlashUrl } from '@/utils'
|
||||||
|
|
||||||
const myAccountUrl = computed<string>(
|
const myAccountUrl = computed<string>(
|
||||||
() => `${import.meta.env.VITE_API_URL}oauth/realms/geovisio/account`
|
() => `${manageSlashUrl()}oauth/realms/geovisio/account`
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,16 @@
|
|||||||
@trigger="menuIsOpen = !menuIsOpen"
|
@trigger="menuIsOpen = !menuIsOpen"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<section class="entry-viewer">
|
<section v-if="sequence" class="entry-viewer">
|
||||||
<Viewer
|
<WebsiteViewer
|
||||||
:fetch-options="{ fetchOptions: { credentials: 'include' } }"
|
:key="viewerType"
|
||||||
|
:fetch-options="{ credentials: 'include' }"
|
||||||
|
:pic-id="pictures[0] && pictures[0].id ? pictures[0].id : undefined"
|
||||||
|
:seq-id="sequence.id"
|
||||||
|
:user-id="getUserId"
|
||||||
|
:viewer-type="viewerType"
|
||||||
|
:seq-brute-deg="seqBruteDeg"
|
||||||
|
:road-degrees="seqDegrees"
|
||||||
ref="viewerRef"
|
ref="viewerRef"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
@@ -20,11 +27,17 @@
|
|||||||
icon="bi bi-arrow-left"
|
icon="bi bi-arrow-left"
|
||||||
:text="$t('pages.sequence.back_button')"
|
:text="$t('pages.sequence.back_button')"
|
||||||
:route="{ name: 'my-sequences' }"
|
:route="{ name: 'my-sequences' }"
|
||||||
|
:disabled="isLoadingOrient || isLoadingSort"
|
||||||
look="link--grey-dark"
|
look="link--grey-dark"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="entry-tab-panel">
|
<div class="entry-tab-panel">
|
||||||
<TabPanel :panel-selected="panelView" @trigger="setPanelView" />
|
<TabPanel
|
||||||
|
:panel-selected="panelView"
|
||||||
|
:is-loading="isLoadingOrient || isLoadingSort"
|
||||||
|
:is-sequence-owner="isSequenceOwner"
|
||||||
|
@trigger="setPanelView"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -40,11 +53,13 @@
|
|||||||
sequenceStatus
|
sequenceStatus
|
||||||
}}</span>
|
}}</span>
|
||||||
<EditText
|
<EditText
|
||||||
|
v-if="isSequenceOwner"
|
||||||
:default-text="sequence.title"
|
:default-text="sequence.title"
|
||||||
:is-loading="isLoadingTitle"
|
:is-loading="isLoadingTitle"
|
||||||
:form-title="$t('pages.sequence.sequence_form_title')"
|
:form-title="$t('pages.sequence.sequence_form_title')"
|
||||||
@triggerNewText="setNewSequenceTitle"
|
@triggerNewText="setNewSequenceTitle"
|
||||||
/>
|
/>
|
||||||
|
<span v-else class="form-title">{{ sequence.title }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@@ -79,10 +94,7 @@
|
|||||||
@trigger="deleteCollection"
|
@trigger="deleteCollection"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div :class="[sequence.status, 'collapse show']" id="collapseTarget">
|
||||||
:class="[sequence.status, 'collapse py-2 show']"
|
|
||||||
id="collapseTarget"
|
|
||||||
>
|
|
||||||
<div class="block-collapse">
|
<div class="block-collapse">
|
||||||
<div class="wrapper-info-top">
|
<div class="wrapper-info-top">
|
||||||
<span v-if="sequence.created"
|
<span v-if="sequence.created"
|
||||||
@@ -141,21 +153,26 @@
|
|||||||
"
|
"
|
||||||
:item-selected="itemSelected"
|
:item-selected="itemSelected"
|
||||||
@triggerInputCheck="triggerCheck"
|
@triggerInputCheck="triggerCheck"
|
||||||
|
@triggerInputCheckItem="triggerCheckItem"
|
||||||
@triggerGoToNextPage="goToNextPage"
|
@triggerGoToNextPage="goToNextPage"
|
||||||
@triggerSelectImageAndMove="selectImageAndMove"
|
@triggerSelectImageAndMove="selectImageAndMove"
|
||||||
@triggerPatchOrDeleteCollectionItems="patchOrDeleteCollectionItems"
|
@triggerPatchOrDeleteCollectionItems="patchOrDeleteCollectionItems"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="panelView === 'orientation'" class="entry-panel">
|
<div
|
||||||
|
v-if="panelView === 'orientation' && isSequenceOwner"
|
||||||
|
class="entry-panel"
|
||||||
|
>
|
||||||
<PanelOrientationManagement
|
<PanelOrientationManagement
|
||||||
:road-degrees="seqDegrees"
|
:road-degrees="seqDegrees"
|
||||||
:seq-brute-deg="seqBruteDeg"
|
:seq-brute-deg="seqBruteDeg"
|
||||||
:is-loading="isLoadingOrient"
|
:is-loading="isLoadingOrient"
|
||||||
@triggerAngle="orientSequence"
|
@triggerAngle="orientSequence"
|
||||||
|
@triggerMovingAngle="orientMovingSequence"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="entry-panel">
|
<div v-if="panelView === 'sort' && isSequenceOwner" class="entry-panel">
|
||||||
<PanelSortManagement
|
<PanelSortManagement
|
||||||
:sequence-sorted="sequence['geovisio:sorted-by']"
|
:sequence-sorted="sequence['geovisio:sorted-by']"
|
||||||
@triggerSort="sortSequence"
|
@triggerSort="sortSequence"
|
||||||
@@ -170,7 +187,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref, watchEffect, computed } from 'vue'
|
import { onMounted, onUnmounted, watchEffect, ref, computed } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useSequenceStore } from '@/store/sequence'
|
import { useSequenceStore } from '@/store/sequence'
|
||||||
@@ -185,8 +202,8 @@ import TabPanel from '@/components/TabPanel.vue'
|
|||||||
import PanelPhotosManagement from '@/components/sequence/PanelPhotosManagement.vue'
|
import PanelPhotosManagement from '@/components/sequence/PanelPhotosManagement.vue'
|
||||||
import PanelOrientationManagement from '@/components/sequence/PanelOrientationManagement.vue'
|
import PanelOrientationManagement from '@/components/sequence/PanelOrientationManagement.vue'
|
||||||
import PanelSortManagement from '@/components/sequence/PanelSortManagement.vue'
|
import PanelSortManagement from '@/components/sequence/PanelSortManagement.vue'
|
||||||
import Viewer from '@/components/Viewer.vue'
|
import WebsiteViewer from '@/components/WebsiteViewer.vue'
|
||||||
import type ViewerType from '@/components/Viewer.vue'
|
import type ViewerType from '@/components/WebsiteViewer.vue'
|
||||||
import { durationCalc, formatDate } from '@/utils/dates'
|
import { durationCalc, formatDate } from '@/utils/dates'
|
||||||
import {
|
import {
|
||||||
deleteACollectionItem,
|
deleteACollectionItem,
|
||||||
@@ -203,6 +220,7 @@ import {
|
|||||||
spliceIntoChunks,
|
spliceIntoChunks,
|
||||||
formatPaginationItems
|
formatPaginationItems
|
||||||
} from '@/views/utils/sequence/index'
|
} from '@/views/utils/sequence/index'
|
||||||
|
import { getEnv } from '@/utils'
|
||||||
import type {
|
import type {
|
||||||
ResponseUserPhotoInterface,
|
ResponseUserPhotoInterface,
|
||||||
ResponseUserPhotoLinksInterface,
|
ResponseUserPhotoLinksInterface,
|
||||||
@@ -232,12 +250,14 @@ let isLoadingSort = ref<boolean>(false)
|
|||||||
let seqDegrees = ref<number>(0)
|
let seqDegrees = ref<number>(0)
|
||||||
let seqBruteDeg = ref<number>(0)
|
let seqBruteDeg = ref<number>(0)
|
||||||
let panelView = ref<string>('photos')
|
let panelView = ref<string>('photos')
|
||||||
|
let viewerType = ref<string>('editor')
|
||||||
const collapseMenu = ref<HTMLDivElement>()
|
const collapseMenu = ref<HTMLDivElement>()
|
||||||
const deleteAll = ref<HTMLDivElement>()
|
const deleteAll = ref<HTMLDivElement>()
|
||||||
const menuHeight = ref<string>('0')
|
const menuHeight = ref<string>('0')
|
||||||
const viewerRef = ref<InstanceType<typeof ViewerType> | null>(null)
|
const viewerRef = ref<InstanceType<typeof ViewerType> | null>(null)
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
isLoading.value = true
|
||||||
try {
|
try {
|
||||||
const fetchAllCollectionInfo = await Promise.all([
|
const fetchAllCollectionInfo = await Promise.all([
|
||||||
fetchCollection(route.params.id),
|
fetchCollection(route.params.id),
|
||||||
@@ -250,60 +270,61 @@ onMounted(async () => {
|
|||||||
fetchAllCollectionInfo[1].data.links
|
fetchAllCollectionInfo[1].data.links
|
||||||
)
|
)
|
||||||
formatSequenceFetched(fetchAllCollectionInfo[0].data)
|
formatSequenceFetched(fetchAllCollectionInfo[0].data)
|
||||||
const collItems = fetchAllCollectionInfo[1].data.features
|
pictures.value = fetchAllCollectionInfo[1].data.features
|
||||||
const collItemsReady = collItems.filter(
|
|
||||||
(el) => el.properties['geovisio:status'] === 'ready'
|
|
||||||
)
|
|
||||||
pictures.value = collItems
|
|
||||||
setHeightValue()
|
setHeightValue()
|
||||||
if (itemSelected.value.length || !getCurrentPicId(collItemsReady[0].id)) {
|
document.addEventListener('keydown', (evt) => {
|
||||||
return
|
if (evt.key === 'Shift') isShiftPressed.value = true
|
||||||
}
|
|
||||||
if (!viewerRef.value) return
|
|
||||||
viewerRef.value.viewer.addEventListener('picture-loaded', (): void => {
|
|
||||||
if (!viewerRef.value) return
|
|
||||||
const seqRelativeDeg = viewerRef.value.viewer.getPictureRelativeHeading()
|
|
||||||
seqBruteDeg.value =
|
|
||||||
viewerRef.value.viewer.getPictureMetadata().properties['view:azimuth']
|
|
||||||
seqDegrees.value = seqBruteDeg.value - seqRelativeDeg
|
|
||||||
})
|
})
|
||||||
if (!viewerRef.value) return
|
document.addEventListener('keyup', (evt) => {
|
||||||
viewerRef.value.viewer._api.onceReady().then(() => {
|
if (evt.key === 'Shift') isShiftPressed.value = false
|
||||||
if (!sequence.value || !viewerRef.value) return
|
|
||||||
viewerRef.value.viewer.goToPicture(
|
|
||||||
getCurrentPicId(collItemsReady[0].id),
|
|
||||||
sequence.value.id
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
await goToThePageAndScroll()
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
onUnmounted(() => {
|
||||||
watchEffect(() => {
|
document.removeEventListener('keydown', (evt) => {
|
||||||
goToThePageAndScroll()
|
if (evt.key === 'Shift') isShiftPressed.value = true
|
||||||
|
})
|
||||||
|
document.removeEventListener('keyup', (evt) => {
|
||||||
|
if (evt.key === 'Shift') isShiftPressed.value = false
|
||||||
|
})
|
||||||
})
|
})
|
||||||
async function goToThePageAndScroll() {
|
watchEffect(() => goToThePageAndScroll())
|
||||||
if (!viewerRef || !viewerRef.value || !viewerRef.value.viewer) return
|
function goToThePageAndScroll(): void {
|
||||||
|
if (!viewerRef?.value?.viewer) return
|
||||||
viewerRef.value.viewer.addEventListener(
|
viewerRef.value.viewer.addEventListener(
|
||||||
'picture-loaded',
|
'psv:picture-loaded',
|
||||||
async (e: { detail: { picId: string } }): Promise<void> => {
|
async (e: { detail: { picId: string } }): Promise<void> => {
|
||||||
if (!pictureExistInList(getCurrentPicId(e.detail.picId))) {
|
if (!pictureExistInList(getCurrentPicId(e.detail.picId))) {
|
||||||
await goToTheGoodPage(getCurrentPicId(e.detail.picId))
|
await goToTheGoodPage(getCurrentPicId(e.detail.picId))
|
||||||
}
|
}
|
||||||
itemSelected.value = getCurrentPicId(e.detail.picId)
|
itemSelected.value = getCurrentPicId(e.detail.picId)
|
||||||
|
if (!sequence.value || !viewerRef?.value?.viewer) return
|
||||||
|
viewerRef.value.viewer.select(sequence.value.id, itemSelected.value)
|
||||||
scrollIntoSelected(
|
scrollIntoSelected(
|
||||||
getCurrentPicId(e.detail.picId),
|
getCurrentPicId(e.detail.picId),
|
||||||
pictures.value.map((e) => e.id)
|
pictures.value.map((e) => e.id)
|
||||||
)
|
)
|
||||||
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
function setPanelView(value: string): void {
|
function setPanelView(value: string): void {
|
||||||
panelView.value = value
|
panelView.value = value
|
||||||
if (value === 'orientation') window.location.hash = '#background=aerial'
|
if (!viewerRef?.value?.viewer) return
|
||||||
else window.location.hash = '#background=streets'
|
if (value === 'orientation') {
|
||||||
|
const raster = getEnv('VITE_RASTER_TILE')
|
||||||
|
if (raster) viewerRef.value.viewer.map.setBackground('aerial')
|
||||||
|
setSeqDegrees()
|
||||||
|
} else viewerRef.value.viewer.map.setBackground('streets')
|
||||||
|
}
|
||||||
|
function setSeqDegrees(): void {
|
||||||
|
if (!viewerRef?.value?.viewer) return
|
||||||
|
const seqRelativeDeg = viewerRef.value.viewer.psv.getPictureRelativeHeading()
|
||||||
|
seqBruteDeg.value =
|
||||||
|
viewerRef.value.viewer.psv.getPictureMetadata().properties['view:azimuth']
|
||||||
|
seqDegrees.value = seqBruteDeg.value - seqRelativeDeg
|
||||||
}
|
}
|
||||||
async function setNewSequenceTitle(value: string | null): Promise<void> {
|
async function setNewSequenceTitle(value: string | null): Promise<void> {
|
||||||
isLoadingTitle.value = true
|
isLoadingTitle.value = true
|
||||||
@@ -329,7 +350,7 @@ const isSequenceOwner = computed((): boolean => {
|
|||||||
sequence.value.providers[0].name === cookies.get('user_name')
|
sequence.value.providers[0].name === cookies.get('user_name')
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
const getUserId = computed<string>((): string => cookies.get('user_id'))
|
||||||
const sequenceStatus = computed((): string => {
|
const sequenceStatus = computed((): string => {
|
||||||
if (sequence.value?.status === 'ready')
|
if (sequence.value?.status === 'ready')
|
||||||
return t('pages.sequence.sequence_published')
|
return t('pages.sequence.sequence_published')
|
||||||
@@ -426,7 +447,7 @@ async function patchCollection(): Promise<void> {
|
|||||||
const { data } = await fetchCollectionItems(route.params.id, '?limit=100')
|
const { data } = await fetchCollectionItems(route.params.id, '?limit=100')
|
||||||
pictures.value = data.features
|
pictures.value = data.features
|
||||||
}
|
}
|
||||||
if (viewerRef.value) viewerRef.value.viewer.reloadVectorTiles()
|
if (viewerRef?.value?.viewer) viewerRef.value.viewer.map.reloadVectorTiles()
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
async function goToNextPage(value: string): Promise<void> {
|
async function goToNextPage(value: string): Promise<void> {
|
||||||
@@ -450,6 +471,14 @@ function triggerCheck(value: CheckboxInterface): void {
|
|||||||
.map((e) => e.id)
|
.map((e) => e.id)
|
||||||
} else picturesToDelete.value = []
|
} else picturesToDelete.value = []
|
||||||
}
|
}
|
||||||
|
function orientMovingSequence(value: number): void {
|
||||||
|
if (
|
||||||
|
!viewerRef?.value?.viewer ||
|
||||||
|
!viewerRef.value.viewer?.psv?.getPictureMetadata()
|
||||||
|
)
|
||||||
|
return
|
||||||
|
viewerRef.value.viewer.previewSequenceHeadingChange(value)
|
||||||
|
}
|
||||||
async function orientSequence(value: number): Promise<void> {
|
async function orientSequence(value: number): Promise<void> {
|
||||||
isLoadingOrient.value = true
|
isLoadingOrient.value = true
|
||||||
const { data }: { data: ResponseUserSequenceInterface } =
|
const { data }: { data: ResponseUserSequenceInterface } =
|
||||||
@@ -457,8 +486,12 @@ async function orientSequence(value: number): Promise<void> {
|
|||||||
relative_heading: value
|
relative_heading: value
|
||||||
})
|
})
|
||||||
formatSequenceFetched(data)
|
formatSequenceFetched(data)
|
||||||
if (viewerRef.value) viewerRef.value.viewer.clearPictureMetadataCache()
|
|
||||||
sequenceStore.addToastText(t('pages.sequence.orientation_updated'), 'success')
|
sequenceStore.addToastText(t('pages.sequence.orientation_updated'), 'success')
|
||||||
|
if (viewerRef.value) {
|
||||||
|
orientMovingSequence(value)
|
||||||
|
await viewerRef.value.viewer._loadSequence()
|
||||||
|
await viewerRef.value.viewer.psv.clearPictureMetadataCache()
|
||||||
|
}
|
||||||
isLoadingOrient.value = false
|
isLoadingOrient.value = false
|
||||||
}
|
}
|
||||||
async function sortSequence(value: string): Promise<void> {
|
async function sortSequence(value: string): Promise<void> {
|
||||||
@@ -475,36 +508,28 @@ async function sortSequence(value: string): Promise<void> {
|
|||||||
sequenceStore.addToastText(t('pages.sequence.sort_updated'), 'success')
|
sequenceStore.addToastText(t('pages.sequence.sort_updated'), 'success')
|
||||||
isLoadingSort.value = false
|
isLoadingSort.value = false
|
||||||
}
|
}
|
||||||
function selectPhotoToDeleteOrPatch(
|
function triggerCheckItem(value: string) {
|
||||||
item: ResponseUserPhotoInterface
|
if (picturesToDelete.value.includes(value)) {
|
||||||
): string[] {
|
|
||||||
document.addEventListener('keydown', function (evt) {
|
|
||||||
if (evt.key === 'Shift') {
|
|
||||||
isShiftPressed.value = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
document.addEventListener('keyup', function (evt) {
|
|
||||||
if (evt.key === 'Shift') {
|
|
||||||
isShiftPressed.value = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (isShiftPressed.value) {
|
|
||||||
const picturesIndex = pictures.value.findIndex((el) => el.id === item.id)
|
|
||||||
const picturesLastIndex = pictures.value.findIndex(
|
|
||||||
(el) => el.id === picturesToDelete.value[0]
|
|
||||||
)
|
|
||||||
const slicedUserPhotos = pictures.value.slice(
|
|
||||||
picturesLastIndex,
|
|
||||||
picturesIndex + 1
|
|
||||||
)
|
|
||||||
return (picturesToDelete.value = slicedUserPhotos.map((el) => el.id))
|
|
||||||
}
|
|
||||||
if (picturesToDelete.value.includes(item.id)) {
|
|
||||||
return (picturesToDelete.value = picturesToDelete.value.filter(
|
return (picturesToDelete.value = picturesToDelete.value.filter(
|
||||||
(el) => el !== item.id
|
(el) => el !== value
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
return (picturesToDelete.value = [...picturesToDelete.value, item.id])
|
picturesToDelete.value = [...picturesToDelete.value, value]
|
||||||
|
}
|
||||||
|
function selectPhotoToDeleteOrPatch(
|
||||||
|
item: ResponseUserPhotoInterface
|
||||||
|
): void | string[] {
|
||||||
|
if (!isShiftPressed.value) return
|
||||||
|
const picturesIndex = pictures.value.findIndex((el) => el.id === item.id)
|
||||||
|
let picturesLastIndex = pictures.value.findIndex(
|
||||||
|
(el) => el.id === picturesToDelete.value[0]
|
||||||
|
)
|
||||||
|
if (picturesLastIndex === -1) picturesLastIndex = 0
|
||||||
|
const slicedUserPhotos = pictures.value.slice(
|
||||||
|
picturesLastIndex,
|
||||||
|
picturesIndex + 1
|
||||||
|
)
|
||||||
|
return (picturesToDelete.value = slicedUserPhotos.map((el) => el.id))
|
||||||
}
|
}
|
||||||
async function selectImageAndMove(
|
async function selectImageAndMove(
|
||||||
item: ResponseUserPhotoInterface
|
item: ResponseUserPhotoInterface
|
||||||
@@ -515,13 +540,9 @@ async function selectImageAndMove(
|
|||||||
await router.push({ name: 'sequence', params: { id: route.params.id } })
|
await router.push({ name: 'sequence', params: { id: route.params.id } })
|
||||||
}
|
}
|
||||||
selectPhotoToDeleteOrPatch(item)
|
selectPhotoToDeleteOrPatch(item)
|
||||||
if (
|
if (!isShiftPressed.value) {
|
||||||
picturesToDelete.value.length < 2 &&
|
if (viewerRef.value && sequence.value) {
|
||||||
item.properties['geovisio:status'] === 'ready'
|
viewerRef.value.viewer.select(sequence.value.id, item.id)
|
||||||
) {
|
|
||||||
if (viewerRef.value) {
|
|
||||||
const viewerMap = await viewerRef.value.viewer
|
|
||||||
viewerMap.goToPicture(item.id, sequence.value?.id)
|
|
||||||
}
|
}
|
||||||
itemSelected.value = item.id
|
itemSelected.value = item.id
|
||||||
await goToTheGoodPage(item.id)
|
await goToTheGoodPage(item.id)
|
||||||
@@ -566,8 +587,9 @@ async function patchOrDeleteCollectionItems(reqType: string): Promise<void> {
|
|||||||
pictures.value = data.features
|
pictures.value = data.features
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
if (viewerRef.value) {
|
if (viewerRef.value) {
|
||||||
viewerRef.value.viewer.reloadVectorTiles()
|
viewerRef.value.viewer.map.reloadVectorTiles()
|
||||||
viewerRef.value.viewer.goToPicture(pictures.value[0].id, route.params.id)
|
viewerRef.value.viewer.map.reloadLayersStyles()
|
||||||
|
viewerRef.value.viewer.select(route.params.id, pictures.value[0].id)
|
||||||
}
|
}
|
||||||
scrollIntoSelected(
|
scrollIntoSelected(
|
||||||
picturesToDelete.value[0],
|
picturesToDelete.value[0],
|
||||||
@@ -620,11 +642,16 @@ async function patchOrDeleteCollectionItems(reqType: string): Promise<void> {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.wrapper-title {
|
.wrapper-title {
|
||||||
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.wrapper-button {
|
.wrapper-button {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
.default {
|
||||||
|
margin-bottom: toRem(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.disable-button {
|
.disable-button {
|
||||||
margin-right: toRem(1);
|
margin-right: toRem(1);
|
||||||
@@ -691,13 +718,14 @@ async function patchOrDeleteCollectionItems(reqType: string): Promise<void> {
|
|||||||
border-bottom-left-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
}
|
}
|
||||||
.header-menu {
|
.header-menu {
|
||||||
|
margin-top: toRem(2);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: toRem(1);
|
margin-bottom: toRem(1);
|
||||||
}
|
}
|
||||||
.sequence-status {
|
.sequence-status {
|
||||||
@include text(xs-r-regular);
|
@include text(xs-r-regular);
|
||||||
height: 100%;
|
height: fit-content;
|
||||||
border-radius: toRem(0.4);
|
border-radius: toRem(0.4);
|
||||||
padding: toRem(0.3) toRem(0.8);
|
padding: toRem(0.3) toRem(0.8);
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
@@ -709,8 +737,8 @@ async function patchOrDeleteCollectionItems(reqType: string): Promise<void> {
|
|||||||
border: toRem(0.1) solid var(--green);
|
border: toRem(0.1) solid var(--green);
|
||||||
}
|
}
|
||||||
&.waiting-for-process {
|
&.waiting-for-process {
|
||||||
background-color: var(--yellow);
|
background-color: var(--orange);
|
||||||
border: toRem(0.1) solid var(--yellow);
|
border: toRem(0.1) solid var(--orange);
|
||||||
}
|
}
|
||||||
&.hidden {
|
&.hidden {
|
||||||
background-color: var(--red-pale);
|
background-color: var(--red-pale);
|
||||||
@@ -737,9 +765,6 @@ async function patchOrDeleteCollectionItems(reqType: string): Promise<void> {
|
|||||||
.menu-right {
|
.menu-right {
|
||||||
width: 55vw;
|
width: 55vw;
|
||||||
}
|
}
|
||||||
.wrapper-title {
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
||||||
.block-collapse {
|
.block-collapse {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
@@ -755,7 +780,7 @@ async function patchOrDeleteCollectionItems(reqType: string): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: toRem(76.8)) {
|
@media (max-width: toRem(84.8)) {
|
||||||
.entry-page {
|
.entry-page {
|
||||||
padding-top: toRem(11.6);
|
padding-top: toRem(11.6);
|
||||||
}
|
}
|
||||||
@@ -820,7 +845,7 @@ async function patchOrDeleteCollectionItems(reqType: string): Promise<void> {
|
|||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
width: 85vw;
|
width: 90vw;
|
||||||
background-color: var(--white);
|
background-color: var(--white);
|
||||||
border-left: toRem(0.1) solid var(--grey-pale);
|
border-left: toRem(0.1) solid var(--grey-pale);
|
||||||
}
|
}
|
||||||
@@ -839,7 +864,7 @@ async function patchOrDeleteCollectionItems(reqType: string): Promise<void> {
|
|||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
.button-close {
|
.button-close {
|
||||||
left: calc(15vw - #{toRem(3)});
|
left: calc(10vw - #{toRem(3.5)});
|
||||||
right: initial;
|
right: initial;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -852,7 +877,7 @@ async function patchOrDeleteCollectionItems(reqType: string): Promise<void> {
|
|||||||
.entry-panel {
|
.entry-panel {
|
||||||
padding-right: toRem(1);
|
padding-right: toRem(1);
|
||||||
padding-left: toRem(1);
|
padding-left: toRem(1);
|
||||||
padding-top: toRem(0);
|
padding-top: toRem(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,14 +19,10 @@
|
|||||||
class-name-handle="resize-handle-map"
|
class-name-handle="resize-handle-map"
|
||||||
@resizing="onResizeMap"
|
@resizing="onResizeMap"
|
||||||
>
|
>
|
||||||
<Viewer
|
<WebsiteViewer
|
||||||
v-if="collectionBbox.length"
|
v-if="collectionBbox.length"
|
||||||
:fetch-options="{
|
:fetch-options="{ credentials: 'include' }"
|
||||||
fetchOptions: {
|
viewer-type="standAlone"
|
||||||
credentials: 'include'
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
:geovisio-viewer="false"
|
|
||||||
:user-id="getUserId"
|
:user-id="getUserId"
|
||||||
:bbox="collectionBbox"
|
:bbox="collectionBbox"
|
||||||
ref="viewerRef"
|
ref="viewerRef"
|
||||||
@@ -95,7 +91,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="sequence-header-item">
|
<div class="sequence-header-item">
|
||||||
<Button
|
<Button
|
||||||
look="link--black no-text"
|
look="no-text-blue-dark"
|
||||||
:icon="iconButtonSort('datetime')"
|
:icon="iconButtonSort('datetime')"
|
||||||
data-test="button-sort-date"
|
data-test="button-sort-date"
|
||||||
@trigger="sortList('datetime')"
|
@trigger="sortList('datetime')"
|
||||||
@@ -103,7 +99,7 @@
|
|||||||
<span class="title">{{ $t('pages.sequences.sequence_date') }}</span>
|
<span class="title">{{ $t('pages.sequences.sequence_date') }}</span>
|
||||||
<div class="button-filter">
|
<div class="button-filter">
|
||||||
<Button
|
<Button
|
||||||
:look="filterClass"
|
look="no-text-blue-dark"
|
||||||
:icon="iconButtonFilter('datetime')"
|
:icon="iconButtonFilter('datetime')"
|
||||||
:tooltip="$t('pages.sequences.sequence_date_tooltip')"
|
:tooltip="$t('pages.sequences.sequence_date_tooltip')"
|
||||||
@trigger="displayModal('datetime')"
|
@trigger="displayModal('datetime')"
|
||||||
@@ -122,7 +118,7 @@
|
|||||||
}}</span>
|
}}</span>
|
||||||
<div class="button-filter">
|
<div class="button-filter">
|
||||||
<Button
|
<Button
|
||||||
:look="filterClass"
|
look="no-text-blue-dark"
|
||||||
:icon="iconButtonFilter('created')"
|
:icon="iconButtonFilter('created')"
|
||||||
:tooltip="$t('pages.sequences.sequence_creation_tooltip')"
|
:tooltip="$t('pages.sequences.sequence_creation_tooltip')"
|
||||||
@trigger="displayModal('created')"
|
@trigger="displayModal('created')"
|
||||||
@@ -294,8 +290,8 @@ import {
|
|||||||
} from '@/views/utils/sequence/index'
|
} from '@/views/utils/sequence/index'
|
||||||
import { useCookies } from 'vue3-cookies'
|
import { useCookies } from 'vue3-cookies'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import Viewer from '@/components/Viewer.vue'
|
import WebsiteViewer from '@/components/WebsiteViewer.vue'
|
||||||
import type ViewerType from '@/components/Viewer.vue'
|
import type ViewerType from '@/components/WebsiteViewer.vue'
|
||||||
import Button from '@/components/Button.vue'
|
import Button from '@/components/Button.vue'
|
||||||
import Link from '@/components/Link.vue'
|
import Link from '@/components/Link.vue'
|
||||||
import Toast from '@/components/Toast.vue'
|
import Toast from '@/components/Toast.vue'
|
||||||
@@ -372,10 +368,10 @@ function triggerBboxFilter(): void {
|
|||||||
if (viewerRef.value) {
|
if (viewerRef.value) {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
const currentBbox = [
|
const currentBbox = [
|
||||||
viewerRef.value.viewer._map.getBounds()._ne.lng,
|
viewerRef.value.viewer.map.getBounds()._ne.lng,
|
||||||
viewerRef.value.viewer._map.getBounds()._ne.lat,
|
viewerRef.value.viewer.map.getBounds()._ne.lat,
|
||||||
viewerRef.value.viewer._map.getBounds()._sw.lng,
|
viewerRef.value.viewer.map.getBounds()._sw.lng,
|
||||||
viewerRef.value.viewer._map.getBounds()._sw.lat
|
viewerRef.value.viewer.map.getBounds()._sw.lat
|
||||||
]
|
]
|
||||||
filterBbox.value = currentBbox
|
filterBbox.value = currentBbox
|
||||||
formatUri()
|
formatUri()
|
||||||
@@ -398,8 +394,8 @@ function iconButtonFilter(type: string): string {
|
|||||||
filterDate.value.start &&
|
filterDate.value.start &&
|
||||||
filterDate.value.end &&
|
filterDate.value.end &&
|
||||||
filterDate.value.type === type
|
filterDate.value.type === type
|
||||||
if (isDateSelected) return 'bi bi-funnel-fill'
|
if (isDateSelected) return 'bi bi-calendar-check-fill'
|
||||||
return 'bi bi-funnel'
|
return 'bi bi-calendar'
|
||||||
}
|
}
|
||||||
async function fetchAndFormatSequence(): Promise<void> {
|
async function fetchAndFormatSequence(): Promise<void> {
|
||||||
const { data } = await axios.get('api/users/me/collection?limit=25')
|
const { data } = await axios.get('api/users/me/collection?limit=25')
|
||||||
@@ -414,18 +410,25 @@ async function patchCollection(sequence: SequenceLinkInterface): Promise<void> {
|
|||||||
else visible = 'true'
|
else visible = 'true'
|
||||||
await patchACollection(sequence.id, { visible: visible })
|
await patchACollection(sequence.id, { visible: visible })
|
||||||
await fetchAndFormatSequence()
|
await fetchAndFormatSequence()
|
||||||
if (viewerRef.value) viewerRef.value.viewer.reloadVectorTiles()
|
if (viewerRef.value) viewerRef.value.viewer.map.reloadVectorTiles()
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
async function deleteCollection(
|
async function deleteCollection(
|
||||||
sequence: SequenceLinkInterface
|
sequence: SequenceLinkInterface
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
if (confirm(t('pages.sequence.conf_sequence_msg'))) {
|
try {
|
||||||
await deleteACollection(sequence.id)
|
if (confirm(t('pages.sequence.conf_sequence_msg'))) {
|
||||||
await fetchAndFormatSequence()
|
await deleteACollection(sequence.id)
|
||||||
if (viewerRef.value) viewerRef.value.viewer.reloadVectorTiles()
|
await fetchAndFormatSequence()
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const err = error as { response: { data: { message: string } } }
|
||||||
|
if (err.response.data.message.includes('No data loaded for user')) {
|
||||||
|
userSequences.value = []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (viewerRef.value) viewerRef.value.viewer.map.reloadVectorTiles()
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
function sequenceStatus(status: string): string {
|
function sequenceStatus(status: string): string {
|
||||||
@@ -488,12 +491,12 @@ function bboxIsInsideOther(mainBbox: number[], bboxInside: number[]): boolean {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
function goToSequence(sequence: SequenceLinkInterface): void {
|
function goToSequence(sequence: SequenceLinkInterface): void {
|
||||||
if (!viewerRef.value) return
|
if (!viewerRef?.value?.viewer) return
|
||||||
const currentBbox = [
|
const currentBbox = [
|
||||||
viewerRef.value.viewer._map.getBounds()._ne.lng,
|
viewerRef.value.viewer.map.getBounds()._ne.lng,
|
||||||
viewerRef.value.viewer._map.getBounds()._ne.lat,
|
viewerRef.value.viewer.map.getBounds()._ne.lat,
|
||||||
viewerRef.value.viewer._map.getBounds()._sw.lng,
|
viewerRef.value.viewer.map.getBounds()._sw.lng,
|
||||||
viewerRef.value.viewer._map.getBounds()._sw.lat
|
viewerRef.value.viewer.map.getBounds()._sw.lat
|
||||||
]
|
]
|
||||||
if (
|
if (
|
||||||
seqId.value === sequence.id &&
|
seqId.value === sequence.id &&
|
||||||
@@ -503,7 +506,7 @@ function goToSequence(sequence: SequenceLinkInterface): void {
|
|||||||
}
|
}
|
||||||
seqId.value = sequence.id
|
seqId.value = sequence.id
|
||||||
viewerRef.value.viewer.select(seqId.value)
|
viewerRef.value.viewer.select(seqId.value)
|
||||||
viewerRef.value.viewer._map.flyTo({
|
viewerRef.value.viewer.map.flyTo({
|
||||||
center: [
|
center: [
|
||||||
sequence.extent.spatial.bbox[0][0],
|
sequence.extent.spatial.bbox[0][0],
|
||||||
sequence.extent.spatial.bbox[0][1]
|
sequence.extent.spatial.bbox[0][1]
|
||||||
@@ -525,9 +528,6 @@ async function goToNextPage(value: string): Promise<void> {
|
|||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
await updateSequence(value)
|
await updateSequence(value)
|
||||||
}
|
}
|
||||||
const filterClass = computed<string>((): string => {
|
|
||||||
return 'no-text-green'
|
|
||||||
})
|
|
||||||
const modalTitle = computed<string>((): string => {
|
const modalTitle = computed<string>((): string => {
|
||||||
if (calendarType.value === 'datetime') {
|
if (calendarType.value === 'datetime') {
|
||||||
return t('pages.sequences.filter_date_title')
|
return t('pages.sequences.filter_date_title')
|
||||||
@@ -562,7 +562,6 @@ onMounted(async () => {
|
|||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
console.log(err)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
function formatUri(): void {
|
function formatUri(): void {
|
||||||
@@ -597,20 +596,19 @@ async function updateFilters(value: {
|
|||||||
await updateSequence(uri.value)
|
await updateSequence(uri.value)
|
||||||
}
|
}
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (viewerRef.value && viewerRef.value.viewer) {
|
if (!viewerRef?.value?.viewer) return
|
||||||
viewerRef.value.viewer.addEventListener(
|
viewerRef.value.viewer.addEventListener(
|
||||||
'select',
|
'select',
|
||||||
(e: { detail: { seqId: string } }) => {
|
(e: { detail: { seqId: string } }) => {
|
||||||
if (seqId.value === e.detail.seqId) return
|
if (seqId.value === e.detail.seqId) return
|
||||||
seqId.value = e.detail.seqId
|
seqId.value = e.detail.seqId
|
||||||
scrollIntoSelected(
|
scrollIntoSelected(
|
||||||
e.detail.seqId,
|
e.detail.seqId,
|
||||||
userSequences.value.map((e) => e.id)
|
userSequences.value.map((e) => e.id)
|
||||||
)
|
)
|
||||||
if (viewerRef.value) viewerRef.value.viewer.select(e.detail.seqId)
|
if (viewerRef.value) viewerRef.value.viewer.select(e.detail.seqId)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -618,20 +616,24 @@ watchEffect(() => {
|
|||||||
top: 0;
|
top: 0;
|
||||||
right: toRem(0);
|
right: toRem(0);
|
||||||
cursor: e-resize;
|
cursor: e-resize;
|
||||||
background-color: var(--black);
|
background-color: var(--blue);
|
||||||
display: block !important;
|
display: block !important;
|
||||||
|
opacity: 0.7;
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.resize-handle-map {
|
.resize-handle-map {
|
||||||
z-index: 999999;
|
z-index: 999999;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: toRem(0.5);
|
width: toRem(0.4);
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: col-resize;
|
cursor: col-resize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: toRem(76.8)) {
|
@media (max-width: toRem(84.8)) {
|
||||||
.resize-handle-map-mr {
|
.resize-handle-map-mr {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
@@ -713,6 +715,8 @@ watchEffect(() => {
|
|||||||
background-color: var(--blue-pale);
|
background-color: var(--blue-pale);
|
||||||
padding-right: toRem(2);
|
padding-right: toRem(2);
|
||||||
padding-left: toRem(2);
|
padding-left: toRem(2);
|
||||||
|
padding-top: toRem(0.5);
|
||||||
|
padding-bottom: toRem(0.5);
|
||||||
&:nth-child(2n) {
|
&:nth-child(2n) {
|
||||||
background-color: var(--white);
|
background-color: var(--white);
|
||||||
}
|
}
|
||||||
@@ -852,11 +856,8 @@ watchEffect(() => {
|
|||||||
color: var(--black);
|
color: var(--black);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.button-filter {
|
.button-filter .icon {
|
||||||
margin-left: toRem(-1);
|
font-size: toRem(1);
|
||||||
.icon {
|
|
||||||
font-size: toRem(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.no-sequence {
|
.no-sequence {
|
||||||
padding-top: toRem(2);
|
padding-top: toRem(2);
|
||||||
@@ -902,7 +903,7 @@ watchEffect(() => {
|
|||||||
width: 70%;
|
width: 70%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: toRem(76.8)) {
|
@media (max-width: toRem(84.8)) {
|
||||||
.section-viewer {
|
.section-viewer {
|
||||||
height: calc(100vh - #{toRem(11.6)});
|
height: calc(100vh - #{toRem(11.6)});
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
|
|||||||
@@ -165,14 +165,13 @@ import InformationCard from '@/components/InformationCard.vue'
|
|||||||
import License from '@/components/License.vue'
|
import License from '@/components/License.vue'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import authConfig from '../composables/auth'
|
import { authConfig } from '../composables/auth'
|
||||||
|
import { manageSlashUrl, getEnv } from '@/utils'
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { authConf } = authConfig()
|
const { authConf } = authConfig()
|
||||||
|
|
||||||
const apiUrl = computed((): string =>
|
const apiUrl = computed((): string =>
|
||||||
import.meta.env.VITE_API_URL
|
getEnv('VITE_API_URL') ? manageSlashUrl() : 'https://panoramax.ign.fr/'
|
||||||
? import.meta.env.VITE_API_URL
|
|
||||||
: 'https://panoramax.ign.fr/'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const formatTextInfoCard = computed((): string => {
|
const formatTextInfoCard = computed((): string => {
|
||||||
@@ -270,7 +269,7 @@ ul {
|
|||||||
.subtitle {
|
.subtitle {
|
||||||
@include text(h2);
|
@include text(h2);
|
||||||
color: var(--blue-dark);
|
color: var(--blue-dark);
|
||||||
margin-bottom: 0;
|
margin-bottom: toRem(2);
|
||||||
}
|
}
|
||||||
.upload-button {
|
.upload-button {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -284,10 +283,6 @@ ul {
|
|||||||
.entry-information-card {
|
.entry-information-card {
|
||||||
margin-top: toRem(2);
|
margin-top: toRem(2);
|
||||||
}
|
}
|
||||||
.subtitle {
|
|
||||||
@include text(h2);
|
|
||||||
color: var(--blue-dark);
|
|
||||||
}
|
|
||||||
.icon-block {
|
.icon-block {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<section class="section">
|
<section class="section">
|
||||||
<InformationCard
|
<InformationCard
|
||||||
v-if="informationCardDisplayed"
|
v-if="informationCardDisplayed"
|
||||||
|
look="blue"
|
||||||
:text="$t('pages.upload.description')"
|
:text="$t('pages.upload.description')"
|
||||||
>
|
>
|
||||||
<template v-slot:cross>
|
<template v-slot:cross>
|
||||||
@@ -45,12 +46,26 @@
|
|||||||
</p>
|
</p>
|
||||||
<div class="wrapper-edit-text">
|
<div class="wrapper-edit-text">
|
||||||
<EditText
|
<EditText
|
||||||
|
:form-title="$t('pages.sequence.sequence_form_title')"
|
||||||
:default-text="newSequenceTitle || sequenceTitle"
|
:default-text="newSequenceTitle || sequenceTitle"
|
||||||
:is-loading="isLoading"
|
:is-loading="isLoading"
|
||||||
:is-loaded="isLoaded"
|
:is-loaded="isLoaded"
|
||||||
@triggerNewText="setNewSequenceTitle"
|
@triggerNewText="setNewSequenceTitle"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="isUploadCanceled" class="entry-tutorial">
|
||||||
|
<p>{{ $t('pages.sequence.sequence_tutorial_text') }}</p>
|
||||||
|
<img
|
||||||
|
src="@/assets/images/android-upload-tutorial.jpg"
|
||||||
|
alt=""
|
||||||
|
class="img-upload-tutorial first-tutorial-image"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src="@/assets/images/android-upload-tutorial-files.jpg"
|
||||||
|
alt=""
|
||||||
|
class="img-upload-tutorial"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<form>
|
<form>
|
||||||
<div class="wrapper-form">
|
<div class="wrapper-form">
|
||||||
<InputUpload
|
<InputUpload
|
||||||
@@ -58,9 +73,9 @@
|
|||||||
:text="$t('pages.upload.input_label')"
|
:text="$t('pages.upload.input_label')"
|
||||||
:text-second-part="$t('pages.upload.import_word')"
|
:text-second-part="$t('pages.upload.import_word')"
|
||||||
:text-picture-type="$t('pages.upload.import_type')"
|
:text-picture-type="$t('pages.upload.import_type')"
|
||||||
accept="image/jpeg"
|
|
||||||
data-displayModal="input-add-pictures"
|
data-displayModal="input-add-pictures"
|
||||||
@trigger="addPictures"
|
@trigger="addPictures"
|
||||||
|
@triggerCancel="isUploadCanceled = true"
|
||||||
/>
|
/>
|
||||||
<div v-else class="wrapper-uploading">
|
<div v-else class="wrapper-uploading">
|
||||||
<img
|
<img
|
||||||
@@ -89,8 +104,10 @@
|
|||||||
v-if="isLoading"
|
v-if="isLoading"
|
||||||
:load-percentage="loadPercentage"
|
:load-percentage="loadPercentage"
|
||||||
:is-loaded="isLoaded"
|
:is-loaded="isLoaded"
|
||||||
|
:upload-error="uploadError"
|
||||||
:uploaded-sequence="uploadedSequence"
|
:uploaded-sequence="uploadedSequence"
|
||||||
:pictures-count="picturesCount"
|
:pictures-count="picturesCount"
|
||||||
|
:other-files-count="otherFilesCount"
|
||||||
@triggerModal="displayModal"
|
@triggerModal="displayModal"
|
||||||
/>
|
/>
|
||||||
<div v-else class="wrapper-no-img">
|
<div v-else class="wrapper-no-img">
|
||||||
@@ -133,21 +150,25 @@ import InformationCard from '@/components/InformationCard.vue'
|
|||||||
import UploadLoader from '@/components/upload/UploadLoader.vue'
|
import UploadLoader from '@/components/upload/UploadLoader.vue'
|
||||||
import License from '@/components/License.vue'
|
import License from '@/components/License.vue'
|
||||||
import type { SequenceInterface } from './interfaces/UploadPicturesView'
|
import type { SequenceInterface } from './interfaces/UploadPicturesView'
|
||||||
|
import type { SequenceCreatedInterface } from './interfaces/UploadPicturesView'
|
||||||
import { formatDate } from '@/utils/dates'
|
import { formatDate } from '@/utils/dates'
|
||||||
import {
|
import {
|
||||||
createAPictureToASequence,
|
createAPictureToASequence,
|
||||||
createASequence
|
createASequence
|
||||||
} 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 { deleteACollection } from '@/views/utils/sequence/request'
|
import { deleteACollection } from '@/views/utils/sequence/request'
|
||||||
|
|
||||||
const { authConf } = authConfig()
|
const { authConf } = authConfig()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
let pictures = ref<File[] | []>([])
|
let pictures = ref<File[] | []>([])
|
||||||
|
let otherFilesCount = ref<number>(0)
|
||||||
let picturesCount = ref<number>(0)
|
let picturesCount = ref<number>(0)
|
||||||
let isLoading = ref<boolean>(false)
|
let isLoading = ref<boolean>(false)
|
||||||
let isLoaded = ref<boolean>(false)
|
let isLoaded = ref<boolean>(false)
|
||||||
|
let isUploadCanceled = ref<boolean>(false)
|
||||||
|
let uploadError = ref<string | undefined>(undefined)
|
||||||
let informationCardDisplayed = ref<boolean>(true)
|
let informationCardDisplayed = ref<boolean>(true)
|
||||||
let uploadedSequence = ref<SequenceInterface | null>(null)
|
let uploadedSequence = ref<SequenceInterface | null>(null)
|
||||||
let picturesUploadingSize = ref<number>(0)
|
let picturesUploadingSize = ref<number>(0)
|
||||||
@@ -184,12 +205,13 @@ onBeforeRouteLeave((to, from, next) => {
|
|||||||
}
|
}
|
||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
const inputIsDisplayed = computed<boolean | null>(
|
const inputIsDisplayed = computed<boolean | null>(() => {
|
||||||
() =>
|
return (
|
||||||
!isLoading.value ||
|
!isLoading.value ||
|
||||||
isLoaded.value ||
|
isLoaded.value ||
|
||||||
(uploadedSequence.value && !uploadedSequence.value.pictures)
|
(uploadedSequence.value && !uploadedSequence.value.pictures)
|
||||||
)
|
)
|
||||||
|
})
|
||||||
function setNewSequenceTitle(value: string | null): void {
|
function setNewSequenceTitle(value: string | null): void {
|
||||||
newSequenceTitle.value = value
|
newSequenceTitle.value = value
|
||||||
}
|
}
|
||||||
@@ -219,8 +241,12 @@ function displayModal(): void {
|
|||||||
if (modal.value) modal.value.show()
|
if (modal.value) modal.value.show()
|
||||||
}
|
}
|
||||||
function addPictures(value: FileList): void {
|
function addPictures(value: FileList): void {
|
||||||
|
isUploadCanceled.value = false
|
||||||
const files = sortByName([].slice.call(value))
|
const files = sortByName([].slice.call(value))
|
||||||
pictures.value = files
|
const jpegFiles = files.filter((e) => e.type === 'image/jpeg')
|
||||||
|
const otherFiles = files.filter((e) => e.type !== 'image/jpeg')
|
||||||
|
otherFilesCount.value = otherFiles.length
|
||||||
|
pictures.value = jpegFiles
|
||||||
picturesCount.value = pictures.value.length
|
picturesCount.value = pictures.value.length
|
||||||
picturesUploadingSize.value = 0
|
picturesUploadingSize.value = 0
|
||||||
picturesToUploadSize.value = 0
|
picturesToUploadSize.value = 0
|
||||||
@@ -238,21 +264,30 @@ async function cancelUpload(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function uploadPicture(): Promise<void> {
|
async function uploadPicture(): Promise<void> {
|
||||||
if (!pictures.value || !pictures.value.length) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
isLoaded.value = false
|
isLoaded.value = false
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
|
if (!pictures.value || !pictures.value.length) {
|
||||||
|
isLoaded.value = true
|
||||||
|
isLoading.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
picturesToUploadSizeText()
|
picturesToUploadSizeText()
|
||||||
uploadedSequence.value = null
|
uploadedSequence.value = null
|
||||||
const picturesToUpload = [...pictures.value]
|
const picturesToUpload = [...pictures.value]
|
||||||
const title = newSequenceTitle.value
|
const title = newSequenceTitle.value
|
||||||
? newSequenceTitle.value
|
? newSequenceTitle.value
|
||||||
: sequenceTitle.value
|
: sequenceTitle.value
|
||||||
const { data } = await createASequence(title)
|
let sequence: SequenceCreatedInterface | null = null
|
||||||
|
try {
|
||||||
|
sequence = await createASequence(title)
|
||||||
|
} catch (e) {
|
||||||
|
uploadError.value = t('pages.upload.error_upload')
|
||||||
|
isLoaded.value = true
|
||||||
|
}
|
||||||
|
if (!sequence || !sequence.data) return
|
||||||
uploadedSequence.value = {
|
uploadedSequence.value = {
|
||||||
title: title,
|
title: title,
|
||||||
id: data.id,
|
id: sequence.data.id,
|
||||||
pictures: [],
|
pictures: [],
|
||||||
picturesOnError: [],
|
picturesOnError: [],
|
||||||
pictureCount: pictures.value.length,
|
pictureCount: pictures.value.length,
|
||||||
@@ -268,7 +303,10 @@ async function uploadPicture(): Promise<void> {
|
|||||||
body.append('position', i.toString())
|
body.append('position', i.toString())
|
||||||
body.append('picture', el)
|
body.append('picture', el)
|
||||||
try {
|
try {
|
||||||
const pictureUploaded = await createAPictureToASequence(data.id, body)
|
const pictureUploaded = await createAPictureToASequence(
|
||||||
|
sequence.data.id,
|
||||||
|
body
|
||||||
|
)
|
||||||
const pictures = { ...pictureUploaded, name: el.name }
|
const pictures = { ...pictureUploaded, name: el.name }
|
||||||
picturesUploadingSize.value = picturesUploadingSize.value + el.size
|
picturesUploadingSize.value = picturesUploadingSize.value + el.size
|
||||||
uploadedSequence.value.pictures = [
|
uploadedSequence.value.pictures = [
|
||||||
@@ -277,21 +315,27 @@ async function uploadPicture(): Promise<void> {
|
|||||||
]
|
]
|
||||||
calcPercentage()
|
calcPercentage()
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
picturesUploadingSize.value = picturesUploadingSize.value + el.size
|
if (
|
||||||
const picturesOnError = {
|
err.response.data.message === 'Impossible to parse picture metadata'
|
||||||
message: err.response.data.message,
|
) {
|
||||||
details: { error: err.response.data.details.error },
|
picturesUploadingSize.value = picturesUploadingSize.value + el.size
|
||||||
name: el.name
|
const picturesOnError = {
|
||||||
|
message: err.response.data.message,
|
||||||
|
details: { error: err.response.data.details.error },
|
||||||
|
name: el.name
|
||||||
|
}
|
||||||
|
uploadedSequence.value.picturesOnError = [
|
||||||
|
...uploadedSequence.value.picturesOnError,
|
||||||
|
picturesOnError
|
||||||
|
]
|
||||||
|
calcPercentage()
|
||||||
|
} else {
|
||||||
|
uploadError.value = t('pages.upload.error_upload_img')
|
||||||
}
|
}
|
||||||
uploadedSequence.value.picturesOnError = [
|
|
||||||
...uploadedSequence.value.picturesOnError,
|
|
||||||
picturesOnError
|
|
||||||
]
|
|
||||||
calcPercentage()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
isLoaded.value = true
|
|
||||||
pictures.value = []
|
pictures.value = []
|
||||||
|
isLoaded.value = true
|
||||||
picturesCount.value = 0
|
picturesCount.value = 0
|
||||||
newSequenceTitle.value = null
|
newSequenceTitle.value = null
|
||||||
sequenceTitle.value = formatSequenceTitle()
|
sequenceTitle.value = formatSequenceTitle()
|
||||||
@@ -394,6 +438,9 @@ h3 {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
.entry-tutorial {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
@media (max-width: toRem(76.8)) {
|
@media (max-width: toRem(76.8)) {
|
||||||
.section {
|
.section {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -426,5 +473,22 @@ h3 {
|
|||||||
.uploading-img {
|
.uploading-img {
|
||||||
height: toRem(10);
|
height: toRem(10);
|
||||||
}
|
}
|
||||||
|
.entry-tutorial {
|
||||||
|
display: block;
|
||||||
|
@include text(s-regular);
|
||||||
|
color: var(--red);
|
||||||
|
background-color: var(--blue-pale);
|
||||||
|
border-radius: toRem(1);
|
||||||
|
padding: toRem(1.5);
|
||||||
|
margin-bottom: toRem(2);
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
.img-upload-tutorial {
|
||||||
|
width: calc(50% - #{toRem(0.5)});
|
||||||
|
border-radius: toRem(1);
|
||||||
|
}
|
||||||
|
.first-tutorial-image {
|
||||||
|
margin-right: toRem(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -11,3 +11,22 @@ export interface uploadErrorInterface {
|
|||||||
details: { error: string }
|
details: { error: string }
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,28 +1,61 @@
|
|||||||
export interface MapInterface {
|
export interface StandAloneInterface extends ParamsViewerInterface {
|
||||||
startWide?: boolean
|
maxZoom: number
|
||||||
|
minZoom: number
|
||||||
|
padding: { top: number; bottom: number; left: number; right: number }
|
||||||
|
speed: number
|
||||||
|
zoom: number
|
||||||
users?: string[]
|
users?: string[]
|
||||||
maxZoom?: number
|
}
|
||||||
minZoom?: number
|
export interface EditorInterface extends ParamsViewerInterface {
|
||||||
style?: object | string
|
users?: string[]
|
||||||
zoom?: number
|
selectedSequence?: string
|
||||||
center?: number[]
|
minZoom: number
|
||||||
bounds?: number[]
|
}
|
||||||
raster?: {
|
export interface ViewerInterface {
|
||||||
type: string
|
widgets: { customWidget: HTMLDivElement }
|
||||||
tiles: string[]
|
map: {
|
||||||
attribution: string
|
startWide: boolean
|
||||||
tileSize: number
|
center?: number[]
|
||||||
|
hash?: boolean
|
||||||
|
maxZoom?: number
|
||||||
|
zoom?: number
|
||||||
|
raster?:
|
||||||
|
| {
|
||||||
|
type: string
|
||||||
|
tiles: string[]
|
||||||
|
tileSize: number
|
||||||
|
}
|
||||||
|
| string
|
||||||
|
}
|
||||||
|
fetchOptions?: { credentials?: string }
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ParamsViewerInterface {
|
||||||
|
lang: string
|
||||||
|
style?: void
|
||||||
|
selectedPicture?: string
|
||||||
|
fetchOptions?: { credentials?: string }
|
||||||
|
map?: {
|
||||||
|
raster?:
|
||||||
|
| {
|
||||||
|
type: string
|
||||||
|
tiles: string[]
|
||||||
|
tileSize: number
|
||||||
|
}
|
||||||
|
| string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ViewerInterface {
|
export interface ParamsEditorStandaloneInterface {
|
||||||
fetchOptions?: {
|
lang: string
|
||||||
credentials: string
|
style?: void
|
||||||
}
|
selectedPicture?: string
|
||||||
hash?: boolean
|
fetchOptions?: { credentials?: string }
|
||||||
picId?: string
|
raster?:
|
||||||
widgets?: {
|
| {
|
||||||
customWidget: HTMLAnchorElement
|
type: string
|
||||||
}
|
tiles: string[]
|
||||||
map: MapInterface
|
tileSize: number
|
||||||
|
}
|
||||||
|
| string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,5 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
import type { SequenceCreatedInterface } from '../../interfaces/UploadPicturesView'
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PictureCreatedInterface {
|
interface PictureCreatedInterface {
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { defineConfig, loadEnv } from 'vite'
|
|||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
import eslintPlugin from 'vite-plugin-eslint'
|
import eslintPlugin from 'vite-plugin-eslint'
|
||||||
import { createHtmlPlugin } from 'vite-plugin-html'
|
import { createHtmlPlugin } from 'vite-plugin-html'
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default ({ mode }) => {
|
export default ({ mode }) => {
|
||||||
process.env = { ...process.env, ...loadEnv(mode, process.cwd()) }
|
process.env = { ...process.env, ...loadEnv(mode, process.cwd()) }
|
||||||
@@ -10,11 +11,7 @@ export default ({ mode }) => {
|
|||||||
server: {
|
server: {
|
||||||
host: true,
|
host: true,
|
||||||
port: 5173,
|
port: 5173,
|
||||||
strictPort: true,
|
strictPort: true
|
||||||
hmr: {
|
|
||||||
clientPort: 5173,
|
|
||||||
overlay: false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
base: '/',
|
base: '/',
|
||||||
plugins: [
|
plugins: [
|
||||||
@@ -46,6 +43,18 @@ export default ({ mode }) => {
|
|||||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js'
|
'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
define: {
|
||||||
|
// Define the feature flag for better tree-shaking
|
||||||
|
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
minify: 'terser',
|
||||||
|
terserOptions: {
|
||||||
|
mangle: false,
|
||||||
|
keep_classnames: true,
|
||||||
|
keep_fnames: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||