Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 78ce7bce83 | |||
|
|
3604909a37 | ||
|
|
14d7027aa2 | ||
|
|
523a6f98f0 | ||
|
|
faf9fcc106 | ||
|
|
21a5793045 | ||
|
|
3dca1773cd | ||
|
|
3ced85eaf5 | ||
|
|
e56c159146 | ||
|
|
ca6619b773 | ||
|
|
518f06c846 | ||
|
|
50f1f7b472 | ||
|
|
2f4000291f | ||
|
|
6ad1d85604 | ||
|
|
707420c69e | ||
|
|
1bd41002af | ||
|
|
c30eeb020d | ||
|
|
c37296066f | ||
|
|
19f5f5a320 | ||
|
|
589f7c64c0 | ||
|
|
459efcfd57 | ||
|
|
966be42f19 | ||
|
|
c4066930ba | ||
|
|
6322a75f42 | ||
|
|
c37f814dc3 | ||
|
|
fe68941ea9 | ||
|
|
e2df50e18f | ||
|
|
5d83e9df13 | ||
|
|
d849f95013 | ||
|
|
f53adeea28 | ||
|
|
7685993710 | ||
|
|
47dfd9bddc | ||
|
|
b72fb7a0df | ||
|
|
136b3d629f | ||
|
|
9d82d73ca8 | ||
|
|
503443f458 | ||
|
|
9b6bdc394b | ||
|
|
cec383e424 | ||
|
|
7e20788591 | ||
|
|
390343916e | ||
|
|
c768b714b9 | ||
|
|
bf0bc4d91c | ||
|
|
5d292b186c | ||
|
|
93e434ecf9 | ||
|
|
721bafbd3e | ||
|
|
127550a19f | ||
|
|
85abc46038 | ||
|
|
40f3560d94 | ||
|
|
3d65fd9b42 | ||
|
|
476699adab | ||
|
|
7a49628e0d | ||
|
|
52d3f27b6d | ||
|
|
70b252bf8b | ||
|
|
bc7bd9719d | ||
|
|
ee6736ec5d | ||
|
|
36bf95db1e | ||
|
|
bda308d686 | ||
|
|
babee7cb57 | ||
|
|
aed3e689f9 | ||
|
|
5e7154c889 | ||
|
|
9af517f407 | ||
|
|
00fe5433c2 | ||
|
|
08eec76104 | ||
|
|
316e1880b0 | ||
|
|
542eefac03 | ||
|
|
edd5d207a1 | ||
|
|
f99d19a13a | ||
|
|
448cd1af41 | ||
|
|
81cbe90d01 | ||
|
|
93c286ce20 | ||
|
|
cb60e404a7 | ||
|
|
1e54768e9d | ||
|
|
0260037912 | ||
|
|
3dad314e68 | ||
|
|
534d0ab44e |
16
.drone.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
kind: pipeline
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: docker
|
||||
image: plugins/docker
|
||||
settings:
|
||||
registry: https://git.ivasoft.cz
|
||||
username:
|
||||
from_secret: repo_user
|
||||
password:
|
||||
from_secret: repo_pass
|
||||
repo: git.ivasoft.cz/sw/geovisio-website
|
||||
tags:
|
||||
- latest
|
||||
- ${DRONE_TAG:-latest}
|
||||
1
.gitignore
vendored
@@ -90,6 +90,7 @@ sw.*
|
||||
*.swp
|
||||
|
||||
# Cypress generated screen and videos files
|
||||
cypress/downloads/*
|
||||
cypress/screenshot/
|
||||
cypress/videos/
|
||||
*.cy.ts.mp4
|
||||
@@ -8,9 +8,10 @@ variables:
|
||||
DOCKER_BUILDKIT: 1 # use buildkit for better performance
|
||||
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
|
||||
IMAGE_NAME: geovisio/api
|
||||
IMAGE_NAME: geovisio/website
|
||||
CI_IMAGE_CACHE: $GITLAB_REGISTRY/$IMAGE_NAME:build_cache
|
||||
DOCKER_TLS_CERTDIR: ""
|
||||
DOCKER_TLS_CERTDIR: ''
|
||||
DOCKER_HOST: tcp://docker:2375
|
||||
|
||||
before_script:
|
||||
## chmod is unfortunately currently mandatory : https://github.com/nodejs/docker-node/issues/661
|
||||
@@ -22,28 +23,29 @@ cache:
|
||||
|
||||
install:
|
||||
stage: Install
|
||||
image: node:18.16.0
|
||||
image: node:18.16.1
|
||||
script:
|
||||
- yarn install
|
||||
- ls node_modules/.bin/cypress
|
||||
|
||||
test:unit:
|
||||
stage: Test
|
||||
image: node:18.16.0
|
||||
image: node:18.16.1
|
||||
script:
|
||||
- yarn test:unit
|
||||
coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/
|
||||
|
||||
test:e2e:
|
||||
stage: Test
|
||||
image: cypress/browsers:node-18.16.0-chrome-113.0.5672.92-1-ff-113.0-edge-113.0.1774.35-1
|
||||
image: node:18.16.1-alpine
|
||||
services:
|
||||
- docker:dind
|
||||
script:
|
||||
- yarn install
|
||||
- ./node_modules/.bin/cypress install
|
||||
- echo "VITE_API_URL=https://geovisio-proxy-dev.osc-fr1.scalingo.io/" > .env
|
||||
- echo "VITE_ENV=dev" >> .env
|
||||
- PORT=5173 yarn start &
|
||||
- yarn test:e2e
|
||||
- apk add --update --no-cache docker-cli docker-cli-compose git
|
||||
- PROJECT_DIR=$PWD docker compose -f cypress/docker-compose-geovisio.yml -f cypress/docker-compose-gitlab-override.yml run --rm e2e
|
||||
after_script:
|
||||
- PROJECT_DIR=$PWD docker compose -f cypress/docker-compose-geovisio.yml -f cypress/docker-compose-gitlab-override.yml logs web || true
|
||||
- PROJECT_DIR=$PWD docker compose -f cypress/docker-compose-geovisio.yml -f cypress/docker-compose-gitlab-override.yml down || true
|
||||
coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/
|
||||
artifacts:
|
||||
when: always
|
||||
@@ -54,7 +56,7 @@ test:e2e:
|
||||
|
||||
deploy:
|
||||
stage: Deploy
|
||||
image: node:18.16.0
|
||||
image: node:18.16.1
|
||||
cache:
|
||||
paths:
|
||||
- node_modules
|
||||
@@ -74,7 +76,7 @@ deploy:develop:
|
||||
stage: Deploy
|
||||
image: docker:latest
|
||||
services:
|
||||
- docker:dind
|
||||
- docker:dind
|
||||
before_script:
|
||||
# login to the gitlab docker registry to use the cache and to publish
|
||||
- echo $CI_DEPLOY_PASSWORD | docker login -u $CI_DEPLOY_USER --password-stdin $GITLAB_REGISTRY
|
||||
@@ -106,7 +108,7 @@ deploy:latest:
|
||||
stage: Deploy
|
||||
image: docker:latest
|
||||
services:
|
||||
- docker:dind
|
||||
- docker:dind
|
||||
before_script:
|
||||
# login to the gitlab docker registry to use the cache and to publish
|
||||
- echo $CI_DEPLOY_PASSWORD | docker login -u $CI_DEPLOY_USER --password-stdin $GITLAB_REGISTRY
|
||||
|
||||
123
CHANGELOG.md
@@ -7,6 +7,116 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
Before _0.1.0_, website development was on rolling release, meaning there are no version tags.
|
||||
|
||||
## [2.5.1] - 2024-03-19
|
||||
|
||||
### Added
|
||||
|
||||
- Panel management to edit a sequence:
|
||||
- 3 tabs to edit in a sequence page
|
||||
- add the possibility to re-orient all the pictures of a sequence with a widget
|
||||
- add the possibility to sort the pictures of a sequence
|
||||
|
||||
## [2.5.0] - 2024-03-11
|
||||
|
||||
### Changed
|
||||
|
||||
- GeoVisio web viewer updated to [2.5.0](https://gitlab.com/geovisio/web-viewer/-/compare/2.4.0...2.5.0) to reduce tiles size.
|
||||
|
||||
## [2.4.1] - 2024-02-01
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix geovisio version yarn.lock
|
||||
|
||||
## [2.4.0] - 2024-01-31
|
||||
|
||||
### Added
|
||||
|
||||
- Possibility to edit a sequence title in the sequence page
|
||||
|
||||
### Changed
|
||||
|
||||
- GeoVisio web viewer updated to [2.4.0](https://gitlab.com/geovisio/web-viewer/-/compare/2.3.1...2.4.0) to manage sequence by user
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix filter reset button to include bbox filter
|
||||
- Fix fullscreen button added by the widget viewer
|
||||
- Some UI and UX fixes before user tests
|
||||
|
||||
## [2.3.1] - 2024-01-29
|
||||
|
||||
### Added
|
||||
|
||||
- Add the possibility to fullscreen the viewer in the homepage : https://gitlab.com/geovisio/website/-/issues/60
|
||||
- In the sequence list page add a filter by bbox in the map : https://gitlab.com/geovisio/website/-/issues/61
|
||||
- In the sequence list page add a filter by date in the list : https://gitlab.com/geovisio/website/-/issues/57
|
||||
- Add a cancel button to when a sequence is uploading to cancel the upload
|
||||
|
||||
### Changed
|
||||
|
||||
- GeoVisio web viewer updated to [2.3.1](https://gitlab.com/geovisio/web-viewer/-/compare/2.3.0...2.3.1)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Some UI and UX fixes before user tests
|
||||
|
||||
## [2.3.0] - 2023-12-06
|
||||
|
||||
### Added
|
||||
|
||||
- Add the possibility to an user to select a sequence in the list using the map : https://gitlab.com/geovisio/website/-/merge_requests/100
|
||||
- For a selected sequence in the list, if the sequence is not displayed in the map, fly to the sequence on the map : https://gitlab.com/geovisio/website/-/merge_requests/108
|
||||
- Add the pagination to the sequence with sort with API routes : https://gitlab.com/geovisio/website/-/merge_requests/107
|
||||
- Add Hungarian translation : https://gitlab.com/geovisio/website/-/merge_requests/105
|
||||
- Add the possibility to hide/delete a sequence in the sequence list : https://gitlab.com/geovisio/website/-/merge_requests/101
|
||||
- Add the possibility for the user to change the title of the sequence before upload the pictures : https://gitlab.com/geovisio/website/-/merge_requests/101
|
||||
- Add the possibility to resize the sequence page by dragging the blocs : https://gitlab.com/geovisio/website/-/merge_requests/101
|
||||
|
||||
### Changed
|
||||
|
||||
- GeoVisio web viewer updated to [2.3.0](https://gitlab.com/geovisio/web-viewer/-/compare/2.2.1...2.3.0)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Nginx server in Docker container was not recognizing routes other than `/` on first loading.
|
||||
- Fix the cookie bug by decoding flask cookie : https://gitlab.com/geovisio/website/-/merge_requests/102
|
||||
|
||||
## [2.2.3] - 2023-11-03
|
||||
|
||||
### Added
|
||||
|
||||
- Add translation based on the browser language (only trad for FR and EN for now)
|
||||
|
||||
### Changed
|
||||
|
||||
- Page My Sequences, add the possibility to select a sequence in the list with the map :
|
||||
|
||||
- the user can only see his sequences on the list
|
||||
- display the selected sequence in blue in the map
|
||||
- display a thumbnail the hovered sequence
|
||||
- hover the sequence selected in the map on the list
|
||||
|
||||
- add some test e2e
|
||||
- maj viewer Geovisio version to 2.2.1
|
||||
- fix some CSS
|
||||
|
||||
## [2.2.2] - 2023-10-16
|
||||
|
||||
### Changed
|
||||
|
||||
- fix lazy loading on imgs
|
||||
- fix css to the license link
|
||||
|
||||
## [2.2.1] - 2023-10-16
|
||||
|
||||
### Changed
|
||||
|
||||
- fix some wordings
|
||||
- licence text in bold
|
||||
- remove footer when logged
|
||||
- add tiles custom to the sequence page list
|
||||
|
||||
## [2.2.0] - 2023-10-13
|
||||
|
||||
### Added
|
||||
@@ -16,7 +126,7 @@ Before _0.1.0_, website development was on rolling release, meaning there are no
|
||||
- new header
|
||||
- add a map to the sequence list page + the uploaded date
|
||||
- new wordings
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Hide buttons delete and hide sequences images when you are not the owner
|
||||
@@ -91,7 +201,16 @@ Before _0.1.0_, website development was on rolling release, meaning there are no
|
||||
- Header have now a new entry `Mes photos` when the user is logged to access to the sequence list
|
||||
- The router guard for logged pages has been changed to not call the api to check the token
|
||||
|
||||
[unreleased]: https://gitlab.com/geovisio/website/-/compare/2.2.0...develop
|
||||
[unreleased]: https://gitlab.com/geovisio/website/-/compare/2.5.1...develop
|
||||
[2.5.1]: https://gitlab.com/geovisio/website/-/compare/2.5.0...2.5.1
|
||||
[2.5.0]: https://gitlab.com/geovisio/website/-/compare/2.4.1...2.5.0
|
||||
[2.4.1]: https://gitlab.com/geovisio/website/-/compare/2.4.0...2.4.1
|
||||
[2.4.0]: https://gitlab.com/geovisio/website/-/compare/2.3.1...2.4.0
|
||||
[2.3.1]: https://gitlab.com/geovisio/website/-/compare/2.3.0...2.3.1
|
||||
[2.3.0]: https://gitlab.com/geovisio/website/-/compare/2.2.3...2.3.0
|
||||
[2.2.3]: https://gitlab.com/geovisio/website/-/compare/2.2.2...2.2.3
|
||||
[2.2.2]: https://gitlab.com/geovisio/website/-/compare/2.2.1...2.2.2
|
||||
[2.2.1]: https://gitlab.com/geovisio/website/-/compare/2.2.0...2.2.1
|
||||
[2.2.0]: https://gitlab.com/geovisio/website/-/compare/2.1.3...2.2.0
|
||||
[2.1.3]: https://gitlab.com/geovisio/website/-/compare/2.1.2...2.1.3
|
||||
[2.1.2]: https://gitlab.com/geovisio/website/-/compare/2.1.1...2.1.2
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#- Build image
|
||||
#-
|
||||
|
||||
FROM node:18.16.0-alpine AS build
|
||||
FROM node:18.16.1-alpine AS build
|
||||
|
||||
WORKDIR /opt/geovisio
|
||||
|
||||
@@ -19,6 +19,7 @@ COPY *.json *.js *.ts *.html ./
|
||||
|
||||
# Replace env variables by placeholder for dynamic change on container start
|
||||
ENV VITE_INSTANCE_NAME=DOCKER_VITE_INSTANCE_NAME
|
||||
ENV VITE_RASTER_TILE=DOCKER_VITE_RASTER_TILE
|
||||
ENV VITE_API_URL=DOCKER_VITE_API_URL
|
||||
ENV VITE_TILES=DOCKER_VITE_TILES
|
||||
ENV VITE_MAX_ZOOM=DOCKER_VITE_MAX_ZOOM
|
||||
@@ -50,6 +51,7 @@ ENV VITE_INSTANCE_NAME="GeoVisio/Docker"
|
||||
ENV VITE_API_URL="https://panoramax.openstreetmap.fr"
|
||||
ENV VITE_TILES="https://tile-vect.openstreetmap.fr/styles/basic/style.json"
|
||||
ENV VITE_MAX_ZOOM=""
|
||||
ENV VITE_RASTER_TILE=""
|
||||
ENV VITE_ZOOM=""
|
||||
ENV VITE_CENTER=""
|
||||
|
||||
|
||||
@@ -12,6 +12,6 @@ export default defineConfig({
|
||||
setupNodeEvents(on, config) {
|
||||
// implement node event listeners here
|
||||
},
|
||||
baseUrl: 'http://localhost:5173'
|
||||
baseUrl: 'http://localhost:5173/'
|
||||
}
|
||||
})
|
||||
|
||||
3
cypress.env.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"api_url": "http://localhost:5000/"
|
||||
}
|
||||
79
cypress/docker-compose-geovisio.yml
Normal file
@@ -0,0 +1,79 @@
|
||||
services:
|
||||
api:
|
||||
image: geovisio/api:develop
|
||||
command: api
|
||||
restart: always
|
||||
ports:
|
||||
- 5000:5000
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
auth:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
DB_URL: postgres://gvs:gvspwd@db/geovisio
|
||||
PICTURE_PROCESS_THREADS_LIMIT: 2
|
||||
PICTURE_PROCESS_DERIVATES_STRATEGY: ON_DEMAND
|
||||
API_FORCE_AUTH_ON_UPLOAD: 'true'
|
||||
OAUTH_CLIENT_ID: geovisio
|
||||
OAUTH_CLIENT_SECRET: what_a_secret
|
||||
OAUTH_OIDC_URL: http://localhost:8183/realms/geovisio
|
||||
OAUTH_PROVIDER: oidc
|
||||
FLASK_SECRET_KEY: a_very_secret_key_never_to_be_used_in_production
|
||||
healthcheck:
|
||||
test: python -c "import requests; requests.get('http://localhost:5000/api').raise_for_status()"
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
extra_hosts:
|
||||
- 'localhost:host-gateway'
|
||||
|
||||
networks:
|
||||
db: {}
|
||||
geovisio:
|
||||
aliases:
|
||||
- api.localtest.me
|
||||
|
||||
db:
|
||||
image: postgis/postgis:13-3.2
|
||||
environment:
|
||||
- POSTGRES_USER=gvs
|
||||
- POSTGRES_PASSWORD=gvspwd
|
||||
- POSTGRES_DB=geovisio
|
||||
healthcheck:
|
||||
test: pg_isready -q -d $$POSTGRES_DB -U $$POSTGRES_USER
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
db: {}
|
||||
|
||||
auth:
|
||||
command: start-dev --import-realm
|
||||
environment:
|
||||
GEOVISIO_BASE_URL: http://localhost:5000
|
||||
GEOVISIO_CLIENT_SECRET: what_a_secret
|
||||
KEYCLOAK_ADMIN: admin
|
||||
KEYCLOAK_ADMIN_PASSWORD: password
|
||||
KEYCLOAK_FRONTEND_URL: http://localhost:5000/api/auth/login
|
||||
KC_HTTP_PORT: 8183
|
||||
ports:
|
||||
- '8183:8183'
|
||||
healthcheck:
|
||||
test: curl --fail http://localhost:8183/realms/geovisio
|
||||
timeout: 5s
|
||||
interval: 2s
|
||||
retries: 20
|
||||
start_period: 15s
|
||||
image: quay.io/keycloak/keycloak:20.0.1
|
||||
volumes:
|
||||
- ./keycloak-realm.json:/opt/keycloak/data/import/geovisio_realm.json
|
||||
|
||||
networks:
|
||||
geovisio:
|
||||
aliases:
|
||||
- keycloak.localtest.me
|
||||
|
||||
networks:
|
||||
db: {}
|
||||
geovisio: {}
|
||||
55
cypress/docker-compose-gitlab-override.yml
Normal file
@@ -0,0 +1,55 @@
|
||||
# Docker-compose used in gitlab-ci to run a container having access to all the other containers
|
||||
services:
|
||||
web:
|
||||
image: node:18.16.1-alpine
|
||||
volumes:
|
||||
- $PROJECT_DIR:/src
|
||||
working_dir: /src
|
||||
command: >
|
||||
sh -c "apk add --update --no-cache curl && yarn install && yarn start"
|
||||
environment:
|
||||
PORT: 5173
|
||||
VITE_API_URL: http://api.localtest.me:5000
|
||||
VITE_ENV: dev
|
||||
depends_on:
|
||||
api:
|
||||
condition: service_healthy
|
||||
auth:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- 5173:5173
|
||||
healthcheck:
|
||||
test: curl --fail http://0.0.0.0:5173
|
||||
timeout: 10s
|
||||
interval: 3s
|
||||
retries: 20
|
||||
start_period: 15s
|
||||
networks:
|
||||
geovisio:
|
||||
aliases:
|
||||
- front.localtest.me
|
||||
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
|
||||
volumes:
|
||||
- $PROJECT_DIR:/src
|
||||
working_dir: /src
|
||||
environment:
|
||||
- CYPRESS_baseUrl=http://front.localtest.me:5173/
|
||||
- CYPRESS_api_url=http://api.localtest.me:5000/
|
||||
- LANG=fr
|
||||
command: sleep 2 && yarn add --dev cypress && ./node_modules/.bin/cypress install && yarn test:e2e
|
||||
depends_on:
|
||||
web:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
geovisio: {}
|
||||
|
||||
api:
|
||||
environment:
|
||||
OAUTH_OIDC_URL: http://keycloak.localtest.me:8183/realms/geovisio
|
||||
FLASK_SESSION_COOKIE_DOMAIN: localtest.me
|
||||
|
||||
auth:
|
||||
environment:
|
||||
GEOVISIO_BASE_URL: http://api.localtest.me:5000
|
||||
KEYCLOAK_FRONTEND_URL: http://api.localtest.me:5000/api/auth/login
|
||||
29
cypress/e2e/contribute.cy.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
describe('In the contribute page', () => {
|
||||
it('go to the login page', () => {
|
||||
cy.visit('/pourquoi-contribuer')
|
||||
cy.get('.upload-text').scrollIntoView()
|
||||
cy.fixture('contribute').then((contributeData: contributeInterface) => {
|
||||
cy.contains(contributeData.textButtonContribute).click()
|
||||
})
|
||||
})
|
||||
it('go to the doc pages', () => {
|
||||
cy.visit('pourquoi-contribuer')
|
||||
cy.fixture('contribute').then((contributeData: contributeInterface) => {
|
||||
cy.get('.upload-text').scrollIntoView()
|
||||
cy.contains(contributeData.textButtonDocPython).click()
|
||||
cy.contains(contributeData.textButtonCli).click()
|
||||
cy.contains(contributeData.textButtonDocCli).click()
|
||||
cy.contains(contributeData.textButtonTiles).click()
|
||||
cy.contains(contributeData.textButtonDoc).click()
|
||||
})
|
||||
})
|
||||
})
|
||||
interface contributeInterface {
|
||||
textButtonContribute: string
|
||||
textButtonDocPython: string
|
||||
textButtonCli: string
|
||||
textButtonDocCli: string
|
||||
textButtonTiles: string
|
||||
textButtonDoc: string
|
||||
}
|
||||
export {}
|
||||
16
cypress/e2e/home.cy.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
describe('In the home page', () => {
|
||||
it('click on the link in the footer to go to Panoramax.fr', () => {
|
||||
cy.visit('/')
|
||||
cy.fixture('home').then((homeData) => {
|
||||
cy.contains(homeData.textLinkPanoramax).click()
|
||||
})
|
||||
})
|
||||
it('click on the link in the footer to go to Gitlab', () => {
|
||||
cy.visit('/')
|
||||
cy.fixture('home').then((homeData) => {
|
||||
cy.contains(homeData.textLinkGitlab).click()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
export {}
|
||||
@@ -1,7 +1,24 @@
|
||||
describe('In the login page', () => {
|
||||
it('type in the form to login', () => {
|
||||
cy.visit('https://geovisio-proxy-dev.osc-fr1.scalingo.io/api/auth/login')
|
||||
cy.get('#password').type('coucouc')
|
||||
cy.visit(`${Cypress.env('api_url')}api/auth/login`)
|
||||
cy.get('#username').type('Elysee')
|
||||
cy.get('#password').type('my password')
|
||||
cy.contains('Sign In').click()
|
||||
cy.visit('/')
|
||||
})
|
||||
it('go to the register form and create an account', () => {
|
||||
cy.visit(`${Cypress.env('api_url')}api/auth/login`)
|
||||
cy.fixture('login').then((loginData) => {
|
||||
cy.contains(loginData.textLinkRegister).click()
|
||||
cy.get('#firstName').type('Tom')
|
||||
cy.get('#lastName').type('Tom')
|
||||
cy.get('#email').type('test@test123.com')
|
||||
cy.get('#username').type('Elysee12445')
|
||||
cy.get('#password').type('my password1')
|
||||
cy.get('#password-confirm').type('my password1')
|
||||
cy.get('form').submit()
|
||||
cy.visit('/')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
9
cypress/e2e/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"types": ["reflect-metadata", "jest", "cypress"],
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"sourceMap": true
|
||||
},
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
32
cypress/e2e/upload.cy.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
describe('In the login page', () => {
|
||||
it('login and go to the upload page to upload images', () => {
|
||||
cy.visit(`${Cypress.env('api_url')}api/auth/login`)
|
||||
cy.get('#username').type('Elysee')
|
||||
cy.get('#password').type('my password')
|
||||
cy.fixture('upload').then((uploadData: uploadInterface) => {
|
||||
cy.contains(uploadData.textLinkLogin).click()
|
||||
cy.visit('/envoyer')
|
||||
cy.get('.edit-button').click()
|
||||
cy.get('#upload-title').clear()
|
||||
cy.get('#upload-title').type(uploadData.textTitle)
|
||||
cy.contains(uploadData.textButtonTitle).click()
|
||||
cy.contains(uploadData.textButtonUpload).click()
|
||||
})
|
||||
cy.get('.input-file').selectFile(
|
||||
[
|
||||
'/src/cypress/fixtures/images/image1.jpg',
|
||||
'/src/cypress/fixtures/images/image2.jpg',
|
||||
'/src/cypress/fixtures/images/image3.jpg'
|
||||
],
|
||||
{ force: true }
|
||||
)
|
||||
})
|
||||
})
|
||||
interface uploadInterface {
|
||||
textLinkLogin: string
|
||||
textLinkUpload: string
|
||||
textButtonUpload: string
|
||||
textTitle: string
|
||||
textButtonTitle: string
|
||||
}
|
||||
export {}
|
||||
8
cypress/fixtures/contribute.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"textButtonContribute": "Partager des images",
|
||||
"textButtonDocCli": "Voir la documentation",
|
||||
"textButtonDoc": "Retrouvez sa documentation ici",
|
||||
"textButtonDocPython": "de python (au moins la version 3.8)",
|
||||
"textButtonCli": "L'outil en ligne de commande",
|
||||
"textButtonTiles": "de tuiles vectorielles"
|
||||
}
|
||||
5
cypress/fixtures/home.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"textLinkContribute": "Pourquoi contribuer ?",
|
||||
"textLinkPanoramax": "Découvrir Panoramax",
|
||||
"textLinkGitlab": "Voir le code"
|
||||
}
|
||||
BIN
cypress/fixtures/images/image1.jpg
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
cypress/fixtures/images/image2.jpg
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
cypress/fixtures/images/image3.jpg
Normal file
|
After Width: | Height: | Size: 65 KiB |
4
cypress/fixtures/login.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"textLinkRegister": "Register",
|
||||
"textLinkLogin": "Sign In"
|
||||
}
|
||||
7
cypress/fixtures/upload.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"textLinkLogin": "Sign In",
|
||||
"textLinkUpload": "Mes photos",
|
||||
"textTitle": "My title",
|
||||
"textButtonTitle": "Valider",
|
||||
"textButtonUpload": "Glissez vos images ici ou cliquez sur"
|
||||
}
|
||||
1987
cypress/keycloak-realm.json
Normal file
@@ -18,5 +18,10 @@ http {
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
gzip_static on;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
try_files $uri /index.html;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ Available parameters are:
|
||||
- `VITE_MAX_ZOOM`: the max zoom to use on the map (defaults to 24).
|
||||
- `VITE_ZOOM`: the zoom to use at the initialization of the map (defaults to 0).
|
||||
- `VITE_CENTER`: the center position to use at the initialization of the map (defaults to 0).
|
||||
- `VITE_RASTER_TILE`: the raster tile. Example : `https://maplibre.org/maplibre-style-spec/sources/#raster`.
|
||||
- Settings for the work environment:
|
||||
- `NPM_CONFIG_PRODUCTION`: is it production environment (`true`, `false`)
|
||||
- `YARN_PRODUCTION`: same as below, but if you use Yarn instead of NPM
|
||||
|
||||
25
index.html
@@ -4,6 +4,31 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" href="/static/favicon.ico" />
|
||||
<title>
|
||||
Panoramax <%- instanceName %> : photo-cartographier les territoires
|
||||
</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="L'instance Panoramax <%- instanceName %> permet la publication de photo de terrain pour cartographier le territoire. Panoramax favorise la réutilisation des photos pour de nombreux cas d'usages."
|
||||
/>
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="Panoramax <%- instanceName %> : photo-cartographier les territoires"
|
||||
/>
|
||||
<meta
|
||||
name="og:title"
|
||||
content="Panoramax <%- instanceName %> : photo-cartographier les territoires"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="L'instance Panoramax <%- instanceName %> permet la publication de photo de terrain pour cartographier le territoire. Panoramax favorise la réutilisation des photos pour de nombreux cas d'usages."
|
||||
/>
|
||||
<meta
|
||||
name="og:description"
|
||||
content="L'instance Panoramax <%- instanceName %> permet la publication de photo de terrain pour cartographier le territoire. Panoramax favorise la réutilisation des photos pour de nombreux cas d'usages."
|
||||
/>
|
||||
<meta name="og:image" content="<%- frontUrl %>/static/meta-img.jpg" />
|
||||
<meta name="twitter:image" content="<%- frontUrl %>/static/meta-img.jpg" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
18
package.json
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "geovisio-website",
|
||||
"version": "2.2.0",
|
||||
"version": "2.5.1",
|
||||
"engines": {
|
||||
"node": "18.16.0"
|
||||
"node": "18.16.1"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -25,14 +25,17 @@
|
||||
"axios": "^1.2.3",
|
||||
"bootstrap": "^5.2.3",
|
||||
"bootstrap-icons": "^1.10.3",
|
||||
"geovisio": "2.2.0",
|
||||
"geovisio": "2.5.1",
|
||||
"moment": "^2.29.4",
|
||||
"pako": "^2.1.0",
|
||||
"pinia": "^2.1.4",
|
||||
"v-calendar": "^3.1.2",
|
||||
"vue": "^3.2.45",
|
||||
"vue-axios": "^3.5.2",
|
||||
"vue-draggable-resizable-vue3": "^2.3.1-beta.13",
|
||||
"vue-eslint-parser": "^9.1.0",
|
||||
"vue-i18n": "9.2.2",
|
||||
"vue-meta": "^3.0.0-alpha.10",
|
||||
"vue-matomo": "^4.2.0",
|
||||
"vue-router": "^4.1.6",
|
||||
"vue3-cookies": "^1.0.6",
|
||||
"vue3-smooth-scroll": "^0.8.1"
|
||||
@@ -40,6 +43,7 @@
|
||||
"devDependencies": {
|
||||
"@pinia/testing": "^0.1.2",
|
||||
"@rushstack/eslint-patch": "^1.1.4",
|
||||
"@types/jest": "^29.5.4",
|
||||
"@types/jsdom": "^20.0.1",
|
||||
"@types/node": "^18.11.18",
|
||||
"@typescript-eslint/eslint-plugin": "^5.46.0",
|
||||
@@ -50,19 +54,23 @@
|
||||
"@vue/eslint-config-typescript": "^11.0.0",
|
||||
"@vue/test-utils": "^2.2.4",
|
||||
"@vue/tsconfig": "^0.1.3",
|
||||
"cypress": "^12.12.0",
|
||||
"cypress": "^13.1.0",
|
||||
"eslint": "^8.29.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-import-resolver-typescript": "^3.5.5",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-vue": "^9.8.0",
|
||||
"jest": "^29.6.4",
|
||||
"jsdom": "^20.0.3",
|
||||
"less": "^4.2.0",
|
||||
"less-loader": "^11.1.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "2.8.1",
|
||||
"sass": "^1.62.0",
|
||||
"typescript": "~4.7.4",
|
||||
"vite": "^3.2.4",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-html": "^3.2.2",
|
||||
"vitest": "^0.25.3",
|
||||
"vue-tsc": "^1.0.9"
|
||||
},
|
||||
|
||||
35
src/App.vue
@@ -1,34 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import Header from '@/components/Header.vue'
|
||||
import Footer from '@/components/Footer.vue'
|
||||
import { RouterView } from 'vue-router'
|
||||
import { useMeta } from 'vue-meta'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { title } from '@/utils/index'
|
||||
import { hasASessionCookieDecoded } from '@/utils/auth'
|
||||
import authConfig from './composables/auth'
|
||||
const { authConf } = authConfig()
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
|
||||
let focusMap = ref<string>('focus-map')
|
||||
|
||||
useMeta({
|
||||
title: title(t('general.title')),
|
||||
og: {
|
||||
title: title(t('general.meta.title')),
|
||||
description: title(t('general.meta.description'))
|
||||
},
|
||||
twitter: {
|
||||
title: title(t('general.meta.title')),
|
||||
description: title(t('general.meta.description'))
|
||||
}
|
||||
})
|
||||
|
||||
function setFocusMap(value: string) {
|
||||
focusMap.value = value
|
||||
}
|
||||
const isLogged = computed((): boolean => {
|
||||
const cookie = hasASessionCookieDecoded()
|
||||
return !!(cookie && cookie.account)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -50,13 +37,7 @@ function setFocusMap(value: string) {
|
||||
: ''
|
||||
"
|
||||
/>
|
||||
<RouterView @trigger="setFocusMap" />
|
||||
<Footer
|
||||
v-if="
|
||||
route.name !== 'sequence' &&
|
||||
route.name !== 'my-sequences' &&
|
||||
focusMap === 'focus-map'
|
||||
"
|
||||
/>
|
||||
<RouterView @trigger="setFocusMap" :class="{ logged: isLogged }" />
|
||||
<Footer v-if="!isLogged" />
|
||||
</template>
|
||||
<style scoped></style>
|
||||
|
||||
14
src/assets/components/index.scss
Normal file
@@ -0,0 +1,14 @@
|
||||
@mixin switch-button-view() {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: toRem(22);
|
||||
z-index: 3;
|
||||
height: toRem(5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-top-left-radius: toRem(0.5);
|
||||
border-bottom-left-radius: toRem(0.5);
|
||||
background-color: var(--white);
|
||||
border: toRem(0.1) solid var(--grey-pale);
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
}
|
||||
@if $size == h4 {
|
||||
font-weight: normal;
|
||||
font-size: toRem(1.6);
|
||||
font-size: toRem(1.8);
|
||||
}
|
||||
@if $size == xxl-regular {
|
||||
font-size: toRem(3.2);
|
||||
|
||||
37
src/assets/images/car.svg
Normal file
@@ -0,0 +1,37 @@
|
||||
<svg width="58" height="114" viewBox="0 0 58 114" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M54.5335 97.255C53.702 100.976 51.4048 104.696 50.7988 106.514C50.0096 108.882 39.7427 110.087 29.2996 110.136C29.1939 110.136 29.0953 110.136 28.9896 110.136C28.8839 110.136 28.7852 110.136 28.6795 110.136C18.2365 110.087 7.96957 108.882 7.18035 106.514C6.57434 104.696 4.2701 100.976 3.44565 97.255L2.7269 99.221C3.42451 103.238 6.02471 107.395 6.68004 109.361C7.4904 111.785 17.9898 113.018 28.6725 113.06C28.7782 113.06 28.8839 113.06 28.9896 113.06C29.0953 113.06 29.201 113.06 29.3067 113.06C39.9893 113.011 50.4887 111.778 51.2991 109.354C51.9544 107.381 54.5546 103.23 55.2522 99.2139L54.5335 97.2479V97.255Z" fill="#160302"/>
|
||||
<path d="M6.30663 32.0951C7.65253 25.2599 10.2386 11.8713 10.2386 11.8713L10.288 11.7304C11.9439 6.12836 27.087 5.0291 28.8276 5.08547C28.8839 5.08547 28.9333 5.08547 28.9896 5.08547C29.046 5.08547 29.0953 5.08547 29.1517 5.08547C30.8852 5.0291 46.0353 6.12836 47.6913 11.7304L47.7406 11.8713C47.7406 11.8713 50.3197 25.2528 51.6726 32.0951C54.5265 27.2752 55.203 21.4759 53.5611 16.1204C52.2786 11.9348 51.0807 8.97519 51.0807 8.97519L51.0173 8.81312C49.0654 2.2175 31.2234 0.920929 29.1799 0.984348C29.1165 0.984348 29.053 0.984348 28.9896 0.984348C28.9262 0.984348 28.8628 0.984348 28.7994 0.984348C26.7559 0.920929 8.91387 2.2175 6.95492 8.81312L6.8915 8.97519C6.8915 8.97519 5.69358 11.9277 4.4111 16.1204C2.76924 21.4759 3.44571 27.2752 6.29958 32.0951H6.30663Z" fill="#160302"/>
|
||||
<path d="M55.0126 70.3581L53.6879 66.3627L53.0396 48.5559C52.9621 43.6022 53.0466 41.8405 53.2017 37.373C53.2228 36.7881 54.7308 33.7863 54.7519 33.2155C55.1324 23.5969 49.5797 9.87012 49.5797 9.87012L49.5233 9.72214C47.7053 3.57046 31.0683 2.35845 29.1657 2.42187C29.1023 2.42187 29.0459 2.42187 28.9895 2.42187C28.9332 2.42187 28.8698 2.42187 28.8134 2.42187C26.9038 2.35845 10.2738 3.57046 8.44871 9.72214L8.39234 9.87012C8.39234 9.87012 2.83962 23.5969 3.22014 33.2155C3.24128 33.7863 4.74925 36.7881 4.77039 37.373C4.92541 41.8405 5.00997 43.6022 4.93246 48.5559L4.29122 66.257L2.95941 70.2595V93.8162L3.21309 95.9302V95.9795C3.7134 100.095 6.50385 104.485 7.17328 106.507C7.9625 108.875 18.2294 110.08 28.6724 110.129C28.7781 110.129 28.8768 110.129 28.9825 110.129C29.0882 110.129 29.1868 110.129 29.2925 110.129C39.7356 110.08 50.0025 108.875 50.7917 106.507C51.4682 104.478 54.2516 100.095 54.7519 95.9795V95.9302L55.0056 93.9149V70.3581H55.0126Z" fill="#17347A"/>
|
||||
<path d="M45.8168 47.795L26.0018 27.9799C24.7898 12.8016 31.9843 6.36803 39.1226 3.68328C34.5845 2.66857 30.1029 2.3867 29.1657 2.42194C29.1023 2.42194 29.0459 2.42194 28.9895 2.42194C28.9332 2.42194 28.8698 2.42194 28.8134 2.42194C26.9038 2.35852 10.2738 3.57053 8.44871 9.72221L8.39234 9.87019C8.39234 9.87019 2.83962 23.597 3.22014 33.2156C3.24128 33.7863 4.74925 36.7882 4.77039 37.3731C4.92541 41.8406 5.00997 43.6023 4.93246 48.556L4.29122 66.2571L2.95941 70.2595V93.8163L3.21309 95.9303V95.9796C3.64293 99.5029 5.74282 103.216 6.76458 105.485C9.08291 100.003 12.268 92.1674 12.268 92.1674L12.8881 51.276C20.139 52.9742 28.6302 52.6642 28.6302 52.6642C45.6054 52.8192 45.8239 47.7879 45.8239 47.7879L45.8168 47.795Z" fill="#1E4084"/>
|
||||
<path d="M43.9213 10.8637L49.0583 18.9673L51.0031 22.0396C49.6572 17.0788 48.0929 13.2173 48.0929 13.2173L48.0436 13.0763C47.4939 11.2231 45.4856 9.85605 42.9771 8.84839C43.2096 9.55305 43.5196 10.2295 43.9142 10.8637H43.9213Z" fill="#C1D1D0"/>
|
||||
<path d="M14.0508 10.8637C14.4524 10.2366 14.7555 9.55305 14.988 8.84839C12.4724 9.84901 10.4711 11.216 9.92149 13.0763L9.87216 13.2173C9.87216 13.2173 8.30781 17.0788 6.96191 22.0396L14.0508 10.8637Z" fill="#C1D1D0"/>
|
||||
<path d="M14.4102 10.2225C14.6427 9.77854 14.833 9.32051 14.988 8.84839C12.4724 9.84901 10.4711 11.216 9.92151 13.0763L9.87218 13.2173C9.87218 13.2173 9.40006 14.387 8.75177 16.2544C9.22389 16.1134 9.66078 15.8809 10.0554 15.5286C11.4154 14.3165 13.1488 12.2589 14.4102 10.2225Z" fill="#DFF3F4"/>
|
||||
<path d="M43.562 10.2225C44.8233 12.266 46.5638 14.3165 47.9167 15.5286C48.3114 15.8809 48.7482 16.1064 49.2204 16.2544C48.5721 14.38 48.1 13.2173 48.1 13.2173L48.0506 13.0763C47.501 11.2231 45.4927 9.85605 42.9841 8.84839C43.1392 9.32051 43.3294 9.78559 43.562 10.2225Z" fill="#DFF3F4"/>
|
||||
<path d="M45.3729 106.655C39.0521 107.578 33.1189 107.578 28.9826 107.578C24.8462 107.578 18.913 107.578 12.5922 106.655C7.37065 105.894 8.97727 104.097 8.97727 104.097L12.3455 97.6073C18.3633 98.8052 39.5877 98.8052 45.6055 97.6073L48.9738 104.097C48.9738 104.097 50.5874 105.894 45.3589 106.655H45.3729Z" fill="#373637"/>
|
||||
<path d="M28.9896 107.656C24.9871 107.656 18.9482 107.656 12.5922 106.733C10.3161 106.401 9.01955 105.838 8.73769 105.048C8.55448 104.534 8.87862 104.125 8.92795 104.055L12.3174 97.5227L12.3737 97.5368C18.4268 98.7488 39.5454 98.7488 45.6055 97.5368L45.6619 97.5227L45.69 97.572L49.0583 104.055C49.1147 104.118 49.4388 104.534 49.2486 105.048C48.9667 105.838 47.6701 106.401 45.3941 106.733C39.0381 107.656 32.9921 107.656 28.9967 107.656H28.9896ZM12.4019 97.6989L9.04774 104.154C9.04774 104.154 8.7095 104.541 8.87862 104.999C9.06183 105.499 9.8581 106.183 12.6133 106.585C18.9623 107.508 24.9942 107.508 28.9896 107.508C32.985 107.508 39.024 107.508 45.3659 106.585C48.1211 106.183 48.9174 105.499 49.1006 104.999C49.2697 104.534 48.9385 104.161 48.9315 104.154L48.9174 104.139L45.5773 97.6989C39.4538 98.8968 18.5254 98.8968 12.4019 97.6989Z" fill="#160302"/>
|
||||
<path d="M44.6753 105.774C38.6223 106.627 32.9427 106.641 28.9896 106.641C25.0365 106.641 19.3499 106.627 13.3039 105.774C8.30079 105.07 9.84399 103.407 9.84399 103.407L12.8177 97.861C19.223 98.9532 38.7562 98.9532 45.1545 97.861L48.1282 103.407C48.1282 103.407 49.6714 105.07 44.6683 105.774H44.6753Z" fill="#DFF3F4"/>
|
||||
<path d="M48.1282 103.407L47.8674 102.92C47.346 102.765 46.6907 102.624 45.8662 102.512C39.3551 101.596 33.2457 101.582 28.9826 101.582C24.7194 101.582 18.61 101.596 12.0989 102.512C11.2745 102.624 10.6191 102.765 10.0977 102.92L9.83694 103.407C9.83694 103.407 8.29374 105.07 13.2968 105.774C19.3499 106.627 25.0294 106.641 28.9826 106.641C32.9357 106.641 38.6223 106.627 44.6683 105.774C49.6714 105.07 48.1282 103.407 48.1282 103.407Z" fill="#C1D1D0"/>
|
||||
<path d="M35.8953 109.03L35.8741 108.988L35.8389 108.931L35.7473 108.769C35.5571 108.713 35.3245 108.67 35.0567 108.628C32.6609 108.29 30.406 108.29 28.9192 108.29C27.4323 108.29 25.1774 108.29 22.7816 108.628C22.5138 108.663 22.2883 108.713 22.091 108.769L22.0135 108.917C22.0135 108.917 21.9853 108.952 21.9712 108.981L21.9501 109.023H21.9007C18.2717 108.868 15.0162 108.537 11.9509 108.022C9.66785 107.635 8.33604 106.986 7.98371 106.084C7.71594 105.394 8.13874 104.837 8.15283 104.816L11.9791 97.3396C12.0919 97.1141 12.4301 96.9872 12.7543 97.0577C19.2724 98.3684 38.6787 98.3684 45.1968 97.0577C45.5209 96.9943 45.8592 97.1141 45.9719 97.3396L49.8053 104.816C49.8546 104.879 50.2281 105.422 49.9744 106.077C49.6221 106.979 48.2832 107.628 46.0072 108.015C42.9137 108.537 39.6159 108.868 35.9376 109.023H35.8882L35.8953 109.03ZM35.9658 108.847C35.9658 108.847 35.9728 108.861 35.9869 108.875C39.6441 108.72 42.9137 108.396 45.9931 107.874C48.7624 107.402 49.615 106.613 49.8476 106.035C50.0871 105.429 49.6996 104.922 49.6996 104.915V104.901L45.8521 97.4171C45.7676 97.255 45.4928 97.1634 45.2391 97.2127C38.7069 98.5234 19.2653 98.5234 12.7331 97.2127C12.4794 97.1634 12.2046 97.255 12.1201 97.4171L8.28672 104.901C8.28672 104.901 7.89211 105.429 8.13169 106.035C8.35718 106.613 9.21687 107.409 11.9862 107.874C15.0303 108.389 18.2647 108.713 21.8655 108.868C21.8796 108.847 21.8937 108.833 21.8937 108.833L21.9923 108.642H22.0205C22.2249 108.572 22.4715 108.523 22.7604 108.48C25.1704 108.142 27.4253 108.142 28.9192 108.142C30.413 108.142 32.6679 108.142 35.0779 108.48C35.3668 108.523 35.6134 108.572 35.8178 108.628H35.846L35.9517 108.84L35.9658 108.847Z" fill="#160302"/>
|
||||
<path d="M45.3166 26.8032C39.0169 24.513 33.1119 24.4778 28.9826 24.4778C24.8533 24.4778 18.9482 24.513 12.6486 26.8032C7.44113 28.6987 9.04775 32.8844 9.04775 32.8844L12.1483 47.7879C13.7478 52.939 44.161 52.8262 45.8099 47.7879L48.9104 32.8844C48.9104 32.8844 50.517 28.6987 45.3095 26.8032H45.3166Z" fill="#160302"/>
|
||||
<path d="M44.6753 29.0792C38.6223 27.0427 32.9427 27.0145 28.9896 27.0145C25.0364 27.0145 19.3498 27.0427 13.3038 29.0792C8.30076 30.7633 9.84396 34.7376 9.84396 34.7376L12.8176 47.9922C14.3538 52.5725 43.569 52.4668 45.1545 47.9922L48.1281 34.7376C48.1281 34.7376 49.6713 30.7633 44.6683 29.0792H44.6753Z" fill="#DFF3F4"/>
|
||||
<path d="M44.6753 29.0792C38.6223 27.0427 32.9427 27.0145 28.9896 27.0145C25.0364 27.0145 19.3498 27.0427 13.3038 29.0792C8.30076 30.7633 9.84396 34.7376 9.84396 34.7376L10.3654 37.0489C10.8728 37.4083 11.514 37.7324 12.2962 38.0002C12.3314 38.9233 12.4583 39.783 12.7401 40.5299C12.1693 40.6356 11.6549 40.7554 11.2321 40.8963L11.7606 43.2428C13.2193 43.6234 15.3403 43.87 17.7009 43.87C22.1121 43.87 25.6847 43.0314 25.6847 41.9956C25.6847 41.4037 24.515 40.8752 22.6829 40.5369C22.7463 40.389 22.7956 40.241 22.8449 40.093C25.3394 40.2903 27.5943 40.2974 29.4899 40.2974C33.8094 40.2974 39.9964 40.2692 46.599 38.0425C46.9161 37.9368 47.205 37.817 47.4728 37.6972L48.1352 34.7446C48.1352 34.7446 49.6784 30.7704 44.6753 29.0862V29.0792Z" fill="#C1D1D0"/>
|
||||
<path d="M6.10921 33.7792C6.1233 33.2789 6.8632 33.2014 6.99708 33.6806C8.54733 39.3037 11.8381 51.29 11.824 51.8185L10.76 92.428C10.7318 93.6823 9.49862 94.2813 8.95604 93.2032C8.95604 93.2032 5.36932 83.4718 5.35522 80.3431L6.10921 33.7792Z" fill="#17347A"/>
|
||||
<path d="M9.72417 93.8443C9.38594 93.8443 9.08293 93.6259 8.88563 93.2383C8.84335 93.1326 5.29186 83.4506 5.27777 80.3431L6.03176 33.7791C6.0388 33.4761 6.27134 33.3141 6.49683 33.2859C6.71528 33.2577 6.99009 33.3634 7.07465 33.6593C8.88563 40.2127 11.9227 51.297 11.9016 51.8184L10.8375 92.428C10.8164 93.2102 10.3513 93.7175 9.90739 93.8162C9.84397 93.8303 9.78759 93.8373 9.72417 93.8373V93.8443ZM6.18678 33.7862L5.4328 80.3501C5.44689 83.4295 8.99133 93.0833 9.02656 93.182C9.22387 93.5695 9.52687 93.7527 9.86511 93.6752C10.2527 93.5907 10.6614 93.1326 10.6755 92.435L11.7395 51.8255C11.7536 51.4591 9.94967 44.6873 6.91963 33.7087C6.86326 33.4973 6.673 33.4268 6.51093 33.448C6.36295 33.462 6.18678 33.5677 6.17974 33.7932L6.18678 33.7862Z" fill="#160302"/>
|
||||
<path d="M6.75751 34.822L10.8304 50.1624C10.9855 50.7473 11.0559 51.3462 11.0348 51.9452L9.87915 91.7303C9.86506 92.294 6.79274 84.1129 6.79274 82.3935L6.75751 34.8079V34.822Z" fill="#373637"/>
|
||||
<path d="M9.87217 91.8431C9.62554 91.8431 8.33601 88.1506 8.18803 87.7278C7.75114 86.4665 6.72234 83.4153 6.72234 82.4006L6.6871 34.223L10.908 50.1413C11.063 50.7332 11.1335 51.3392 11.1194 51.9452L9.96377 91.7303C9.96377 91.8149 9.9074 91.836 9.87217 91.836V91.8431ZM6.83508 35.4139L6.87031 82.4006C6.87031 83.9297 9.26616 90.3421 9.80875 91.5189L10.9573 51.9452C10.9714 51.3463 10.908 50.7543 10.753 50.1836L6.83508 35.4139Z" fill="#160302"/>
|
||||
<path d="M9.70307 82.5698L10.5205 52.3046C10.5346 51.7409 10.4782 51.1772 10.3443 50.6275L7.25085 37.6759L7.31427 75.4245C7.31427 77.4399 7.84982 79.427 8.86453 81.1675C9.13934 81.6396 9.42121 82.1188 9.70307 82.5768V82.5698Z" fill="#C1D1D0"/>
|
||||
<path d="M9.70304 82.5698L10.5204 52.3046C10.5345 51.7409 10.4782 51.1772 10.3443 50.6275L9.69599 47.9005C8.25144 54.0592 7.3847 61.9585 7.3847 70.5765C7.3847 73.0921 7.46222 75.5514 7.60315 77.9261C7.85683 79.0606 8.27962 80.1528 8.87154 81.1675C9.14635 81.6396 9.42822 82.1188 9.71008 82.5768L9.70304 82.5698Z" fill="#DFF3F4"/>
|
||||
<path d="M6.75049 69.3011C6.87028 70.6752 9.42819 75.2554 10.4147 76.0728L10.5275 72.2043C9.7312 70.4638 7.18033 67.2787 6.74344 65.5593L6.75753 69.294L6.75049 69.3011Z" fill="#160302"/>
|
||||
<path d="M6.7928 34.8502C6.7928 34.8502 0.373348 38.3383 0.373348 39.4587C0.373348 40.5791 -0.00012207 43.2075 -0.00012207 43.2075L5.2355 41.3472L7.66658 38.0705L6.78575 34.8502H6.7928Z" fill="#160302"/>
|
||||
<path d="M5.62918 66.4665L5.49713 66.5477L10.3844 74.4949L10.5164 74.4137L5.62918 66.4665Z" fill="#160302"/>
|
||||
<path d="M51.8629 33.8778C51.8488 33.3775 51.1089 33.3 50.975 33.7792C49.4248 39.4024 46.134 51.3886 46.1481 51.9171L47.2122 92.5267C47.2403 93.781 48.4735 94.3799 49.0161 93.3018C49.0161 93.3018 52.6028 83.5705 52.6169 80.4418L51.8629 33.8778Z" fill="#17347A"/>
|
||||
<path d="M48.2479 93.943C48.1916 93.943 48.1282 93.943 48.0647 93.9219C47.6138 93.8232 47.1487 93.3159 47.1275 92.5337L46.0705 51.9241C46.0494 51.3957 49.0865 40.3184 50.8975 33.7651C50.975 33.4691 51.2568 33.3634 51.4753 33.3916C51.7008 33.4198 51.9333 33.5818 51.9404 33.8849L52.6943 80.4488C52.6803 83.5563 49.1217 93.2383 49.0865 93.337C48.8892 93.7316 48.5862 93.95 48.2479 93.95V93.943ZM51.4119 33.5325C51.2639 33.5325 51.1018 33.61 51.0525 33.8003C48.0225 44.7789 46.2185 51.5507 46.2326 51.9171L47.2896 92.5337C47.3107 93.2313 47.7124 93.6893 48.1 93.7739C48.4382 93.8514 48.7483 93.6682 48.9456 93.2736C48.9808 93.182 52.5252 83.5281 52.5393 80.4488L51.7853 33.8849C51.7783 33.6594 51.6021 33.5607 51.4541 33.5466C51.4401 33.5466 51.4189 33.5466 51.4048 33.5466L51.4119 33.5325Z" fill="#160302"/>
|
||||
<path d="M51.2145 34.9207L47.1416 50.2612C46.9866 50.846 46.9161 51.445 46.9373 52.0439L48.0929 91.829C48.107 92.3928 51.1793 84.2117 51.1793 82.4923L51.2145 34.9066V34.9207Z" fill="#373637"/>
|
||||
<path d="M48.1 91.9418C48.1 91.9418 48.0154 91.9207 48.0083 91.8361L46.8598 52.051C46.8457 51.438 46.9091 50.832 47.0712 50.24L51.2921 34.3218V34.9137L51.2568 82.4923C51.2568 83.507 50.228 86.5582 49.7911 87.8195C49.6432 88.2423 48.3536 91.9277 48.107 91.9348L48.1 91.9418ZM51.137 35.5127L47.2191 50.2823C47.0641 50.8601 47.0007 51.4521 47.0148 52.044L48.1634 91.6177C48.713 90.4479 51.1018 84.0285 51.1018 82.4994L51.137 35.5127Z" fill="#160302"/>
|
||||
<path d="M48.2691 82.6615L47.4517 52.3963C47.4376 51.8326 47.494 51.2689 47.6279 50.7192L50.7213 37.7676L50.6579 75.5162C50.6579 77.5315 50.1224 79.5187 49.1076 81.2592C48.8328 81.7313 48.551 82.2105 48.2691 82.6685V82.6615Z" fill="#C1D1D0"/>
|
||||
<path d="M48.2691 82.5698L47.4517 52.3046C47.4376 51.7409 47.494 51.1772 47.6279 50.6275L48.2761 47.9005C49.7207 54.0592 50.5874 61.9585 50.5874 70.5765C50.5874 73.0921 50.5099 75.5514 50.369 77.9261C50.1153 79.0606 49.6925 80.1528 49.1006 81.1675C48.8258 81.6396 48.5439 82.1188 48.2621 82.5768L48.2691 82.5698Z" fill="#DFF3F4"/>
|
||||
<path d="M51.2217 69.3998C51.1019 70.7739 48.544 75.3542 47.5574 76.1716L47.4447 72.303C48.241 70.5625 50.7918 67.3775 51.2287 65.6581L51.2146 69.3928L51.2217 69.3998Z" fill="#160302"/>
|
||||
<path d="M51.1864 34.9489C51.1864 34.9489 57.6058 38.4299 57.6058 39.5573C57.6058 40.6848 57.9793 43.3061 57.9793 43.3061L52.7437 41.4458L50.3126 38.1691L51.1934 34.9489H51.1864Z" fill="#160302"/>
|
||||
<path d="M52.342 66.5611L47.4576 74.51L47.5897 74.5912L52.4741 66.6423L52.342 66.5611Z" fill="#160302"/>
|
||||
<path d="M7.87093 29.7697L7.80047 29.509C7.26492 27.4584 6.55322 24.6821 6.25021 23.0754V23.0472L6.25726 23.0191L13.9803 10.8425C14.6498 9.78555 15.0866 8.62286 15.2699 7.38266L15.5024 5.82536L15.5376 5.80422C15.5729 5.78308 19.3428 3.95097 27.3971 3.38019C27.5944 3.35905 27.7987 3.34496 28.0242 3.33791H28.1158C28.1158 3.33791 28.3906 3.31677 28.7852 3.31677H28.9684H29.1446C29.5533 3.31677 29.8211 3.33087 29.8211 3.33087H29.9127C30.1382 3.35201 30.3425 3.3661 30.5398 3.38019C38.5871 3.95097 42.364 5.78308 42.3993 5.80422L42.4345 5.82536L42.667 7.38266C42.8573 8.62286 43.2871 9.78555 43.9566 10.8425L51.7008 23.0472V23.0754C51.3837 24.6821 50.679 27.4584 50.1435 29.509L50.073 29.7697L49.9955 29.516C49.5093 27.9799 48.2338 26.8454 46.2044 26.1337C39.5243 23.8083 33.1682 23.8083 28.9614 23.8083C24.7546 23.8083 18.3985 23.8083 11.7184 26.1337C9.68895 26.8383 8.41352 27.9799 7.9273 29.516L7.84979 29.7697H7.87093ZM28.9896 23.6603C33.2035 23.6603 39.5806 23.6603 46.2819 25.9927C48.255 26.6762 49.5304 27.7685 50.0871 29.2271C50.6086 27.2259 51.2709 24.6257 51.5669 23.0825L43.8579 10.9271C43.1744 9.84897 42.7305 8.67219 42.5402 7.41085L42.3147 5.93811C41.913 5.74785 38.1502 4.08485 30.5469 3.54226C30.3496 3.52112 30.1452 3.50703 29.9197 3.49998H29.8281C29.8281 3.49998 29.5604 3.47884 29.1658 3.47884H28.9825H28.8134C28.4118 3.47884 28.144 3.49294 28.1369 3.49294H28.0453C27.8199 3.51408 27.6155 3.52817 27.4252 3.54226C19.822 4.08485 16.0591 5.74785 15.6574 5.93811L15.4319 7.41085C15.2417 8.67219 14.7977 9.85602 14.1142 10.9271L6.40524 23.0825C6.7012 24.6186 7.36358 27.2188 7.88502 29.2271C8.44171 27.7614 9.71714 26.6762 11.6902 25.9927C18.3915 23.6603 24.7687 23.6603 28.9825 23.6603H28.9896Z" fill="#160302"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 16 KiB |
6
src/assets/images/cloud.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="65" height="66" viewBox="0 0 65 66" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="32.5" cy="33" r="32.5" fill="#F2F5FF"/>
|
||||
<path d="M47.5742 20.5197C47.4489 11.3719 39.9685 3.96826 30.7914 3.96826C26.7092 3.96826 22.7763 5.45326 19.7175 8.14974C16.9875 10.5562 15.12 13.772 14.3804 17.3028C14.3264 17.3022 14.2728 17.3017 14.2194 17.3017C6.37897 17.3017 0 23.6806 0 31.5211C0 39.3616 6.37897 45.7405 14.2195 45.7405H24.1503C24.8568 45.7405 25.4299 45.1677 25.4299 44.4609C25.4299 43.7542 24.8568 43.1814 24.1503 43.1814H14.2195C7.78991 43.1814 2.55914 37.9506 2.55914 31.5211C2.55914 25.0916 7.78991 19.8608 14.2195 19.8608C14.562 19.8608 14.9242 19.8792 15.3267 19.9171C16.009 19.981 16.6215 19.4957 16.7144 18.816C17.1752 15.4387 18.8427 12.3324 21.4098 10.0694C24.0009 7.78517 27.3326 6.52728 30.7913 6.52728C38.6352 6.52728 45.0166 12.9087 45.0166 20.7525C45.0166 21.0349 44.9956 21.326 44.9734 21.6342L44.9639 21.7653C44.9373 22.1417 45.0782 22.5107 45.3491 22.7735C45.6197 23.0364 45.9923 23.1662 46.3683 23.1286C46.7086 23.0943 47.052 23.0771 47.3888 23.0771C52.9314 23.0771 57.4409 27.5864 57.4409 33.1291C57.4409 38.6718 52.9315 43.1811 47.3888 43.1811H36.9461C36.2396 43.1811 35.6665 43.7539 35.6665 44.4607C35.6665 45.1674 36.2396 45.7403 36.9461 45.7403H47.3888C54.3426 45.7403 60 40.083 60 33.1291C59.9999 26.2377 54.4429 20.6192 47.5742 20.5197Z" fill="#0A1F69"/>
|
||||
<path d="M30.7916 9.98218C25.4411 9.98218 20.8609 13.9807 20.1376 19.283C20.0421 19.9832 20.5324 20.6283 21.2325 20.7238C21.2912 20.7318 21.3495 20.7358 21.407 20.7358C22.0369 20.7358 22.5858 20.2705 22.6731 19.629C23.2242 15.5884 26.7144 12.5413 30.7915 12.5413C31.4982 12.5413 32.071 11.9685 32.071 11.2617C32.0712 10.5551 31.4982 9.98218 30.7916 9.98218Z" fill="#0A1F69"/>
|
||||
<path d="M36.6105 35.161L31.9561 31.0032C31.1537 30.286 29.9432 30.2861 29.1404 31.0031L24.486 35.1611C23.9589 35.6319 23.9134 36.4408 24.3842 36.9679C24.8549 37.4948 25.6637 37.5406 26.1909 37.0697L29.2687 34.3203V50.0622C29.2687 50.7689 29.8416 51.3417 30.5483 51.3417C31.255 51.3417 31.8279 50.7689 31.8279 50.0622V34.3201L34.9058 37.0696C35.1498 37.2877 35.4544 37.3949 35.7578 37.3949C36.1096 37.3949 36.4597 37.2509 36.7126 36.9678C37.1831 36.4407 37.1375 35.6319 36.6105 35.161Z" fill="#0A1F69"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
3
src/assets/images/cursor.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="202" height="135" viewBox="0 0 202 135" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.18153 39.6234C17.011 27.3107 32.2015 17.7116 48.8458 11.4109C66.2695 4.81515 84.892 1.98332 103.49 3.10152C122.087 4.21972 140.239 9.26262 156.751 17.8989C172.525 26.149 186.463 37.4998 197.732 51.2619L95.8009 131.621L4.18153 39.6234Z" fill="#2954E9" fill-opacity="0.55" stroke="white" stroke-width="5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 424 B |
BIN
src/assets/images/how-to-share-map.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
18
src/assets/images/icon/cursor-arrow.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<svg width="33" height="33" viewBox="0 0 33 33" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d_193_56)">
|
||||
<circle cx="16.8921" cy="14.0595" r="12" transform="rotate(2.04823 16.8921 14.0595)" fill="white" fill-opacity="0.45"/>
|
||||
<path d="M12.542 9.32783C12.2352 9.04959 11.7609 9.07278 11.4826 9.37962C11.2044 9.68647 11.2276 10.1608 11.5344 10.439L12.542 9.32783ZM22.446 20.0368C22.8597 20.0165 23.1787 19.6648 23.1584 19.251L22.8288 12.5091C22.8086 12.0954 22.4568 11.7764 22.0431 11.7966C21.6294 11.8168 21.3104 12.1686 21.3306 12.5823L21.6236 18.5752L15.6308 18.8682C15.217 18.8884 14.8981 19.2402 14.9183 19.6539C14.9385 20.0676 15.2903 20.3866 15.704 20.3664L22.446 20.0368ZM11.5344 10.439L21.9055 19.8433L22.9131 18.7321L12.542 9.32783L11.5344 10.439Z" fill="#0A1F69"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_193_56" x="0.891907" y="0.0593262" width="32.0003" height="32.0004" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="2"/>
|
||||
<feGaussianBlur stdDeviation="2"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.12 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_193_56"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_193_56" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -1,6 +0,0 @@
|
||||
<svg width="79" height="71" viewBox="0 0 79 71" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="46.5" cy="38" r="32.5" fill="#F2F5FF"/>
|
||||
<path d="M66.6685 59.2848L63.3349 23.738C63.2996 23.3631 63.0772 23.0311 62.7434 22.8568L49.9643 16.1787C49.9406 16.1663 49.9132 16.1689 49.8888 16.1583C49.816 16.1268 49.7387 16.112 49.6586 16.0965C49.5815 16.0814 49.5086 16.0638 49.4304 16.0651C49.4129 16.0654 49.3978 16.0572 49.3799 16.0582C49.3169 16.0621 49.264 16.0913 49.2045 16.1052C49.1441 16.1191 49.0811 16.1132 49.023 16.1374L45.067 17.7819C44.5 18.0174 44.232 18.668 44.4674 19.2344C44.7034 19.8019 45.3529 20.0701 45.9199 19.834L48.4374 18.7875L50.5118 51.9502L34.4479 57.8008V34.7293C34.4479 34.1155 33.9503 33.6181 33.3367 33.6181C32.723 33.6181 32.2255 34.1157 32.2255 34.7293V57.8008L16.1616 51.9502L18.2361 18.7875L20.7535 19.834C21.321 20.0699 21.9711 19.8019 22.206 19.2344C22.4415 18.668 22.1735 18.0174 21.6065 17.7819L17.6505 16.1374C17.5926 16.1134 17.5299 16.1193 17.4697 16.1055C17.4099 16.0915 17.3567 16.0622 17.2934 16.0582C17.2757 16.0573 17.2606 16.0655 17.243 16.0654C17.1623 16.0639 17.087 16.0825 17.0074 16.0987C16.9329 16.1139 16.8607 16.1272 16.7924 16.1564C16.7655 16.1678 16.7353 16.1651 16.7091 16.1787L3.93003 22.8568C3.59641 23.0311 3.37386 23.3631 3.33857 23.738L0.0048901 59.2848C-0.0326138 59.6837 0.147613 60.0716 0.476423 60.3005C0.804191 60.5289 1.23275 60.5637 1.59255 60.3901L15.0613 53.9148L32.9565 60.4324C33.2017 60.5225 33.4719 60.5225 33.7172 60.4324L51.6124 53.9148L65.0811 60.3901C65.2336 60.4633 65.3986 60.4998 65.5624 60.4998C65.7854 60.4998 66.0079 60.4324 66.1973 60.3007C66.5258 60.0715 66.7059 59.6835 66.6685 59.2848ZM13.9321 51.9912L2.40071 57.5354L5.49452 24.547L15.9918 19.061L13.9321 51.9912ZM52.7409 51.9912L50.6812 19.061L61.1785 24.547L64.2723 57.5354L52.7409 51.9912Z" fill="#0A1F69"/>
|
||||
<path d="M23.5455 16.259L32.3821 31.0685C32.5829 31.4048 32.9453 31.6104 33.3364 31.6104C33.7276 31.6104 34.09 31.4048 34.2908 31.0685L43.1514 16.2167C43.5779 15.4245 43.8633 14.7534 44.0489 14.1033C44.3141 13.1749 44.4488 12.2108 44.4488 11.2384C44.4488 9.7837 44.1531 8.37249 43.5693 7.04254C43.0071 5.76259 42.2025 4.61338 41.1792 3.62799C40.1592 2.64534 38.9709 1.87377 37.6485 1.33505C34.9138 0.221651 31.7592 0.221651 29.0245 1.33505C27.7023 1.87377 26.514 2.64547 25.4945 3.62799C24.4706 4.61338 23.6659 5.76259 23.1039 7.04306C22.5201 8.37236 22.2244 9.7837 22.2244 11.2384C22.2244 12.2108 22.3589 13.1749 22.6243 14.1038C22.8097 14.7532 23.0952 15.4244 23.5455 16.259ZM25.1386 7.93664C25.5836 6.92312 26.2222 6.01209 27.0361 5.22855C27.8522 4.4424 28.8033 3.82489 29.8624 3.39346C30.9628 2.94524 32.1315 2.71852 33.3366 2.71852C34.5417 2.71852 35.7104 2.94537 36.8108 3.39346C37.8699 3.82476 38.8211 4.44227 39.6376 5.22855C40.451 6.01209 41.0896 6.92299 41.5346 7.93612C41.9936 8.98219 42.2264 10.093 42.2264 11.2383C42.2264 12.0044 42.1207 12.7629 41.9123 13.4928C41.7718 13.9844 41.5439 14.5151 41.2189 15.1206L33.3366 28.3305L25.4782 15.1629C25.1294 14.5151 24.9015 13.9844 24.761 13.4933C24.5526 12.763 24.4469 12.0045 24.4469 11.2383C24.4467 10.093 24.6796 8.98219 25.1386 7.93664Z" fill="#0A1F69"/>
|
||||
<path d="M33.3367 17.1678C36.4002 17.1678 38.8929 14.6751 38.8929 11.6116C38.8929 8.54812 36.4002 6.05542 33.3367 6.05542C30.2732 6.05542 27.7805 8.54812 27.7805 11.6116C27.7805 14.6751 30.2732 17.1678 33.3367 17.1678ZM33.3367 8.27791C35.175 8.27791 36.6704 9.77325 36.6704 11.6116C36.6704 13.4499 35.175 14.9453 33.3367 14.9453C31.4984 14.9453 30.003 13.4499 30.003 11.6116C30.003 9.77325 31.4984 8.27791 33.3367 8.27791Z" fill="#0A1F69"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.5 KiB |
6
src/assets/images/photos.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="66" height="66" viewBox="0 0 66 66" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="32.75" cy="33" r="32.5" fill="#F2F5FF"/>
|
||||
<path d="M55.0182 15.3341H54.5036C54.2024 12.7533 52.1255 10.7071 49.53 10.4567C49.232 7.67438 46.8718 5.5 44.0121 5.5H8.59589C3.8562 5.5 0 9.3562 0 14.0954V38.9277C0 41.8075 2.20367 44.181 5.01251 44.4525C5.30731 47.205 7.62131 49.3611 10.4398 49.4064C10.4471 49.8948 10.5235 50.3663 10.6622 50.8113C11.2935 52.8378 13.1877 54.3132 15.4193 54.3132H55.0182C57.7652 54.3132 60 52.0784 60 49.3318V20.316C60 17.5689 57.7652 15.3341 55.0182 15.3341ZM57.6562 20.316V39.5041L52.5586 35.6533C51.1656 34.6014 49.2819 34.5849 47.8711 35.6126L41.4226 40.3111L29.1252 27.6329C27.6604 26.1223 25.2768 26.0293 23.6989 27.4218L12.7817 37.0536V20.316C12.7817 18.8612 13.965 17.6779 15.4193 17.6779H55.0182C56.4729 17.6779 57.6562 18.8612 57.6562 20.316ZM57.6562 49.3313C57.6562 50.7861 56.4729 51.9694 55.0186 51.9694H15.4193C14.2378 51.9694 13.2349 51.188 12.9007 50.115C12.8233 49.8674 12.7817 49.6042 12.7817 49.3318V40.1788L25.2498 29.1792C25.8875 28.6161 26.8506 28.6537 27.4429 29.2643L32.9929 34.9864C32.9929 34.9868 32.9929 34.9868 32.9933 34.9868L44.0396 46.3755C44.2694 46.6127 44.5747 46.7317 44.881 46.7317C45.1749 46.7317 45.4692 46.6218 45.6967 46.4012C46.1609 45.9503 46.1723 45.2087 45.7219 44.7441L43.0705 42.0103L49.2513 37.5073C49.8216 37.0916 50.5829 37.0981 51.1459 37.5233L57.6562 42.4411V49.3313ZM7.32422 43.8578V16.1888C7.32422 16.0711 7.33017 15.9544 7.34207 15.84C7.51694 14.1201 8.974 12.7739 10.7391 12.7739H25.0735C25.7208 12.7739 26.2454 12.2493 26.2454 11.602C26.2454 10.9547 25.7208 10.4301 25.0735 10.4301H10.7391C7.56363 10.4301 4.98047 13.0133 4.98047 16.1888V42.0845C3.48312 41.8148 2.34375 40.502 2.34375 38.9277V14.0954C2.34375 10.6485 5.14847 7.84375 8.59589 7.84375H44.0121C45.5676 7.84375 46.8681 8.95749 47.1579 10.4301H34.4485C33.8013 10.4301 33.2767 10.9547 33.2767 11.602C33.2767 12.2493 33.8013 12.7739 34.4485 12.7739H48.3902H48.3916H48.9926C50.5389 12.7739 51.8335 13.8748 52.1333 15.3341H15.4193C12.6727 15.3341 10.4379 17.5689 10.4379 20.316V39.6492V39.6501V47.0626C8.71262 47.0137 7.32422 45.5951 7.32422 43.8578Z" fill="#0A1F69"/>
|
||||
<path d="M45.5292 22.4473C42.7634 22.4473 40.5139 24.6972 40.5139 27.4621C40.5139 30.2274 42.7638 32.4773 45.5292 32.4773C48.2941 32.4773 50.544 30.2274 50.544 27.4621C50.544 24.6972 48.2941 22.4473 45.5292 22.4473ZM45.5292 30.1336C44.0561 30.1336 42.8577 28.9351 42.8577 27.4625C42.8577 25.9894 44.0561 24.791 45.5292 24.791C47.0018 24.791 48.2002 25.9894 48.2002 27.4625C48.2002 28.9351 47.0018 30.1336 45.5292 30.1336Z" fill="#0A1F69"/>
|
||||
<path d="M28.6793 12.0506C28.7086 12.1211 28.7448 12.1888 28.7869 12.2524C28.8294 12.3165 28.8784 12.3765 28.9324 12.4305C28.9865 12.4841 29.046 12.5335 29.1105 12.577C29.1737 12.6191 29.2428 12.6553 29.3133 12.6846C29.3833 12.7139 29.4575 12.7363 29.5326 12.7514C29.6072 12.7665 29.6845 12.7738 29.761 12.7738C29.8383 12.7738 29.9143 12.7665 29.9908 12.7514C30.0654 12.7363 30.1382 12.7139 30.2096 12.6846C30.2801 12.6553 30.3478 12.6191 30.4115 12.577C30.4756 12.5335 30.5355 12.4841 30.5895 12.4305C30.6445 12.3765 30.6925 12.3165 30.736 12.2524C30.7781 12.1888 30.8143 12.1211 30.8436 12.0506C30.8729 11.9791 30.8953 11.9054 30.9104 11.8317C30.9255 11.7553 30.9329 11.6779 30.9329 11.602C30.9329 11.5255 30.9255 11.4481 30.9104 11.3735C30.8953 11.2985 30.8729 11.2243 30.8436 11.1543C30.8143 11.0828 30.7781 11.0146 30.736 10.9515C30.6925 10.8869 30.6445 10.8274 30.5895 10.7734C30.5355 10.7194 30.4756 10.6704 30.4115 10.6278C30.3478 10.5857 30.2801 10.5496 30.2096 10.5203C30.1382 10.491 30.0654 10.4685 29.9908 10.4534C29.8393 10.4228 29.6836 10.4228 29.5326 10.4534C29.4575 10.4685 29.3833 10.491 29.3133 10.5203C29.2428 10.5496 29.1737 10.5857 29.1105 10.6278C29.046 10.6704 28.9865 10.7194 28.9324 10.7734C28.8784 10.8274 28.8294 10.8869 28.7869 10.9515C28.7448 11.0146 28.7086 11.0828 28.6793 11.1543C28.65 11.2243 28.6276 11.2985 28.6125 11.3735C28.5974 11.4481 28.5891 11.5255 28.5891 11.602C28.5891 11.6779 28.5974 11.7553 28.6125 11.8317C28.6276 11.9054 28.65 11.9791 28.6793 12.0506Z" fill="#0A1F69"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
@@ -1,5 +0,0 @@
|
||||
<svg width="65" height="71" viewBox="0 0 65 71" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="32.5" cy="38" r="32.5" fill="#F2F5FF"/>
|
||||
<path d="M34.8327 45.2796C39.8898 36.8918 46.2816 25.1 46.2816 19.0388C46.2816 8.81429 37.9673 0.5 27.7429 0.5C17.5184 0.5 9.20408 8.81429 9.20408 19.0388C9.20408 25.1 15.5959 36.8918 20.6531 45.2796C14.6531 45.7327 1 47.3612 1 52.7612C1 58.0755 14.8612 60.5 27.7551 60.5C40.6367 60.5 54.5102 58.0755 54.5102 52.7612C54.498 47.3612 40.8327 45.7327 34.8327 45.2796ZM11.7633 19.0388C11.7633 10.2224 18.9388 3.04694 27.7429 3.04694C36.5592 3.04694 43.7347 10.2224 43.7347 19.0388C43.7347 26.7408 31.4653 46.0633 27.7429 51.7449C24.0204 46.0633 11.7633 26.7408 11.7633 19.0388ZM27.7429 57.9408C12.0816 57.9408 3.54694 54.5122 3.54694 52.749C3.54694 51.3898 9.17959 48.4633 22.1592 47.7163C24.5959 51.6469 26.4449 54.4265 26.6898 54.7571C27.0204 55.198 27.9878 55.7245 28.8082 54.7571C29.0653 54.451 30.902 51.6469 33.3388 47.7163C46.3184 48.4633 51.951 51.3776 51.951 52.749C51.9388 54.5245 43.4041 57.9408 27.7429 57.9408Z" fill="#0A1F69"/>
|
||||
<path d="M35.8121 19.0387C35.8121 14.5816 32.1999 10.9694 27.7427 10.9694C23.2856 10.9694 19.6733 14.5816 19.6733 19.0387C19.6733 23.4959 23.2856 27.1081 27.7427 27.1081C32.1999 27.1081 35.8121 23.4959 35.8121 19.0387ZM22.2203 19.0387C22.2203 15.9898 24.6937 13.5163 27.7427 13.5163C30.7917 13.5163 33.2652 15.9898 33.2652 19.0387C33.2652 22.0877 30.7917 24.5612 27.7427 24.5612C24.6937 24.5612 22.2203 22.0877 22.2203 19.0387Z" fill="#0A1F69"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
6
src/assets/images/road.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="173" height="360" viewBox="0 0 173 360" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="173" height="360" fill="#D5DAE8"/>
|
||||
<line x1="6.5" y1="-1.09278e-07" x2="6.50002" y2="360" stroke="white" stroke-width="5"/>
|
||||
<line x1="166.5" y1="1.3381e-07" x2="166.5" y2="358.776" stroke="white" stroke-width="5"/>
|
||||
<line x1="85.0001" y1="5.35242e-08" x2="85" y2="358.776" stroke="white" stroke-width="2" stroke-dasharray="4 4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 448 B |
@@ -34,11 +34,12 @@ h5 {
|
||||
--grey-pale: #cfd2cf;
|
||||
--grey-semi-dark: #808080;
|
||||
--grey-dark: #3e3e3e;
|
||||
--blue: #2954e9;
|
||||
--blue-dark: #0a1f69;
|
||||
--blue-geovisio: #34495e;
|
||||
--blue-semi: #d7dffc;
|
||||
--blue: #2954e9;
|
||||
--blue-semi-pale: #f2f5ff;
|
||||
--blue-pale: #f2f5ff;
|
||||
--blue-very-pale: #f9faff;
|
||||
--blue-geovisio: #34495e;
|
||||
--beige: #f5f3ec;
|
||||
--yellow: #fec868;
|
||||
--orange: #ff6f00;
|
||||
|
||||
@@ -1,3 +1,45 @@
|
||||
export function createLink(href: string, text: string): string {
|
||||
return `<a href='mailto:signalement.ign@panoramax.fr${href}' target='_blank' title='${text}' class='gvs-btn gvs-widget-bg gvs-btn-large' style='font-size: 1.6em;display: block'><i class="bi bi-exclamation-triangle"></i></a>`
|
||||
return `<a href='mailto:signalement.ign@panoramax.fr${href}' target='_blank' title='${text}' class='gvs-btn gvs-widget-bg gvs-btn-large' style='font-size: 1.6em;display: block; margin-top: 0.5em;'><i class="bi bi-exclamation-triangle"></i></a>`
|
||||
}
|
||||
export function createSequenceLink(href: string, title: string): string {
|
||||
return `<a href='${href}' title='${title}' class='gvs-btn gvs-widget-bg gvs-btn-large' style='font-size: 1.6em;display: block; position: relative; margin-top: 0.5em;'>
|
||||
<i class="bi bi-images"></i>
|
||||
</a>`
|
||||
}
|
||||
export function createFullScreenButton(): string {
|
||||
return `<button type='button' onClick="
|
||||
const header = document.getElementById('navHeader')
|
||||
const footer = document.getElementById('navFooter')
|
||||
const icon = document.getElementById('iconScreen')
|
||||
const home = document.getElementById('homePage')
|
||||
if (header) {
|
||||
const isHiddenHeader = header.classList.contains('hidden')
|
||||
if (isHiddenHeader) header.classList.remove('hidden')
|
||||
else header.classList.add('hidden')
|
||||
}
|
||||
if (footer) {
|
||||
const isHiddenHeader = footer.classList.contains('hidden')
|
||||
if (isHiddenHeader) footer.classList.remove('hidden')
|
||||
else footer.classList.add('hidden')
|
||||
}
|
||||
if (icon) {
|
||||
const isIconNotFull = icon.classList.contains('bi-fullscreen')
|
||||
if(isIconNotFull) {
|
||||
icon.classList.remove('bi-fullscreen')
|
||||
icon.classList.add('bi-fullscreen-exit')
|
||||
} else {
|
||||
icon.classList.remove('bi-fullscreen-exit')
|
||||
icon.classList.add('bi-fullscreen')
|
||||
}
|
||||
}
|
||||
if (home) {
|
||||
const isHomeFull = home.classList.contains('full-viewer')
|
||||
if(isHomeFull) home.classList.remove('full-viewer')
|
||||
else home.classList.add('full-viewer')
|
||||
}
|
||||
"
|
||||
class='gvs-btn gvs-widget-bg gvs-btn-large'
|
||||
>
|
||||
<i id='iconScreen' class="bi bi-fullscreen"></i>
|
||||
</button>`
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ defineProps({
|
||||
}
|
||||
}
|
||||
.default {
|
||||
height: toRem(3.5);
|
||||
height: toRem(3);
|
||||
min-width: toRem(3.5);
|
||||
@include text(s-regular);
|
||||
display: flex;
|
||||
@@ -46,7 +46,8 @@ defineProps({
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
border-radius: toRem(1);
|
||||
padding: toRem(1.3) toRem(2) toRem(1.3);
|
||||
padding: toRem(1.3);
|
||||
|
||||
.icon {
|
||||
font-size: toRem(2);
|
||||
}
|
||||
@@ -54,13 +55,34 @@ defineProps({
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
.row-reverse {
|
||||
flex-direction: row-reverse;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
font-size: toRem(1.2);
|
||||
.icon {
|
||||
margin-left: toRem(0.5);
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
.button--black {
|
||||
color: var(--white);
|
||||
background-color: var(--black);
|
||||
}
|
||||
.button-border--black {
|
||||
font-size: toRem(1.4);
|
||||
background-color: var(--white);
|
||||
border: toRem(0.1) solid var(--black);
|
||||
.icon {
|
||||
font-size: toRem(1.4);
|
||||
color: var(--black);
|
||||
}
|
||||
}
|
||||
.button--blue {
|
||||
color: var(--white);
|
||||
background-color: var(--blue);
|
||||
height: toRem(4);
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
color: var(--white);
|
||||
@@ -74,28 +96,37 @@ defineProps({
|
||||
color: var(--white);
|
||||
}
|
||||
.button--red {
|
||||
color: var(--red);
|
||||
background-color: transparent;
|
||||
border: toRem(0.1) solid var(--red);
|
||||
color: var(--white);
|
||||
background-color: var(--red-pale);
|
||||
border: toRem(0.1) solid var(--red-pale);
|
||||
.icon {
|
||||
margin-right: 0;
|
||||
font-size: toRem(1.4);
|
||||
color: var(--red);
|
||||
color: var(--white);
|
||||
}
|
||||
.text {
|
||||
margin-left: toRem(1);
|
||||
}
|
||||
&.background-white {
|
||||
color: var(--red-pale);
|
||||
.icon {
|
||||
color: var(--red-pale);
|
||||
}
|
||||
&.disabled {
|
||||
.icon {
|
||||
color: var(--grey-pale);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.button--white {
|
||||
color: var(--blue);
|
||||
background-color: var(--white);
|
||||
border: toRem(0.1) solid var(--blue);
|
||||
.icon {
|
||||
font-size: toRem(1.4);
|
||||
color: var(--blue);
|
||||
margin-right: 0;
|
||||
}
|
||||
.text {
|
||||
margin-left: toRem(1);
|
||||
margin-right: toRem(1);
|
||||
}
|
||||
}
|
||||
.no-text {
|
||||
@@ -103,14 +134,36 @@ defineProps({
|
||||
width: toRem(3);
|
||||
padding: 0;
|
||||
.icon {
|
||||
color: var(---black);
|
||||
font-size: toRem(1.8);
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
.no-text-white .icon {
|
||||
color: var(--white);
|
||||
margin-right: 0;
|
||||
font-size: toRem(1.4);
|
||||
}
|
||||
.no-text-blue-dark .icon {
|
||||
color: var(--blue-dark);
|
||||
margin-right: 0;
|
||||
font-size: toRem(1.4);
|
||||
}
|
||||
.no-text-green .icon {
|
||||
color: var(--blue);
|
||||
margin-right: 0;
|
||||
font-size: toRem(1.6);
|
||||
}
|
||||
.background-white {
|
||||
background-color: var(--white);
|
||||
}
|
||||
.link--blue {
|
||||
color: var(--blue);
|
||||
text-decoration: underline;
|
||||
.icon {
|
||||
font-size: toRem(1.4);
|
||||
color: var(--blue);
|
||||
}
|
||||
}
|
||||
.link--grey {
|
||||
color: var(--grey-semi-dark);
|
||||
.icon {
|
||||
@@ -118,12 +171,19 @@ defineProps({
|
||||
color: var(--grey-semi-dark);
|
||||
}
|
||||
}
|
||||
.link--red {
|
||||
color: var(--red);
|
||||
background-color: var(--white);
|
||||
.link--black {
|
||||
color: var(--black);
|
||||
.icon {
|
||||
font-size: toRem(1.4);
|
||||
color: var(--red);
|
||||
color: var(--black);
|
||||
}
|
||||
}
|
||||
.link--red {
|
||||
color: var(--red-pale);
|
||||
text-decoration: underline;
|
||||
.icon {
|
||||
font-size: toRem(1.4);
|
||||
color: var(--red-pale);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,8 +198,10 @@ defineProps({
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
padding: 0;
|
||||
height: toRem(2.5);
|
||||
width: toRem(2.5);
|
||||
height: toRem(4);
|
||||
width: toRem(4);
|
||||
background-color: var(--white);
|
||||
border: toRem(0.1) solid var(--grey-pale);
|
||||
.icon {
|
||||
color: var(---black);
|
||||
font-size: toRem(1.8);
|
||||
@@ -155,8 +217,9 @@ defineProps({
|
||||
position: absolute;
|
||||
bottom: -100%;
|
||||
visibility: hidden;
|
||||
width: toRem(18);
|
||||
width: toRem(20);
|
||||
right: 0;
|
||||
z-index: 9;
|
||||
@include text(xss-regular);
|
||||
}
|
||||
|
||||
|
||||
159
src/components/EditText.vue
Normal file
@@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<div :class="['entry-edit', { 'edit-mode': isEditTitle }]">
|
||||
<span v-if="isEditTitle && formTitle" class="form-title">{{
|
||||
formTitle
|
||||
}}</span>
|
||||
<div class="entry-form">
|
||||
<form
|
||||
v-if="isEditTitle && !isDisabled"
|
||||
@submit.prevent="isEditTitle = false"
|
||||
class="edit-form"
|
||||
>
|
||||
<div class="wrapper-input">
|
||||
<Input
|
||||
id="upload-title"
|
||||
:text="text || ''"
|
||||
:placeholder="$t('pages.upload.edit_placeholder_input')"
|
||||
@input="changeTextValue"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
id="valid-button"
|
||||
:text="$t('pages.upload.ok_button')"
|
||||
type="submit"
|
||||
look="button--white"
|
||||
@trigger="validNewName"
|
||||
/>
|
||||
<div class="close-button">
|
||||
<Button
|
||||
id="close-button"
|
||||
look="no-text-white"
|
||||
icon="bi bi-x"
|
||||
@trigger="closeEdition"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<span v-else class="title">{{ text }}</span>
|
||||
<div v-if="!isEditTitle" class="edit-button">
|
||||
<Button
|
||||
look="no-text-white"
|
||||
icon="bi bi-pen"
|
||||
:tooltip="$t('pages.upload.edit_title_tooltip')"
|
||||
:disabled="isDisabled"
|
||||
@trigger="goToEditMode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import Button from '@/components/Button.vue'
|
||||
import Input from '@/components/Input.vue'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'triggerNewText', value: string | null): void
|
||||
}>()
|
||||
const props = defineProps({
|
||||
defaultText: { type: String, default: null },
|
||||
isLoading: { type: Boolean, default: false },
|
||||
isLoaded: { type: Boolean, default: false },
|
||||
formTitle: { type: String, default: null }
|
||||
})
|
||||
let titleToEdit = ref<string | null>(null)
|
||||
let isEditTitle = ref<boolean>(false)
|
||||
|
||||
function changeTextValue(value: string): void {
|
||||
titleToEdit.value = value
|
||||
}
|
||||
function closeEdition(): void {
|
||||
isEditTitle.value = false
|
||||
titleToEdit.value = null
|
||||
}
|
||||
function validNewName(): void {
|
||||
emit('triggerNewText', titleToEdit.value)
|
||||
isEditTitle.value = false
|
||||
}
|
||||
function goToEditMode(): void {
|
||||
isEditTitle.value = true
|
||||
titleToEdit.value = props.defaultText
|
||||
}
|
||||
|
||||
const text = computed<string | null>(() => {
|
||||
if (isEditTitle.value) return titleToEdit.value
|
||||
if (props.defaultText) return props.defaultText
|
||||
return null
|
||||
})
|
||||
|
||||
const isDisabled = computed<boolean>(() => props.isLoading && !props.isLoaded)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.title {
|
||||
color: var(--blue-dark);
|
||||
text-align: left;
|
||||
}
|
||||
.form-title {
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
@include text(xs-r-regular);
|
||||
margin-bottom: toRem(0.3);
|
||||
}
|
||||
.entry-edit {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
.entry-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.edit-mode {
|
||||
background-color: var(--blue-very-pale);
|
||||
padding: toRem(1);
|
||||
border-radius: toRem(0.4);
|
||||
width: 100%;
|
||||
}
|
||||
.wrapper-edit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: toRem(1);
|
||||
width: 100%;
|
||||
}
|
||||
.edit-button {
|
||||
background-color: var(--blue);
|
||||
border-radius: 50%;
|
||||
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;
|
||||
}
|
||||
.wrapper-input {
|
||||
position: relative;
|
||||
margin-right: toRem(1.5);
|
||||
width: 100%;
|
||||
}
|
||||
.edit-form {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.close-button {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: toRem(2);
|
||||
width: toRem(2);
|
||||
top: toRem(-1);
|
||||
left: toRem(-1);
|
||||
background-color: var(--blue-dark);
|
||||
color: var(--white);
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<footer class="footer">
|
||||
<footer id="navFooter" class="footer">
|
||||
<ul class="link-list">
|
||||
<li class="link-item">
|
||||
<div class="link">
|
||||
@@ -62,6 +62,9 @@ ul {
|
||||
padding: toRem(1.5) toRem(3);
|
||||
border-top: toRem(0.1) solid var(--grey);
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.link-list {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -89,12 +92,31 @@ ul {
|
||||
margin-right: toRem(2);
|
||||
height: toRem(2);
|
||||
}
|
||||
@media (max-width: toRem(76.8)) {
|
||||
.footer {
|
||||
padding: toRem(1);
|
||||
height: toRem(6.5);
|
||||
}
|
||||
.link-list {
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
.logo {
|
||||
margin-right: toRem(0.5);
|
||||
height: toRem(1.5);
|
||||
}
|
||||
.link-item {
|
||||
margin-right: toRem(1);
|
||||
margin-bottom: toRem(1);
|
||||
}
|
||||
.link {
|
||||
height: toRem(1.5);
|
||||
}
|
||||
}
|
||||
@media (max-width: toRem(50)) {
|
||||
.title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.footer {
|
||||
padding: toRem(2);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<header class="header">
|
||||
<header id="navHeader" class="header">
|
||||
<div class="responsive entry-instance">
|
||||
<InstanceName />
|
||||
</div>
|
||||
@@ -107,16 +107,13 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { onClickOutside } from '@vueuse/core'
|
||||
import { useCookies } from 'vue3-cookies'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { getAuthRoute } from '@/utils/auth'
|
||||
import { getAuthRoute, hasASessionCookieDecoded } from '@/utils/auth'
|
||||
import Link from '@/components/Link.vue'
|
||||
import InstanceName from '@/components/InstanceName.vue'
|
||||
import HeaderOpen from '@/components/header/HeaderOpen.vue'
|
||||
import AccountButton from '@/components/header/AccountButton.vue'
|
||||
|
||||
const { cookies } = useCookies()
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
defineProps({
|
||||
@@ -128,26 +125,31 @@ let menuIsClosed = ref<boolean>(true)
|
||||
|
||||
onClickOutside(list, () => closeModal())
|
||||
|
||||
function closeModal() {
|
||||
function closeModal(): void {
|
||||
menuIsClosed.value = true
|
||||
}
|
||||
|
||||
function toggleMenu(): void {
|
||||
menuIsClosed.value = !menuIsClosed.value
|
||||
}
|
||||
const isLogged = computed((): boolean => !!cookies.get('user_id'))
|
||||
const isLogged = computed((): boolean => {
|
||||
const cookie = hasASessionCookieDecoded()
|
||||
return !!(cookie && cookie.account)
|
||||
})
|
||||
const ariaLabel = computed((): string =>
|
||||
menuIsClosed.value
|
||||
? t('general.header.burger_menu_aria_label_open')
|
||||
: t('general.header.burger_menu_aria_label_closed')
|
||||
)
|
||||
const userName = computed((): string =>
|
||||
cookies!
|
||||
.get('user_name')
|
||||
.match(/\b(\w)/g)!
|
||||
.join('')
|
||||
.toUpperCase()
|
||||
)
|
||||
const userName = computed((): string => {
|
||||
const cookie = hasASessionCookieDecoded()
|
||||
if (cookie && cookie.account) {
|
||||
return cookie.account.name
|
||||
.match(/\b(\w)/g)!
|
||||
.join('')
|
||||
.toUpperCase()
|
||||
}
|
||||
return ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -158,6 +160,9 @@ const userName = computed((): string =>
|
||||
background-color: var(--white);
|
||||
border-bottom: toRem(0.1) solid var(--grey-pale);
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.nav {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
<i v-if="status === 'hidden'" class="bi bi-eye-slash icon-hidden"></i>
|
||||
<img
|
||||
v-if="href"
|
||||
loading="lazy"
|
||||
:src="href"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
class="photo-img"
|
||||
/>
|
||||
</div>
|
||||
@@ -141,7 +141,6 @@ defineProps({
|
||||
}
|
||||
.waiting-info {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
color: var(--black);
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
@@ -10,9 +10,7 @@
|
||||
alt=""
|
||||
class="icon-block-img"
|
||||
/>
|
||||
<h3 v-if="title" class="subtitle">
|
||||
{{ title }}
|
||||
</h3>
|
||||
<slot name="title"></slot>
|
||||
</div>
|
||||
<p v-if="text" v-html="text" class="information-text"></p>
|
||||
</div>
|
||||
@@ -23,7 +21,6 @@
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
text: { type: String, default: null },
|
||||
title: { type: String, default: null },
|
||||
look: { type: String, default: '' }
|
||||
})
|
||||
</script>
|
||||
@@ -36,11 +33,11 @@ h3 {
|
||||
.information-block {
|
||||
position: relative;
|
||||
border-left: toRem(1.4) solid var(--blue);
|
||||
padding: toRem(2) toRem(2) toRem(1.5);
|
||||
padding: toRem(1.8) toRem(1.8) toRem(1.3);
|
||||
background-color: var(--white);
|
||||
border-radius: toRem(1.5);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@@ -61,10 +58,6 @@ h3 {
|
||||
margin-right: toRem(0.5);
|
||||
height: toRem(2.2);
|
||||
}
|
||||
.subtitle {
|
||||
@include text(h2);
|
||||
color: var(--blue-dark);
|
||||
}
|
||||
.information-text {
|
||||
margin-top: toRem(1);
|
||||
@include text(m-r-regular);
|
||||
|
||||
39
src/components/Input.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<input
|
||||
:value="text"
|
||||
:required="true"
|
||||
:placeholder="placeholder"
|
||||
:type="type"
|
||||
:min="min"
|
||||
:max="max"
|
||||
@input="emitValue"
|
||||
class="input"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const emit = defineEmits<{ (e: 'input', value: string): void }>()
|
||||
defineProps({
|
||||
type: { type: String, default: 'text' },
|
||||
min: { type: String, default: '' },
|
||||
max: { type: String, default: '' },
|
||||
text: { type: [String, Number], default: null },
|
||||
placeholder: { type: String, default: '' }
|
||||
})
|
||||
|
||||
function emitValue(event: Event): void {
|
||||
const inputValue = (event.target as HTMLInputElement).value
|
||||
emit('input', inputValue)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.input {
|
||||
padding: toRem(0.5) toRem(1);
|
||||
border-radius: toRem(0.5);
|
||||
border: toRem(0.1) solid var(--blue-dark);
|
||||
color: var(--blue-dark);
|
||||
width: 100%;
|
||||
@include text(s-r-regular);
|
||||
}
|
||||
</style>
|
||||
@@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<div class="wrapper-checkbox">
|
||||
<div :class="['wrapper-checkbox', { checked: isChecked || isIndeterminate }]">
|
||||
<div class="input-checkbox">
|
||||
<i v-if="isChecked && !isIndeterminate" class="icon bi bi-check-square" />
|
||||
<i v-if="!isChecked && !isIndeterminate" class="icon bi bi-square" />
|
||||
<i v-if="isIndeterminate && !isChecked" class="icon bi bi-dash-square" />
|
||||
<input
|
||||
id="checkbox"
|
||||
:id="name"
|
||||
v-model="inputValue"
|
||||
type="checkbox"
|
||||
@input="updateValue(!inputValue)"
|
||||
class="input"
|
||||
/>
|
||||
</div>
|
||||
<label v-if="label && label.length" for="checkbox" class="label">{{
|
||||
<label v-if="label && label.length" :for="name" class="label">{{
|
||||
label
|
||||
}}</label>
|
||||
</div>
|
||||
@@ -35,6 +35,7 @@ watchEffect(async () => {
|
||||
if (htmlCheckbox) {
|
||||
htmlCheckbox.indeterminate = props.isIndeterminate
|
||||
}
|
||||
inputValue.value = props.isChecked
|
||||
})
|
||||
|
||||
function updateValue(value: boolean): void {
|
||||
@@ -42,7 +43,11 @@ function updateValue(value: boolean): void {
|
||||
htmlCheckbox.indeterminate = false
|
||||
}
|
||||
inputValue.value = value
|
||||
emit('trigger', { isChecked: value, isIndeterminate: false })
|
||||
emit('trigger', {
|
||||
isChecked: value,
|
||||
isIndeterminate: false,
|
||||
name: props.name
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -54,6 +59,8 @@ function updateValue(value: boolean): void {
|
||||
align-items: center;
|
||||
height: toRem(2);
|
||||
width: toRem(2);
|
||||
background-color: var(--white);
|
||||
border-radius: toRem(0.5);
|
||||
}
|
||||
.input {
|
||||
-webkit-appearance: none;
|
||||
@@ -68,7 +75,7 @@ function updateValue(value: boolean): void {
|
||||
.icon {
|
||||
font-size: toRem(2);
|
||||
position: absolute;
|
||||
color: var(--grey-semi-dark);
|
||||
color: var(--blue);
|
||||
}
|
||||
.wrapper-checkbox {
|
||||
display: flex;
|
||||
@@ -77,5 +84,14 @@ function updateValue(value: boolean): void {
|
||||
.label {
|
||||
cursor: pointer;
|
||||
margin-left: toRem(0.5);
|
||||
@include text(s-r-regular);
|
||||
}
|
||||
.checked {
|
||||
.input-checkbox {
|
||||
background-color: var(--blue);
|
||||
}
|
||||
.icon {
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
36
src/components/InputRadio.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="input-radio">
|
||||
<input
|
||||
:id="id"
|
||||
:name="name"
|
||||
:value="id"
|
||||
type="radio"
|
||||
:checked="props.value === id"
|
||||
@change="updateValue"
|
||||
class="input"
|
||||
/>
|
||||
<label v-if="label && label.length" :for="id" class="label">{{
|
||||
label
|
||||
}}</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const emit = defineEmits<{ (e: 'trigger', value: string): void }>()
|
||||
const props = defineProps({
|
||||
name: { type: String, default: null },
|
||||
id: { type: String, default: '' },
|
||||
label: { type: String, default: '' },
|
||||
value: { type: String, default: null }
|
||||
})
|
||||
|
||||
function updateValue(): void {
|
||||
emit('trigger', props.id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.label {
|
||||
margin-left: toRem(0.5);
|
||||
}
|
||||
</style>
|
||||
76
src/components/InputSwitch.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<label class="toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="inputValue"
|
||||
@input="updateValue(inputValue)"
|
||||
class="input"
|
||||
/>
|
||||
<div :class="['labels increase', { increaseChecked: !inputValue }]">
|
||||
<span>{{ $t('pages.sequence.sort_panel_settings_order_increase') }}</span>
|
||||
<i class="bi bi-sort-up"></i>
|
||||
</div>
|
||||
<div :class="['labels decrease', { decreaseChecked: inputValue }]">
|
||||
<span>{{ $t('pages.sequence.sort_panel_settings_order_decrease') }}</span>
|
||||
<i class="bi bi-sort-down"></i>
|
||||
</div>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watchEffect } from 'vue'
|
||||
const emit = defineEmits<{
|
||||
(e: 'trigger', value: { isIncreaseChecked: boolean }): void
|
||||
}>()
|
||||
let inputValue = ref<boolean>(false)
|
||||
const props = defineProps({
|
||||
increased: { type: Boolean, default: false }
|
||||
})
|
||||
watchEffect(() => {
|
||||
inputValue.value = !props.increased
|
||||
})
|
||||
|
||||
function updateValue(value: boolean): void {
|
||||
inputValue.value = value
|
||||
emit('trigger', { isIncreaseChecked: value })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.toggle {
|
||||
position: relative;
|
||||
width: 14.5rem;
|
||||
background-color: var(--blue-very-pale);
|
||||
padding: toRem(0.7);
|
||||
border-radius: toRem(0.7);
|
||||
height: toRem(4.5);
|
||||
cursor: pointer;
|
||||
}
|
||||
.input {
|
||||
width: 100%;
|
||||
border: none;
|
||||
appearance: none;
|
||||
}
|
||||
.labels {
|
||||
position: absolute;
|
||||
top: toRem(0.7);
|
||||
@include text(s-r-regular);
|
||||
padding: toRem(0.5) toRem(1);
|
||||
color: var(--grey-semi-dark);
|
||||
span {
|
||||
margin-right: toRem(0.5);
|
||||
}
|
||||
}
|
||||
.increase {
|
||||
left: toRem(0.7);
|
||||
}
|
||||
.decrease {
|
||||
right: toRem(0.7);
|
||||
}
|
||||
.increaseChecked,
|
||||
.decreaseChecked {
|
||||
background-color: var(--blue-dark);
|
||||
color: var(--white);
|
||||
border-radius: toRem(0.7);
|
||||
}
|
||||
</style>
|
||||
@@ -10,8 +10,8 @@
|
||||
type="file"
|
||||
multiple
|
||||
:accept="accept"
|
||||
class="input-file"
|
||||
@change="changeFile"
|
||||
class="input-file"
|
||||
/>
|
||||
<i class="bi bi-cloud-upload-fill"></i>
|
||||
<span v-if="text" class="input-text">
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
<template>
|
||||
<div class="icon-block">
|
||||
<slot></slot>
|
||||
<p class="license-text">
|
||||
{{
|
||||
$t('pages.share_pictures.information_text2', {
|
||||
word: 'la license'
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
<Link
|
||||
:href="url"
|
||||
:text="text"
|
||||
target="_blank"
|
||||
type="external"
|
||||
look="link--grey"
|
||||
/>
|
||||
<div class="text-block">
|
||||
<p class="license-text">
|
||||
{{
|
||||
$t('pages.share_pictures.information_text2', {
|
||||
word: 'la licence'
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
<Link
|
||||
:href="url"
|
||||
:text="text"
|
||||
target="_blank"
|
||||
type="external"
|
||||
look="link--grey"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -29,8 +31,15 @@ defineProps({
|
||||
<style scoped lang="scss">
|
||||
.icon-block {
|
||||
display: flex;
|
||||
font-weight: 600;
|
||||
}
|
||||
.text-block {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
.license-text {
|
||||
font-weight: bold;
|
||||
margin-right: toRem(0.5);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const titleImg = computed<string>(() =>
|
||||
props.disabled ? t('general.header.contribute_text') : ''
|
||||
props.disabled ? t('general.header.about_text') : ''
|
||||
)
|
||||
function triggerButton() {
|
||||
if (props.disabled) return
|
||||
@@ -80,16 +80,6 @@ function triggerButton() {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
color: var(--black);
|
||||
font-size: toRem(2.4);
|
||||
margin-right: toRem(1);
|
||||
}
|
||||
.logo {
|
||||
height: inherit;
|
||||
border-radius: toRem(0.5);
|
||||
margin-right: toRem(1);
|
||||
}
|
||||
.button {
|
||||
height: toRem(4);
|
||||
border-radius: toRem(1);
|
||||
@@ -105,6 +95,16 @@ function triggerButton() {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
color: var(--black);
|
||||
font-size: toRem(2.4);
|
||||
margin-right: toRem(1);
|
||||
}
|
||||
.logo {
|
||||
height: inherit;
|
||||
border-radius: toRem(0.5);
|
||||
margin-right: toRem(1);
|
||||
}
|
||||
.text {
|
||||
width: 100%;
|
||||
white-space: break-spaces;
|
||||
@@ -116,6 +116,24 @@ function triggerButton() {
|
||||
.link--grey {
|
||||
color: var(--grey-semi-dark);
|
||||
text-decoration: underline;
|
||||
font-weight: inherit;
|
||||
font-size: toRem(1.4);
|
||||
.icon {
|
||||
color: var(--grey-semi-dark);
|
||||
font-size: toRem(1.4);
|
||||
margin-right: toRem(0.5);
|
||||
}
|
||||
}
|
||||
.link--grey-dark {
|
||||
color: var(--grey-dark);
|
||||
text-decoration: underline;
|
||||
font-weight: inherit;
|
||||
font-size: toRem(1.4);
|
||||
.icon {
|
||||
color: var(--grey-dark);
|
||||
font-size: toRem(1.4);
|
||||
margin-right: toRem(0.5);
|
||||
}
|
||||
}
|
||||
.link--blue-dark {
|
||||
color: var(--blue-dark);
|
||||
@@ -162,7 +180,7 @@ function triggerButton() {
|
||||
}
|
||||
}
|
||||
.button--blue-bleu {
|
||||
background-color: var(--blue-semi);
|
||||
background-color: var(--blue-semi-pale);
|
||||
color: var(--blue);
|
||||
}
|
||||
.disabled {
|
||||
@@ -199,10 +217,6 @@ function triggerButton() {
|
||||
}
|
||||
}
|
||||
@media (max-width: toRem(50)) {
|
||||
.default {
|
||||
min-height: toRem(4);
|
||||
min-width: toRem(4);
|
||||
}
|
||||
.icon {
|
||||
margin-right: toRem(0.5);
|
||||
}
|
||||
|
||||
@@ -13,15 +13,10 @@
|
||||
<i class="bi bi-x-circle-fill"></i>
|
||||
</button>
|
||||
<div class="modal-header">
|
||||
<h5>{{ $t('pages.upload.modal_error_title') }}</h5>
|
||||
<h5>{{ title }}</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<ul>
|
||||
<li v-for="item in uploadErrors" class="error-item">
|
||||
<span>{{ item.name }} - </span>
|
||||
<span>{{ item.message }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<slot name="body"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -30,15 +25,13 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { Modal } from 'bootstrap'
|
||||
let bsModal = ref()
|
||||
import type { uploadErrorInterface } from '@/views/interfaces/UploadPicturesView'
|
||||
|
||||
defineProps({
|
||||
uploadErrors: {
|
||||
type: Array as PropType<uploadErrorInterface[]>,
|
||||
default: []
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
@@ -62,12 +55,6 @@ ul {
|
||||
.modal {
|
||||
background: rgba(10, 31, 105, 0.6);
|
||||
}
|
||||
.error-item {
|
||||
padding: toRem(1);
|
||||
&:nth-child(odd) {
|
||||
background-color: var(--grey);
|
||||
}
|
||||
}
|
||||
.modal-content {
|
||||
border-radius: toRem(1.5);
|
||||
}
|
||||
|
||||
49
src/components/TabPanel.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div class="tab">
|
||||
<button
|
||||
:class="['tablinks', { selected: panelSelected === 'photos' }]"
|
||||
@click="$emit('trigger', 'photos')"
|
||||
>
|
||||
{{ $t('pages.sequence.button_panel_photos') }}
|
||||
</button>
|
||||
<button
|
||||
:class="['tablinks', { selected: panelSelected === 'orientation' }]"
|
||||
@click="$emit('trigger', 'orientation')"
|
||||
>
|
||||
{{ $t('pages.sequence.button_panel_orientation') }}
|
||||
</button>
|
||||
<button
|
||||
:class="['tablinks', { selected: panelSelected === 'sort' }]"
|
||||
@click="$emit('trigger', 'sort')"
|
||||
>
|
||||
{{ $t('pages.sequence.button_panel_sort') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
panelSelected: { type: String, default: '' }
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.tab {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
@include text(s-regular);
|
||||
}
|
||||
.tablinks {
|
||||
border: none;
|
||||
border-top-right-radius: toRem(0.5);
|
||||
border-top-left-radius: toRem(0.5);
|
||||
margin-right: toRem(0.3);
|
||||
background-color: var(--blue-very-pale);
|
||||
padding: toRem(1);
|
||||
}
|
||||
.selected {
|
||||
background-color: var(--blue-semi-pale);
|
||||
color: var(--blue-dark);
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@@ -39,6 +39,7 @@ defineProps({
|
||||
min-width: toRem(10);
|
||||
padding-right: toRem(1);
|
||||
padding-left: toRem(1);
|
||||
z-index: 1;
|
||||
}
|
||||
.button-close {
|
||||
position: absolute;
|
||||
|
||||
@@ -1,114 +1,196 @@
|
||||
<template>
|
||||
<div id="viewer" class="entry-viewer"></div>
|
||||
<div :id="id" class="entry-viewer"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import type { ViewerInterface } from '@/views/interfaces/common'
|
||||
import { getIgnTiles } from '@/utils/mapAndViewer'
|
||||
import axios from 'axios'
|
||||
import { onMounted, onUnmounted, ref, computed } from 'vue'
|
||||
import { useSequenceStore } from '@/store/sequence'
|
||||
import { Viewer, StandaloneMap } from 'geovisio'
|
||||
import { createUrlLink } from '@/utils'
|
||||
import { createLink } from '@/components-viewer/reportLink'
|
||||
import {
|
||||
createLink,
|
||||
createSequenceLink,
|
||||
createFullScreenButton
|
||||
} from '@/components-viewer/reportLink'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { hasASessionCookieDecoded } from '@/utils/auth'
|
||||
import type { ViewerInterface, MapInterface } from '@/views/interfaces/common'
|
||||
const sequenceStore = useSequenceStore()
|
||||
const { t } = useI18n()
|
||||
const emit = defineEmits<{ (e: 'triggerReady', value: boolean): void }>()
|
||||
let mapIsLoaded = ref<boolean>(false)
|
||||
let viewer = ref()
|
||||
|
||||
const props = defineProps({
|
||||
id: { type: String, default: 'viewer' },
|
||||
fetchOptions: { type: Object, default: {} },
|
||||
geovisioViewer: { type: Boolean, default: true },
|
||||
bbox: { type: Array, default: null }
|
||||
bbox: { type: Array, default: null },
|
||||
userId: { type: String, default: '' }
|
||||
})
|
||||
const isLogged = computed((): boolean => {
|
||||
const cookie = hasASessionCookieDecoded()
|
||||
return !!(cookie && cookie.account)
|
||||
})
|
||||
const userName = computed((): string => {
|
||||
const cookie = hasASessionCookieDecoded()
|
||||
if (cookie && cookie.account) return cookie.account.name
|
||||
return ''
|
||||
})
|
||||
defineExpose({
|
||||
viewer
|
||||
})
|
||||
onMounted(async () => {
|
||||
const tiles = import.meta.env.VITE_TILES
|
||||
async function getSequenceId(imgId: string): Promise<{
|
||||
sequenceId: string
|
||||
username: string
|
||||
}> {
|
||||
const { data } = await axios.get(`api/search?ids=${imgId}`)
|
||||
return {
|
||||
sequenceId: data.features[0].collection,
|
||||
username: data.features[0].properties['geovisio:producer']
|
||||
}
|
||||
}
|
||||
function createViewerButton(link: HTMLDivElement): void {
|
||||
link.innerHTML = `<div>${createFullScreenButton()}</div>`
|
||||
viewer.value.addEventListener(
|
||||
'picture-loaded',
|
||||
async (e: { detail: { picId: string } }): Promise<void> => {
|
||||
const sequenceInformation = await getSequenceId(e.detail.picId)
|
||||
let href: string
|
||||
if (isLogged.value && sequenceInformation.username === userName.value) {
|
||||
href = `${window.location.origin}/sequence/${sequenceInformation.sequenceId}?currentPic=${e.detail.picId}`
|
||||
link.innerHTML = `<div>
|
||||
${createFullScreenButton()}
|
||||
${createSequenceLink(
|
||||
href,
|
||||
t('pages.home.sequence_title')
|
||||
)}
|
||||
</div>`
|
||||
sequenceStore.addSequence(e.detail.picId)
|
||||
} else {
|
||||
href = t('pages.home.report_mail', {
|
||||
picId: e.detail.picId,
|
||||
link: createUrlLink(e.detail.picId)
|
||||
})
|
||||
link.innerHTML = `<div>
|
||||
${createFullScreenButton()}
|
||||
${createLink(
|
||||
href,
|
||||
t('pages.home.report_button_text')
|
||||
)}
|
||||
</div>`
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
async function setupViewerMap(tiles: string): Promise<void> {
|
||||
const maxZoom = import.meta.env.VITE_MAX_ZOOM
|
||||
const zoom = import.meta.env.VITE_ZOOM
|
||||
const center = import.meta.env.VITE_CENTER
|
||||
let paramsViewer: ViewerInterface
|
||||
|
||||
try {
|
||||
if (props.geovisioViewer) {
|
||||
paramsViewer = { map: { startWide: true } }
|
||||
if (center && center !== '') {
|
||||
const centerMap = center.split(',').map((el: string) => parseInt(el))
|
||||
paramsViewer = {
|
||||
map: {
|
||||
...paramsViewer.map,
|
||||
center: centerMap
|
||||
}
|
||||
}
|
||||
const raster = import.meta.env.VITE_RASTER_TILE
|
||||
let paramsViewer: ViewerInterface = { map: { startWide: true } }
|
||||
if (raster && raster !== '') {
|
||||
paramsViewer = {
|
||||
map: {
|
||||
...paramsViewer.map,
|
||||
raster: JSON.parse(raster)
|
||||
}
|
||||
if (zoom && zoom !== '') {
|
||||
paramsViewer = {
|
||||
map: {
|
||||
...paramsViewer.map,
|
||||
zoom: parseFloat(zoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (maxZoom && maxZoom !== '') {
|
||||
paramsViewer = {
|
||||
map: {
|
||||
...paramsViewer.map,
|
||||
maxZoom: parseInt(maxZoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tiles) {
|
||||
const style = tiles.includes('wxs.ign.fr') ? await getIgnTiles() : tiles
|
||||
paramsViewer = {
|
||||
map: {
|
||||
...paramsViewer.map,
|
||||
style
|
||||
}
|
||||
}
|
||||
}
|
||||
if (props.fetchOptions) {
|
||||
paramsViewer = {
|
||||
...paramsViewer,
|
||||
...props.fetchOptions
|
||||
}
|
||||
}
|
||||
const reportLink = document.createElement('div')
|
||||
reportLink.className = 'gvs-group gvs-group-large gvs-group-btnpanel'
|
||||
viewer.value = await new Viewer(
|
||||
'viewer', // Div ID
|
||||
`${import.meta.env.VITE_API_URL}/api/search`,
|
||||
{
|
||||
...paramsViewer,
|
||||
widgets: { customWidget: reportLink }
|
||||
}
|
||||
)
|
||||
if (viewer.value && viewer.value.addEventListener) {
|
||||
viewer.value.addEventListener(
|
||||
'picture-loaded',
|
||||
async (e: { detail: { picId: string } }): Promise<void> => {
|
||||
const href = t('pages.home.report_mail', {
|
||||
picId: e.detail.picId,
|
||||
link: createUrlLink(e.detail.picId)
|
||||
})
|
||||
reportLink.innerHTML = createLink(
|
||||
href,
|
||||
t('pages.home.report_button_text')
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
viewer.value = await new StandaloneMap(
|
||||
'viewer', // Div ID
|
||||
`${import.meta.env.VITE_API_URL}/api/search`
|
||||
)
|
||||
}
|
||||
}
|
||||
if (center && center !== '') {
|
||||
const centerMap = center.split(',').map((el: string) => parseInt(el))
|
||||
paramsViewer = {
|
||||
map: {
|
||||
...paramsViewer.map,
|
||||
center: centerMap
|
||||
}
|
||||
}
|
||||
}
|
||||
if (zoom && zoom.length) {
|
||||
paramsViewer = {
|
||||
map: {
|
||||
...paramsViewer.map,
|
||||
zoom: parseFloat(zoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (maxZoom && maxZoom.length) {
|
||||
paramsViewer = {
|
||||
map: {
|
||||
...paramsViewer.map,
|
||||
maxZoom: parseInt(maxZoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tiles && tiles.length) {
|
||||
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', // Div ID
|
||||
`${import.meta.env.VITE_API_URL}/api/search`,
|
||||
{
|
||||
...paramsViewer,
|
||||
widgets: { customWidget: reportLink }
|
||||
}
|
||||
)
|
||||
if (viewer.value && viewer.value.addEventListener) {
|
||||
createViewerButton(reportLink)
|
||||
}
|
||||
}
|
||||
async function setupMap(tiles: string): Promise<void> {
|
||||
let paramsMap: MapInterface
|
||||
paramsMap = { users: [props.userId], minZoom: 7 }
|
||||
if (tiles && tiles.length) {
|
||||
paramsMap = {
|
||||
...paramsMap,
|
||||
style: tiles
|
||||
}
|
||||
}
|
||||
const bbox = [props.bbox[0], props.bbox[1], props.bbox[2], props.bbox[3]]
|
||||
viewer.value = new StandaloneMap(
|
||||
props.id, // Div ID
|
||||
`${import.meta.env.VITE_API_URL}/api/search`,
|
||||
{
|
||||
...paramsMap,
|
||||
bounds: bbox,
|
||||
zoom: 14
|
||||
}
|
||||
)
|
||||
viewer.value.addEventListener('ready', () => {
|
||||
viewer.value.fitBounds(bbox, {
|
||||
padding: { top: 70, bottom: 70, left: 70, right: 70 },
|
||||
maxZoom: 14,
|
||||
speed: 10
|
||||
})
|
||||
})
|
||||
}
|
||||
onMounted(async (): Promise<void> => {
|
||||
const tiles = import.meta.env.VITE_TILES
|
||||
try {
|
||||
if (props.geovisioViewer) await setupViewerMap(tiles)
|
||||
else await setupMap(tiles)
|
||||
mapIsLoaded.value = true
|
||||
emit('triggerReady', mapIsLoaded.value)
|
||||
} catch (err) {
|
||||
mapIsLoaded.value = true
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
onUnmounted(() => {
|
||||
onUnmounted((): void => {
|
||||
if (viewer.value && props.geovisioViewer) viewer.value.destroy()
|
||||
})
|
||||
</script>
|
||||
|
||||
166
src/components/filters/CalendarFilter.vue
Normal file
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<div class="wrapper-calendar">
|
||||
<div class="inputs-wrapper">
|
||||
<div class="input-wrapper">
|
||||
<i class="bi bi-calendar-plus"></i>
|
||||
<input
|
||||
:value="
|
||||
range && range.start
|
||||
? formatDate(new Date(range.start), 'YYYY-MM-DD')
|
||||
: null
|
||||
"
|
||||
:placeholder="$t('pages.sequences.radio_date_placeholder')"
|
||||
@input="setStartValue"
|
||||
class="input"
|
||||
/>
|
||||
</div>
|
||||
<i class="bi bi-arrow-right"></i>
|
||||
<div class="input-wrapper">
|
||||
<i class="bi bi-calendar-plus"></i>
|
||||
<input
|
||||
:value="
|
||||
range && range.end
|
||||
? formatDate(new Date(range.end), 'YYYY-MM-DD')
|
||||
: null
|
||||
"
|
||||
:placeholder="$t('pages.sequences.radio_date_placeholder')"
|
||||
@input="setEndValue"
|
||||
class="input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<v-date-picker
|
||||
ref="datePicker"
|
||||
v-model="range"
|
||||
mode="date"
|
||||
:masks="{
|
||||
input: 'YYYY-MM-DD'
|
||||
}"
|
||||
is-range
|
||||
expanded
|
||||
:max-date="new Date()"
|
||||
/>
|
||||
<div class="footer-modal">
|
||||
<Button
|
||||
:text="$t('pages.sequences.filter_date_close_button')"
|
||||
look="button--transparent"
|
||||
@trigger="$emit('triggerCloseModal')"
|
||||
/>
|
||||
<Button
|
||||
v-if="range && (range.start || range.end)"
|
||||
:text="$t('pages.sequences.filter_date_reset_button')"
|
||||
icon="bi bi-trash"
|
||||
look="button--red"
|
||||
@trigger="resetCalendar"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import moment from 'moment'
|
||||
import { ref, watch } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { formatDate } from '@/utils/dates'
|
||||
import Button from '@/components/Button.vue'
|
||||
interface CalendarDateInterface {
|
||||
start: Date | string | null
|
||||
end: Date | string | null
|
||||
type: string
|
||||
}
|
||||
const emit = defineEmits(['triggerDate', 'triggerCloseModal'])
|
||||
const props = defineProps({
|
||||
type: { type: String, default: '' },
|
||||
rangeSelected: {
|
||||
type: Object as PropType<CalendarDateInterface>,
|
||||
default: { start: null, end: null, type: '' }
|
||||
}
|
||||
})
|
||||
let range = ref<CalendarDateInterface>({
|
||||
start: props.rangeSelected.start,
|
||||
end: props.rangeSelected.end,
|
||||
type: props.rangeSelected.type
|
||||
})
|
||||
const datePicker = ref()
|
||||
function checkValidityDate(dateToValid: string): boolean {
|
||||
const date = moment(dateToValid, 'YYYY-MM-DD', true)
|
||||
return date.isValid() && date.format('YYYY-MM-DD') === dateToValid
|
||||
}
|
||||
function setStartValue(event: Event): void {
|
||||
const value = (event.target as HTMLInputElement).value
|
||||
if (checkValidityDate(value)) {
|
||||
range.value.start = new Date(value)
|
||||
const startDate = `${formatDate(new Date(value), 'YYYY-MM-DD')} 12:00 AM`
|
||||
if (range && range.value.end && range.value.start) {
|
||||
range.value = {
|
||||
start: new Date(startDate),
|
||||
end: range.value.end,
|
||||
type: props.type
|
||||
}
|
||||
datePicker.value.updateValue(range.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
function setEndValue(event: Event): void {
|
||||
const value = (event.target as HTMLInputElement).value
|
||||
if (checkValidityDate(value)) {
|
||||
range.value.end = new Date(value)
|
||||
const endDate = `${formatDate(new Date(value), 'YYYY-MM-DD')} 11:59 PM`
|
||||
if (range && range.value.end && range.value.start) {
|
||||
range.value = {
|
||||
end: new Date(endDate),
|
||||
start: range.value.start,
|
||||
type: props.type
|
||||
}
|
||||
datePicker.value.updateValue(range.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
function resetCalendar(): void {
|
||||
range.value = { start: null, end: null, type: '' }
|
||||
emit('triggerDate', { start: null, end: null, type: props.type })
|
||||
}
|
||||
watch(range, (range) => {
|
||||
if (range && range.start && range.end) {
|
||||
const startDate = `${formatDate(range.start, 'YYYY-MM-DD')} 12:00 AM`
|
||||
const endDate = `${formatDate(range.end, 'YYYY-MM-DD')} 11:59 PM`
|
||||
range.type = props.type
|
||||
emit('triggerDate', { start: startDate, end: endDate, type: props.type })
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.wrapper-calendar {
|
||||
margin-bottom: toRem(2);
|
||||
}
|
||||
.inputs-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@include text(xs-r-regular);
|
||||
}
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
.footer-modal {
|
||||
margin-top: toRem(2);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.bi-arrow-right {
|
||||
margin-right: toRem(1);
|
||||
margin-left: toRem(1);
|
||||
}
|
||||
.bi-calendar-plus {
|
||||
position: absolute;
|
||||
left: 5%;
|
||||
top: 20%;
|
||||
}
|
||||
.input {
|
||||
padding: toRem(0.5) toRem(0.5) toRem(0.5) toRem(2.5);
|
||||
border-radius: toRem(0.3);
|
||||
border: toRem(0.1) solid var(--grey-pale);
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -53,7 +53,7 @@ defineProps({
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--blue);
|
||||
background-color: var(--blue-semi);
|
||||
background-color: var(--blue-semi-pale);
|
||||
height: toRem(3);
|
||||
width: toRem(3);
|
||||
border-radius: 50%;
|
||||
|
||||
@@ -110,7 +110,7 @@ ul {
|
||||
width: toRem(25);
|
||||
top: toRem(8);
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
z-index: 3;
|
||||
background-color: var(--white);
|
||||
box-shadow: 0 toRem(0.2) toRem(0.4) rgb(0 0 0 / 10%);
|
||||
border-radius: toRem(1);
|
||||
@@ -157,6 +157,7 @@ ul {
|
||||
}
|
||||
.logged-link {
|
||||
padding: toRem(0);
|
||||
margin-bottom: toRem(2);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
144
src/components/sequence/PanelOrientationManagement.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<div class="wrapper-orientation">
|
||||
<h2 class="orientation-title">
|
||||
{{ $t('pages.sequence.orientation_panel_title') }}
|
||||
</h2>
|
||||
<div class="entry-information-card">
|
||||
<InformationCard>
|
||||
<template v-slot:title>
|
||||
<h3 class="subtitle">
|
||||
{{ $t('pages.sequence.orientation_panel_tooltip') }}
|
||||
</h3>
|
||||
</template>
|
||||
</InformationCard>
|
||||
<div class="wrapper-input-angle">
|
||||
<label for="inputAngle" name="inputAngle">{{
|
||||
$t('pages.sequence.orientation_input_label')
|
||||
}}</label>
|
||||
<Input
|
||||
id="inputAngle"
|
||||
name="inputAngle"
|
||||
type="number"
|
||||
min="-180"
|
||||
max="180"
|
||||
:text="Number(angleInputValue)"
|
||||
:placeholder="$t('pages.sequence.orientation_input_placeholder')"
|
||||
@input="captureAngle"
|
||||
/>
|
||||
<span v-if="errorAngleValue" class="errorValue">{{
|
||||
$t('pages.sequence.orientation_input_error_value')
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry-compass">
|
||||
<WidgetOrientation
|
||||
:road-degrees="roadDegrees"
|
||||
:seq-brute-deg="angleValue"
|
||||
@triggerAngle="captureAngle"
|
||||
/>
|
||||
</div>
|
||||
<div class="entry-button">
|
||||
<Button
|
||||
look="button--blue"
|
||||
:text="$t('pages.sequence.orientation_panel_button')"
|
||||
:disabled="isDisabled(Number(angleInputValue)) || isLoading"
|
||||
@trigger="triggerAngle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watchEffect } from 'vue'
|
||||
import InformationCard from '@/components/InformationCard.vue'
|
||||
import Button from '@/components/Button.vue'
|
||||
import Input from '@/components/Input.vue'
|
||||
import WidgetOrientation from '@/components/sequence/WidgetOrientation.vue'
|
||||
const emit = defineEmits<{
|
||||
(e: 'triggerAngle', value: number): void
|
||||
}>()
|
||||
let angleValue = ref<number>(0)
|
||||
let angleInputValue = ref<number>(0)
|
||||
let errorAngleValue = ref<boolean>(false)
|
||||
const props = defineProps({
|
||||
roadDegrees: { type: Number, default: 0 },
|
||||
seqBruteDeg: { type: Number, default: 0 },
|
||||
isLoading: { type: Boolean, default: false }
|
||||
})
|
||||
watchEffect(() => {
|
||||
angleValue.value = props.seqBruteDeg
|
||||
angleInputValue.value = Math.round(props.seqBruteDeg - props.roadDegrees)
|
||||
})
|
||||
function isDisabled(value: number): boolean {
|
||||
return value < -180 || value > 180
|
||||
}
|
||||
function captureAngle(value: number | string) {
|
||||
errorAngleValue.value = false
|
||||
const valueNum = Number(value)
|
||||
angleInputValue.value = valueNum
|
||||
angleValue.value = valueNum + props.roadDegrees
|
||||
if (isDisabled(valueNum)) return (errorAngleValue.value = true)
|
||||
}
|
||||
function triggerAngle() {
|
||||
const valueToSend = angleValue.value - Number(props.roadDegrees)
|
||||
if (isDisabled(valueToSend)) return
|
||||
emit('triggerAngle', valueToSend)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.wrapper-orientation {
|
||||
background-color: var(--blue-semi-pale);
|
||||
}
|
||||
.orientation-title {
|
||||
@include text(h4);
|
||||
margin-bottom: toRem(2);
|
||||
}
|
||||
.subtitle {
|
||||
@include text(m-r-regular);
|
||||
color: var(--blue-dark);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.entry-compass {
|
||||
width: 100%;
|
||||
margin-bottom: toRem(2);
|
||||
}
|
||||
.entry-information-card {
|
||||
margin-top: toRem(2);
|
||||
margin-bottom: toRem(2.5);
|
||||
display: flex;
|
||||
}
|
||||
.wrapper-input-angle {
|
||||
margin-left: toRem(1);
|
||||
width: 45%;
|
||||
background-color: var(--white);
|
||||
padding: toRem(1);
|
||||
border-radius: toRem(1);
|
||||
}
|
||||
.entry-button {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.errorValue {
|
||||
color: var(--red);
|
||||
@include text(xs-r-regular);
|
||||
text-align: center;
|
||||
}
|
||||
@media (max-width: toRem(102.4)) {
|
||||
.entry-information-card {
|
||||
flex-direction: column;
|
||||
}
|
||||
.wrapper-input-angle {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
margin-top: toRem(1);
|
||||
}
|
||||
}
|
||||
@media (max-width: toRem(50)) {
|
||||
.orientation-title {
|
||||
@include text(m-regular);
|
||||
margin-bottom: toRem(2);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
256
src/components/sequence/PanelPhotosManagement.vue
Normal file
@@ -0,0 +1,256 @@
|
||||
<template>
|
||||
<div v-if="pictures && pictures.length">
|
||||
<div v-if="isSequenceOwner" class="delete-all">
|
||||
<div class="wrapper-select">
|
||||
<InputCheckbox
|
||||
:is-checked="pictures.length === picturesToDelete.length"
|
||||
:is-indeterminate="isIndeterminate"
|
||||
:label="selectedText"
|
||||
name="picture-selected"
|
||||
@trigger="triggerInputCheck"
|
||||
/>
|
||||
<div v-if="picturesToDelete.length" class="wrapper-photo-selected">
|
||||
<span class="photo-selected-separator">-</span>
|
||||
<span>{{
|
||||
$t('pages.sequence.picture_selected', picturesToDelete.length)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<Button
|
||||
look="button--white background-white no-text"
|
||||
:icon="
|
||||
picturesToDeleteStatus === 'hidden' ||
|
||||
imagesSelectedHaveDifferentStatus
|
||||
? 'bi bi-eye'
|
||||
: 'bi bi-eye-slash'
|
||||
"
|
||||
:tooltip="$t('pages.sequence.hide_photo_tooltip')"
|
||||
:disabled="!picturesToDelete.length || sequence.status === 'hidden'"
|
||||
@trigger="triggerPatchOrDeleteCollectionItems('PATCH')"
|
||||
/>
|
||||
<div class="button-hidde">
|
||||
<Button
|
||||
look="button--red background-white no-text"
|
||||
icon="bi bi-trash"
|
||||
:tooltip="$t('pages.sequence.delete_photo_tooltip')"
|
||||
:disabled="!picturesToDelete.length"
|
||||
@trigger="triggerPatchOrDeleteCollectionItems('DELETE')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="photo-list">
|
||||
<li v-for="(item, i) in pictures" :id="`el-list${i}`" class="photo-item">
|
||||
<ImageItem
|
||||
:href="item.assets.thumb.href"
|
||||
:href-hd="item.assets.hd.href"
|
||||
:created="formatDate(item.properties.datetime, 'HH:mm:ss')"
|
||||
:selected="photoToDeleteOrPatchSelected(item, picturesToDelete)"
|
||||
:selected-on-map="itemSelected === item.id"
|
||||
:status="
|
||||
imageStatus(item.properties['geovisio:status'], sequence.status)
|
||||
"
|
||||
@trigger="triggerSelectImageAndMove(item)"
|
||||
/>
|
||||
</li>
|
||||
<div class="entry-pagination">
|
||||
<Pagination
|
||||
v-for="item in paginationLinks"
|
||||
:type="item.rel"
|
||||
:href="item.href"
|
||||
:self-link="selfLink[0]"
|
||||
@trigger="triggerGoToNextPage"
|
||||
/>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
<p v-else class="no-photo">{{ $t('pages.sequence.no_image') }}</p>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { PropType } from 'vue'
|
||||
import Pagination from '@/components/Pagination.vue'
|
||||
import InputCheckbox from '@/components/InputCheckbox.vue'
|
||||
import ImageItem from '@/components/ImageItem.vue'
|
||||
import Button from '@/components/Button.vue'
|
||||
import { formatDate } from '@/utils/dates'
|
||||
import {
|
||||
imageStatus,
|
||||
photoToDeleteOrPatchSelected
|
||||
} from '@/views/utils/sequence/index'
|
||||
import type {
|
||||
ResponseUserPhotoLinksInterface,
|
||||
ResponseUserPhotoInterface,
|
||||
UserSequenceInterface,
|
||||
CheckboxInterface
|
||||
} from '../../views/interfaces/MySequenceView'
|
||||
const { t } = useI18n()
|
||||
const emit = defineEmits<{
|
||||
(e: 'triggerInputCheck', value: CheckboxInterface): void
|
||||
(e: 'triggerGoToNextPage', value: string): void
|
||||
(e: 'triggerSelectImageAndMove', value: ResponseUserPhotoInterface): void
|
||||
(e: 'triggerPatchOrDeleteCollectionItems', value: string): void
|
||||
}>()
|
||||
|
||||
const props = defineProps({
|
||||
pictures: {
|
||||
type: Array as PropType<ResponseUserPhotoInterface[]>,
|
||||
default: []
|
||||
},
|
||||
picturesToDelete: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: []
|
||||
},
|
||||
sequence: {
|
||||
type: Object as PropType<UserSequenceInterface>,
|
||||
default: {}
|
||||
},
|
||||
paginationLinks: {
|
||||
type: Array as PropType<ResponseUserPhotoLinksInterface[]>,
|
||||
default: []
|
||||
},
|
||||
selfLink: {
|
||||
type: Array as PropType<ResponseUserPhotoLinksInterface[]>,
|
||||
default: []
|
||||
},
|
||||
fullImagesToDelete: {
|
||||
type: Array as PropType<ResponseUserPhotoInterface[]>,
|
||||
default: []
|
||||
},
|
||||
menuHeight: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
isSequenceOwner: { type: Boolean, default: false },
|
||||
imagesSelectedHaveDifferentStatus: { type: Boolean, default: false },
|
||||
itemSelected: { type: String, default: '' }
|
||||
})
|
||||
|
||||
const isIndeterminate = computed(
|
||||
(): boolean =>
|
||||
!!props.picturesToDelete.length &&
|
||||
!!props.sequence &&
|
||||
props.pictures.length !== props.picturesToDelete.length
|
||||
)
|
||||
|
||||
const selectedText = computed((): string =>
|
||||
props.picturesToDelete.length === props.pictures.length
|
||||
? t('pages.sequence.unselect_text')
|
||||
: t('pages.sequence.select_text')
|
||||
)
|
||||
const picturesToDeleteStatus = computed((): string => {
|
||||
if (props.fullImagesToDelete.length) {
|
||||
return props.fullImagesToDelete[0].properties['geovisio:status']
|
||||
}
|
||||
return 'hidden'
|
||||
})
|
||||
function triggerInputCheck(value: CheckboxInterface): void {
|
||||
emit('triggerInputCheck', value)
|
||||
}
|
||||
function triggerGoToNextPage(value: string): void {
|
||||
emit('triggerGoToNextPage', value)
|
||||
}
|
||||
function triggerSelectImageAndMove(value: ResponseUserPhotoInterface): void {
|
||||
emit('triggerSelectImageAndMove', value)
|
||||
}
|
||||
function triggerPatchOrDeleteCollectionItems(value: string): void {
|
||||
emit('triggerPatchOrDeleteCollectionItems', value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.delete-all {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-left: toRem(1);
|
||||
margin-right: toRem(2);
|
||||
margin-bottom: toRem(1);
|
||||
@include text(xs-r-regular);
|
||||
}
|
||||
.wrapper-select {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.wrapper-photo-selected {
|
||||
@include text(xs-regular);
|
||||
}
|
||||
.photo-selected-separator {
|
||||
margin-right: toRem(0.5);
|
||||
margin-left: toRem(0.5);
|
||||
}
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.button-hidde {
|
||||
margin-right: toRem(1);
|
||||
margin-left: toRem(1);
|
||||
}
|
||||
.photo-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
.photo-item {
|
||||
width: calc(33% - #{toRem(2)});
|
||||
height: fit-content;
|
||||
margin: toRem(1);
|
||||
border-radius: toRem(0.5);
|
||||
background-color: var(--grey);
|
||||
}
|
||||
.no-photo {
|
||||
@include text(s-regular);
|
||||
text-align: center;
|
||||
margin-top: toRem(10);
|
||||
color: var(--grey-dark);
|
||||
}
|
||||
.entry-pagination {
|
||||
margin-top: toRem(2);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
@media (max-width: toRem(102.4)) {
|
||||
.photo-item {
|
||||
width: calc(50% - #{toRem(2)});
|
||||
}
|
||||
}
|
||||
@media (max-width: toRem(76.8)) {
|
||||
.delete-all {
|
||||
text-align: left;
|
||||
}
|
||||
.wrapper-select {
|
||||
flex-direction: column;
|
||||
align-items: initial;
|
||||
}
|
||||
.wrapper-photo-selected:nth-child(2) {
|
||||
margin-top: toRem(0.5);
|
||||
}
|
||||
.photo-selected-separator {
|
||||
display: none;
|
||||
}
|
||||
.entry-pagination {
|
||||
margin-top: toRem(0.5);
|
||||
margin-bottom: toRem(1);
|
||||
}
|
||||
.photo-list {
|
||||
padding-right: toRem(2);
|
||||
}
|
||||
.photo-item {
|
||||
width: 100%;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
@media (max-width: toRem(50)) {
|
||||
.entry-pagination {
|
||||
margin-top: toRem(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
118
src/components/sequence/PanelSortManagement.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<form class="wrapper-sort" @submit.prevent="triggerSort">
|
||||
<h2 class="sort-title">
|
||||
{{ $t('pages.sequence.sort_panel_title') }}
|
||||
</h2>
|
||||
<span class="sort-subtitle">
|
||||
{{ $t('pages.sequence.sort_panel_settings') }}
|
||||
</span>
|
||||
<div class="wrapper-checkboxs">
|
||||
<div class="checkbox-item">
|
||||
<InputRadio
|
||||
:value="sortValue"
|
||||
:label="$t('pages.sequence.sort_panel_check_gps')"
|
||||
name="sort"
|
||||
id="gpsdate"
|
||||
@trigger="triggerInputCheck"
|
||||
/>
|
||||
</div>
|
||||
<div class="checkbox-item">
|
||||
<InputRadio
|
||||
:value="sortValue"
|
||||
:label="$t('pages.sequence.sort_panel_check_file')"
|
||||
name="sort"
|
||||
id="filedate"
|
||||
@trigger="triggerInputCheck"
|
||||
/>
|
||||
</div>
|
||||
<div class="checkbox-item">
|
||||
<InputRadio
|
||||
:value="sortValue"
|
||||
:label="$t('pages.sequence.sort_panel_check_name')"
|
||||
name="sort"
|
||||
id="filename"
|
||||
@trigger="triggerInputCheck"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span class="sort-subtitle">
|
||||
{{ $t('pages.sequence.sort_panel_settings_order') }}
|
||||
</span>
|
||||
<div class="wrapper-checkboxs">
|
||||
<InputSwitch @trigger="triggerInputSwitch" :increased="increaseChecked" />
|
||||
</div>
|
||||
<Button
|
||||
look="button--blue"
|
||||
type="submit"
|
||||
:text="$t('pages.sequence.orientation_panel_button')"
|
||||
:disabled="sortValue.length === 0"
|
||||
/>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { watchEffect, ref } from 'vue'
|
||||
import InputRadio from '@/components/InputRadio.vue'
|
||||
import InputSwitch from '@/components/InputSwitch.vue'
|
||||
import Button from '@/components/Button.vue'
|
||||
const props = defineProps({
|
||||
sequenceSorted: { type: String, default: null }
|
||||
})
|
||||
const emit = defineEmits<{
|
||||
(e: 'triggerSort', value: string): void
|
||||
}>()
|
||||
let sortValue = ref<string>('')
|
||||
let increaseChecked = ref<boolean>(true)
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.sequenceSorted) {
|
||||
const filtersType = ['gpsdate', 'filedate', 'filename']
|
||||
const filtersValue = ['+', '-']
|
||||
filtersType.map((e: string) => {
|
||||
if (props.sequenceSorted.includes(e)) sortValue.value = e
|
||||
})
|
||||
filtersValue.map((e: string) => {
|
||||
if (props.sequenceSorted.includes(e) && e === '+') {
|
||||
increaseChecked.value = true
|
||||
}
|
||||
if (props.sequenceSorted.includes(e) && e === '-') {
|
||||
increaseChecked.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
function triggerInputSwitch(value: { isIncreaseChecked: boolean }): void {
|
||||
increaseChecked.value = value.isIncreaseChecked
|
||||
}
|
||||
function triggerInputCheck(value: string): void {
|
||||
sortValue.value = value
|
||||
}
|
||||
function triggerSort() {
|
||||
const value = increaseChecked.value
|
||||
? `+${sortValue.value}`
|
||||
: `-${sortValue.value}`
|
||||
emit('triggerSort', value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.sort-title {
|
||||
@include text(h4);
|
||||
color: var(--blue-dark);
|
||||
margin-bottom: toRem(2);
|
||||
}
|
||||
.sort-subtitle {
|
||||
color: var(--grey-semi-dark);
|
||||
}
|
||||
.wrapper-checkboxs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-top: toRem(1.5);
|
||||
margin-bottom: toRem(2.5);
|
||||
}
|
||||
.checkbox-item {
|
||||
margin-right: toRem(4);
|
||||
margin-bottom: toRem(1);
|
||||
}
|
||||
</style>
|
||||
232
src/components/sequence/WidgetOrientation.vue
Normal file
@@ -0,0 +1,232 @@
|
||||
<template>
|
||||
<div class="wrapper-widget-orientation">
|
||||
<div
|
||||
class="wrapper-img-road"
|
||||
:style="{
|
||||
transform: `rotate(${roadDegrees}deg)`
|
||||
}"
|
||||
></div>
|
||||
<div class="wrapper-widget">
|
||||
<div class="wrapper-car">
|
||||
<div class="wrapper-elements">
|
||||
<div class="rounded-transparent"></div>
|
||||
<img
|
||||
src="@/assets/images/car.svg"
|
||||
alt=""
|
||||
:style="{
|
||||
transform: `rotate(${roadDegrees}deg)`
|
||||
}"
|
||||
class="car-img"
|
||||
/>
|
||||
<div
|
||||
:style="{
|
||||
transform: `rotate(${angle}deg)`
|
||||
}"
|
||||
id="rotateWrapper"
|
||||
class="rotate-wrapper"
|
||||
>
|
||||
<div
|
||||
id="rotate"
|
||||
@mousedown="mousedown"
|
||||
@mousemove="handleMouseMove"
|
||||
@mouseup="mouseup"
|
||||
class="cursor-img"
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watchEffect } from 'vue'
|
||||
let angleValue = ref<number>(0)
|
||||
let angle = ref<number>(0)
|
||||
let prevRotation = ref<number>(0)
|
||||
let active = ref<boolean>(false)
|
||||
let rotation = ref<number>(0)
|
||||
let startAngle = ref<number>(0)
|
||||
let rotateWrapper = ref<HTMLElement | null>(null)
|
||||
let rotate = ref<HTMLElement | null>(null)
|
||||
let center: { x: number; y: number } = { x: 0, y: 0 }
|
||||
const R2D: number = 180 / Math.PI
|
||||
const emit = defineEmits<{
|
||||
(e: 'triggerAngle', value: number): void
|
||||
}>()
|
||||
const props = defineProps({
|
||||
roadDegrees: { type: Number, default: 0 },
|
||||
seqBruteDeg: { type: Number, default: 0 }
|
||||
})
|
||||
watchEffect(() => {
|
||||
angle.value = Math.round(props.seqBruteDeg)
|
||||
})
|
||||
onMounted(() => {
|
||||
const app = document.getElementById('app')
|
||||
rotateWrapper.value = document.getElementById('rotateWrapper')
|
||||
rotate.value = document.getElementById('rotate')
|
||||
if (app) app.addEventListener('mouseup', () => (active.value = false))
|
||||
})
|
||||
function mousedown(e: MouseEvent): 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.clientX - center.x
|
||||
let y = e.clientY - center.y
|
||||
startAngle.value = R2D * Math.atan2(y, x)
|
||||
active.value = true
|
||||
}
|
||||
function handleMouseMove(e: MouseEvent): void {
|
||||
e.preventDefault()
|
||||
if (!active.value || !rotateWrapper.value) return
|
||||
const x = e.clientX - center.x
|
||||
const y = e.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 mouseup(): void {
|
||||
if (!active.value || !rotate.value) return
|
||||
angle.value += Math.round(rotation.value)
|
||||
if (rotation.value !== prevRotation.value) {
|
||||
prevRotation.value = rotation.value
|
||||
angleValue.value = angle.value
|
||||
if (angleValue.value !== 0) {
|
||||
emit('triggerAngle', modulo180(angle.value, props.roadDegrees))
|
||||
}
|
||||
}
|
||||
active.value = false
|
||||
}
|
||||
function clickAndMove(value: number): void {
|
||||
const moduloAngle = modulo180(angle.value, Math.round(props.roadDegrees))
|
||||
if (moduloAngle % 45 === 0) {
|
||||
return emit('triggerAngle', moduloAngle + value)
|
||||
}
|
||||
let closestMultiple = Math.ceil(moduloAngle / 45) * value
|
||||
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>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.wrapper-widget-orientation {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: toRem(40);
|
||||
width: 100%;
|
||||
border-radius: toRem(1);
|
||||
}
|
||||
.wrapper-img-road {
|
||||
background-image: url('@/assets/images/road.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
background-position: center;
|
||||
height: 210%;
|
||||
width: 250%;
|
||||
}
|
||||
.wrapper-widget {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.wrapper-car {
|
||||
height: toRem(40);
|
||||
width: toRem(40);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.wrapper-elements {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.rotate-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
transform: rotate(0);
|
||||
background-color: transparent;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
.cursor-img {
|
||||
background-image: url('@/assets/images/cursor.svg');
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: toRem(2);
|
||||
right: 0;
|
||||
top: 0;
|
||||
margin: auto;
|
||||
height: 50%;
|
||||
width: 74%;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
.arrow-img {
|
||||
position: absolute;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
height: toRem(5);
|
||||
width: toRem(5);
|
||||
padding: 0;
|
||||
img {
|
||||
height: 100%;
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
.arrow-img-1 {
|
||||
right: toRem(4);
|
||||
top: toRem(6);
|
||||
}
|
||||
.arrow-img-2 {
|
||||
left: toRem(3.2);
|
||||
bottom: toRem(10.5);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.car-img {
|
||||
position: absolute;
|
||||
height: 45%;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
.rounded-transparent {
|
||||
background-color: rgba(white, 0.4);
|
||||
height: toRem(25);
|
||||
width: toRem(25);
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
@@ -121,7 +121,7 @@ defineProps({
|
||||
margin-bottom: toRem(1);
|
||||
}
|
||||
.sequence-button {
|
||||
width: fit-content;
|
||||
width: toRem(22);
|
||||
}
|
||||
.text-information {
|
||||
@include text(s-r-regular);
|
||||
@@ -167,7 +167,6 @@ defineProps({
|
||||
margin-bottom: toRem(2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: toRem(102.4)) {
|
||||
.loader-title {
|
||||
flex-direction: column;
|
||||
|
||||
224
src/locales/cs.json
Normal file
@@ -0,0 +1,224 @@
|
||||
{
|
||||
"general": {
|
||||
"title": "Instance Panoramax",
|
||||
"meta": {
|
||||
"title": "Instance Panoramax",
|
||||
"description": "Panoramax, bezplatná alternativa k fotomapování území"
|
||||
},
|
||||
"header": {
|
||||
"login_text": "Připojit",
|
||||
"register_text": "Registrovat",
|
||||
"contribute_text": "Proč přispívat?",
|
||||
"my_account": "Můj účet",
|
||||
"upload_text": "+ Sdílejte obrázky",
|
||||
"sequences_text": "Moje obrázky",
|
||||
"alt_logo": "Logo instance",
|
||||
"alt_photos": "Ikona obrázků",
|
||||
"alt_information": "Ikona uživatele",
|
||||
"alt_settings": "Ikona parametrů",
|
||||
"alt_logout": "Ikona odhlášení",
|
||||
"title": "Panoramax",
|
||||
"beta_text": "Beta verze",
|
||||
"logout_text": "Odhlásit se",
|
||||
"my_information_text": "Moje detaily",
|
||||
"my_settings_text": "Moje parametry",
|
||||
"burger_menu_aria_label_open": "Zobrazit menu",
|
||||
"burger_menu_aria_label_closed": "Skrýt nabídku"
|
||||
},
|
||||
"footer": {
|
||||
"panoramax_site": "Objevte Panoramax",
|
||||
"information_gitlab": "Zobrazit zdrojový kód",
|
||||
"gitlab_logo": "Logo Gitlab",
|
||||
"ay11_text": "Přístupnost: nevyhovuje"
|
||||
},
|
||||
"error_text": "Nastala chyba",
|
||||
"success_text": "Aktualizace provedena"
|
||||
},
|
||||
"pages": {
|
||||
"home": {
|
||||
"report_mail": "?subject=⚠️ Nahlásit k obrázku {picId}&body=Hello, %0D%0A%0D%0A Problém na obrázku (typ problému ponechat nahlášený) : %0D%0A%0D%0A %0D%0A%0D%0A nevhodné obsah / chybějící rozmazání na prvku, který má být anonymizován nebo rozmazán z bezpečnostních důvodů / nadměrné rozmazání (příliš velké rozmazání) %0D%0A%0D%0A Odkaz na dotčenou fotografii: {link} %0D%0A%0D%0A Podrobnosti o dotčených prvky (zejména pro problémy s rozmazáním - co by mělo být rozmazané nebo nerozmazané?):",
|
||||
"report_button_text": "Nahlásit tento obrázek",
|
||||
"sequence_title": "Podívejte se na sekvenci",
|
||||
"open_fullscreen": "Režim celé obrazovky",
|
||||
"close_fullscreen": "Normální mód"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Moje tokeny",
|
||||
"setting_tooltip": "Zobrazit nebo skrýt token"
|
||||
},
|
||||
"sequence": {
|
||||
"sequence_published": "Publikováno",
|
||||
"sequence_waiting": "Stále zpracovávám",
|
||||
"sequence_hidden": "Skrytý",
|
||||
"sequence_form_title": "Upravte název",
|
||||
"hide_sequence_tooltip": "Skryjte tyto sekvence",
|
||||
"back_button": "Zpět k mému seznamu sekvencí",
|
||||
"delete_sequence_tooltip": "Trvale odstranit tuto sekvenci",
|
||||
"hide_photo_tooltip": "Skrýt vybrané obrázky",
|
||||
"delete_photo_tooltip": "Trvale odstranit vybrané snímky",
|
||||
"conf_pic_msg": "⚠️ Vybrané fotografie budou trvale smazány",
|
||||
"conf_sequence_msg": "⚠️ Tato sekvence bude trvale smazána",
|
||||
"button_panel_photos": "Správa obrázků",
|
||||
"button_panel_orientation": "Nastavte orientaci",
|
||||
"button_panel_sort": "Posloupnost řazení",
|
||||
"orientation_panel_title": "Úprava orientace všech fotografií v sekvenci",
|
||||
"orientation_panel_tooltip": "Přetáhněte modrý rámeček požadovaným směrem\"",
|
||||
"orientation_input_label": "nebo zde změňte úhel",
|
||||
"orientation_input_placeholder": "Hodnota mezi -180 a 180",
|
||||
"orientation_input_error_value": "Hodnota musí být mezi -180 a 180",
|
||||
"orientation_panel_button": "Potvrďte pozici",
|
||||
"orientation_updated": "Orientace aktualizována",
|
||||
"sort_updated": "Sekvence seřazena",
|
||||
"sort_panel_title": "Nastavení sekvenčního řazení",
|
||||
"sort_panel_settings": "Seřadit sekvenci podle:",
|
||||
"sort_panel_settings_order": "Objednat :",
|
||||
"sort_panel_settings_order_increase": "Vzestupně",
|
||||
"sort_panel_settings_order_decrease": "Klesající",
|
||||
"sort_panel_check_gps": "Datum GPS",
|
||||
"sort_panel_check_file": "Datum souboru",
|
||||
"sort_panel_check_name": "Název souboru",
|
||||
"created": "Nahráno:",
|
||||
"taken": "Vyfoceno dne:",
|
||||
"duration": "Doba trvání :",
|
||||
"duration_begin": "Start :",
|
||||
"duration_end": "Konec :",
|
||||
"camera": "Fotoaparát :",
|
||||
"button_delete": "Vymazat",
|
||||
"button_disable": "Skrýt",
|
||||
"button_enable": "Ukázat",
|
||||
"picture_selected": "{count} obrázek vybrán| Počet vybraných obrázků: {count}",
|
||||
"hours": "{count} hodina| {count} hodin",
|
||||
"minutes": "{count} minut| {count} minut",
|
||||
"seconds": "{count} sekund| {count} sekund",
|
||||
"select_text": "Vybrat vše",
|
||||
"unselect_text": "Odznačit vše",
|
||||
"select_shift_text": "Vyberte více fotografií pomocí posunu",
|
||||
"waiting_process": "Foto v procesu",
|
||||
"broken": "Chyba zpracování fotografie",
|
||||
"no_image": "V této sekvenci není žádný obrázek"
|
||||
},
|
||||
"sequences": {
|
||||
"title": "Moje sekvence",
|
||||
"filter_date_upload_title": "Filtrujte podle data nahrání",
|
||||
"filter_date_title": "Filtrovat podle data focení:",
|
||||
"radio_date_placeholder": "03/01/2024",
|
||||
"radio_date": "datum",
|
||||
"hide_button": "Skrýt",
|
||||
"show_button": "Ukázat",
|
||||
"delete_button": "Vymazat",
|
||||
"filter_date_reset_button": "Resetovat",
|
||||
"filter_date_close_button": "Zavřít",
|
||||
"no_sequence_found": "Nebyla nalezena žádná sekvence",
|
||||
"sequence_name": "název",
|
||||
"sequence_photos": "Fotky",
|
||||
"sequence_date": "Vyfoceno dne",
|
||||
"sequence_creation": "nahrát",
|
||||
"sequence_creation_tooltip": "Filtrujte podle data nahrání",
|
||||
"sequence_date_tooltip": "Filtrujte podle data pořízení",
|
||||
"reset_filter_button": "Resetujte filtry",
|
||||
"filter_bbox_button": "Hledejte v této oblasti",
|
||||
"sequence_status": "Postavení",
|
||||
"sequence_published": "Publikováno",
|
||||
"sequence_waiting": "Stále zpracovávám",
|
||||
"sequence_hidden": "Skrytý",
|
||||
"no_sequences_text": "Ještě nemáte zveřejněné žádné fotky 😢",
|
||||
"button_upload": "Nahrajte obrázky",
|
||||
"sequence_deleted": "Sekvence byla smazána"
|
||||
},
|
||||
"share_pictures": {
|
||||
"title": "Proč přispívat na Panoramax?",
|
||||
"description": "Přispívat do Panoramax znamená podílet se na vývoji geo-common, suverénního, bezplatného a opakovaně použitelného digitálního zdroje. Každou geolokalizovanou fotografii zveřejněnou na Panoramaxu může kdokoli použít k různým účelům, například místní úřad, který potřebuje sledovat stav svých silnic, nebo telekomunikační operátor k přípravě zásahu. Každý přispěvatel může posílat své obrazové sekvence, upravovat je a konzultovat je, stejně jako všechny pohledy – 360° nebo ne – přidané komunitou. Povinné rozmazání obličejů a SPZ je na platformě automatizované.",
|
||||
"alt_img_map": "Ilustrace ženy při pohledu na mapu se svým geolokovaným smartphonem",
|
||||
"card_photo1": "Místa viditelná z veřejné komunikace",
|
||||
"card_photo2": "Fotografie zveřejněné ve formátu 360° nebo ne",
|
||||
"card_photo3": "Snadno znovu použitelné fotografie",
|
||||
"card_photo4": "Rychlý a snadný příspěvek k obrázku",
|
||||
"card_alt_photo1": "Obrázek budovy",
|
||||
"card_alt_photo2": "Obrázek ukazuje 360 stupňů",
|
||||
"card_alt_photo3": "Obrázek zobrazující mapu s ukazatelem",
|
||||
"card_alt_photo4": "Obrázek představující ukazatel",
|
||||
"card_description1": "Všechny fotografie pořízené z veřejné komunikace jsou přijímány, pokud jsou geolokovány a pozorovány ze země.",
|
||||
"card_description2": "360° snímky nejsou povinné: stačí pouze fotografie pořízené chytrým telefonem. Data, umístění a formát jpg jsou jedinými předpoklady.",
|
||||
"card_description3": "Všechny fotografie jsou snadno dostupné a znovu použitelné bez účtu: přes webové stránky nebo standardní API (standard STAC).",
|
||||
"card_description4": "Pro usnadnění příspěvků je k dispozici několik nástrojů, včetně příkazového řádku a webového rozhraní.",
|
||||
"upload_subtitle": "Jednoduše nahrajte své obrázky online",
|
||||
"upload_illustration_alt": "Ilustrace zobrazující online nahrávání fotografií",
|
||||
"upload_description": "Webová aplikace Panoramax vám umožňuje nahrát všechny vaše terénní fotografie ve formátu JPEG jediným kliknutím na tlačítko. Nejsou vyžadovány žádné znalosti programování. Pro větší čísla však doporučujeme použít nástroj příkazového řádku",
|
||||
"upload_button": "+ Nahrát obrázky",
|
||||
"command_line_subtitle": "Nástroj příkazového řádku",
|
||||
"comment_install": "Nainstalujte nástroj příkazového řádku geovisio",
|
||||
"comment_upload": "LanceSpusťte příkaz pro nahrání obrázku ve vybrané složce",
|
||||
"description_terminal": "<a href='https://gitlab.com/geovisio/cli' target='_blank' style='color:black'>CLI</a> vám umožňuje sdílet velké objemy fotografií. Postup je jednoduchý a vyžaduje <a target='_blank' href='https://www.python.org/downloads/' style='color:black'>python (verze 3.8 nebo vyšší)</a>. Nástroj se před importem zeptá na vaše přihlašovací údaje. Po nahrání obrázků je před zveřejněním vyžadována doba zpracování.",
|
||||
"terminal_install": "pip install geovisio_cli",
|
||||
"terminal_text": "upload geovisio --api-url {url} <DOSSIER_PHOTOS>",
|
||||
"button_copy": "kopírovat",
|
||||
"information_subtitle": "Zde jsou vaše fotografie přístupné všem:",
|
||||
"information_text1": "Automaticky rozmazané v souladu s legislativou.",
|
||||
"information_text2": "Nahrané obrázky budou zveřejněny pod {word}",
|
||||
"information_text3": "V původním formátu a rozlišení pro různé opětovné použití.",
|
||||
"information_about_title": "Potřebujete přístup k obrázkům?",
|
||||
"information_about_description": "Pro načtení všech metadat a obrázků je k dispozici API. <a href='{docLink}' target='_blank' style='color:#0a1f69'> Více informací naleznete zde</a> Data se také zobrazují ve tvaru <a href='{docTiles}' target=' _blank' style='color:#0a1f69'>vektorových dlaždic</a>",
|
||||
"doc_subtitle": "Potřebujete pomoci s přispěním do Panoramax?",
|
||||
"doc_description": "Dokumentace Panoramax je k dispozici u nás a výukové programy můžete získat na fóru geo-commons.",
|
||||
"doc_button": "Viz dokumentace",
|
||||
"doc_illustration_alt": "Ilustrace postavy s listem dokumentů"
|
||||
},
|
||||
"upload": {
|
||||
"title": "Přispějte na projekt Panoramax",
|
||||
"description": "Pro velké objemy obrázků je vhodnější nástroj příkazového řádku.",
|
||||
"know_more_button": "Přečtěte si více",
|
||||
"input_label": "Přetáhněte své obrázky sem nebo klikněte na",
|
||||
"import_word": "nahrát",
|
||||
"import_type": "Pouze formát JPEG",
|
||||
"subtitle_import": "Nahrání obrázku",
|
||||
"title_sequence": "Název sekvence",
|
||||
"description_title_sequence": "Ve výchozím nastavení bude názvem sekvence datum dne. Pokud chcete, můžete zde upravit název.",
|
||||
"text_import": "Zde nahrajte své soubory jpg. Každý obrázek nebo série obrázků tvoří \"sekvenci\". Poté je můžete najít v sekci „moje obrázky“ a zvolit, zda je chcete skrýt, zobrazit nebo odstranit.",
|
||||
"subtitle_process": "Zpracování nahrávání",
|
||||
"uploading_process": "Nahrávání probíhá...",
|
||||
"uploading_cancel": "Zrušit odesílání fotografií",
|
||||
"cancel_message": "⚠️ Upozorňujeme, že stahování bude přerušeno, pokud potvrdíte a sekvence bude smazána.",
|
||||
"sequence_title": "Sekvence",
|
||||
"import": "Nahrávání",
|
||||
"upload_pending": "Nahrávání probíhá...",
|
||||
"images_count_text": "Obrázky byly nahrány",
|
||||
"no_img_text": "zatím nebyl nahrán žádný obrázek",
|
||||
"upload_done": "Nahrání sekvence bylo dokončeno",
|
||||
"sequence_link": "Ukažte tuto sekvenci",
|
||||
"edit_title_tooltip": "Upravte název sekvence",
|
||||
"edit_placeholder_input": "Upravte název sekvence",
|
||||
"ok_button": "OK",
|
||||
"pictures_error": "{count} obrázek se nepodařilo nahrát| {count} obrázků se nepodařilo nahrát",
|
||||
"sequence_loading_information": "Po nahrání bude sekvence zpracována a poté zveřejněna na Panoramaxu (obvykle během několika minut).",
|
||||
"sequence_loaded_information": "Sekvence byly nahrány a zpracovává se. Během pár minut by měl být veřejně dostupný na Panoramaxu.",
|
||||
"leave_message": "⚠️ VAROVÁNÍ, pokud stránku opustíte před koncem, stahování se přeruší.",
|
||||
"error_button": "Zobrazit chyby",
|
||||
"modal_error_title": "Chybné obrázky"
|
||||
},
|
||||
"ay11": {
|
||||
"title": "Déclaration d'accessibilité",
|
||||
"date": "Établie le 18 září 2023.",
|
||||
"introduction": "Služba IGN je přístupná, v souladu s článkem 47 ze zákona č. 2005-102 z 11. února 2005. Prohlášení o přístupu k aplikaci pro Panoramax Instance IGN.ign: https://panora. fr",
|
||||
"subtitle_conformity": "État de conformité",
|
||||
"conformity_text": "Instance Panoramax IGN není konformní průměr",
|
||||
"conformity_text2": "Le site n'a encore pas été audité.",
|
||||
"subtitle_conformity2": "Nepřístupný obsah",
|
||||
"subtitle_increase": "Améliorace a kontakt",
|
||||
"increase_text": "Pokud se k vám připojíte a připojíte se k servisu, je odpovědný kontaktér společnosti Panoramax Instance IGN nalézající se v jiné orientaci nebo alternativě, která je přístupná nebo získaná v souladu s vlastní formou.",
|
||||
"phone": "Telefon: +33 14 398 84 61",
|
||||
"email_text": "E-mailem :",
|
||||
"email": "signalement.ign@panoramax.fr",
|
||||
"address": "Adresa: 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": "Postup uživatele v daném případu : vous avez signalé au odpovědný z webu internet a défaut d'accessibilité qui vous empêche d'accessider à un contenu ou à un des services du satiss a vous obtenu.n'a pasis vous pouvez:",
|
||||
"write_message": "Écrire un message au",
|
||||
"defenseur_droits": "Défenseur des droits",
|
||||
"contact": "Kontaktní osoba",
|
||||
"contact_text": "le délégué du Défenseur des droits dans votre région",
|
||||
"send_letter": "Envoyer un courier par la poste (gratuit, ne pas mettre de timbre): Défenseur des droits Libre réponse 71120 75342 Paris CEDEX 07",
|
||||
"end": "Cette déclaration d'accessibilité a été créé le 18. září 2023 grâce au",
|
||||
"generator_betagouv": "Générateur de Déclaration d'Accessibilité de BetaGouv"
|
||||
}
|
||||
}
|
||||
}
|
||||
224
src/locales/en.json
Normal file
@@ -0,0 +1,224 @@
|
||||
{
|
||||
"general": {
|
||||
"title": "Panoramax instance",
|
||||
"meta": {
|
||||
"title": "Panoramax instance",
|
||||
"description": "Panoramax, the free alternative to photo-mapping territories"
|
||||
},
|
||||
"header": {
|
||||
"login_text": "Connect",
|
||||
"register_text": "Register",
|
||||
"contribute_text": "Why contribute ?",
|
||||
"my_account": "My account",
|
||||
"upload_text": "+ Share pictures",
|
||||
"sequences_text": "My pictures",
|
||||
"alt_logo": "Instance logo",
|
||||
"alt_photos": "Pictures icon",
|
||||
"alt_information": "User icon",
|
||||
"alt_settings": "Parameters icon",
|
||||
"alt_logout": "Logout icon",
|
||||
"title": "Panoramax",
|
||||
"beta_text": "Beta version",
|
||||
"logout_text": "Logout",
|
||||
"my_information_text": "My details",
|
||||
"my_settings_text": "My parameters",
|
||||
"burger_menu_aria_label_open": "Show menu",
|
||||
"burger_menu_aria_label_closed": "Hide menu"
|
||||
},
|
||||
"footer": {
|
||||
"panoramax_site": "Discover Panoramax",
|
||||
"information_gitlab": "Show source code",
|
||||
"gitlab_logo": "Gitlab logo",
|
||||
"ay11_text": "Accessibility: not compliant"
|
||||
},
|
||||
"error_text": "An error occured",
|
||||
"success_text": "Update done"
|
||||
},
|
||||
"pages": {
|
||||
"home": {
|
||||
"report_mail": "?subject=⚠️ Report on picture {picId}&body=HEllo, %0D%0A%0D%0A Problem on image (keep type of problem reported) : %0D%0A%0D%0A %0D%0A%0D%0A inappropriate content / lack of blurring on an element to be anonymized or blurred for security reasons / overblurring (too much blurring) %0D%0A%0D%0A Link to affected photo : {link} %0D%0A%0D%0A Details of affected elements (especially for blurring problems - what should be blurred or unblurred?) :",
|
||||
"report_button_text": "Report this picture",
|
||||
"sequence_title": "See the séquence",
|
||||
"open_fullscreen": "Fullscreen mode",
|
||||
"close_fullscreen": "Normal mode"
|
||||
},
|
||||
"settings": {
|
||||
"title": "My tokens",
|
||||
"setting_tooltip": "Show or hide token"
|
||||
},
|
||||
"sequence": {
|
||||
"sequence_published": "Published",
|
||||
"sequence_waiting": "Still processing",
|
||||
"sequence_hidden": "Hidden",
|
||||
"sequence_form_title": "Edit the title",
|
||||
"hide_sequence_tooltip": "Hide this sequences",
|
||||
"back_button": "Back to my sequence list",
|
||||
"delete_sequence_tooltip": "Permanently delete this sequence",
|
||||
"hide_photo_tooltip": "Hide selected pictures",
|
||||
"delete_photo_tooltip": "Permanently delete selected pictures",
|
||||
"conf_pic_msg": "⚠️ Selected photos will be permanently deleted",
|
||||
"conf_sequence_msg": "⚠️ This sequence will be permanently deleted",
|
||||
"button_panel_photos": "Manage pictures",
|
||||
"button_panel_orientation": "Set orientation",
|
||||
"button_panel_sort": "Sort sequence",
|
||||
"orientation_panel_title": "Adjusting the orientation of all photos in the sequence",
|
||||
"orientation_panel_tooltip": "Drag the blue box in the desired direction\"",
|
||||
"orientation_input_label": "or change the angle here",
|
||||
"orientation_input_placeholder": "Value between -180 and 180",
|
||||
"orientation_input_error_value": "Value must be between -180 and 180",
|
||||
"orientation_panel_button": "Validate position",
|
||||
"orientation_updated": "Orientation updated",
|
||||
"sort_updated": "Sequence sorted",
|
||||
"sort_panel_title": "Sequence sort setting",
|
||||
"sort_panel_settings": "Sort sequence by:",
|
||||
"sort_panel_settings_order": "Order :",
|
||||
"sort_panel_settings_order_increase": "Ascending",
|
||||
"sort_panel_settings_order_decrease": "Decreasing",
|
||||
"sort_panel_check_gps": "GPS Date",
|
||||
"sort_panel_check_file": "File date",
|
||||
"sort_panel_check_name": "File name",
|
||||
"created": "Uploaded :",
|
||||
"taken": "Shot on :",
|
||||
"duration": "Duration :",
|
||||
"duration_begin": "Start :",
|
||||
"duration_end": "End :",
|
||||
"camera": "Camera :",
|
||||
"button_delete": "Delete",
|
||||
"button_disable": "Hide",
|
||||
"button_enable": "Show",
|
||||
"picture_selected": "{count} picture selected| {count} pictures selected",
|
||||
"hours": "{count} hour| {count} hours",
|
||||
"minutes": "{count} minute| {count} minutes",
|
||||
"seconds": "{count} second| {count} seconds",
|
||||
"select_text": "Select all",
|
||||
"unselect_text": "Deselect all",
|
||||
"select_shift_text": "Select multiple photos with shift",
|
||||
"waiting_process": "Photo in process",
|
||||
"broken": "Photo error processing",
|
||||
"no_image": "No picture in this sequence"
|
||||
},
|
||||
"sequences": {
|
||||
"title": "My sequences",
|
||||
"filter_date_upload_title": "Filter by upload date",
|
||||
"filter_date_title": "Filter by shooting date :",
|
||||
"radio_date_placeholder": "03/01/2024",
|
||||
"radio_date": "date",
|
||||
"hide_button": "Hide",
|
||||
"show_button": "Show",
|
||||
"delete_button": "Delete",
|
||||
"filter_date_reset_button": "Reset",
|
||||
"filter_date_close_button": "Close",
|
||||
"no_sequence_found": "No sequence found",
|
||||
"sequence_name": "Name",
|
||||
"sequence_photos": "Photos",
|
||||
"sequence_date": "Shot on",
|
||||
"sequence_creation": "Upload",
|
||||
"sequence_creation_tooltip": "Filter by uploaded date",
|
||||
"sequence_date_tooltip": "Filter by shooting date",
|
||||
"reset_filter_button": "Reset the filters",
|
||||
"filter_bbox_button": "Search on this area",
|
||||
"sequence_status": "Status",
|
||||
"sequence_published": "Published",
|
||||
"sequence_waiting": "Still processing",
|
||||
"sequence_hidden": "Hidden",
|
||||
"no_sequences_text": "You have no photos published yet \uD83D\uDE22",
|
||||
"button_upload": "Upload pictures",
|
||||
"sequence_deleted": "The sequence has been deleted"
|
||||
},
|
||||
"share_pictures": {
|
||||
"title": "Why contribute to Panoramax?",
|
||||
"description": "Contributing to Panoramax means participating in the development of a geo-common, a sovereign, free and reusable digital resource. Each geolocalized photo published on Panoramax can be used by anyone for a variety of purposes, for example by a local authority needing to observe the status of its roads, or by a telecoms operator to prepare an intervention.\n\nEach contributor can send his or her image sequences, modify them and consult them, as well as all the views - 360° or not - contributed by the community. The compulsory blurring of faces and license plates is automated on the platform.",
|
||||
"alt_img_map": "Illustration of a woman looking at a map with her geolocated smartphone",
|
||||
"card_photo1": "Places visible from the public highway",
|
||||
"card_photo2": "Photos published in 360° format or not",
|
||||
"card_photo3": "Easily reusable photos",
|
||||
"card_photo4": "A quick and easy image contribution",
|
||||
"card_alt_photo1": "Image of a building",
|
||||
"card_alt_photo2": "Image showing 360-degree ",
|
||||
"card_alt_photo3": "Image showing a map with a pointer",
|
||||
"card_alt_photo4": "Image representing a pointer",
|
||||
"card_description1": "All photos taken from the public highway are accepted, as long as they are geolocated and viewed from the ground.",
|
||||
"card_description2": "360° pictures are not mandatory: photos taken with a smartphone are all that's needed. Dates, locations and jpg format are the only prerequisites.",
|
||||
"card_description3": "All photos easily accessible and reusable without an account: via the website or a standard API (STAC standard).",
|
||||
"card_description4": "Several tools are available to facilitate contributions, including a command line and a web interface.",
|
||||
"upload_subtitle": "Simply upload your images online",
|
||||
"upload_illustration_alt": "Illustration showing online photo uploading",
|
||||
"upload_description": "Panoramax's web application lets you upload all your field photos in JPEG format at the click of a button. No programming skills are required. For larger numbers, however, we recommend using the command-line tool",
|
||||
"upload_button": "+ Upload pictures",
|
||||
"command_line_subtitle": "Command line tool",
|
||||
"comment_install": "Install the geovisio command-line tool",
|
||||
"comment_upload": "LanceStart the image upload command on the chosen folder",
|
||||
"description_terminal": "<a href='https://gitlab.com/geovisio/cli' target='_blank' style='color:black'>The CLI</a> lets you share large volumes of photos. The procedure is simple and requires <a target='_blank' href='https://www.python.org/downloads/' style='color:black'>python (version 3.8 or above)</a>.\n\nThe tool will ask for your login details before importing. Once the pictures have been uploaded, a processing time is required before publication.",
|
||||
"terminal_install": "pip install geovisio_cli",
|
||||
"terminal_text": "geovisio upload --api-url {url} <DOSSIER_PHOTOS>",
|
||||
"button_copy": "Copy",
|
||||
"information_subtitle": "Here, your photos are accessible to all : ",
|
||||
"information_text1": "Automatically blurred in compliance with legislation.",
|
||||
"information_text2": "The uploaded pictures will be published under {word}",
|
||||
"information_text3": "In its original format and resolution for various reuse.",
|
||||
"information_about_title": "Need to access pictures ?",
|
||||
"information_about_description": "An API is available to retrieve all metadata and pictures. <a href='{docLink}' target='_blank' style='color:#0a1f69'>\nFind out more here</a>\nData is also displayed in the form <a href='{docTiles}' target='_blank' style='color:#0a1f69'>of vector tiles</a>",
|
||||
"doc_subtitle": "Need help contributing to Panoramax?",
|
||||
"doc_description": "Panoramax documentation is available from us, and you can access tutorials on the geo-commons forum.",
|
||||
"doc_button": "See the documentation",
|
||||
"doc_illustration_alt": "Illustration of a character with a sheet of documents"
|
||||
},
|
||||
"upload": {
|
||||
"title": "Contribute to the Panoramax project",
|
||||
"description": "For large volumes of pictures, the command line tool is more suitable.",
|
||||
"know_more_button": "Read more",
|
||||
"input_label": "Drag your pictures here or click on ",
|
||||
"import_word": "upload",
|
||||
"import_type": "JPEG format only",
|
||||
"subtitle_import": "Picture upload",
|
||||
"title_sequence": "Séquence title",
|
||||
"description_title_sequence": "By default, the sequence title will be the date of the day. You can, if you want, edit the title here.",
|
||||
"text_import": "Upload your jpg files here. Each picture or series of pictures constitutes a \"sequence\". You can then find them in the \"my pictures\" section and choose to hide, show or delete them.",
|
||||
"subtitle_process": "Upload processing",
|
||||
"uploading_process": "Upload in progress...",
|
||||
"uploading_cancel": "Cancel sending photos",
|
||||
"cancel_message": "⚠️ Please note, the download will be interrupted if you validate and the sequence will be deleted.",
|
||||
"sequence_title": "Sequence ",
|
||||
"import": "Uploads",
|
||||
"upload_pending": "Upload in progress...",
|
||||
"images_count_text": "Pictures uploaded",
|
||||
"no_img_text": "no picture upload so far",
|
||||
"upload_done": "Sequence upload done",
|
||||
"sequence_link": "Show this sequence",
|
||||
"edit_title_tooltip": "Edit the sequence's title",
|
||||
"edit_placeholder_input": "Edit the sequence's title",
|
||||
"ok_button": "OK",
|
||||
"pictures_error": "{count} picture could not be uploaded| {count} pictures could not be uploaded",
|
||||
"sequence_loading_information": "Once uploaded, the sequence will be processed then published on Panoramax (usually within a couple of minutes).",
|
||||
"sequence_loaded_information": "The sequences has been uploaded and is under processing. It should be publicly available on Panoramax within a couple of minutes.",
|
||||
"leave_message": "⚠️ WARNING, the download will be interrupted if you leave the page before the end.",
|
||||
"error_button": "Show errors",
|
||||
"modal_error_title": "Pictures in error"
|
||||
},
|
||||
"ay11": {
|
||||
"title": "Déclaration d’accessibilité",
|
||||
"date": "Établie le 18 septembre 2023.",
|
||||
"introduction": "IGN s’engage à rendre son service accessible, conformément à l’article 47 de la loi n° 2005-102 du 11 février 2005.\n Cette déclaration d’accessibilité s’applique à Panoramax Instance IGN : https://panoramax.ign.fr",
|
||||
"subtitle_conformity": "État de conformité",
|
||||
"conformity_text": "Panoramax Instance IGN est non conforme avec le ",
|
||||
"conformity_text2": "Le site n’a encore pas été audité.",
|
||||
"subtitle_conformity2": "Contenus non accessibles",
|
||||
"subtitle_increase": "Amélioration et contact",
|
||||
"increase_text": "Si vous n’arrivez pas à accéder à un contenu ou à un service, vous pouvez\n contacter le responsable de Panoramax Instance IGN pour être orienté vers une alternative accessible ou obtenir le contenu sous une autre forme.",
|
||||
"phone": "Téléphone : +33 14 398 84 61",
|
||||
"email_text": "E-mail :",
|
||||
"email": "signalement.ign@panoramax.fr",
|
||||
"address": "Adresse : IGN, Saint-Mandé",
|
||||
"increase_info": "Nous essayons de répondre dans les 5 jours ouvrés.",
|
||||
"subtitle_to_do": "Voie de recours",
|
||||
"to_do_text": "Cette procédure est à utiliser dans le cas suivant : vous avez signalé au responsable du site internet un défaut d’accessibilité qui vous\n empêche d’accéder à un contenu ou à un des services du portail et vous n’avez pas obtenu de réponse satisfaisante. \n vous pouvez :",
|
||||
"write_message": "Écrire un message au",
|
||||
"defenseur_droits": "Défenseur des droits",
|
||||
"contact": "Contacter",
|
||||
"contact_text": "le délégué du Défenseur des droits dans votre région",
|
||||
"send_letter": "Envoyer un courrier par la poste (gratuit, ne pas mettre de\n timbre):\n Défenseur des droits\n Libre réponse 71120 75342 Paris CEDEX 07",
|
||||
"end": "Cette déclaration d’accessibilité a été créé le\n 18 septembre 2023 grâce au",
|
||||
"generator_betagouv": "Générateur de Déclaration d’Accessibilité de BetaGouv"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@
|
||||
"burger_menu_aria_label_closed": "Masquer le menu"
|
||||
},
|
||||
"footer": {
|
||||
"panoramax_site": "Découvrir Panoramax.fr",
|
||||
"panoramax_site": "Découvrir Panoramax",
|
||||
"information_gitlab": "Voir le code",
|
||||
"gitlab_logo": "Logo Gitlab",
|
||||
"ay11_text": "Accessibilité : non conforme"
|
||||
@@ -37,7 +37,10 @@
|
||||
"pages": {
|
||||
"home": {
|
||||
"report_mail": "?subject=⚠️ Signalement sur l`image {picId}&body=Bonjour, %0D%0A%0D%0A Problème sur l`image (garder le type de problème signalé) : %0D%0A%0D%0A contenu inapproprié / absence de floutage sur un élément à anonymiser ou flouter pour des raisons de sécurité /surfloutage (floutage en trop) %0D%0A%0D%0A Lien vers la photo concernée : {link} %0D%0A%0D%0A Précision sur les éléments concernés (en particulier pour les problèmes de floutage - que faut-il flouter ou déflouter?) :",
|
||||
"report_button_text": "Signaler la photo"
|
||||
"report_button_text": "Signaler la photo",
|
||||
"sequence_title": "Voir la séquence",
|
||||
"open_fullscreen": "Mode plein écran",
|
||||
"close_fullscreen": "Mode normal"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Mes Tokens",
|
||||
@@ -47,12 +50,33 @@
|
||||
"sequence_published": "Publiée",
|
||||
"sequence_waiting": "En cours de publication",
|
||||
"sequence_hidden": "Masquée",
|
||||
"sequence_form_title": "Modifier le titre",
|
||||
"hide_sequence_tooltip": "Masque la séquence sur la carte",
|
||||
"back_button": "Retourner à la liste de mes séquences",
|
||||
"delete_sequence_tooltip": "Supprime définitivement la séquence",
|
||||
"hide_photo_tooltip": "Masque les photos sur la carte",
|
||||
"delete_photo_tooltip": "Supprime définitivement les photos",
|
||||
"confirm_pictures_dialog": "⚠️ Les photos sélectionnées vont être définitivement supprimées",
|
||||
"confirm_sequence_dialog": "⚠️ La séquence va être définitivement supprimée",
|
||||
"conf_pic_msg": "⚠️ Les photos sélectionnées vont être définitivement supprimées",
|
||||
"conf_sequence_msg": "⚠️ La séquence va être définitivement supprimée",
|
||||
"button_panel_photos": "Gérer les photos",
|
||||
"button_panel_orientation": "Régler l'orientation",
|
||||
"button_panel_sort": "Trier la séquence",
|
||||
"orientation_panel_title": "Définir l'orientation de la caméra sur le véhicule",
|
||||
"orientation_panel_tooltip": "Faites glisser la zone bleu dans la direction souhaitée",
|
||||
"orientation_input_label": "ou modifiez l'angle ici",
|
||||
"orientation_input_placeholder": "Valeur entre -180 et 180",
|
||||
"orientation_input_error_value": "La valeur doit être entre -180 et 180",
|
||||
"orientation_panel_button": "Valider la position",
|
||||
"orientation_updated": "L'orientation a bien été modifiée",
|
||||
"sort_updated": "La séquence a bien triée",
|
||||
"sort_panel_title": "Réglage du tri de la séquence",
|
||||
"sort_panel_settings": "Trier la séquence par :",
|
||||
"sort_panel_settings_order": "Ordre :",
|
||||
"sort_panel_settings_order_increase": "Croissant",
|
||||
"sort_panel_settings_order_decrease": "Décroissant",
|
||||
"sort_panel_check_gps": "Date du GPS",
|
||||
"sort_panel_check_file": "Date de la caméra",
|
||||
"sort_panel_check_name": "Nom du fichier",
|
||||
"created": "Versement :",
|
||||
"taken": "Prise de vue :",
|
||||
"duration": "Durée :",
|
||||
@@ -75,10 +99,24 @@
|
||||
},
|
||||
"sequences": {
|
||||
"title": "Mes séquences de photos",
|
||||
"filter_date_upload_title": "Filtrer par date de versement :",
|
||||
"filter_date_title": "Filtrer par date de prise de vue :",
|
||||
"radio_date_placeholder": "2024-01-03",
|
||||
"radio_date": "date",
|
||||
"hide_button": "Masquer",
|
||||
"show_button": "Afficher",
|
||||
"delete_button": "Supprimer",
|
||||
"filter_date_reset_button": "Réinitialiser",
|
||||
"filter_date_close_button": "Fermer",
|
||||
"no_sequence_found": "Aucune séquence trouvée",
|
||||
"sequence_name": "Nom",
|
||||
"sequence_photos": "Photos",
|
||||
"sequence_date": "Prise de vue",
|
||||
"sequence_creation": "Versement",
|
||||
"sequence_creation_tooltip": "Filtre par date de versement",
|
||||
"sequence_date_tooltip": "Filtre par date de prise de vue",
|
||||
"reset_filter_button": "Réinitialiser les filtres",
|
||||
"filter_bbox_button": "Chercher dans cette zone",
|
||||
"sequence_status": "Statut",
|
||||
"sequence_published": "Publiée",
|
||||
"sequence_waiting": "En cours de publication",
|
||||
@@ -89,7 +127,7 @@
|
||||
},
|
||||
"share_pictures": {
|
||||
"title": "Pourquoi contribuer à la base de photographies de Panoramax ?",
|
||||
"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, les modifier et les consulter tous 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.",
|
||||
"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.",
|
||||
"alt_img_map": "Illustration d'une femme qui regarde une carte avec son smartphone geolocalisé",
|
||||
"card_photo1": "Des lieux visibles depuis la voie publique",
|
||||
"card_photo2": "Des photos publiées au format 360° ou non",
|
||||
@@ -116,7 +154,7 @@
|
||||
"button_copy": "Copier",
|
||||
"information_subtitle": "Ici, vos photos sont accessibles à tous : ",
|
||||
"information_text1": "Automatiquement floutées dans le respect de la législation.",
|
||||
"information_text2": "Les données déposées seront sous {word}.",
|
||||
"information_text2": "Les données déposées seront publiées sous {word}",
|
||||
"information_text3": "Sous forme «brute» pour des réutilisations variées (ex: préparation des chantiers).",
|
||||
"information_about_title": "Besoin de récupérer les données ?",
|
||||
"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>",
|
||||
@@ -133,9 +171,13 @@
|
||||
"import_word": "importer",
|
||||
"import_type": "Format JPEG uniquement",
|
||||
"subtitle_import": "Dépôt des images",
|
||||
"text_import": "Déposez ici vos fichiers jpg. Chaque image ou série d’images constitue une “séquence”. Vous pourrez ensuite les retrouver dans la section « mes images » et choisir de les masquer, les afficher ou les supprimer.",
|
||||
"title_sequence": "Titre de ma séquence",
|
||||
"description_title_sequence": "Le titre d'une séquence est par défaut la date du jour. Vous pouvez, si vous le souhaitez le modifier ci-dessous.",
|
||||
"text_import": "Déposez ici vos fichiers jpg. Chaque image ou série d’images constitue une « séquence ». Vous pourrez ensuite les retrouver dans la section « mes images » et choisir de les masquer, les afficher ou les supprimer.",
|
||||
"subtitle_process": "Traitements de l'import",
|
||||
"uploading_process": "Téléchargement en cours...",
|
||||
"uploading_process": "Envoi en cours...",
|
||||
"uploading_cancel": "Annuler l'envoi des photos",
|
||||
"cancel_message": "⚠️ Attention, le téléchargement sera interrompu si vous validez et la séquence sera supprimée.",
|
||||
"sequence_title": "Séquence du ",
|
||||
"import": "Imports",
|
||||
"upload_pending": "Transfert en cours...",
|
||||
@@ -143,7 +185,10 @@
|
||||
"no_img_text": "aucune image chargée actuellement",
|
||||
"upload_done": "Le chargement de la séquence est terminé",
|
||||
"sequence_link": "Accéder à cette séquence",
|
||||
"pictures_error": "{count} image n'a pas pu être chargée| {count} image n'ont pas pu être chargées",
|
||||
"edit_title_tooltip": "Modifier le titre de la séquence",
|
||||
"edit_placeholder_input": "Modifier le titre de la séquence",
|
||||
"ok_button": "Valider",
|
||||
"pictures_error": "{count} image n'a pas pu être chargée| {count} images n'ont pas pu être chargées",
|
||||
"sequence_loading_information": "Une fois chargée, la séquence sera en traitement et accessible sur Panoramax dans les prochaines minutes.",
|
||||
"sequence_loaded_information": "La séquence est chargée et est en cours de traitement. Elle sera accessible sur Panoramax dans quelques minutes.",
|
||||
"leave_message": "⚠️ Attention, le téléchargement sera interrompu si vous quittez la page avant la fin.",
|
||||
|
||||
224
src/locales/hu.json
Normal file
@@ -0,0 +1,224 @@
|
||||
{
|
||||
"general": {
|
||||
"title": "Panoramax-példány",
|
||||
"meta": {
|
||||
"title": "Panoramax-példány",
|
||||
"description": "Panoramax, szabad alternatíva a világ fotós feltérképezéséhez"
|
||||
},
|
||||
"header": {
|
||||
"login_text": "Kapcsolódás",
|
||||
"register_text": "Regisztráció",
|
||||
"contribute_text": "Miért működjön közre?",
|
||||
"my_account": "Saját fiók",
|
||||
"upload_text": "+ Fényképek megosztása",
|
||||
"sequences_text": "Saját fényképek",
|
||||
"alt_logo": "A példány logója",
|
||||
"alt_photos": "Képek ikon",
|
||||
"alt_information": "Felhasználó ikon",
|
||||
"alt_settings": "Paraméterek ikon",
|
||||
"alt_logout": "Kijelentkezés ikon",
|
||||
"title": "Panoramax",
|
||||
"beta_text": "Béta verzió",
|
||||
"logout_text": "Kijelentkezés",
|
||||
"my_information_text": "Saját adatok",
|
||||
"my_settings_text": "Saját beállítások",
|
||||
"burger_menu_aria_label_open": "A menü megjelenítése",
|
||||
"burger_menu_aria_label_closed": "A menü bezárása"
|
||||
},
|
||||
"footer": {
|
||||
"panoramax_site": "A Panoramax felfedezése",
|
||||
"information_gitlab": "Forráskód megjelenítése",
|
||||
"gitlab_logo": "Gitlab logó",
|
||||
"ay11_text": "Akadálymentesítés: nem felel meg"
|
||||
},
|
||||
"error_text": "Hiba történt",
|
||||
"success_text": "Sikeres frissítés"
|
||||
},
|
||||
"pages": {
|
||||
"home": {
|
||||
"report_mail": "?subject=⚠️ Report on picture {picId}&body=Hello, %0D%0A%0D%0A Problem on image (keep type of problem reported) : %0D%0A%0D%0A %0D%0A%0D%0A inappropriate content / lack of blurring on an element to be anonymized or blurred for security reasons / overblurring (too much blurring) %0D%0A%0D%0A Link to affected photo : {link} %0D%0A%0D%0A Details of affected elements (especially for blurring problems - what should be blurred or unblurred?) :",
|
||||
"report_button_text": "Fénykép jelentése",
|
||||
"sequence_title": "lásd a sorrendet",
|
||||
"open_fullscreen": "Teljes képernyős mód",
|
||||
"close_fullscreen": "Normál mód"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Saját tokenek",
|
||||
"setting_tooltip": "A token megjelenítése vagy elrejtése"
|
||||
},
|
||||
"sequence": {
|
||||
"sequence_published": "Közzétéve",
|
||||
"sequence_waiting": "Feldolgozás alatt",
|
||||
"sequence_hidden": "Rejtett",
|
||||
"sequence_form_title": "Szerkessze a címet",
|
||||
"hide_sequence_tooltip": "A sorozat elrejtése",
|
||||
"back_button": "Vissza a sorozatlistámhoz",
|
||||
"delete_sequence_tooltip": "A sorozat végleges törlése",
|
||||
"hide_photo_tooltip": "A kiválasztott fényképek elrejtése",
|
||||
"delete_photo_tooltip": "A kiválasztottt fényképek végleges törlése",
|
||||
"conf_pic_msg": "⚠️ A kiválasztott fényképek véglegesen elvesznek",
|
||||
"conf_sequence_msg": "⚠️ A kiválasztott sorozat véglegesen elvész",
|
||||
"button_panel_photos": "Fényképek kezelése",
|
||||
"button_panel_orientation": "Tájolás beállítása",
|
||||
"button_panel_sort": "Rendezési sorrend",
|
||||
"orientation_panel_title": "Az összes kép tájolásának beállítása a sorozatban",
|
||||
"orientation_panel_tooltip": "Húzza a kék dobozt a kívánt irányba",
|
||||
"orientation_input_label": "vagy módosítsa a szöget itt",
|
||||
"orientation_input_placeholder": "-180 és 180 közötti érték",
|
||||
"orientation_input_error_value": "Az értéknek -180 és 180 között kell lennie",
|
||||
"orientation_panel_button": "Pozíció ellenőrzése",
|
||||
"orientation_updated": "A tájolás megváltozott",
|
||||
"sort_updated": "A sorozat jól van rendezve",
|
||||
"sort_panel_title": "Szekvenciás rendezés beállítása",
|
||||
"sort_panel_settings": "Sorrend rendezése:",
|
||||
"sort_panel_settings_order": "Rendelés :",
|
||||
"sort_panel_settings_order_increase": "Növekvő",
|
||||
"sort_panel_settings_order_decrease": "Csökkenő",
|
||||
"sort_panel_check_gps": "GPS dátum",
|
||||
"sort_panel_check_file": "Fájl dátuma",
|
||||
"sort_panel_check_name": "Fájlnév",
|
||||
"created": "Feltöltés ideje:",
|
||||
"taken": "Elkészítés ideje:",
|
||||
"duration": "Hossz:",
|
||||
"duration_begin": "Kezdet:",
|
||||
"duration_end": "Vég:",
|
||||
"camera": "Kamera:",
|
||||
"button_delete": "Törlés",
|
||||
"button_disable": "Elrejtés",
|
||||
"button_enable": "Megjelenítés",
|
||||
"picture_selected": "{count} fénykép kiválasztva| {count} fénykép kiválasztva",
|
||||
"hours": "{count} óra| {count} óra",
|
||||
"minutes": "{count} perc| {count} perc",
|
||||
"seconds": "{count} másodperc| {count} másodperc",
|
||||
"select_text": "Kiválasztás",
|
||||
"unselect_text": "Kiválasztás törlése",
|
||||
"select_shift_text": "Több elemet a Shift segítségével választhat ki",
|
||||
"waiting_process": "A fénykép feldolgozás alatt áll",
|
||||
"broken": "Fénykép-feldolgozási hiba",
|
||||
"no_image": "Nincsenek fényképek ebben a sorozatban"
|
||||
},
|
||||
"sequences": {
|
||||
"title": "Saját fényképsorozatok",
|
||||
"filter_date_upload_title": "Szűrés feltöltés dátuma szerint :",
|
||||
"filter_date_title": "Szűrés forgatás dátuma szerint :",
|
||||
"radio_date_placeholder": "03/01/2024",
|
||||
"radio_date": "dátum",
|
||||
"hide_button": "Elrejt",
|
||||
"show_button": "Előadás",
|
||||
"delete_button": "Töröl",
|
||||
"filter_date_reset_button": "Visszaállítás",
|
||||
"filter_date_close_button": "Bezárás",
|
||||
"no_sequence_found": "Nem található felvétel",
|
||||
"sequence_name": "Név",
|
||||
"sequence_photos": "Fényképek",
|
||||
"sequence_date": "Elkészítés ideje",
|
||||
"sequence_creation": "Feltöltés ideje",
|
||||
"sequence_creation_tooltip": "Szűrés feltöltés dátuma szerint",
|
||||
"sequence_date_tooltip": "Szűrés forgatás dátuma szerint",
|
||||
"reset_filter_button": "Szűrők visszaállítása",
|
||||
"filter_bbox_button": "Keresés ezen a területen",
|
||||
"sequence_status": "Állapot",
|
||||
"sequence_published": "Közzétéve",
|
||||
"sequence_waiting": "Feldolgozás alatt",
|
||||
"sequence_hidden": "Rejtett",
|
||||
"no_sequences_text": "Még nincsenek közzétett fényképei \uD83D\uDE22",
|
||||
"button_upload": "Fényképek feltöltése",
|
||||
"sequence_deleted": "A sorozat törlésre került"
|
||||
},
|
||||
"share_pictures": {
|
||||
"title": "Miért működjön közre a Panoramaxban?",
|
||||
"description": "A Panoramaxban való közreműködés azt jelenti, hogy részt vesz egy közös, független, szabad és újrafelhasználható digitális erőforrás fejlesztésében. A Panoramaxon található minden georeferált fénykép bárki által használható, számos célra, például egy önkormányzat megtekintheti az utai állapotát, vagy egy távközlési vállalat felkészülhet a beavatkozásaira.\n\nMinden közreműködő beküldheti a saját képsorozatait, módosíthatja és megnézheti azokat, ahogyan ezt teheti a közösség más tagjai által feltöltött képekkel is. Az arcok és rendszámtáblák kötelező kitakarását maga a platform automatizálja.",
|
||||
"alt_img_map": "Illusztráció egy nőről, aki egy térképet néz a földrajzi helymeghatározással rendelkező okostelefonján",
|
||||
"card_photo1": "Közútról látható helyek",
|
||||
"card_photo2": "Közzétett fényképek 360°-os vagy más formátumban",
|
||||
"card_photo3": "Könnyen újrafelhasználható fényképek",
|
||||
"card_photo4": "Képek gyors és egyszerű megosztása",
|
||||
"card_alt_photo1": "Egy épületet tartalmazó kép",
|
||||
"card_alt_photo2": "Egy 360°-ot megjelenítő kép",
|
||||
"card_alt_photo3": "Egy kép egy térképről mutatóval",
|
||||
"card_alt_photo4": "Egy mutatót ábrázoló kép",
|
||||
"card_description1": "Minden közútról készült kép elfogadható, ha az georeferált és a földről készült.",
|
||||
"card_description2": "A 360°-os képek nem kötelezők: az okostelefonnal készült képek is megfelelők. Az előfeltételek a dátum, a helyszín és a jpg formátum használata.",
|
||||
"card_description3": "Minden fénykép fiók nélkül is könnyedén elérhető és felhasználható: a weboldalon vagy egy szabványos API-n keresztül (STAC szabvány).",
|
||||
"card_description4": "Számos eszköz elérhető a közreműködések lehetővé tételéhez, köztük egy parancssoros és egy webes felület is.",
|
||||
"upload_subtitle": "Töltse fel egyszerűen a fényképeit",
|
||||
"upload_illustration_alt": "Online képfeltöltést ábrázoló illusztráció",
|
||||
"upload_description": "A Panoramax webalkalmazása lehetővé teszi, hogy egy gombnyomással feltöltse az összes fényképét JPG formátumban. Programozói készségek nem szükségesek. Sok kép esetén viszont a parancssoros eszköz használatát javasoljuk.",
|
||||
"upload_button": "+ Képek feltöltése",
|
||||
"command_line_subtitle": "Parancssoros eszköz",
|
||||
"comment_install": "A geovisio parancssoros eszköz telepítése",
|
||||
"comment_upload": "A képfeltöltési parancs indítása a kiválasztott mappán",
|
||||
"description_terminal": "<a href='https://gitlab.com/geovisio/cli' target='_blank' style='color:black'>A parancssor</a> lehetővé teszi, hogy nagy számú képet osszon meg. A folyamat egyszerű, és <a target='_blank' href='https://www.python.org/downloads/' style='color:black'>Pythont (3.8-as vagy újabb verzió)</a> igényli.\n\nAz importálás előtt az eszköz bekéri a bejelentkezési adatait. Amint a képek felöltésre kerültek, azok közzététele előtt feldolgozási idő szükséges.",
|
||||
"terminal_install": "pip install geovisio_cli",
|
||||
"terminal_text": "geovisio upload --api-url {webcím} <FÉNYKÉPMAPPA>",
|
||||
"button_copy": "Másolás",
|
||||
"information_subtitle": "Itt a fényképek mindenki számára elérhetők: ",
|
||||
"information_text1": "Automatikusan kitakarva, a törvényi előírásoknak megfelelően.",
|
||||
"information_text2": "A feltöltött képek a következő alatt lesznek közzétéve: {word}",
|
||||
"information_text3": "Az eredeti formátumban és felbontásban, számos újrafeldolgozáshoz.",
|
||||
"information_about_title": "Hozzá kell férnie a fényképeihez?",
|
||||
"information_about_description": "Az API elérhető az összes metaadat és kép letöltéséhez. <a href='{docLink}' target='_blank' style='color:#0a1f69'>\nTudjon meg többet itt.</a>\nAz adatok <a href='{docTiles}' target='_blank' style='color:#0a1f69'>vektorcsempék</a> formájában is megjelennek.",
|
||||
"doc_subtitle": "Segítségre van szüksége a Panoramaxban történő közreműködéshez?",
|
||||
"doc_description": "A Panoramax dokumentációja elérhető tőlünk, oktatóanyagokat pedig a geo-commons fórumán találhat.",
|
||||
"doc_button": "A dokumentáció megtekintése",
|
||||
"doc_illustration_alt": "Illusztráció egy karakterről dokumentumokkal"
|
||||
},
|
||||
"upload": {
|
||||
"title": "Közreműködés a Panoramax projektben",
|
||||
"description": "Nagy számú fényképhez a parancssoros eszköz jobban megfelelő.",
|
||||
"know_more_button": "További tudnivalók",
|
||||
"input_label": "Húzza ide a képeket, vagy kattintson a",
|
||||
"import_word": "feltöltésre",
|
||||
"import_type": "Csak JPEG formátum",
|
||||
"subtitle_import": "Képfeltöltés",
|
||||
"title_sequence": "Sorozat címe",
|
||||
"description_title_sequence": "Alapértelmezés szerint a sorozat cím a nap dátuma lesz. Ha akarja, itt szerkesztheti a címet.",
|
||||
"text_import": "Ide töltse fel a JPG-fájljait. Minden kép vagy képsorozat egy „sorozatot” alkot. Megtalálhatja azokat a „saját fényképek” szakaszban, és elrejtheti, megjelenítheti vagy törölheti azokat.",
|
||||
"subtitle_process": "Feltöltés feldolgozása",
|
||||
"uploading_process": "Feltöltés folyamatban…",
|
||||
"uploading_cancel": "Fényképek küldésének megszakítása",
|
||||
"cancel_message": "⚠️ Felhívjuk figyelmét, hogy a letöltés megszakad, ha érvényesíti, és a sorozat törlődik.",
|
||||
"sequence_title": "Sorozat ",
|
||||
"import": "Feltöltések",
|
||||
"upload_pending": "Feltöltés folyamatban…",
|
||||
"images_count_text": "Feltöltött képek",
|
||||
"no_img_text": "még nem volt képfeltöltés",
|
||||
"upload_done": "A sorozat feltöltése elkészült",
|
||||
"sequence_link": "A sorozat megjelenítése",
|
||||
"edit_title_tooltip": "A sorozat címének szerkesztése",
|
||||
"edit_placeholder_input": "A sorozat címének szerkesztése",
|
||||
"ok_button": "OK",
|
||||
"pictures_error": "{count} kép feltöltése nem sikerült| {count} kép feltöltése nem sikerült",
|
||||
"sequence_loading_information": "Amint feltöltötte, a sorozat feldolgozásra, majd közzétételre került a Panoramaxon (általában néhány perc múlva).",
|
||||
"sequence_loaded_information": "A sorozatok fel lettek töltve, és feldolgozás alatt vannak. Néhány perc múlva nyilvánosan elérhetőnek kellene lenniük a Panoramaxon.",
|
||||
"leave_message": "⚠️ FIGYELMEZTETÉS, a feltöltés meg fog szakadni, ha a végezte előtt hagyja el a lapot.",
|
||||
"error_button": "Hibák megjelenítése",
|
||||
"modal_error_title": "A hibában érintett képek"
|
||||
},
|
||||
"ay11": {
|
||||
"title": "Déclaration d’accessibilité",
|
||||
"date": "Établie le 18 septembre 2023.",
|
||||
"introduction": "IGN s’engage à rendre son service accessible, conformément à l’article 47 de la loi n° 2005-102 du 11 février 2005.\n Cette déclaration d’accessibilité s’applique à Panoramax Instance IGN : https://panoramax.ign.fr",
|
||||
"subtitle_conformity": "État de conformité",
|
||||
"conformity_text": "Panoramax Instance IGN est non conforme avec le ",
|
||||
"conformity_text2": "Le site n’a encore pas été audité.",
|
||||
"subtitle_conformity2": "Contenus non accessibles",
|
||||
"subtitle_increase": "Amélioration et contact",
|
||||
"increase_text": "Si vous n’arrivez pas à accéder à un contenu ou à un service, vous pouvez\n contacter le responsable de Panoramax Instance IGN pour être orienté vers une alternative accessible ou obtenir le contenu sous une autre forme.",
|
||||
"phone": "Téléphone : +33 14 398 84 61",
|
||||
"email_text": "E-mail :",
|
||||
"email": "signalement.ign@panoramax.fr",
|
||||
"address": "Adresse : IGN, Saint-Mandé",
|
||||
"increase_info": "Nous essayons de répondre dans les 5 jours ouvrés.",
|
||||
"subtitle_to_do": "Voie de recours",
|
||||
"to_do_text": "Cette procédure est à utiliser dans le cas suivant : vous avez signalé au responsable du site internet un défaut d’accessibilité qui vous\n empêche d’accéder à un contenu ou à un des services du portail et vous n’avez pas obtenu de réponse satisfaisante. \n vous pouvez :",
|
||||
"write_message": "Écrire un message au",
|
||||
"defenseur_droits": "Défenseur des droits",
|
||||
"contact": "Contacter",
|
||||
"contact_text": "le délégué du Défenseur des droits dans votre région",
|
||||
"send_letter": "Envoyer un courrier par la poste (gratuit, ne pas mettre de\n timbre):\n Défenseur des droits\n Libre réponse 71120 75342 Paris CEDEX 07",
|
||||
"end": "Cette déclaration d’accessibilité a été créé le\n 18 septembre 2023 grâce au",
|
||||
"generator_betagouv": "Générateur de Déclaration d’Accessibilité de BetaGouv"
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/main.ts
@@ -1,41 +1,64 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import VueMatomo from 'vue-matomo'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import axios from 'axios'
|
||||
import VueAxios from 'vue-axios'
|
||||
import { globalCookiesConfig } from 'vue3-cookies'
|
||||
import { createMetaManager } from 'vue-meta'
|
||||
import { VueDraggableResizable } from 'vue-draggable-resizable-vue3'
|
||||
import VCalendar from 'v-calendar'
|
||||
import 'v-calendar/style.css'
|
||||
import { pinia } from './store'
|
||||
import fr from './locales/fr.json'
|
||||
import en from './locales/en.json'
|
||||
import hu from './locales/hu.json'
|
||||
import cs from './locales/cs.json'
|
||||
import './assets/main.scss'
|
||||
import 'bootstrap/dist/css/bootstrap.css'
|
||||
import 'bootstrap-icons/font/bootstrap-icons.css'
|
||||
import 'geovisio/build/index.css'
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
_paq: any[]
|
||||
}
|
||||
}
|
||||
|
||||
axios.defaults.baseURL = import.meta.env.VITE_API_URL
|
||||
axios.defaults.withCredentials = true
|
||||
const matomoHost = import.meta.env.VITE_MATOMO_HOST
|
||||
const matomoSiteId = import.meta.env.VITE_MATOMO_SITE_ID
|
||||
const matomoExist = matomoHost && matomoSiteId
|
||||
|
||||
const i18n = createI18n({
|
||||
locale: 'fr',
|
||||
fallbackLocale: 'fr',
|
||||
locale: navigator.language.split('-')[0],
|
||||
fallbackLocale: 'en',
|
||||
warnHtmlMessage: false,
|
||||
globalInjection: true,
|
||||
legacy: false,
|
||||
messages: {
|
||||
fr
|
||||
fr,
|
||||
en,
|
||||
hu,
|
||||
cs
|
||||
}
|
||||
})
|
||||
globalCookiesConfig({
|
||||
expireTimes: '7d'
|
||||
})
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(pinia)
|
||||
app.use(i18n)
|
||||
app.use(router)
|
||||
app.use(VueAxios, axios)
|
||||
app.provide('axios', app.config.globalProperties.axios)
|
||||
app.use(createMetaManager())
|
||||
app.use(VueDraggableResizable)
|
||||
app.use(VCalendar)
|
||||
if (matomoExist) {
|
||||
app.use(VueMatomo, {
|
||||
host: matomoHost,
|
||||
siteId: matomoExist
|
||||
})
|
||||
}
|
||||
app.mount('#app')
|
||||
if (matomoExist) {
|
||||
window._paq.push(['trackPageView']) // Pour suivre les visites sur vos pages
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { useCookies } from 'vue3-cookies'
|
||||
import type {
|
||||
RouteRecordRaw,
|
||||
NavigationGuardNext,
|
||||
RouteLocationNormalized
|
||||
} from 'vue-router'
|
||||
import axios from 'axios'
|
||||
import { getAuthRoute } from '@/utils/auth'
|
||||
import { getAuthRoute, hasASessionCookieDecoded } from '@/utils/auth'
|
||||
import HomeView from '../views/HomeView.vue'
|
||||
import MyInformationView from '../views/MyInformationView.vue'
|
||||
import MySettingsView from '../views/MySettingsView.vue'
|
||||
@@ -15,7 +14,6 @@ import MySequenceView from '../views/MySequenceView.vue'
|
||||
import SharePicturesView from '../views/SharePicturesView.vue'
|
||||
import UploadPicturesView from '../views/UploadPicturesView.vue'
|
||||
import Ay11View from '../views/Ay11View.vue'
|
||||
const { cookies } = useCookies()
|
||||
let routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/',
|
||||
@@ -77,8 +75,9 @@ router.beforeResolve(
|
||||
to.name === 'upload-pictures'
|
||||
|
||||
if (siteLoggedRoutes) {
|
||||
if (!isSiteLogged()) goToLoginPage(to.path)
|
||||
else return next()
|
||||
if (!isSiteLogged()) {
|
||||
goToLoginPage(to.path)
|
||||
} else return next()
|
||||
}
|
||||
if (to.name === 'my-information') {
|
||||
try {
|
||||
@@ -95,7 +94,8 @@ router.beforeResolve(
|
||||
)
|
||||
|
||||
function isSiteLogged(): boolean {
|
||||
return !!cookies.get('user_id')
|
||||
const cookie = hasASessionCookieDecoded()
|
||||
return !!(cookie && cookie.account)
|
||||
}
|
||||
|
||||
async function isKeycloakLogout(): Promise<{ status: number }> {
|
||||
|
||||
@@ -3,7 +3,8 @@ import { defineStore } from 'pinia'
|
||||
export const useSequenceStore = defineStore('sequence', {
|
||||
state: () => ({
|
||||
toastText: <string>'',
|
||||
toastLook: <string>''
|
||||
toastLook: <string>'',
|
||||
picId: <string>''
|
||||
}),
|
||||
actions: {
|
||||
addToastText(text: string, look: string): void {
|
||||
@@ -12,6 +13,9 @@ export const useSequenceStore = defineStore('sequence', {
|
||||
setTimeout(() => {
|
||||
this.toastText = ''
|
||||
}, 3000)
|
||||
},
|
||||
addSequence(id: string): void {
|
||||
this.picId = id
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
88
src/tests/unit/components/EditText.spec.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import { test, describe, expect, vi } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import EditText from '../../../components/EditText.vue'
|
||||
import Button from '../../../components/Button.vue'
|
||||
import i18n from '../config'
|
||||
describe('Template', () => {
|
||||
describe('Props', () => {
|
||||
test('should have default props', () => {
|
||||
const wrapper = shallowMount(EditText, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
}
|
||||
})
|
||||
expect(wrapper.vm.defaultText).toBe(null)
|
||||
})
|
||||
})
|
||||
describe('When the props are filled', () => {
|
||||
test('should render the component with the defaultText', () => {
|
||||
const wrapper = shallowMount(EditText, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
defaultText: 'My default text'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="title">My default text</span>')
|
||||
expect(wrapper.html()).contains('icon="bi bi-pen"')
|
||||
})
|
||||
})
|
||||
describe('When the component is in editable mode', () => {
|
||||
test('should render the component with the element to edit the title', async () => {
|
||||
const wrapper = shallowMount(EditText, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
}
|
||||
})
|
||||
wrapper.vm.isEditTitle = true
|
||||
wrapper.vm.titleToEdit = 'title to edit'
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.html()).contains('text="title to edit"')
|
||||
expect(wrapper.html()).contains('icon="bi bi-x"')
|
||||
expect(wrapper.html()).contains('text="Valider"')
|
||||
})
|
||||
test('should valid the name and emit', async () => {
|
||||
const wrapper = shallowMount(EditText, {
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
components: {
|
||||
Button
|
||||
}
|
||||
}
|
||||
})
|
||||
wrapper.vm.isEditTitle = true
|
||||
wrapper.vm.titleToEdit = 'title to edit'
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const validationButton = wrapper.findComponent('#valid-button')
|
||||
await validationButton.vm.$emit('trigger')
|
||||
|
||||
expect(wrapper.emitted()).toHaveProperty('triggerNewText')
|
||||
expect(wrapper.emitted().triggerNewText[0][0]).toEqual(
|
||||
wrapper.vm.titleToEdit
|
||||
)
|
||||
})
|
||||
test('should close the edit mode', async () => {
|
||||
const wrapper = shallowMount(EditText, {
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
components: {
|
||||
Button
|
||||
}
|
||||
}
|
||||
})
|
||||
wrapper.vm.isEditTitle = true
|
||||
wrapper.vm.titleToEdit = 'title to edit'
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const closeButton = wrapper.findComponent('#close-button')
|
||||
await closeButton.vm.$emit('trigger')
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.vm.isEditTitle).toEqual(false)
|
||||
expect(wrapper.vm.titleToEdit).toEqual(null)
|
||||
})
|
||||
})
|
||||
})
|
||||
39
src/tests/unit/components/Footer.spec.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { test, describe, expect } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import Footer from '../../../components/Footer.vue'
|
||||
import Link from '../../../components/Link.vue'
|
||||
import i18n from '../config'
|
||||
describe('Template', () => {
|
||||
test('should have the links without the ay11 link', () => {
|
||||
const wrapper = shallowMount(Footer, {
|
||||
global: {
|
||||
stubs: { Link },
|
||||
plugins: [i18n]
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('href="https://panoramax.fr/"')
|
||||
expect(wrapper.html()).contains('logo.jpeg')
|
||||
expect(wrapper.html()).contains('Découvrir Panoramax')
|
||||
|
||||
expect(wrapper.html()).contains('href="https://gitlab.com/geovisio"')
|
||||
expect(wrapper.html()).contains('gitlab-logo.svg')
|
||||
expect(wrapper.html()).contains('Voir le code')
|
||||
})
|
||||
test('should have the ay11 link', () => {
|
||||
Object.create(window)
|
||||
const url = 'http://test.ign.fr'
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: {
|
||||
href: url
|
||||
},
|
||||
writable: true // possibility to override
|
||||
})
|
||||
const wrapper = shallowMount(Footer, {
|
||||
global: {
|
||||
stubs: { Link },
|
||||
plugins: [i18n]
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('Accessibilité : non conforme')
|
||||
})
|
||||
})
|
||||
@@ -28,7 +28,7 @@ const i18n = createI18n({
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: []
|
||||
routes: [{ path: '/', component: { template: '<div></div>' } }]
|
||||
})
|
||||
|
||||
describe('Template', () => {
|
||||
@@ -96,7 +96,9 @@ describe('Template', () => {
|
||||
})
|
||||
describe('When the user is logged', () => {
|
||||
it('should render the component with good wording keys', async () => {
|
||||
vi.spyOn(useCookies().cookies, 'get').mockReturnValue('user_id=id')
|
||||
vi.spyOn(useCookies().cookies, 'get').mockReturnValue(
|
||||
'.eJw9y0EKgzAQQNG7zLoDJpmYxMuUySRDra0pooUi3r3SRZcf_tuBRdo2rzDsMBYYgFxRytljkeyQrK0YVT16m6OhUlIihgvM_Kznfa88n9V4W2_XnzcuiqgmDBQMUtYec00WO3XqAndd7OUvXkt7j6Uup5vqRx6NJziOL8SPLNU.ZVy19Q.4DkVxu-LSF11uREkn6YIwHbn_0U'
|
||||
)
|
||||
|
||||
import.meta.env.VITE_API_URL = 'api-url/'
|
||||
const wrapper = shallowMount(Header, {
|
||||
@@ -116,7 +118,7 @@ describe('Template', () => {
|
||||
expect(wrapper.html()).contains('general.header.upload_text')
|
||||
expect(wrapper.html()).contains('data-test="link-logged-upload"')
|
||||
expect(wrapper.html()).contains('<account-button')
|
||||
expect(wrapper.html()).contains('username="UI"')
|
||||
expect(wrapper.html()).contains('username="J"')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
30
src/tests/unit/components/InformationCard.spec.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { test, describe, expect } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import InformationCard from '../../../components/InformationCard.vue'
|
||||
import i18n from '../config'
|
||||
describe('Template', () => {
|
||||
describe('Props', () => {
|
||||
test('should have default props', () => {
|
||||
const wrapper = shallowMount(InformationCard, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
}
|
||||
})
|
||||
expect(wrapper.vm.text).toBe(null)
|
||||
expect(wrapper.vm.look).toBe('')
|
||||
})
|
||||
test('should have all the props filled', () => {
|
||||
const wrapper = shallowMount(InformationCard, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
text: 'my text',
|
||||
look: 'my-look'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('my text</p>')
|
||||
expect(wrapper.html()).contains('class="information-block my-look"')
|
||||
})
|
||||
})
|
||||
})
|
||||
43
src/tests/unit/components/Input.spec.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { test, describe, expect } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import Input from '../../../components/Input.vue'
|
||||
import i18n from '../config'
|
||||
describe('Template', () => {
|
||||
describe('Props', () => {
|
||||
test('should have default props', () => {
|
||||
const wrapper = shallowMount(Input, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
}
|
||||
})
|
||||
expect(wrapper.vm.text).toBe(null)
|
||||
expect(wrapper.vm.placeholder).toBe('')
|
||||
})
|
||||
})
|
||||
describe('When the props are filled', () => {
|
||||
test('should render the button with the placeholder', () => {
|
||||
const wrapper = shallowMount(Input, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
placeholder: 'My placeholder'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('placeholder="My placeholder"')
|
||||
})
|
||||
test('should emit an input event with the prop value', async () => {
|
||||
const wrapper = shallowMount(Input, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
text: 'My text'
|
||||
}
|
||||
})
|
||||
await wrapper.trigger('input')
|
||||
expect(wrapper.emitted()).toHaveProperty('input')
|
||||
expect(wrapper.emitted().input[0][0]).toEqual(wrapper.vm.text)
|
||||
})
|
||||
})
|
||||
})
|
||||
80
src/tests/unit/components/InputUpload.spec.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import { test, describe, expect } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import InputUpload from '../../../components/InputUpload.vue'
|
||||
import i18n from '../config'
|
||||
describe('Template', () => {
|
||||
describe('Props', () => {
|
||||
test('should have default props', () => {
|
||||
const wrapper = shallowMount(InputUpload, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
}
|
||||
})
|
||||
expect(wrapper.vm.text).toBe(null)
|
||||
expect(wrapper.vm.textPictureType).toBe(null)
|
||||
expect(wrapper.vm.textSecondPart).toBe(null)
|
||||
expect(wrapper.vm.accept).toBe('')
|
||||
})
|
||||
test('should have all the props filled', () => {
|
||||
const wrapper = shallowMount(InputUpload, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
text: 'my text',
|
||||
textPictureType: 'my textPictureType',
|
||||
textSecondPart: 'my textSecondPart',
|
||||
accept: 'accept'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('accept="accept"')
|
||||
expect(wrapper.html()).contains('my text')
|
||||
expect(wrapper.html()).contains('my textSecondPart')
|
||||
expect(wrapper.html()).contains('my textPictureType')
|
||||
})
|
||||
})
|
||||
describe('When the input is dragover', () => {
|
||||
test('should set the component dragging state', async () => {
|
||||
const wrapper = shallowMount(InputUpload, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
}
|
||||
})
|
||||
await wrapper.find('label').trigger('dragover')
|
||||
expect(wrapper.vm.isDragging).toBe(true)
|
||||
expect(wrapper.html()).contains('class="file-upload dragging"')
|
||||
})
|
||||
})
|
||||
describe('When the input is dragleave', () => {
|
||||
test('should set the component dragging state', async () => {
|
||||
const wrapper = shallowMount(InputUpload, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
}
|
||||
})
|
||||
await wrapper.find('label').trigger('dragover')
|
||||
await wrapper.find('label').trigger('dragleave')
|
||||
|
||||
expect(wrapper.vm.isDragging).toBe(false)
|
||||
expect(wrapper.html()).contains('class="file-upload"')
|
||||
})
|
||||
})
|
||||
describe('When the input is dropped', () => {
|
||||
test('should emit with value', async () => {
|
||||
const wrapper = shallowMount(InputUpload, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
}
|
||||
})
|
||||
const dataTransfer = {
|
||||
files: [{ type: 'image/jpeg' }]
|
||||
}
|
||||
const label = await wrapper.find('label')
|
||||
await label.trigger('drop', { dataTransfer })
|
||||
|
||||
expect(wrapper.emitted()).toHaveProperty('trigger')
|
||||
expect(wrapper.emitted().trigger[0][0]).toEqual(dataTransfer.files)
|
||||
})
|
||||
})
|
||||
// TODO TEST -> When the input is changed should emit with value
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
import { it, describe, expect } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import BetaText from '../../../components/InstanceName.vue'
|
||||
import InstanceName from '../../../components/InstanceName.vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import fr from '../../../locales/fr.json'
|
||||
|
||||
@@ -16,7 +16,7 @@ const i18n = createI18n({
|
||||
|
||||
describe('Template', () => {
|
||||
it('should render the component with good wording keys', async () => {
|
||||
const wrapper = shallowMount(BetaText, {
|
||||
const wrapper = shallowMount(InstanceName, {
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
mocks: {
|
||||
@@ -17,7 +17,7 @@ const i18n = createI18n({
|
||||
})
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: []
|
||||
routes: [{ path: '/', component: { template: '<div></div>' } }]
|
||||
})
|
||||
const stubs = {
|
||||
'router-link': {
|
||||
|
||||
49
src/tests/unit/components/Modal.spec.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import { it, describe, expect } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import Modal from '../../../components/Modal.vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import fr from '../../../locales/fr.json'
|
||||
|
||||
const i18n = createI18n({
|
||||
locale: 'fr',
|
||||
fallbackLocale: 'fr',
|
||||
globalInjection: true,
|
||||
legacy: false,
|
||||
messages: {
|
||||
fr
|
||||
}
|
||||
})
|
||||
|
||||
describe('Template', () => {
|
||||
describe('Props', () => {
|
||||
it('should render the default props', async () => {
|
||||
document.body.innerHTML = '<div id="bs-modal"></div>'
|
||||
const wrapper = shallowMount(Modal, {
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
expect(wrapper.vm.title).toEqual('')
|
||||
})
|
||||
it('should render the props filled', async () => {
|
||||
document.body.innerHTML = '<div id="bs-modal"></div>'
|
||||
const wrapper = shallowMount(Modal, {
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
title: 'My title'
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.vm.title).toEqual('My title')
|
||||
expect(wrapper.html()).contains('My title')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -7,7 +7,7 @@ vi.mock('vue-router')
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: []
|
||||
routes: [{ path: '/', component: { template: '<div></div>' } }]
|
||||
})
|
||||
describe('Template', () => {
|
||||
describe('Props', () => {
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { it, describe, expect } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import PanelOrientationManagement from '../../../../components/sequence/PanelOrientationManagement.vue'
|
||||
import i18n from '../../config'
|
||||
|
||||
describe('Template', () => {
|
||||
describe('Props', () => {
|
||||
it('should have default props', () => {
|
||||
const wrapper = shallowMount(PanelOrientationManagement, {
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.vm.roadDegrees).toBe(0)
|
||||
expect(wrapper.vm.seqBruteDeg).toBe(0)
|
||||
})
|
||||
describe('When the component have props filled', () => {
|
||||
it('should render the component all the element', () => {
|
||||
const wrapper = shallowMount(PanelOrientationManagement, {
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
roadDegrees: 45,
|
||||
seqBruteDeg: 22
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains(
|
||||
'pages.sequence.orientation_panel_title'
|
||||
)
|
||||
expect(wrapper.html()).contains(
|
||||
'pages.sequence.orientation_input_label'
|
||||
)
|
||||
expect(wrapper.html()).contains(
|
||||
'pages.sequence.orientation_input_placeholder'
|
||||
)
|
||||
expect(wrapper.html()).contains('<widget-orientation')
|
||||
expect(wrapper.html()).contains('<information-card')
|
||||
expect(wrapper.html()).contains('<input')
|
||||
expect(wrapper.html()).contains('<button')
|
||||
expect(wrapper.html()).contains(
|
||||
'type="number" min="-180" max="180" text="-23"'
|
||||
)
|
||||
})
|
||||
})
|
||||
describe('Emit functions', () => {
|
||||
it('should emit triggerAngle event', async () => {
|
||||
const wrapper = shallowMount(PanelOrientationManagement, {
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
roadDegrees: 45,
|
||||
seqBruteDeg: 22
|
||||
}
|
||||
})
|
||||
const valueToEmit = 22 - 45
|
||||
await wrapper.vm.triggerAngle(valueToEmit)
|
||||
expect(wrapper.emitted('triggerAngle')).toHaveLength(1)
|
||||
expect(wrapper.emitted('triggerAngle')[0]).toEqual([valueToEmit])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
347
src/tests/unit/components/sequence/PanelPhotosManagement.spec.js
Normal file
@@ -0,0 +1,347 @@
|
||||
import { it, describe, expect } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import PanelPhotosManagement from '../../../../components/sequence/PanelPhotosManagement.vue'
|
||||
import i18n from '../../config'
|
||||
import { createPinia } from 'pinia'
|
||||
const pinia = createPinia()
|
||||
|
||||
describe('Template', () => {
|
||||
describe('Props', () => {
|
||||
it('should have default props', () => {
|
||||
const wrapper = shallowMount(PanelPhotosManagement, {
|
||||
global: {
|
||||
plugins: [i18n, pinia],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.vm.isSequenceOwner).toBe(false)
|
||||
expect(wrapper.vm.imagesSelectedHaveDifferentStatus).toBe(false)
|
||||
expect(wrapper.vm.itemSelected).toBe('')
|
||||
expect(wrapper.vm.menuHeight).toBe('0')
|
||||
expect(wrapper.vm.fullImagesToDelete).toStrictEqual([])
|
||||
expect(wrapper.vm.selfLink).toStrictEqual([])
|
||||
expect(wrapper.vm.paginationLinks).toStrictEqual([])
|
||||
expect(wrapper.vm.pictures).toStrictEqual([])
|
||||
expect(wrapper.vm.picturesToDelete).toStrictEqual([])
|
||||
expect(wrapper.vm.sequence).toStrictEqual({})
|
||||
})
|
||||
describe('When the component have props filled', () => {
|
||||
it('should render the component with images selected', () => {
|
||||
const pictures = [
|
||||
{
|
||||
assets: { thumb: { href: 'thumbHref' }, hd: { href: 'hdHref' } },
|
||||
properties: { datetime: new Date(), 'geovisio:status': 'ready' },
|
||||
id: 'pictureId',
|
||||
bbox: [1, 2, 3, 4]
|
||||
}
|
||||
]
|
||||
const sequence = [
|
||||
{
|
||||
id: 'seqId',
|
||||
title: 'title',
|
||||
description: 'descr',
|
||||
license: 'license',
|
||||
created: new Date(),
|
||||
taken: 'taken date',
|
||||
location: 'location',
|
||||
imageCount: 4,
|
||||
duration: 'duration',
|
||||
camera: 'camera',
|
||||
cameraModel: 'camera model',
|
||||
status: 'ready',
|
||||
providers: [{ name: 'provider', roles: ['role1'] }],
|
||||
extent: {
|
||||
temporal: { interval: ['date'] },
|
||||
spatial: { bbox: ['1', '2'] }
|
||||
}
|
||||
}
|
||||
]
|
||||
const paginationLinks = [
|
||||
{
|
||||
href: 'href',
|
||||
rel: 'rel',
|
||||
title: 'title',
|
||||
type: 'type'
|
||||
}
|
||||
]
|
||||
const selfLink = [
|
||||
{
|
||||
href: 'href',
|
||||
rel: 'self',
|
||||
title: 'title',
|
||||
type: 'type'
|
||||
}
|
||||
]
|
||||
const fullImagesToDelete = [
|
||||
{
|
||||
assets: { thumb: { href: 'thumbHref' }, hd: { href: 'hdHref' } },
|
||||
properties: { datetime: new Date(), 'geovisio:status': 'ready' },
|
||||
id: 'pictureId',
|
||||
bbox: [1, 2, 3, 4]
|
||||
}
|
||||
]
|
||||
const wrapper = shallowMount(PanelPhotosManagement, {
|
||||
global: {
|
||||
plugins: [i18n, pinia],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
isSequenceOwner: true,
|
||||
imagesSelectedHaveDifferentStatus: true,
|
||||
itemSelected: 'pictureId',
|
||||
menuHeight: '10px',
|
||||
pictures,
|
||||
picturesToDelete: ['1', '2'],
|
||||
sequence,
|
||||
paginationLinks,
|
||||
selfLink,
|
||||
fullImagesToDelete
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('<image-item')
|
||||
expect(wrapper.html()).contains('selectedonmap="true"')
|
||||
expect(wrapper.html()).contains('href="thumbHref"')
|
||||
expect(wrapper.html()).contains('hrefhd="hdHref"')
|
||||
expect(wrapper.html()).contains('ischecked="false"')
|
||||
expect(wrapper.html()).contains('isindeterminate="true"')
|
||||
expect(wrapper.html()).contains('pages.sequence.picture_selected')
|
||||
expect(wrapper.html()).contains(
|
||||
'icon="bi bi-eye" disabled="false" isloading="false"'
|
||||
)
|
||||
expect(wrapper.html()).contains(
|
||||
'icon="bi bi-trash" disabled="false" isloading="false"'
|
||||
)
|
||||
expect(wrapper.html()).contains('pages.sequence.hide_photo_tooltip')
|
||||
expect(wrapper.html()).contains('pages.sequence.delete_photo_tooltip')
|
||||
expect(wrapper.html()).contains('<pagination')
|
||||
expect(wrapper.html()).contains('selflink="')
|
||||
})
|
||||
it('should render the component with no images', () => {
|
||||
const pictures = []
|
||||
const sequence = [
|
||||
{
|
||||
id: 'seqId',
|
||||
title: 'title',
|
||||
description: 'descr',
|
||||
license: 'license',
|
||||
created: new Date(),
|
||||
taken: 'taken date',
|
||||
location: 'location',
|
||||
imageCount: 4,
|
||||
duration: 'duration',
|
||||
camera: 'camera',
|
||||
cameraModel: 'camera model',
|
||||
status: 'ready',
|
||||
providers: [{ name: 'provider', roles: ['role1'] }],
|
||||
extent: {
|
||||
temporal: { interval: ['date'] },
|
||||
spatial: { bbox: ['1', '2'] }
|
||||
}
|
||||
}
|
||||
]
|
||||
const paginationLinks = []
|
||||
const selfLink = [
|
||||
{
|
||||
href: 'href',
|
||||
rel: 'self',
|
||||
title: 'title',
|
||||
type: 'type'
|
||||
}
|
||||
]
|
||||
const fullImagesToDelete = []
|
||||
const wrapper = shallowMount(PanelPhotosManagement, {
|
||||
global: {
|
||||
plugins: [i18n, pinia],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
isSequenceOwner: true,
|
||||
imagesSelectedHaveDifferentStatus: false,
|
||||
itemSelected: '',
|
||||
menuHeight: '10px',
|
||||
pictures,
|
||||
picturesToDelete: [],
|
||||
sequence,
|
||||
paginationLinks,
|
||||
selfLink,
|
||||
fullImagesToDelete
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="no-photo"')
|
||||
expect(wrapper.html()).contains('pages.sequence.no_image')
|
||||
})
|
||||
it('should render the component with all the images selected', () => {
|
||||
const pictures = [
|
||||
{
|
||||
assets: { thumb: { href: 'thumbHref' }, hd: { href: 'hdHref' } },
|
||||
properties: { datetime: new Date(), 'geovisio:status': 'ready' },
|
||||
id: 'pictureId',
|
||||
bbox: [1, 2, 3, 4]
|
||||
}
|
||||
]
|
||||
const sequence = [
|
||||
{
|
||||
id: 'seqId',
|
||||
title: 'title',
|
||||
description: 'descr',
|
||||
license: 'license',
|
||||
created: new Date(),
|
||||
taken: 'taken date',
|
||||
location: 'location',
|
||||
imageCount: 4,
|
||||
duration: 'duration',
|
||||
camera: 'camera',
|
||||
cameraModel: 'camera model',
|
||||
status: 'ready',
|
||||
providers: [{ name: 'provider', roles: ['role1'] }],
|
||||
extent: {
|
||||
temporal: { interval: ['date'] },
|
||||
spatial: { bbox: ['1', '2'] }
|
||||
}
|
||||
}
|
||||
]
|
||||
const wrapper = shallowMount(PanelPhotosManagement, {
|
||||
global: {
|
||||
plugins: [i18n, pinia],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
isSequenceOwner: true,
|
||||
pictures,
|
||||
picturesToDelete: [1],
|
||||
sequence
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains(
|
||||
'ischecked="true" isindeterminate="false"></input'
|
||||
)
|
||||
})
|
||||
it('should render the component with images hidden', () => {
|
||||
const pictures = [
|
||||
{
|
||||
assets: { thumb: { href: 'thumbHref' }, hd: { href: 'hdHref' } },
|
||||
properties: { datetime: new Date(), 'geovisio:status': 'hidden' },
|
||||
id: 'pictureId',
|
||||
bbox: [1, 2, 3, 4]
|
||||
}
|
||||
]
|
||||
const sequence = [
|
||||
{
|
||||
id: 'seqId',
|
||||
title: 'title',
|
||||
description: 'descr',
|
||||
license: 'license',
|
||||
created: new Date(),
|
||||
taken: 'taken date',
|
||||
location: 'location',
|
||||
imageCount: 4,
|
||||
duration: 'duration',
|
||||
camera: 'camera',
|
||||
cameraModel: 'camera model',
|
||||
status: 'ready',
|
||||
providers: [{ name: 'provider', roles: ['role1'] }],
|
||||
extent: {
|
||||
temporal: { interval: ['date'] },
|
||||
spatial: { bbox: ['1', '2'] }
|
||||
}
|
||||
}
|
||||
]
|
||||
const wrapper = shallowMount(PanelPhotosManagement, {
|
||||
global: {
|
||||
plugins: [i18n, pinia],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
isSequenceOwner: true,
|
||||
pictures,
|
||||
picturesToDelete: [],
|
||||
sequence
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('status="hidden"></image-item')
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('Emit functions', () => {
|
||||
it('should emit triggerInputCheck event', async () => {
|
||||
const wrapper = shallowMount(PanelPhotosManagement, {
|
||||
global: {
|
||||
plugins: [i18n, pinia],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
await wrapper.vm.triggerInputCheck({
|
||||
isChecked: true,
|
||||
isIndeterminate: false
|
||||
})
|
||||
expect(wrapper.emitted('triggerInputCheck')).toHaveLength(1)
|
||||
expect(wrapper.emitted('triggerInputCheck')[0]).toEqual([
|
||||
{ isChecked: true, isIndeterminate: false }
|
||||
])
|
||||
})
|
||||
it('should emit triggerGoToNextPage event', async () => {
|
||||
const wrapper = shallowMount(PanelPhotosManagement, {
|
||||
global: {
|
||||
plugins: [i18n, pinia],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
await wrapper.vm.triggerGoToNextPage('nextPage')
|
||||
expect(wrapper.emitted('triggerGoToNextPage')).toHaveLength(1)
|
||||
expect(wrapper.emitted('triggerGoToNextPage')[0]).toEqual(['nextPage'])
|
||||
})
|
||||
|
||||
it('should emit triggerSelectImageAndMove event', async () => {
|
||||
const wrapper = shallowMount(PanelPhotosManagement, {
|
||||
global: {
|
||||
plugins: [i18n, pinia],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
const picture = {
|
||||
assets: { thumb: { href: 'thumbHref' }, hd: { href: 'hdHref' } },
|
||||
properties: { datetime: new Date(), 'geovisio:status': 'ready' },
|
||||
id: 'pictureId',
|
||||
bbox: [1, 2, 3, 4]
|
||||
}
|
||||
await wrapper.vm.triggerSelectImageAndMove(picture)
|
||||
expect(wrapper.emitted('triggerSelectImageAndMove')).toHaveLength(1)
|
||||
expect(wrapper.emitted('triggerSelectImageAndMove')[0]).toEqual([picture])
|
||||
})
|
||||
|
||||
it('should emit triggerPatchOrDeleteCollectionItems event', async () => {
|
||||
const wrapper = shallowMount(PanelPhotosManagement, {
|
||||
global: {
|
||||
plugins: [i18n, pinia],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
await wrapper.vm.triggerPatchOrDeleteCollectionItems('itemToDelete')
|
||||
expect(
|
||||
wrapper.emitted('triggerPatchOrDeleteCollectionItems')
|
||||
).toHaveLength(1)
|
||||
expect(wrapper.emitted('triggerPatchOrDeleteCollectionItems')[0]).toEqual(
|
||||
['itemToDelete']
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,83 @@
|
||||
import { it, describe, expect } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import PanelSortManagement from '../../../../components/sequence/PanelSortManagement.vue'
|
||||
import i18n from '../../config'
|
||||
|
||||
describe('Template', () => {
|
||||
describe('Props', () => {
|
||||
it('should have default props', () => {
|
||||
const wrapper = shallowMount(PanelSortManagement, {
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.vm.sequenceSorted).toBe(null)
|
||||
})
|
||||
describe('When the component have props filled', () => {
|
||||
it('should render the component all the element', () => {
|
||||
const wrapper = shallowMount(PanelSortManagement, {
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('pages.sequence.sort_panel_title')
|
||||
expect(wrapper.html()).contains('pages.sequence.sort_panel_settings')
|
||||
expect(wrapper.html()).contains(
|
||||
'name="sort" id="gpsdate" label="pages.sequence.sort_panel_check_gps" value=""'
|
||||
)
|
||||
expect(wrapper.html()).contains(
|
||||
'name="sort" id="filedate" label="pages.sequence.sort_panel_check_file" value=""'
|
||||
)
|
||||
expect(wrapper.html()).contains(
|
||||
'name="sort" id="filename" label="pages.sequence.sort_panel_check_name" value=""'
|
||||
)
|
||||
expect(wrapper.html()).contains(
|
||||
'pages.sequence.sort_panel_settings_order'
|
||||
)
|
||||
expect(wrapper.html()).contains('increased="true"></input-switch')
|
||||
})
|
||||
it('should render the component with gpsdate selected', () => {
|
||||
const wrapper = shallowMount(PanelSortManagement, {
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
sequenceSorted: '-gpsdate'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains(
|
||||
'name="sort" id="gpsdate" label="pages.sequence.sort_panel_check_gps" value="gpsdate"'
|
||||
)
|
||||
expect(wrapper.html()).contains('increased="false"></input-switch')
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('Emit functions', () => {
|
||||
it('should emit triggerSort event', async () => {
|
||||
const wrapper = shallowMount(PanelSortManagement, {
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
sequenceSorted: '-gpsdate'
|
||||
}
|
||||
})
|
||||
await wrapper.vm.triggerSort('-gpsdate')
|
||||
expect(wrapper.emitted('triggerSort')).toHaveLength(1)
|
||||
expect(wrapper.emitted('triggerSort')[0]).toEqual(['-gpsdate'])
|
||||
})
|
||||
})
|
||||
})
|
||||
53
src/tests/unit/components/sequence/WidgetOrientation.spec.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import { it, describe, expect } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import WidgetOrientation from '../../../../components/sequence/WidgetOrientation.vue'
|
||||
import i18n from '../../config'
|
||||
|
||||
describe('Template', () => {
|
||||
describe('Props', () => {
|
||||
it('should have default props', () => {
|
||||
const wrapper = shallowMount(WidgetOrientation, {
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.vm.roadDegrees).toBe(0)
|
||||
expect(wrapper.vm.seqBruteDeg).toBe(0)
|
||||
})
|
||||
describe('When the component have props filled', () => {
|
||||
it('should render the component all the element', () => {
|
||||
const wrapper = shallowMount(WidgetOrientation, {
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
roadDegrees: 45,
|
||||
seqBruteDeg: 22
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains(
|
||||
'class="wrapper-img-road" style="transform: rotate(45deg);"'
|
||||
)
|
||||
expect(wrapper.html()).contains(
|
||||
'src="/assets/images/car.svg" alt="" style="transform: rotate(45deg);" class="car-img"'
|
||||
)
|
||||
expect(wrapper.html()).contains(
|
||||
'style="transform: rotate(22deg);" id="rotateWrapper" class="rotate-wrapper"'
|
||||
)
|
||||
expect(wrapper.html()).contains(
|
||||
'src="/assets/images/icon/cursor-arrow.svg"'
|
||||
)
|
||||
expect(wrapper.html()).contains('class="arrow-img arrow-img-1"')
|
||||
expect(wrapper.html()).contains('class="arrow-img arrow-img-2"')
|
||||
})
|
||||
})
|
||||
})
|
||||
// TODO TEST -> All Events
|
||||
})
|
||||
36
src/tests/unit/components/share-pictures/Card.spec.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import { test, describe, expect } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import Card from '../../../../components/share-pictures/Card.vue'
|
||||
import i18n from '../../config'
|
||||
describe('Template', () => {
|
||||
describe('Props', () => {
|
||||
test('should have default props', () => {
|
||||
const wrapper = shallowMount(Card, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
}
|
||||
})
|
||||
expect(wrapper.vm.title).toBe(null)
|
||||
expect(wrapper.vm.description).toBe(null)
|
||||
expect(wrapper.vm.imgSrc).toBe(null)
|
||||
expect(wrapper.vm.imgAlt).toBe(null)
|
||||
})
|
||||
test('should have all the props filled', () => {
|
||||
const wrapper = shallowMount(Card, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
title: 'my title',
|
||||
description: 'my description',
|
||||
imgSrc: 'my-imgSrc.jpg',
|
||||
imgAlt: 'my imgAlt'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('my title</h2>')
|
||||
expect(wrapper.html()).contains('my description</p>')
|
||||
expect(wrapper.html()).contains('/my-imgSrc.jpg')
|
||||
expect(wrapper.html()).contains('alt="my imgAlt"')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
import { it, describe, expect } from 'vitest'
|
||||
import { it, describe, expect, vi, beforeEach } from 'vitest'
|
||||
import {
|
||||
imageStatus,
|
||||
photoToDeleteOrPatchSelected,
|
||||
@@ -6,10 +6,25 @@ import {
|
||||
formatPaginationItems
|
||||
} from '../../views/utils/sequence/index'
|
||||
import { sortByName } from '../../views/utils/upload/index'
|
||||
import { getAuthRoute } from '../../utils/auth'
|
||||
import {
|
||||
getAuthRoute,
|
||||
decodingFlaskCookie,
|
||||
hasASessionCookieDecoded
|
||||
} from '../../utils/auth'
|
||||
import { img } from '../../utils/image'
|
||||
import { title } from '../../utils/index'
|
||||
|
||||
import { useCookies } from 'vue3-cookies'
|
||||
vi.mock('vue3-cookies', () => {
|
||||
const mockCookies = {
|
||||
get: vi.fn(),
|
||||
remove: vi.fn(),
|
||||
keys: vi.fn()
|
||||
}
|
||||
return {
|
||||
useCookies: () => ({
|
||||
cookies: mockCookies
|
||||
})
|
||||
}
|
||||
})
|
||||
describe('imageStatus', () => {
|
||||
it('should render the "status" value', () => {
|
||||
const sequenceStatus = 'hidden'
|
||||
@@ -126,6 +141,42 @@ describe('getAuthRoute', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('decodingFlaskCookie', () => {
|
||||
it('should return a decoded cookie', () => {
|
||||
const cookie =
|
||||
'.eJw9y0EKgzAQQNG7zLoDJpmYxMuUySRDra0pooUi3r3SRZcf_tuBRdo2rzDsMBYYgFxRytljkeyQrK0YVT16m6OhUlIihgvM_Kznfa88n9V4W2_XnzcuiqgmDBQMUtYec00WO3XqAndd7OUvXkt7j6Uup5vqRx6NJziOL8SPLNU.ZVy19Q.4DkVxu-LSF11uREkn6YIwHbn_0U'
|
||||
expect(decodingFlaskCookie(cookie)).toEqual(
|
||||
JSON.stringify({
|
||||
account: {
|
||||
id: '43df4bb5-dcb3-422e-8ff5-52b814dd994a',
|
||||
name: 'jean',
|
||||
oauth_id: '138ccff9-7471-4bf6-be92-0f3f37a0086c',
|
||||
oauth_provider: 'keycloak'
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
describe('hasASessionCookieDecoded', () => {
|
||||
it('should return a cookie parsed', () => {
|
||||
vi.spyOn(useCookies().cookies, 'get').mockReturnValue(
|
||||
'.eJw9y0EKgzAQQNG7zLoDJpmYxMuUySRDra0pooUi3r3SRZcf_tuBRdo2rzDsMBYYgFxRytljkeyQrK0YVT16m6OhUlIihgvM_Kznfa88n9V4W2_XnzcuiqgmDBQMUtYec00WO3XqAndd7OUvXkt7j6Uup5vqRx6NJziOL8SPLNU.ZVy19Q.4DkVxu-LSF11uREkn6YIwHbn_0U'
|
||||
)
|
||||
expect(hasASessionCookieDecoded()).toEqual({
|
||||
account: {
|
||||
id: '43df4bb5-dcb3-422e-8ff5-52b814dd994a',
|
||||
name: 'jean',
|
||||
oauth_id: '138ccff9-7471-4bf6-be92-0f3f37a0086c',
|
||||
oauth_provider: 'keycloak'
|
||||
}
|
||||
})
|
||||
})
|
||||
it('should return null', () => {
|
||||
vi.spyOn(useCookies().cookies, 'get').mockReturnValue(null)
|
||||
expect(hasASessionCookieDecoded()).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('img', () => {
|
||||
it('should render the formated img path', () => {
|
||||
const name = 'my-img'
|
||||
@@ -133,19 +184,6 @@ describe('img', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('title', () => {
|
||||
it('should return the formated title with instance name', () => {
|
||||
import.meta.env.VITE_INSTANCE_NAME = 'my instance'
|
||||
const myTitle = 'my title'
|
||||
expect(title(myTitle)).toEqual('my title my instance')
|
||||
})
|
||||
it('should return the formated title without instance name', () => {
|
||||
import.meta.env.VITE_INSTANCE_NAME = ''
|
||||
const myTitle = 'my title'
|
||||
expect(title(myTitle)).toEqual('my title')
|
||||
})
|
||||
})
|
||||
|
||||
describe('sortByName', () => {
|
||||
it('should return the the list sorted by name', () => {
|
||||
const list1 = [
|
||||
|
||||
@@ -6,7 +6,7 @@ import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: []
|
||||
routes: [{ path: '/', component: { template: '<div></div>' } }]
|
||||
})
|
||||
describe('Template', () => {
|
||||
it('should render the view with the button link', async () => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import * as createAPictureToASequence from '@/views/utils/upload/request'
|
||||
import * as createASequence from '@/views/utils/upload/request'
|
||||
import { formatDate } from '../../../utils/dates'
|
||||
import * as sortByName from '../../../views/utils/upload/index'
|
||||
|
||||
describe('Template', () => {
|
||||
it('should render the view with the input upload and the good wordings', () => {
|
||||
const wrapper = shallowMount(UploadPicturesView, {
|
||||
@@ -80,7 +81,7 @@ describe('Template', () => {
|
||||
spyPicture.mockReturnValue({ data: {} })
|
||||
const sequenceTitle = `Séquence du ${formatDate(
|
||||
new Date(),
|
||||
'Do MMMM YYYY, hh:mm:ss'
|
||||
'Do MMMM YYYY, HH:mm:ss'
|
||||
)}`
|
||||
const body = new FormData()
|
||||
body.append('position', '1')
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import pako from 'pako'
|
||||
import { useCookies } from 'vue3-cookies'
|
||||
const { cookies } = useCookies()
|
||||
|
||||
function getAuthRoute(authRoute: string, nextRoute: string): string {
|
||||
const next = `${location.protocol}//${location.host}${nextRoute}`
|
||||
return `${
|
||||
@@ -5,4 +9,25 @@ function getAuthRoute(authRoute: string, nextRoute: string): string {
|
||||
}api/${authRoute}?next_url=${encodeURIComponent(`${next}`)}`
|
||||
}
|
||||
|
||||
export { getAuthRoute }
|
||||
// This function to decode the flask cookie and have the user information like username
|
||||
function decodingFlaskCookie(cookie: string): string {
|
||||
const cookieFormatted = cookie
|
||||
.split('.')[1]
|
||||
.replace(/_/g, '/')
|
||||
.replace(/-/g, '+')
|
||||
const binaryString = atob(cookieFormatted)
|
||||
const bytes = new Uint8Array(binaryString.length)
|
||||
for (let i = 0; i < binaryString.length; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i)
|
||||
}
|
||||
return pako.inflate(bytes.buffer, { to: 'string' })
|
||||
}
|
||||
|
||||
function hasASessionCookieDecoded(): { account: { name: string } } | null {
|
||||
if (cookies.get('session')) {
|
||||
return JSON.parse(decodingFlaskCookie(cookies.get('session')))
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export { getAuthRoute, hasASessionCookieDecoded, decodingFlaskCookie }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import moment from 'moment'
|
||||
import 'moment/dist/locale/fr'
|
||||
|
||||
function formatDate(date: Date, formatType: string): string {
|
||||
function formatDate(date: Date | null | string, formatType: string): string {
|
||||
const formatDate = moment(date)
|
||||
return formatDate.locale(window.navigator.language).format(formatType)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
export function title(title: string): string {
|
||||
const instanceName = import.meta.env.VITE_INSTANCE_NAME
|
||||
if (instanceName) return `${title} ${instanceName}`
|
||||
return title
|
||||
}
|
||||
|
||||
export function createUrlLink(picId: string): string {
|
||||
return encodeURIComponent(`${window.location.origin}/#focus=pic&pic=${picId}`)
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import axios from 'axios'
|
||||
|
||||
async function getIgnTiles(): Promise<object> {
|
||||
const { data } = await axios.get(
|
||||
'https://wxs.ign.fr/essentiels/static/vectorTiles/styles/PLAN.IGN/attenue.json'
|
||||
)
|
||||
data.sources.plan_ign.scheme = 'xyz'
|
||||
data.sources.plan_ign.attribution = 'Données cartographiques : © IGN'
|
||||
const objIndex = data.layers.findIndex(
|
||||
(el: any) => el.id === 'toponyme - parcellaire - adresse'
|
||||
)
|
||||
data.layers[objIndex].layout = {
|
||||
...data.layers[objIndex].layout,
|
||||
'text-field': ['concat', ['get', 'numero'], ['get', 'indice_de_repetition']]
|
||||
}
|
||||
// Patch tms scheme to xyz to make it compatible for Maplibre GL JS / Mapbox GL JS
|
||||
// Patch num_repetition
|
||||
return data
|
||||
}
|
||||
|
||||
export { getIgnTiles }
|
||||
@@ -1,30 +1,16 @@
|
||||
<template>
|
||||
<main class="entry-page">
|
||||
<section :class="['entry-section', focus]">
|
||||
<Viewer ref="viewerRef" />
|
||||
<main id="homePage" class="entry-page">
|
||||
<section class="entry-section">
|
||||
<Viewer ref="viewerRef"> </Viewer>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watchEffect } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import Viewer from '@/components/Viewer.vue'
|
||||
const emit = defineEmits<{ (e: 'trigger', value: string): void }>()
|
||||
const viewerRef = ref<any>(null)
|
||||
let focus = ref<string>('focus-map')
|
||||
|
||||
watchEffect(() => {
|
||||
if (viewerRef.value && viewerRef.value.viewer) {
|
||||
viewerRef.value.viewer.addEventListener(
|
||||
'focus-changed',
|
||||
(e: { detail: { focus: string } }) => {
|
||||
if (e.detail.focus === 'pic') focus.value = 'focus-pic'
|
||||
else focus.value = 'focus-map'
|
||||
emit('trigger', focus.value)
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
import type ViewerType from '@/components/Viewer.vue'
|
||||
const viewerRef = ref<InstanceType<typeof ViewerType>>()
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.entry-page {
|
||||
@@ -32,23 +18,42 @@ watchEffect(() => {
|
||||
}
|
||||
.entry-section {
|
||||
width: 100%;
|
||||
height: calc(100vh - #{toRem(13.2)});
|
||||
}
|
||||
.focus-pic {
|
||||
.mobile {
|
||||
display: none;
|
||||
}
|
||||
.focus-pic,
|
||||
.logged .entry-section {
|
||||
height: calc(100vh - #{toRem(8)});
|
||||
}
|
||||
.focus-map {
|
||||
height: calc(100vh - #{toRem(13.2)});
|
||||
.full-viewer {
|
||||
.entry-section {
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
.gvs-focus-map .entry-report-button {
|
||||
display: none;
|
||||
}
|
||||
@media (max-width: toRem(76.8)) {
|
||||
.entry-page {
|
||||
padding-top: toRem(11);
|
||||
padding-top: toRem(11.5);
|
||||
overflow: hidden;
|
||||
}
|
||||
.entry-section {
|
||||
height: calc(100vh - #{toRem(11)});
|
||||
height: calc(100dvh - #{toRem(18)});
|
||||
}
|
||||
.full-viewer {
|
||||
height: 100vh;
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
@media (max-width: toRem(50)) {
|
||||
.mobile {
|
||||
display: block;
|
||||
}
|
||||
.desktop {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -87,7 +87,7 @@ onMounted(async () => {
|
||||
<style lang="scss" scoped>
|
||||
.entry-page {
|
||||
padding: toRem(5);
|
||||
min-height: calc(100vh - #{toRem(19.82)});
|
||||
min-height: calc(100vh - #{toRem(8)});
|
||||
background-color: var(--grey);
|
||||
}
|
||||
.settings-title {
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<Card
|
||||
:title="$t('pages.share_pictures.card_photo3')"
|
||||
:description="$t('pages.share_pictures.card_description3')"
|
||||
img-src="map-pointer.svg"
|
||||
img-src="photos.svg"
|
||||
:img-alt="$t('pages.share_pictures.card_alt_photo3')"
|
||||
/>
|
||||
</li>
|
||||
@@ -45,7 +45,7 @@
|
||||
<Card
|
||||
:title="$t('pages.share_pictures.card_photo4')"
|
||||
:description="$t('pages.share_pictures.card_description4')"
|
||||
img-src="pointer.svg"
|
||||
img-src="cloud.svg"
|
||||
:img-alt="$t('pages.share_pictures.card_alt_photo4')"
|
||||
/>
|
||||
</li>
|
||||
@@ -118,11 +118,13 @@
|
||||
<span>{{ $t('pages.share_pictures.information_text3') }}</span>
|
||||
</div>
|
||||
<div class="entry-information-card">
|
||||
<InformationCard
|
||||
:title="$t('pages.share_pictures.information_about_title')"
|
||||
:text="formatTextInfoCard"
|
||||
look="blue"
|
||||
/>
|
||||
<InformationCard :text="formatTextInfoCard" look="blue">
|
||||
<template v-slot:title>
|
||||
<h3 class="subtitle">
|
||||
{{ $t('pages.upload.title') }}
|
||||
</h3>
|
||||
</template>
|
||||
</InformationCard>
|
||||
</div>
|
||||
</section>
|
||||
<section class="section">
|
||||
@@ -268,7 +270,7 @@ ul {
|
||||
.subtitle {
|
||||
@include text(h2);
|
||||
color: var(--blue-dark);
|
||||
margin-bottom: toRem(2);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.upload-button {
|
||||
display: flex;
|
||||
@@ -282,7 +284,16 @@ ul {
|
||||
.entry-information-card {
|
||||
margin-top: toRem(2);
|
||||
}
|
||||
.subtitle {
|
||||
@include text(h2);
|
||||
color: var(--blue-dark);
|
||||
}
|
||||
.icon-block {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.icon-block-img {
|
||||
height: toRem(2);
|
||||
margin-right: toRem(0.5);
|
||||
}
|
||||
.about-subtitle {
|
||||
|
||||