forked from Ivasoft/geovisio-website
Compare commits
1 Commits
fix/maxzoo
...
tech/test-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a64d0b0db |
@@ -1,7 +0,0 @@
|
||||
docs/
|
||||
.git/
|
||||
.idea/
|
||||
dist/
|
||||
node_modules/
|
||||
*.md
|
||||
LICENSE
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -90,6 +90,5 @@ sw.*
|
||||
*.swp
|
||||
|
||||
# Cypress generated screen and videos files
|
||||
cypress/screenshot/
|
||||
cypress/videos/
|
||||
*.cy.ts.mp4
|
||||
src/tests/cypress/screenshot/
|
||||
src/tests/cypress/videos/
|
||||
@@ -5,12 +5,6 @@ stages:
|
||||
|
||||
variables:
|
||||
CYPRESS_CACHE_FOLDER: '$CI_PROJECT_DIR/cache/Cypress'
|
||||
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
|
||||
CI_IMAGE_CACHE: $GITLAB_REGISTRY/$IMAGE_NAME:build_cache
|
||||
DOCKER_TLS_CERTDIR: ""
|
||||
|
||||
before_script:
|
||||
## chmod is unfortunately currently mandatory : https://github.com/nodejs/docker-node/issues/661
|
||||
@@ -62,72 +56,5 @@ deploy:
|
||||
CI: 1
|
||||
script:
|
||||
- yarn install
|
||||
- yarn upgrade geovisio@develop
|
||||
- yarn build
|
||||
|
||||
deploy:develop:
|
||||
rules:
|
||||
# run job only for fork that have the credentials to pull images from the gitlab-registry
|
||||
# and only for merge on 'develop' branch
|
||||
- if: $CI_DEPLOY_PASSWORD == null || $CI_DEPLOY_USER == null
|
||||
when: never
|
||||
- if: $CI_COMMIT_REF_SLUG == "develop"
|
||||
stage: Deploy
|
||||
image: docker:latest
|
||||
services:
|
||||
- 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
|
||||
- docker buildx create --use --name "geovisio-image-builder" --driver=docker-container # use docker-container driver to be able to publish a full cache
|
||||
# login to dockerhub
|
||||
- echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
|
||||
script:
|
||||
# build image using repository as cache
|
||||
- docker buildx build
|
||||
--cache-from "type=registry,ref=$CI_IMAGE_CACHE"
|
||||
--cache-to "type=registry,mode=max,ref=$CI_IMAGE_CACHE"
|
||||
--tag "$CI_REGISTRY_IMAGE:develop"
|
||||
--label "org.opencontainers.image.title=$CI_PROJECT_TITLE"
|
||||
--label "org.opencontainers.image.url=$CI_PROJECT_URL"
|
||||
--label "org.opencontainers.image.created=$CI_JOB_STARTED_AT"
|
||||
--label "org.opencontainers.image.revision=$CI_COMMIT_SHORT_SHA"
|
||||
--load
|
||||
--progress=plain
|
||||
.
|
||||
|
||||
# publish image to dockerhub with the develop tag
|
||||
- docker push "$CI_REGISTRY_IMAGE:develop"
|
||||
|
||||
deploy:latest:
|
||||
# we consider that tag always land on main
|
||||
# and they always should publish a tagged image and the `latest` docker image
|
||||
only:
|
||||
- tags
|
||||
stage: Deploy
|
||||
image: docker:latest
|
||||
services:
|
||||
- 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
|
||||
- docker buildx create --use --name "geovisio-image-builder" --driver=docker-container # use docker-container driver to be able to publish a full cache
|
||||
# login to dockerhub
|
||||
- echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
|
||||
script:
|
||||
# build image using repository as cache
|
||||
- docker buildx build
|
||||
--cache-from "type=registry,ref=$CI_IMAGE_CACHE"
|
||||
--cache-to "type=registry,mode=max,ref=$CI_IMAGE_CACHE"
|
||||
--tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME"
|
||||
--tag "$CI_REGISTRY_IMAGE:latest"
|
||||
--label "org.opencontainers.image.title=$CI_PROJECT_TITLE"
|
||||
--label "org.opencontainers.image.url=$CI_PROJECT_URL"
|
||||
--label "org.opencontainers.image.created=$CI_JOB_STARTED_AT"
|
||||
--label "org.opencontainers.image.revision=$GIT_DESCRIBE"
|
||||
--load
|
||||
--progress=plain
|
||||
.
|
||||
|
||||
# publish image to dockerhub
|
||||
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
|
||||
- docker push $CI_REGISTRY_IMAGE:latest
|
||||
|
||||
39
CHANGELOG.md
39
CHANGELOG.md
@@ -2,41 +2,14 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
Before _0.1.0_ Changelog didn't exist.
|
||||
|
||||
Before _0.1.0_, website development was on rolling release, meaning there are no version tags.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
- GeoVisio web viewer upgraded to 2.1.3, [with alls its changes embedded](https://gitlab.com/geovisio/web-viewer/-/blob/develop/CHANGELOG.md?ref_type=heads#213-2023-08-30).
|
||||
- Dockerfile creates smaller and faster containers, using pre-built website and Nginx for HTTP serving.
|
||||
|
||||
|
||||
## [2.1.0] - 2023-08-29
|
||||
|
||||
### Added
|
||||
|
||||
- A new page `/envoyer` to upload picture with an interface ([#13](https://gitlab.com/geovisio/website/-/issues/13)) :
|
||||
- the user can upload multiples pictures with the interface
|
||||
- the pictures are sorted by name
|
||||
- the user can see all the pictures uploaded and all the errors
|
||||
|
||||
### Changed
|
||||
|
||||
- Website releases now follow the synced `MAJOR.MINOR` API version rule, meaning that any version >= 2.1 of the website will be compatible with corresponding [GeoVisio API](https://gitlab.com/geovisio/api) version.
|
||||
|
||||
### Fixed
|
||||
|
||||
- fix a bug in the header hidden sub menu when authentication is not with keycloak
|
||||
|
||||
|
||||
## [0.1.0] - 2023-07-04
|
||||
## [0.1.0] -
|
||||
|
||||
### Added
|
||||
|
||||
- A new page `/mes-sequences` to access to a list of sequences for a logged user ([#14](https://gitlab.com/geovisio/website/-/issues/14)) :
|
||||
|
||||
- the user can see all his sequences
|
||||
- the user can filter sequences
|
||||
- the user can enter to a specific sequence
|
||||
@@ -47,12 +20,10 @@ Before _0.1.0_, website development was on rolling release, meaning there are no
|
||||
- the user can see all the sequence's photos
|
||||
- the user can disable and delete one or many photo(s) of the sequence
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- 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.1.0...develop
|
||||
[2.1.0]: https://gitlab.com/geovisio/website/-/compare/0.1.0...2.1.0
|
||||
[0.1.0]: https://gitlab.com/geovisio/website/-/commits/0.1.0
|
||||
### Fixed
|
||||
|
||||
53
Dockerfile
53
Dockerfile
@@ -1,53 +0,0 @@
|
||||
#--------------------------------------------------------------
|
||||
#- Build image
|
||||
#-
|
||||
|
||||
FROM node:18.16.0-alpine AS build
|
||||
|
||||
WORKDIR /opt/geovisio
|
||||
|
||||
# Import dependencies files
|
||||
COPY package.json yarn.lock ./
|
||||
|
||||
# Install NodeJS dependencies
|
||||
RUN yarn install --frozen-lockfile
|
||||
|
||||
# Import source code
|
||||
COPY static ./static
|
||||
COPY src ./src
|
||||
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_API_URL=DOCKER_VITE_API_URL
|
||||
ENV VITE_TILES=DOCKER_VITE_TILES
|
||||
|
||||
# Build code
|
||||
ENV PORT=3000
|
||||
RUN yarn deploy
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
#- Final image
|
||||
#-
|
||||
|
||||
FROM nginx:1-alpine
|
||||
RUN apk add bash
|
||||
|
||||
# Retrieve files from build
|
||||
COPY --from=build /opt/geovisio/dist /usr/share/nginx/html
|
||||
|
||||
# Add Docker scripts and Nginx conf
|
||||
COPY docker/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY docker/docker-entrypoint.sh /etc/nginx/docker-entrypoint.sh
|
||||
RUN chmod +x /etc/nginx/docker-entrypoint.sh
|
||||
|
||||
# Define env variables defaults
|
||||
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"
|
||||
|
||||
# Start Nginx
|
||||
EXPOSE 3000
|
||||
ENTRYPOINT ["/etc/nginx/docker-entrypoint.sh"]
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -12,6 +12,11 @@ export default defineConfig({
|
||||
setupNodeEvents(on, config) {
|
||||
// implement node event listeners here
|
||||
},
|
||||
baseUrl: 'http://localhost:5173'
|
||||
baseUrl: 'http://localhost:5173',
|
||||
supportFile: 'src/tests/cypress/support/e2e.{js,jsx,ts,tsx}',
|
||||
specPattern: 'src/tests/cypress/**/*.cy.{js,jsx,ts,tsx}',
|
||||
fixturesFolder: 'src/tests/cypress/fixtures',
|
||||
videosFolder: 'src/tests/cypress/videos',
|
||||
screenshotsFolder: 'src/tests/cypress/screenshot'
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
ROOT_DIR=/usr/share/nginx/html
|
||||
DOCKER_VARS=(VITE_INSTANCE_NAME VITE_API_URL VITE_TILES)
|
||||
|
||||
echo "Setting env variables in web files"
|
||||
for file in $ROOT_DIR/assets/*.js $ROOT_DIR/index.html; do
|
||||
echo "Processing $file...";
|
||||
|
||||
for i in ${!DOCKER_VARS[@]}; do
|
||||
sed -i "s|DOCKER_${DOCKER_VARS[i]}|${!DOCKER_VARS[i]}|g" $file
|
||||
done
|
||||
done
|
||||
|
||||
echo "GeoVisio website is now ready !"
|
||||
exec "$@"
|
||||
@@ -1,22 +0,0 @@
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
sendfile on;
|
||||
|
||||
server {
|
||||
listen 3000;
|
||||
listen [::]:3000;
|
||||
|
||||
resolver 127.0.0.11;
|
||||
autoindex off;
|
||||
|
||||
server_name _;
|
||||
server_tokens off;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
gzip_static on;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,6 @@
|
||||
# Setup
|
||||
|
||||
GeoVisio website can be installed through classic method, or using Docker.
|
||||
|
||||
__Contents__
|
||||
|
||||
[[_TOC_]]
|
||||
|
||||
|
||||
## Classic install
|
||||
|
||||
### System requirements
|
||||
## System requirements
|
||||
|
||||
**You need to have [Nodejs installed](https://nodejs.org/en/download)**
|
||||
Node version : >=18.13.0
|
||||
@@ -18,7 +9,7 @@ Node version : >=18.13.0
|
||||
|
||||
You can use npm or [yarn](https://yarnpkg.com/) as package manager
|
||||
|
||||
### Install
|
||||
## Install
|
||||
|
||||
The website can be installed locally by retrieving this repository and installing dependencies:
|
||||
|
||||
@@ -31,7 +22,7 @@ cd website/
|
||||
npm install
|
||||
```
|
||||
|
||||
### Build for production
|
||||
## Build for production
|
||||
|
||||
Before building, you need to define a bit of settings. At least, you have to create a `.env` file and edit its content.
|
||||
|
||||
@@ -50,33 +41,6 @@ PORT=3000 npm run start
|
||||
|
||||
The website is now available at [localhost:3000](http://localhost:3000).
|
||||
|
||||
|
||||
## Docker setup
|
||||
|
||||
The [Docker](https://docs.docker.com/get-docker/) deployment is a really convenient way to have a Geovisio website running in an easy and fast way. Note that this setup documentation only covers __GeoVisio front-end__ (website), if you also need an API running, please refer to [Docker API deployment](https://gitlab.com/geovisio/api/-/blob/develop/docs/14_Running_Docker.md).
|
||||
|
||||
You can use the provided __Docker Hub__ `geovisio/website:latest` image directly:
|
||||
|
||||
```bash
|
||||
docker run \
|
||||
-e VITE_API_URL="https://your.geovisio.api/" \
|
||||
-e VITE_INSTANCE_NAME="My Own GeoVisio" \
|
||||
-e VITE_TILES="https://your.geovisio.api/vector/tiles/style.json" \
|
||||
-p 3000:3000 \
|
||||
--name geovisio-website \
|
||||
-d \
|
||||
geovisio/website:latest
|
||||
```
|
||||
|
||||
This will run a container bound on [localhost:3000](http://localhost:3000).
|
||||
|
||||
You can also build the image from the local source with:
|
||||
|
||||
```bash
|
||||
docker build -t geovisio/website:latest .
|
||||
```
|
||||
|
||||
|
||||
## Next steps
|
||||
|
||||
You can check out [the available settings for your instance](./03_Settings.md).
|
||||
|
||||
@@ -8,9 +8,7 @@ Low-level settings can be changed through the `.env` file. An example is given i
|
||||
|
||||
Available parameters are:
|
||||
|
||||
- `VITE_API_URL`: the URL to the GeoVisio API (with trailing `/`, example: `https://geovisio.fr/`)
|
||||
- `VITE_INSTANCE_NAME`: the name of the instance (example: `IGN`)
|
||||
- `VITE_TILES`: the URL of your tiles : default tiles are the Open Street Map Tiles (example: `https://wxs.ign.fr/essentiels/static/vectorTiles/styles/PLAN.IGN/attenue.json`)
|
||||
- `VITE_API_URL`: the URL to the GeoVisio API (example: `https://geovisio.fr`)
|
||||
- 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
|
||||
@@ -28,6 +26,7 @@ All the wordings of the website are on this [locale file](./src/locales/fr.json)
|
||||
|
||||
- The website title (properties `title` and `meta.title`)
|
||||
- The description (property `meta.description`)
|
||||
- The used API URL (property `pages.upload.terminal_text`)
|
||||
- Links to help pages:
|
||||
- `upload.description`
|
||||
- `upload.footer_description_terminal`
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
# Make a release
|
||||
|
||||
The web site uses [semantic versioning](https://semver.org/) for its release numbers.
|
||||
|
||||
__Note__ : make sure that versions are in-sync with other Website components. Each component can have different `PATCH` versions, but compatibility __must__ be ensured between `MAJOR.MINOR` versions.
|
||||
|
||||
Run these commands in order to issue a new release:
|
||||
|
||||
```bash
|
||||
git checkout develop
|
||||
|
||||
vim package.json # Change version
|
||||
npm run doc
|
||||
|
||||
vim CHANGELOG.md # Replace unreleased to version number and update versions links (at bottom)
|
||||
|
||||
git add *
|
||||
git commit -m "Release x.x.x"
|
||||
git tag -a x.x.x -m "Release x.x.x"
|
||||
git push origin develop
|
||||
git checkout main
|
||||
git merge develop
|
||||
git push origin main --tags
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "geovisio-website",
|
||||
"version": "2.1.0",
|
||||
"version": "1.0.0",
|
||||
"engines": {
|
||||
"node": "18.16.0"
|
||||
},
|
||||
@@ -25,9 +25,8 @@
|
||||
"axios": "^1.2.3",
|
||||
"bootstrap": "^5.2.3",
|
||||
"bootstrap-icons": "^1.10.3",
|
||||
"geovisio": "2.1.3",
|
||||
"geovisio": "2.0.6",
|
||||
"moment": "^2.29.4",
|
||||
"pinia": "^2.1.4",
|
||||
"vue": "^3.2.45",
|
||||
"vue-axios": "^3.5.2",
|
||||
"vue-eslint-parser": "^9.1.0",
|
||||
@@ -38,7 +37,6 @@
|
||||
"vue3-smooth-scroll": "^0.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pinia/testing": "^0.1.2",
|
||||
"@rushstack/eslint-patch": "^1.1.4",
|
||||
"@types/jsdom": "^20.0.1",
|
||||
"@types/node": "^18.11.18",
|
||||
|
||||
18
src/App.vue
18
src/App.vue
@@ -3,20 +3,19 @@ import Header from '@/components/Header.vue'
|
||||
import { RouterView } from 'vue-router'
|
||||
import { useMeta } from 'vue-meta'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import title from '@/utils/index'
|
||||
import authConfig from './composables/auth'
|
||||
const { authConf } = authConfig()
|
||||
const { t } = useI18n()
|
||||
|
||||
useMeta({
|
||||
title: title(t('general.title')),
|
||||
title: t('general.title'),
|
||||
og: {
|
||||
title: title(t('general.meta.title')),
|
||||
description: title(t('general.meta.description'))
|
||||
title: t('general.meta.title'),
|
||||
description: t('general.meta.description')
|
||||
},
|
||||
twitter: {
|
||||
title: title(t('general.meta.title')),
|
||||
description: title(t('general.meta.description'))
|
||||
title: t('general.meta.title'),
|
||||
description: t('general.meta.description')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -26,12 +25,9 @@ useMeta({
|
||||
<template v-slot:title="{ content }">{{ content }}</template>
|
||||
</metainfo>
|
||||
<Header
|
||||
v-if="authConf.user_profile"
|
||||
:auth-enabled="authConf.enabled"
|
||||
:user-profile-url="
|
||||
authConf.user_profile && authConf.user_profile.url
|
||||
? authConf.user_profile.url
|
||||
: ''
|
||||
"
|
||||
:user-profile-url="authConf.user_profile.url"
|
||||
/>
|
||||
<RouterView />
|
||||
</template>
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
@mixin text($size) {
|
||||
@if $size == h1 {
|
||||
font-weight: normal;
|
||||
font-size: toRem(4);
|
||||
font-size: 4rem;
|
||||
|
||||
@media (max-width: toRem(50)) {
|
||||
font-size: toRem(2.6);
|
||||
@media (max-width: 500px) {
|
||||
font-size: 2.6rem;
|
||||
}
|
||||
}
|
||||
@if $size == h2 {
|
||||
font-weight: normal;
|
||||
font-size: toRem(2);
|
||||
font-size: 2rem;
|
||||
|
||||
@media (max-width: toRem(50)) {
|
||||
font-size: toRem(1.8);
|
||||
@media (max-width: 500px) {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
}
|
||||
@if $size == h4 {
|
||||
font-weight: normal;
|
||||
font-size: toRem(1.6);
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
@if $size == m-regular {
|
||||
font-size: toRem(1.6);
|
||||
font-size: 1.6rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
@if $size == m-r-regular {
|
||||
font-size: toRem(1.6);
|
||||
font-size: 1.6rem;
|
||||
font-weight: normal;
|
||||
@media (max-width: toRem(50)) {
|
||||
font-size: toRem(1.2);
|
||||
@media (max-width: 500px) {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
@if $size == s-regular {
|
||||
font-size: toRem(1.4);
|
||||
font-size: 1.4rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
@if $size == xs-r-regular {
|
||||
font-size: toRem(1.2);
|
||||
font-size: 1.2rem;
|
||||
font-weight: normal;
|
||||
@media (max-width: toRem(50)) {
|
||||
font-size: toRem(1);
|
||||
@media (max-width: 500px) {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
@if $size == xss-regular {
|
||||
font-size: toRem(0.9);
|
||||
font-size: 0.9rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
html {
|
||||
font-size: 62.5%; /* 1rem = 10px */
|
||||
height: -webkit-fill-available;
|
||||
}
|
||||
body {
|
||||
@@ -24,35 +25,32 @@ h5 {
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
:root {
|
||||
--white: #ffffff;
|
||||
--black: #181818;
|
||||
--black-pale: #1b1a17;
|
||||
--red: #f70000;
|
||||
--red-pale: #ff726f;
|
||||
--grey: #e6e6e6;
|
||||
--grey-pale: #cfd2cf;
|
||||
--grey-pale: #CFD2CF;
|
||||
--grey-semi-dark: #808080;
|
||||
--grey-dark: #54595e;
|
||||
--blue: #4945ff;
|
||||
--blue-dark: #051f61;
|
||||
--blue-geovisio: #34495e;
|
||||
--blue-semi: rgba(207, 226, 255, 0.5);
|
||||
--blue-pale: #f9fafd;
|
||||
--beige: #f5f3ec;
|
||||
--yellow: #fec868;
|
||||
--orange: #ff6f00;
|
||||
--green: #59ce8f;
|
||||
--green-pale: #f0ffee;
|
||||
}
|
||||
|
||||
@media (min-width: toRem(102.4)) {
|
||||
@media (min-width: 1024px) {
|
||||
body {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
}
|
||||
}
|
||||
@media (max-width: toRem(50)) {
|
||||
@media (max-width: 500px) {
|
||||
@supports (-webkit-touch-callout: none) {
|
||||
/* CSS specific to iOS devices */
|
||||
body {
|
||||
@@ -1,4 +0,0 @@
|
||||
@function toRem($value) {
|
||||
$remValue: calc($value / 1.6) + rem;
|
||||
@return $remValue;
|
||||
}
|
||||
@@ -1,44 +1,40 @@
|
||||
<template>
|
||||
<p class="instance-beta">
|
||||
{{ title($t('general.header.title')) }}
|
||||
{{ $t('general.header.title') }}
|
||||
<span class="beta">{{ $t('general.header.beta_text') }}</span>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import title from '@/utils/index'
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.beta {
|
||||
@include text(xs-r-regular);
|
||||
color: var(--red);
|
||||
border: toRem(0.1) solid var(--red);
|
||||
border-radius: toRem(0.5);
|
||||
padding: toRem(0.2) toRem(0.3);
|
||||
margin-left: toRem(0.5);
|
||||
border: 1px solid var(--red);
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.2rem 0.3rem;
|
||||
margin-left: 0.5rem;
|
||||
position: absolute;
|
||||
top: toRem(-1);
|
||||
right: toRem(-9.5);
|
||||
width: toRem(9);
|
||||
top: -1rem;
|
||||
right: -9.5rem;
|
||||
width: 9rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@media (max-width: toRem(76.8)) {
|
||||
@media (max-width: 768px) {
|
||||
.instance-beta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--grey);
|
||||
padding: toRem(1);
|
||||
padding: 1rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.beta {
|
||||
position: relative;
|
||||
top: initial;
|
||||
right: initial;
|
||||
margin-left: toRem(1);
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<button
|
||||
:disabled="isLoading || disabled"
|
||||
:type="type"
|
||||
:class="[look, 'default', { disabled: disabled || isLoading }]"
|
||||
type="button"
|
||||
:class="[look, 'default', { disabled }]"
|
||||
@click="$emit('trigger')"
|
||||
>
|
||||
<i v-if="icon" :class="[icon, 'icon']"></i>
|
||||
@@ -14,120 +14,109 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
type TypeInterface = 'button' | 'submit' | 'reset'
|
||||
defineProps({
|
||||
icon: { type: String, default: null },
|
||||
disabled: { type: Boolean, default: false },
|
||||
isLoading: { type: Boolean, default: false },
|
||||
text: { type: String, default: '' },
|
||||
tooltip: { type: String, default: '' },
|
||||
look: { type: String, default: '' },
|
||||
type: { type: String as PropType<TypeInterface>, default: 'button' }
|
||||
look: { type: String, default: '' }
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@media (min-width: toRem(76.8)) {
|
||||
@media (min-width: 768px) {
|
||||
.default:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
.default {
|
||||
height: toRem(3.5);
|
||||
min-width: toRem(3.5);
|
||||
@include text(s-regular);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
padding: 1rem;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
border-radius: toRem(0.5);
|
||||
padding: toRem(1.3) toRem(2) toRem(1.3);
|
||||
.icon {
|
||||
font-size: toRem(2);
|
||||
}
|
||||
&:hover .tooltip-button {
|
||||
visibility: visible;
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
.button--black {
|
||||
height: 3.5rem;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1.3rem 2rem 1.3rem;
|
||||
color: var(--white);
|
||||
background-color: var(--black);
|
||||
}
|
||||
.button--blue {
|
||||
color: var(--white);
|
||||
background-color: var(--blue);
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
color: var(--white);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.button--transparent {
|
||||
border: toRem(0.1) solid var(--white);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
border: 0.1rem solid var(--white);
|
||||
background-color: var(--black);
|
||||
color: var(--white);
|
||||
}
|
||||
.button--red {
|
||||
height: 3.5rem;
|
||||
min-width: 3.5rem;
|
||||
border-radius: 0.5rem;
|
||||
color: var(--red);
|
||||
background-color: var(--white);
|
||||
border: toRem(0.1) solid var(--red);
|
||||
border: 0.1rem solid var(--red);
|
||||
.icon {
|
||||
margin-right: 0;
|
||||
font-size: toRem(1.4);
|
||||
font-size: 1.4rem;
|
||||
color: var(--red);
|
||||
}
|
||||
.text {
|
||||
margin-left: toRem(1);
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
.button--white {
|
||||
height: 3.5rem;
|
||||
border-radius: 0.5rem;
|
||||
color: var(--black);
|
||||
background-color: var(--white);
|
||||
border: toRem(0.1) solid var(--black);
|
||||
border: 0.1rem solid var(--black);
|
||||
.icon {
|
||||
font-size: toRem(1.4);
|
||||
font-size: 1.4rem;
|
||||
color: var(--black);
|
||||
margin-right: 0;
|
||||
}
|
||||
.text {
|
||||
margin-left: toRem(1);
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
.no-text {
|
||||
height: toRem(3);
|
||||
width: toRem(3);
|
||||
height: 3rem;
|
||||
width: 3rem;
|
||||
padding: 0;
|
||||
.icon {
|
||||
color: var(---black);
|
||||
font-size: toRem(1.8);
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
.link--grey {
|
||||
color: var(--grey-semi-dark);
|
||||
.icon {
|
||||
font-size: toRem(1.4);
|
||||
font-size: 1.4rem;
|
||||
color: var(--grey-semi-dark);
|
||||
}
|
||||
}
|
||||
.link--red {
|
||||
height: 3rem;
|
||||
color: var(--red);
|
||||
background-color: var(--white);
|
||||
.icon {
|
||||
font-size: toRem(1.4);
|
||||
font-size: 1.4rem;
|
||||
color: var(--red);
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: toRem(1);
|
||||
font-size: toRem(2);
|
||||
margin-right: 1rem;
|
||||
font-size: 2rem;
|
||||
color: var(--white);
|
||||
}
|
||||
.button--rounded {
|
||||
@@ -136,28 +125,32 @@ defineProps({
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
padding: 0;
|
||||
height: toRem(2.5);
|
||||
width: toRem(2.5);
|
||||
height: 2.5rem;
|
||||
width: 2.5rem;
|
||||
.icon {
|
||||
color: var(---black);
|
||||
font-size: toRem(1.8);
|
||||
font-size: 1.8rem;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
.tooltip-button {
|
||||
|
||||
.default .tooltip-button {
|
||||
background-color: var(--black);
|
||||
color: var(--white);
|
||||
text-align: center;
|
||||
border-radius: toRem(0.5);
|
||||
padding: toRem(0.5) toRem(1);
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
position: absolute;
|
||||
bottom: -100%;
|
||||
visibility: hidden;
|
||||
width: toRem(18);
|
||||
width: 18rem;
|
||||
right: 0;
|
||||
@include text(xss-regular);
|
||||
}
|
||||
|
||||
.default:hover .tooltip-button {
|
||||
visibility: visible;
|
||||
}
|
||||
.disabled {
|
||||
color: var(--grey-pale);
|
||||
border-color: var(--grey-pale);
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
<nav class="nav">
|
||||
<div class="wrapper-logo desktop">
|
||||
<Link
|
||||
:image="{ url: 'logo.jpeg', alt: $t('general.header.alt_logo') }"
|
||||
:text="title($t('general.header.title'))"
|
||||
:route="{ name: 'home' }"
|
||||
:image="{
|
||||
url: 'logo.jpeg',
|
||||
alt: $t('general.header.alt_logo')
|
||||
}"
|
||||
:text="$t('general.header.title')"
|
||||
path="/"
|
||||
/>
|
||||
</div>
|
||||
<div class="wrapper-logo responsive">
|
||||
@@ -17,66 +20,56 @@
|
||||
url: 'logo.jpeg',
|
||||
alt: $t('general.header.alt_logo')
|
||||
}"
|
||||
:route="{ name: 'home' }"
|
||||
path="/"
|
||||
/>
|
||||
</div>
|
||||
<div ref="list" class="wrapper-entries">
|
||||
<ul
|
||||
v-if="isLogged && authEnabled"
|
||||
:class="['nav-list', { 'menu-open': !menuIsClosed }]"
|
||||
>
|
||||
<li class="logged-link">
|
||||
<ul :class="['nav-list', { 'menu-open': !menuIsClosed }]">
|
||||
<li v-if="isLogged" class="logged-link">
|
||||
<Link
|
||||
:text="$t('general.header.sequences_text')"
|
||||
icon="bi bi-images"
|
||||
:route="{ name: 'my-sequences' }"
|
||||
path="/mes-sequences"
|
||||
@click.native="closeModal"
|
||||
/>
|
||||
</li>
|
||||
<li v-if="userProfileUrl.length" class="logged-link">
|
||||
<li v-if="userProfileUrl && isLogged" class="logged-link">
|
||||
<Link
|
||||
:route="{ name: 'my-information' }"
|
||||
path="/mes-informations"
|
||||
icon="bi bi-person"
|
||||
:text="$t('general.header.my_information_text')"
|
||||
@click.native="closeModal"
|
||||
/>
|
||||
</li>
|
||||
<li class="logged-link">
|
||||
<li v-if="userProfileUrl && isLogged" class="logged-link">
|
||||
<Link
|
||||
:route="{ name: 'my-settings' }"
|
||||
path="/mes-parametres"
|
||||
icon="bi bi-gear"
|
||||
:text="$t('general.header.my_settings_text')"
|
||||
@click.native="closeModal"
|
||||
/>
|
||||
</li>
|
||||
<li class="logged-link">
|
||||
<li v-if="userProfileUrl && isLogged" class="logged-link">
|
||||
<Link
|
||||
type="external"
|
||||
icon="bi bi-power"
|
||||
:text="$t('general.header.logout_text')"
|
||||
:path-external="getAuthRoute('auth/logout', route.path)"
|
||||
:path="getAuthRoute('auth/logout', route.path)"
|
||||
@click.native="closeModal"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="wrapper-right-entries">
|
||||
<div v-if="isLogged && authEnabled">
|
||||
<Link
|
||||
:text="$t('general.header.upload_text')"
|
||||
look="button button--blue"
|
||||
:route="{ name: 'upload-pictures' }"
|
||||
@click.native="closeModal"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Link
|
||||
:text="$t('general.header.contribute_text')"
|
||||
:route="{ name: 'share-pictures' }"
|
||||
look="button white"
|
||||
path="/partager-des-photos"
|
||||
@click.native="closeModal"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
v-if="isLogged && authEnabled"
|
||||
v-if="isLogged"
|
||||
class="menu-burger"
|
||||
:aria-label="ariaLabel"
|
||||
@click="toggleMenu"
|
||||
@@ -89,11 +82,11 @@
|
||||
<i v-else class="bi bi-list"></i>
|
||||
</div>
|
||||
</button>
|
||||
<div v-else-if="!isLogged && authEnabled">
|
||||
<div v-else>
|
||||
<Link
|
||||
type="external"
|
||||
icon="bi bi-person-circle"
|
||||
:path-external="getAuthRoute('auth/login', route.path)"
|
||||
:path="getAuthRoute('auth/login', route.path)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -109,7 +102,6 @@ import { useCookies } from 'vue3-cookies'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { getAuthRoute } from '@/utils/auth'
|
||||
import title from '@/utils/index'
|
||||
import Link from '@/components/Link.vue'
|
||||
import BetaText from '@/components/BetaText.vue'
|
||||
|
||||
@@ -118,7 +110,7 @@ const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
defineProps({
|
||||
authEnabled: { type: Boolean, default: true },
|
||||
userProfileUrl: { type: String, default: '' }
|
||||
userProfileUrl: { type: String, default: null }
|
||||
})
|
||||
const list = ref(null)
|
||||
let menuIsClosed = ref<boolean>(true)
|
||||
@@ -151,13 +143,13 @@ const userName = computed((): string =>
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: toRem(8);
|
||||
background-color: var(--blue-pale);
|
||||
height: 8rem;
|
||||
border-bottom: 0.1rem solid var(--black);
|
||||
}
|
||||
.nav {
|
||||
width: 100%;
|
||||
padding-right: toRem(2);
|
||||
padding-left: toRem(2);
|
||||
padding-right: 2rem;
|
||||
padding-left: 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
@@ -177,7 +169,7 @@ const userName = computed((): string =>
|
||||
.wrapper-logo p {
|
||||
@include text(m-r-regular);
|
||||
margin-bottom: 0;
|
||||
margin-left: toRem(1);
|
||||
margin-left: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
.item-with-sub {
|
||||
@@ -190,42 +182,45 @@ const userName = computed((): string =>
|
||||
}
|
||||
.sub-nav-block {
|
||||
display: none;
|
||||
border-radius: toRem(0.5);
|
||||
border: toRem(0.1) solid var(--black);
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--black);
|
||||
background-color: var(--white);
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: toRem(3.5);
|
||||
top: 3.5rem;
|
||||
z-index: 1;
|
||||
width: toRem(15);
|
||||
width: 15rem;
|
||||
}
|
||||
.logged-link {
|
||||
display: flex;
|
||||
padding: toRem(0.5) toRem(2) toRem(0.7);
|
||||
padding: 0.5rem 2rem 0.7rem;
|
||||
}
|
||||
.logged-link:hover {
|
||||
border-radius: toRem(0.5);
|
||||
border-radius: 0.5rem;
|
||||
background-color: var(--grey);
|
||||
}
|
||||
.nav-list-item {
|
||||
margin-right: toRem(1.5);
|
||||
margin-right: 1.5rem;
|
||||
}
|
||||
.menu-burger {
|
||||
display: none;
|
||||
}
|
||||
.wrapper-right-entries {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
div {
|
||||
margin-right: toRem(2);
|
||||
div:first-child {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
}
|
||||
.cross {
|
||||
font-size: toRem(2);
|
||||
font-size: 2rem;
|
||||
}
|
||||
.item-with-sub {
|
||||
margin-right: toRem(1.5);
|
||||
margin-right: 1.5rem;
|
||||
}
|
||||
.nav {
|
||||
align-items: center;
|
||||
padding: toRem(1.5);
|
||||
padding: 1.5rem;
|
||||
}
|
||||
.nav-list {
|
||||
display: none;
|
||||
@@ -233,23 +228,23 @@ const userName = computed((): string =>
|
||||
justify-content: center;
|
||||
align-items: initial;
|
||||
position: absolute;
|
||||
width: toRem(20);
|
||||
top: toRem(8);
|
||||
width: 20rem;
|
||||
top: 8rem;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
background-color: var(--white);
|
||||
box-shadow: 0 toRem(0.2) toRem(0.4) rgb(0 0 0 / 10%);
|
||||
box-shadow: 0 0.2rem 0.4rem rgb(0 0 0 / 10%);
|
||||
padding-left: 0;
|
||||
padding-top: toRem(1);
|
||||
padding-bottom: toRem(1);
|
||||
border-radius: toRem(1);
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
.menu-burger {
|
||||
display: block;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
width: toRem(2.5);
|
||||
font-size: toRem(2.5);
|
||||
width: 2.5rem;
|
||||
font-size: 2.5rem;
|
||||
padding: 0;
|
||||
.item-with-sub {
|
||||
@include text(s-regular);
|
||||
@@ -258,8 +253,8 @@ const userName = computed((): string =>
|
||||
align-items: center;
|
||||
background-color: var(--blue);
|
||||
color: var(--white);
|
||||
height: toRem(3);
|
||||
width: toRem(3);
|
||||
height: 3rem;
|
||||
width: 3rem;
|
||||
border-radius: 50%;
|
||||
margin-right: 0;
|
||||
}
|
||||
@@ -267,18 +262,19 @@ const userName = computed((): string =>
|
||||
.menu-open {
|
||||
display: flex;
|
||||
}
|
||||
@media (max-width: toRem(50)) {
|
||||
@media (max-width: 500px) {
|
||||
.header {
|
||||
flex-direction: column;
|
||||
height: toRem(11);
|
||||
height: 11rem;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 4;
|
||||
background: var(--white);
|
||||
}
|
||||
.nav-list {
|
||||
top: toRem(11);
|
||||
top: 11rem;
|
||||
width: 100%;
|
||||
}
|
||||
.desktop {
|
||||
|
||||
@@ -8,17 +8,11 @@
|
||||
@click="$emit('trigger')"
|
||||
>
|
||||
<div
|
||||
v-if="status.length && status !== 'waiting-for-process'"
|
||||
v-if="href && status !== 'waiting-for-process'"
|
||||
class="photo-img-wrapper"
|
||||
>
|
||||
<i v-if="status === 'hidden'" class="bi bi-eye-slash icon-hidden"></i>
|
||||
<img
|
||||
v-if="href"
|
||||
:src="href"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
class="photo-img"
|
||||
/>
|
||||
<img :src="href" alt="" loading="lazy" class="photo-img" />
|
||||
</div>
|
||||
<div v-else class="waiting-wrapper">
|
||||
<i class="bi bi-card-image icon-waiting"></i>
|
||||
@@ -37,21 +31,19 @@
|
||||
<i class="bi bi-check-lg" />
|
||||
</div>
|
||||
<div v-if="status === 'waiting-for-process'" class="photo-info">
|
||||
<span class="waiting info">{{
|
||||
<span class="waiting">{{
|
||||
$t('pages.sequence.waiting_process')
|
||||
}}</span>
|
||||
</div>
|
||||
<div v-else class="photo-info">
|
||||
<span v-if="created" class="info"
|
||||
><i class="bi bi-clock"></i> {{ created }}</span
|
||||
>
|
||||
<span v-if="created"><i class="bi bi-clock"></i> {{ created }}</span>
|
||||
<div class="button-info">
|
||||
<Link
|
||||
look="button button--white-blue no-text"
|
||||
look="button--blue no-text"
|
||||
icon="bi bi-cloud-download-fill"
|
||||
type="external"
|
||||
target="_blank"
|
||||
:path-external="hrefHd"
|
||||
:path="hrefHd"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -69,12 +61,7 @@ defineProps({
|
||||
hrefHd: { type: String, default: null },
|
||||
selected: { type: Boolean, default: false },
|
||||
selectedOnMap: { type: Boolean, default: false },
|
||||
status: {
|
||||
type: String,
|
||||
validator: (value: string): boolean =>
|
||||
['waiting-for-process', 'ready', 'hidden', ''].includes(value),
|
||||
default: ''
|
||||
}
|
||||
status: { type: String, default: null }
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -84,59 +71,61 @@ defineProps({
|
||||
border: none;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
}
|
||||
.selected {
|
||||
border: toRem(0.1) solid var(--blue);
|
||||
border-radius: toRem(0.5);
|
||||
border: 0.1rem solid var(--blue);
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0px 4px 4px 0px #00000040;
|
||||
}
|
||||
.wrapper-image {
|
||||
display: flex;
|
||||
position: relative;
|
||||
border-top-right-radius: 0.5rem;
|
||||
border-top-left-radius: 0.5rem;
|
||||
}
|
||||
.photo-img-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-top-right-radius: toRem(0.5);
|
||||
border-top-left-radius: toRem(0.5);
|
||||
height: toRem(16);
|
||||
border-top-right-radius: 0.5rem;
|
||||
border-top-left-radius: 0.5rem;
|
||||
height: 12rem;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.photo-img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-radius: toRem(0.5);
|
||||
border-top-right-radius: 0.5rem;
|
||||
border-top-left-radius: 0.5rem;
|
||||
}
|
||||
.icon-hidden {
|
||||
color: var(--grey-dark);
|
||||
position: absolute;
|
||||
font-size: toRem(4);
|
||||
font-size: 4rem;
|
||||
}
|
||||
.waiting-wrapper {
|
||||
height: toRem(16);
|
||||
height: 12rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--blue);
|
||||
}
|
||||
.icon-waiting {
|
||||
height: toRem(4);
|
||||
font-size: toRem(4);
|
||||
height: 4rem;
|
||||
font-size: 4rem;
|
||||
}
|
||||
.icon-img {
|
||||
top: toRem(1);
|
||||
right: toRem(1);
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
background-color: var(--white);
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
height: toRem(2);
|
||||
width: toRem(2);
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: toRem(1.3);
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
.pointer-map,
|
||||
.button-check-pointer {
|
||||
@@ -146,14 +135,11 @@ defineProps({
|
||||
opacity: 1;
|
||||
}
|
||||
.photo-info {
|
||||
height: toRem(5);
|
||||
height: 5rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: toRem(1);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.waiting {
|
||||
@@ -162,28 +148,21 @@ defineProps({
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.info {
|
||||
@include text(xs-r-regular);
|
||||
padding: toRem(0.5) toRem(0.8);
|
||||
background-color: var(--white);
|
||||
border-radius: toRem(0.5);
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.button-image-item:hover {
|
||||
.photo-img {
|
||||
.photo-img,
|
||||
.photo-info {
|
||||
&:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.hidden {
|
||||
.photo-img,
|
||||
.info {
|
||||
.photo-info {
|
||||
opacity: 0.3;
|
||||
}
|
||||
&:hover {
|
||||
.photo-img,
|
||||
.info {
|
||||
.photo-info {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,11 +24,11 @@ import type { CheckboxInterface } from '@/views/interfaces/MySequenceView'
|
||||
const emit = defineEmits<{ (e: 'trigger', value: CheckboxInterface): void }>()
|
||||
const props = defineProps({
|
||||
name: { type: String, default: null },
|
||||
label: { type: String, default: '' },
|
||||
label: { type: String, label: '' },
|
||||
isChecked: { type: Boolean, default: false },
|
||||
isIndeterminate: { type: Boolean, default: false }
|
||||
})
|
||||
let inputValue = ref<boolean>(props.isChecked)
|
||||
|
||||
const htmlCheckbox = <HTMLInputElement>document.getElementById('checkbox')
|
||||
|
||||
watchEffect(async () => {
|
||||
@@ -37,6 +37,8 @@ watchEffect(async () => {
|
||||
}
|
||||
})
|
||||
|
||||
let inputValue = ref<boolean>(props.isChecked)
|
||||
|
||||
function updateValue(value: boolean): void {
|
||||
if (htmlCheckbox) {
|
||||
htmlCheckbox.indeterminate = false
|
||||
@@ -52,8 +54,8 @@ function updateValue(value: boolean): void {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: toRem(2);
|
||||
width: toRem(2);
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
}
|
||||
.input {
|
||||
-webkit-appearance: none;
|
||||
@@ -66,7 +68,7 @@ function updateValue(value: boolean): void {
|
||||
width: 100%;
|
||||
}
|
||||
.icon {
|
||||
font-size: toRem(2);
|
||||
font-size: 2rem;
|
||||
position: absolute;
|
||||
color: var(--grey-semi-dark);
|
||||
}
|
||||
@@ -76,6 +78,7 @@ function updateValue(value: boolean): void {
|
||||
}
|
||||
.label {
|
||||
cursor: pointer;
|
||||
margin-left: toRem(0.5);
|
||||
margin-left: 0.5rem;
|
||||
@include text(s-regular);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
<template>
|
||||
<label
|
||||
@dragover="dragover"
|
||||
@dragleave="dragleave"
|
||||
@drop="drop"
|
||||
:class="['file-upload', { dragging: isDragging }]"
|
||||
>
|
||||
<input
|
||||
ref="upload"
|
||||
type="file"
|
||||
multiple
|
||||
:accept="accept"
|
||||
capture="environment"
|
||||
class="input-file"
|
||||
@change="changeFile"
|
||||
/>
|
||||
<i class="bi bi-cloud-upload-fill"></i>
|
||||
<span v-if="text" class="input-text">
|
||||
{{ text }}
|
||||
<span v-if="textSecondPart" class="last-word">{{ textSecondPart }}</span>
|
||||
</span>
|
||||
<span v-if="textPictureType" class="input-text-type">{{
|
||||
textPictureType
|
||||
}}</span>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
const emit = defineEmits<{ (e: 'trigger', value: FileList): void }>()
|
||||
let isDragging = ref<boolean>(false)
|
||||
|
||||
defineProps({
|
||||
text: { type: String, default: null },
|
||||
textPictureType: { type: String, default: null },
|
||||
textSecondPart: { type: String, default: null },
|
||||
accept: { type: String, default: '' }
|
||||
})
|
||||
|
||||
interface HTMLInputChangeEvent extends Event {
|
||||
target: HTMLInputElement & EventTarget
|
||||
}
|
||||
function changeFile(event: Event): void {
|
||||
const { target } = event as HTMLInputChangeEvent
|
||||
if (target && target.files) {
|
||||
if (!checkPicturesType(target.files)) return
|
||||
emit('trigger', target.files)
|
||||
}
|
||||
}
|
||||
|
||||
function dragover(event: DragEvent): void {
|
||||
event.preventDefault()
|
||||
isDragging.value = true
|
||||
}
|
||||
|
||||
function dragleave(): void {
|
||||
isDragging.value = false
|
||||
}
|
||||
function drop(event: DragEvent): void | boolean {
|
||||
event.preventDefault()
|
||||
const { dataTransfer } = event
|
||||
if (dataTransfer && dataTransfer.files) {
|
||||
if (!checkPicturesType(dataTransfer.files))
|
||||
return (isDragging.value = false)
|
||||
emit('trigger', dataTransfer.files)
|
||||
isDragging.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function checkPicturesType(files: FileList): number {
|
||||
const picturesToUpload = [...files].filter((p) => p.type == 'image/jpeg')
|
||||
return picturesToUpload.length
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.file-upload {
|
||||
border: toRem(0.1) dashed var(--blue);
|
||||
background-color: var(--blue-semi);
|
||||
border-radius: toRem(0.5);
|
||||
padding: toRem(0.3) toRem(0.3) toRem(3);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.dragging {
|
||||
border-color: var(--green);
|
||||
background-color: var(--green-pale);
|
||||
.last-word,
|
||||
.bi-cloud-upload-fill {
|
||||
color: var(--green);
|
||||
}
|
||||
}
|
||||
.file-upload input {
|
||||
overflow: hidden;
|
||||
width: 0;
|
||||
}
|
||||
.bi-cloud-upload-fill {
|
||||
color: var(--blue);
|
||||
font-size: toRem(7);
|
||||
}
|
||||
.input-text {
|
||||
font-size: toRem(2);
|
||||
@include text(m-regular);
|
||||
font-weight: bold;
|
||||
width: toRem(21);
|
||||
text-align: center;
|
||||
margin-bottom: toRem(1);
|
||||
}
|
||||
.input-text-type {
|
||||
@include text(xs-r-regular);
|
||||
}
|
||||
.last-word {
|
||||
color: var(--blue);
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<a
|
||||
v-if="type === 'external'"
|
||||
:href="pathExternal"
|
||||
:href="path"
|
||||
:target="target"
|
||||
:class="['default', look, { disabled }]"
|
||||
:title="titleImg"
|
||||
@@ -14,8 +14,7 @@
|
||||
</a>
|
||||
<router-link
|
||||
v-else
|
||||
:to="route"
|
||||
:target="target"
|
||||
:to="path"
|
||||
:class="['default', look, { disabled }]"
|
||||
:title="text"
|
||||
>
|
||||
@@ -29,7 +28,6 @@
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { computed } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import type { RouteLocationRaw } from 'vue-router'
|
||||
import { img } from '../utils/image'
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -40,8 +38,7 @@ interface ImageInterface {
|
||||
|
||||
const props = defineProps({
|
||||
text: { type: String, default: null },
|
||||
route: { type: Object as PropType<RouteLocationRaw>, default: {} },
|
||||
pathExternal: { type: String, default: '' },
|
||||
path: { type: String, default: '' },
|
||||
look: { type: String, default: '' },
|
||||
type: { type: String, default: null },
|
||||
alt: { type: String, default: '' },
|
||||
@@ -57,6 +54,11 @@ const titleImg = computed<string>(() =>
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icon {
|
||||
color: var(--black);
|
||||
font-size: 2.4rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
.default {
|
||||
@include text(s-regular);
|
||||
display: flex;
|
||||
@@ -64,77 +66,58 @@ const titleImg = computed<string>(() =>
|
||||
color: var(--black);
|
||||
text-decoration: none;
|
||||
.icon {
|
||||
margin-right: toRem(1);
|
||||
margin-right: 1rem;
|
||||
}
|
||||
&:hover {
|
||||
opacity: toRem(0.8);
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
color: var(--black);
|
||||
font-size: toRem(2.4);
|
||||
}
|
||||
.logo {
|
||||
height: toRem(4);
|
||||
border-radius: toRem(0.5);
|
||||
margin-right: toRem(1);
|
||||
}
|
||||
.button {
|
||||
height: toRem(4);
|
||||
border-radius: toRem(0.5);
|
||||
padding: toRem(1.3) toRem(2) toRem(1.3);
|
||||
background-color: var(--black);
|
||||
color: var(--white);
|
||||
}
|
||||
.text {
|
||||
width: 100%;
|
||||
white-space: break-spaces;
|
||||
text-align: center;
|
||||
}
|
||||
.link:hover {
|
||||
background-color: transparent;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.button--white {
|
||||
.button {
|
||||
height: 4rem;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1.3rem 2rem 1.3rem;
|
||||
background-color: var(--black);
|
||||
color: var(--white);
|
||||
}
|
||||
.white {
|
||||
background-color: var(--white);
|
||||
color: var(--black);
|
||||
border: toRem(0.1) solid var(--black);
|
||||
.icon {
|
||||
font-size: toRem(1.6);
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--black);
|
||||
color: var(--white);
|
||||
.icon {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
border: 0.1rem solid var(--black);
|
||||
}
|
||||
.button--white-blue {
|
||||
background-color: var(--white);
|
||||
border: toRem(0.1) solid var(--blue);
|
||||
.icon {
|
||||
font-size: toRem(1.4);
|
||||
color: var(--blue);
|
||||
}
|
||||
.white .icon {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
.button--blue {
|
||||
background-color: var(--blue);
|
||||
border: toRem(0.1) solid var(--blue);
|
||||
.icon {
|
||||
font-size: toRem(1.4);
|
||||
color: var(--white);
|
||||
}
|
||||
.button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.white:hover {
|
||||
background-color: var(--black);
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.white:hover > .icon {
|
||||
color: white;
|
||||
}
|
||||
.logo {
|
||||
height: 4rem;
|
||||
border-radius: 0.5rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
.disabled {
|
||||
color: grey;
|
||||
cursor: not-allowed;
|
||||
.icon {
|
||||
color: grey;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
.disabled .icon {
|
||||
color: grey;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.disabled:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
.button--rounded {
|
||||
background-color: var(--black);
|
||||
@@ -143,29 +126,50 @@ const titleImg = computed<string>(() =>
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
padding: 0;
|
||||
height: toRem(4);
|
||||
width: toRem(4);
|
||||
height: 4rem;
|
||||
width: 4rem;
|
||||
.icon {
|
||||
color: var(--white);
|
||||
font-size: toRem(2.8);
|
||||
font-size: 2.8rem;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
.button--white {
|
||||
height: 4rem;
|
||||
border-radius: 0.5rem;
|
||||
color: var(--black);
|
||||
background-color: var(--white);
|
||||
border: 0.1rem solid var(--black);
|
||||
.icon {
|
||||
font-size: 1.4rem;
|
||||
color: var(--black);
|
||||
}
|
||||
}
|
||||
.button--blue {
|
||||
height: 4rem;
|
||||
border-radius: 0.5rem;
|
||||
background-color: var(--white);
|
||||
border: 0.1rem solid var(--blue);
|
||||
.icon {
|
||||
font-size: 1.4rem;
|
||||
color: var(--blue);
|
||||
}
|
||||
}
|
||||
.no-text {
|
||||
height: toRem(3);
|
||||
width: toRem(3);
|
||||
height: 3rem;
|
||||
width: 3rem;
|
||||
padding: 0;
|
||||
.icon {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
@media (max-width: toRem(50)) {
|
||||
@media (max-width: 500px) {
|
||||
.icon {
|
||||
margin-right: toRem(0.5);
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.button {
|
||||
padding-right: toRem(1);
|
||||
padding-left: toRem(1);
|
||||
padding-right: 1rem;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
.disable-mobile {
|
||||
pointer-events: none;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div :class="['lds-ring', look, { loaded: isLoaded }]">
|
||||
<div class="lds-ring">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
@@ -7,66 +7,27 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
text: { type: String, default: null },
|
||||
look: {
|
||||
type: String,
|
||||
validator: (value: string): boolean => ['sm', 'md', 'lg'].includes(value),
|
||||
default: 'sm'
|
||||
},
|
||||
isLoaded: { type: Boolean, default: true }
|
||||
})
|
||||
</script>
|
||||
<script setup></script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
<style scoped scss>
|
||||
.lds-ring {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
.lds-ring div {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
margin: 10px;
|
||||
border: 10px solid var(--blue);
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin: 8px;
|
||||
border: 8px solid var(--blue);
|
||||
border-radius: 50%;
|
||||
animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
||||
border-color: var(--blue) transparent transparent transparent;
|
||||
}
|
||||
.loaded {
|
||||
div {
|
||||
border-color: var(--blue);
|
||||
}
|
||||
}
|
||||
.sm {
|
||||
width: toRem(8);
|
||||
height: toRem(8);
|
||||
div {
|
||||
width: toRem(8.4);
|
||||
height: toRem(8.4);
|
||||
}
|
||||
}
|
||||
.md {
|
||||
width: toRem(16);
|
||||
height: toRem(16);
|
||||
div {
|
||||
width: toRem(16.8);
|
||||
height: toRem(16.8);
|
||||
}
|
||||
}
|
||||
.lg {
|
||||
width: toRem(32);
|
||||
height: toRem(32);
|
||||
div {
|
||||
width: toRem(25.6);
|
||||
height: toRem(25.6);
|
||||
}
|
||||
}
|
||||
.lds-ring div:nth-child(1) {
|
||||
animation-delay: -0.45s;
|
||||
}
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
:class="['wrapper-pagination', { 'no-border': type.includes('double') }]"
|
||||
>
|
||||
<button
|
||||
:disabled="linkDisabled"
|
||||
:class="['pagination-button', { disabled: linkDisabled }]"
|
||||
>
|
||||
<i
|
||||
v-if="type.length"
|
||||
:class="`bi bi-chevron-${type} chevron`"
|
||||
@click="triggerPagination"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
const emit = defineEmits<{ (e: 'trigger', value: string): void }>()
|
||||
|
||||
const props = defineProps({
|
||||
href: { type: String, default: null },
|
||||
selfLink: { type: Object, default: {} },
|
||||
type: { type: String, default: '' }
|
||||
})
|
||||
|
||||
const linkDisabled = computed((): boolean => {
|
||||
return (
|
||||
(props.type === 'double-left' || props.type === 'double-right') &&
|
||||
props.href === props.selfLink.href
|
||||
)
|
||||
})
|
||||
|
||||
function triggerPagination(): void {
|
||||
if (linkDisabled.value) return
|
||||
emit('trigger', props.href)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wrapper-pagination {
|
||||
border: toRem(0.1) solid var(--black);
|
||||
border-radius: 50%;
|
||||
background-color: var(--blue-pale);
|
||||
height: toRem(3);
|
||||
width: toRem(3);
|
||||
display: flex;
|
||||
&:first-child {
|
||||
margin-right: toRem(2);
|
||||
}
|
||||
&:last-child {
|
||||
margin-left: toRem(2);
|
||||
}
|
||||
&:nth-child(2) {
|
||||
margin-right: toRem(1);
|
||||
}
|
||||
&:nth-child(3) {
|
||||
margin-left: toRem(1);
|
||||
}
|
||||
}
|
||||
.pagination-button {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
font-size: toRem(1.7);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.chevron {
|
||||
width: toRem(3.5);
|
||||
}
|
||||
}
|
||||
.no-border {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
.disabled {
|
||||
cursor: default;
|
||||
pointer-events: unset;
|
||||
}
|
||||
</style>
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="entry-button-terminal">
|
||||
<Button
|
||||
look="button--transparent"
|
||||
:text="$t('pages.share_pictures.button_copy')"
|
||||
:text="$t('pages.upload.button_copy')"
|
||||
:icon="clipboardIcon"
|
||||
@trigger="copyText(textInstall)"
|
||||
class="entry-button"
|
||||
@@ -28,7 +28,7 @@
|
||||
<div class="entry-button-terminal">
|
||||
<Button
|
||||
look="button--transparent"
|
||||
:text="$t('pages.share_pictures.button_copy')"
|
||||
:text="$t('pages.upload.button_copy')"
|
||||
:icon="clipboardIcon"
|
||||
@trigger="copyText(textUpload)"
|
||||
class="entry-button"
|
||||
@@ -65,23 +65,23 @@ async function copyText(text: string): Promise<void> {
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-top-right-radius: toRem(1.5);
|
||||
border-top-left-radius: toRem(1.5);
|
||||
border-top-right-radius: 1.5rem;
|
||||
border-top-left-radius: 1.5rem;
|
||||
background-color: var(--grey);
|
||||
padding-left: toRem(0.5);
|
||||
height: toRem(3);
|
||||
padding-left: 0.5rem;
|
||||
height: 3rem;
|
||||
width: 100%;
|
||||
}
|
||||
.editor {
|
||||
border-bottom-right-radius: toRem(1.5);
|
||||
border-bottom-left-radius: toRem(1.5);
|
||||
border-bottom-right-radius: 1.5rem;
|
||||
border-bottom-left-radius: 1.5rem;
|
||||
background-color: var(--black-pale);
|
||||
height: toRem(32);
|
||||
height: 32rem;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.screen {
|
||||
padding: toRem(3) toRem(2) toRem(2);
|
||||
padding: 3rem 2rem 2rem;
|
||||
height: 100%;
|
||||
}
|
||||
.screen:nth-child(2n) {
|
||||
@@ -90,10 +90,10 @@ async function copyText(text: string): Promise<void> {
|
||||
.upload-command {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: toRem(2);
|
||||
margin-top: 2rem;
|
||||
}
|
||||
.entry-button-terminal {
|
||||
margin-top: toRem(2);
|
||||
margin-top: 2rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
.code {
|
||||
@@ -105,13 +105,13 @@ async function copyText(text: string): Promise<void> {
|
||||
}
|
||||
.tilde {
|
||||
color: var(--green);
|
||||
margin-right: toRem(1);
|
||||
margin-right: 1rem;
|
||||
}
|
||||
.round {
|
||||
height: toRem(1.5);
|
||||
width: toRem(1.5);
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
border-radius: 50%;
|
||||
margin: toRem(0.5);
|
||||
margin: 0.5rem;
|
||||
}
|
||||
.red {
|
||||
background-color: var(--red);
|
||||
@@ -123,9 +123,9 @@ async function copyText(text: string): Promise<void> {
|
||||
background-color: var(--green);
|
||||
}
|
||||
|
||||
@media (max-width: toRem(50)) {
|
||||
@media (max-width: 500px) {
|
||||
.editor {
|
||||
min-height: toRem(27);
|
||||
min-height: 27rem;
|
||||
}
|
||||
.upload-command {
|
||||
margin-top: 0;
|
||||
|
||||
@@ -14,12 +14,7 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
text: { type: String, default: '' },
|
||||
look: {
|
||||
type: String,
|
||||
validator: (value: string): boolean =>
|
||||
['success', 'error', ''].includes(value),
|
||||
default: ''
|
||||
}
|
||||
look: { type: String, default: '' }
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -27,26 +22,26 @@ defineProps({
|
||||
.toast-wrapper {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: toRem(2);
|
||||
bottom: 2rem;
|
||||
transform: translateX(100%);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--white);
|
||||
@include text(s-regular);
|
||||
border-radius: toRem(0.5);
|
||||
height: toRem(4);
|
||||
min-width: toRem(10);
|
||||
padding-right: toRem(1);
|
||||
padding-left: toRem(1);
|
||||
border-radius: 0.5rem;
|
||||
height: 4rem;
|
||||
min-width: 10rem;
|
||||
padding-right: 1rem;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
.button-close {
|
||||
position: absolute;
|
||||
top: toRem(-0.5);
|
||||
right: toRem(-0.5);
|
||||
height: toRem(1.8);
|
||||
width: toRem(1.8);
|
||||
border: toRem(0.1) solid var(--black);
|
||||
top: -0.5rem;
|
||||
right: -0.5rem;
|
||||
height: 1.8rem;
|
||||
width: 1.8rem;
|
||||
border: 0.1rem solid var(--black);
|
||||
background-color: var(--white);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
@@ -55,10 +50,10 @@ defineProps({
|
||||
}
|
||||
.toast-text {
|
||||
margin-bottom: 0;
|
||||
margin-left: toRem(1);
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.display {
|
||||
transform: translateX(toRem(-3));
|
||||
transform: translateX(-3rem);
|
||||
transition: transform 0.3s ease-in-out;
|
||||
}
|
||||
.error {
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
<template>
|
||||
<section :class="['information-section', { 'first-sequence': index === 0 }]">
|
||||
<div class="uploaded-pictures">
|
||||
<p v-if="index === 0" class="title-current-upload">
|
||||
{{ $t('pages.upload.sequence_uploading_title') }}
|
||||
</p>
|
||||
<p v-if="sequence.pictures" class="uploaded-title">
|
||||
<span
|
||||
>{{ $t('pages.upload.import') }} {{ sequence.title }} -
|
||||
{{ sequence.pictures.length }}/{{ sequence.pictureCount }}</span
|
||||
>
|
||||
</p>
|
||||
<ul class="uploaded-picture-list">
|
||||
<PictureItem
|
||||
v-for="picture in uploadPictures"
|
||||
:text="$t('pages.upload.uploaded_word')"
|
||||
:name="picture.name"
|
||||
look="success"
|
||||
>
|
||||
<i class="bi bi-check-circle"></i>
|
||||
</PictureItem>
|
||||
</ul>
|
||||
<div v-if="sequence.id" class="wrapper-button-sequence">
|
||||
<Link
|
||||
:text="$t('pages.upload.sequence_link')"
|
||||
look="button button--white"
|
||||
target="_blank"
|
||||
:route="{ name: 'sequence', params: { id: sequence.id } }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="errors-pictures">
|
||||
<p v-if="uploadErrors.length" class="uploaded-title">
|
||||
{{ uploadErrors.length }} {{ $t('pages.upload.error_word') }}
|
||||
</p>
|
||||
<ul class="uploaded-error-list">
|
||||
<PictureItem
|
||||
v-for="error in uploadErrors"
|
||||
:text="error.message"
|
||||
:name="error.name"
|
||||
look="error"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { PropType } from 'vue'
|
||||
import Link from '@/components/Link.vue'
|
||||
import PictureItem from '@/components/upload/PictureItem.vue'
|
||||
import type { uploadErrorInterface } from '@/views/interfaces/UploadPicturesView'
|
||||
defineProps({
|
||||
index: { type: Number, default: 0 },
|
||||
sequence: { type: Object, default: {} },
|
||||
picturesCount: { type: Number, default: null },
|
||||
uploadErrors: {
|
||||
type: Array as PropType<uploadErrorInterface[]>,
|
||||
default: []
|
||||
},
|
||||
uploadPictures: {
|
||||
type: Array as PropType<uploadErrorInterface[]>,
|
||||
default: []
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.information-section {
|
||||
display: flex;
|
||||
margin-bottom: toRem(4);
|
||||
padding-top: toRem(2);
|
||||
padding-bottom: toRem(2);
|
||||
width: 80%;
|
||||
box-shadow: 0px 6.45694px 8.60925px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.first-sequence {
|
||||
background-color: var(--blue-pale);
|
||||
border-radius: toRem(0.5);
|
||||
}
|
||||
.uploaded-pictures,
|
||||
.errors-pictures {
|
||||
width: 50%;
|
||||
}
|
||||
.title-current-upload {
|
||||
margin-bottom: toRem(2);
|
||||
@include text(m-regular);
|
||||
color: var(--blue-dark);
|
||||
margin-left: toRem(2);
|
||||
}
|
||||
.uploaded-title {
|
||||
margin-left: toRem(2);
|
||||
margin-bottom: toRem(2);
|
||||
width: 100%;
|
||||
color: var(--grey-semi-dark);
|
||||
font-weight: bold;
|
||||
@include text(s-regular);
|
||||
}
|
||||
.uploaded-picture-list {
|
||||
padding-left: 0;
|
||||
}
|
||||
.uploaded-picture-list,
|
||||
.uploaded-error-list {
|
||||
padding: 0rem toRem(2) toRem(2);
|
||||
overflow-y: auto;
|
||||
max-height: toRem(35);
|
||||
}
|
||||
.bi-check-circle {
|
||||
color: var(--green);
|
||||
font-size: toRem(2);
|
||||
}
|
||||
.wrapper-button-sequence {
|
||||
padding-top: toRem(2);
|
||||
width: fit-content;
|
||||
margin: auto;
|
||||
}
|
||||
.errors-pictures {
|
||||
padding-left: toRem(2);
|
||||
padding-right: toRem(2);
|
||||
.uploaded-title {
|
||||
margin-top: toRem(4);
|
||||
}
|
||||
}
|
||||
@media (max-width: toRem(76.8)) {
|
||||
.uploaded-pictures,
|
||||
.errors-pictures {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,52 +0,0 @@
|
||||
<template>
|
||||
<li :class="['uploaded-picture-item', look]">
|
||||
<div class="uploaded-information">
|
||||
<span v-if="itemUploadedText">{{ itemUploadedText }}</span>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
const props = defineProps({
|
||||
name: { type: String, default: '' },
|
||||
text: { type: String, default: '' },
|
||||
look: {
|
||||
type: String,
|
||||
validator: (value: string): boolean =>
|
||||
['error', 'success', 'default'].includes(value),
|
||||
default: 'default'
|
||||
}
|
||||
})
|
||||
|
||||
const itemUploadedText = computed<string | null>(
|
||||
() => `${props.name} - ${props.text}`
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.uploaded-picture-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: toRem(1) toRem(2);
|
||||
margin-top: toRem(1);
|
||||
border-radius: toRem(0.5);
|
||||
width: 100%;
|
||||
@include text(s-regular);
|
||||
}
|
||||
.success {
|
||||
background-color: var(--white);
|
||||
border: toRem(0.1) solid var(--grey);
|
||||
color: var(--black);
|
||||
}
|
||||
.error {
|
||||
background-color: var(--white);
|
||||
border: toRem(0.1) solid var(--red-pale);
|
||||
color: var(--red);
|
||||
}
|
||||
.uploaded-information {
|
||||
margin-right: toRem(1);
|
||||
}
|
||||
</style>
|
||||
@@ -1,108 +0,0 @@
|
||||
<template>
|
||||
<div class="wrapper-loader">
|
||||
<div class="loader">
|
||||
<span class="loader-percentage">{{ loadPercentage }}</span>
|
||||
<Loader look="lg" :is-loaded="isLoaded" />
|
||||
</div>
|
||||
<div v-if="loadPercentage === '100%'" class="wrapper-button-new-upload">
|
||||
<Button
|
||||
:text="$t('pages.upload.button_new_upload')"
|
||||
look="button button--blue"
|
||||
@trigger="$emit('triggerNewUpload')"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="uploadedSequences[0]" class="loader-information">
|
||||
<span class="loader-title">{{ uploadPendingTitle }}</span>
|
||||
<span v-if="loadPercentage !== '100%'" class="loader-text">{{
|
||||
$t('pages.upload.upload_pending_pictures', { count: picturesCount })
|
||||
}}</span>
|
||||
<span class="loader-text-size"
|
||||
>{{ uploadedSequences[0].pictureSize }}/{{ loadTextSize }}
|
||||
</span>
|
||||
<span class="loader-text-warning">{{
|
||||
$t('pages.upload.leave_message')
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Loader from '@/components/Loader.vue'
|
||||
import Button from '@/components/Button.vue'
|
||||
import { computed } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import type { sequenceInterface } from '@/views/interfaces/UploadPicturesView'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const { t } = useI18n()
|
||||
const props = defineProps({
|
||||
loadPercentage: { type: String, default: '0%' },
|
||||
loadTextSize: { type: String, default: '0 Mo' },
|
||||
isLoaded: { type: Boolean, default: false },
|
||||
uploadedSequences: {
|
||||
type: Array as PropType<sequenceInterface[]>,
|
||||
default: []
|
||||
},
|
||||
picturesCount: { type: Number, default: null }
|
||||
})
|
||||
|
||||
const uploadPendingTitle = computed<string>(() => {
|
||||
if (props.loadPercentage !== '100%') return t('pages.upload.upload_pending')
|
||||
return t('pages.upload.upload_done')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.wrapper-loader {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: toRem(4);
|
||||
}
|
||||
.loader {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.loader-percentage {
|
||||
width: toRem(10);
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
@include text(h1);
|
||||
color: var(--blue);
|
||||
}
|
||||
.wrapper-button-new-upload {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.loader-information {
|
||||
margin-top: toRem(2);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.loader-title,
|
||||
.loader-text,
|
||||
.loader-text-size {
|
||||
text-align: center;
|
||||
@include text(s-regular);
|
||||
}
|
||||
.loader-title {
|
||||
@include text(h2);
|
||||
margin-bottom: toRem(0.5);
|
||||
}
|
||||
.loader-text {
|
||||
color: var(--blue);
|
||||
border-bottom: toRem(0.1) solid var(--grey);
|
||||
}
|
||||
.loader-text-size {
|
||||
color: var(--grey-semi-dark);
|
||||
}
|
||||
.loader-text-warning {
|
||||
text-align: center;
|
||||
@include text(s-regular);
|
||||
color: var(--orange);
|
||||
margin-top: toRem(1);
|
||||
width: toRem(31);
|
||||
}
|
||||
</style>
|
||||
@@ -1,16 +1,15 @@
|
||||
{
|
||||
"general": {
|
||||
"title": "Instance Panoramax",
|
||||
"title": "Instance Panoramax IGN",
|
||||
"meta": {
|
||||
"title": "Instance Panoramax",
|
||||
"title": "Instance Panoramax IGN",
|
||||
"description": "Panoramax, l’alternative libre pour photo-cartographier les territoires"
|
||||
},
|
||||
"header": {
|
||||
"contribute_text": "À propos",
|
||||
"upload_text": "Contribuer",
|
||||
"contribute_text": "Partager vos photos",
|
||||
"sequences_text": "Mes photos",
|
||||
"alt_logo": "Logo de l'instance",
|
||||
"title": "Instance\nPanoramax",
|
||||
"title": "Instance Panoramax IGN",
|
||||
"beta_text": "Version beta",
|
||||
"logout_text": "Déconnexion",
|
||||
"my_information_text": "Mes informations",
|
||||
@@ -33,25 +32,19 @@
|
||||
"setting_tooltip": "Afficher ou masquer le token"
|
||||
},
|
||||
"sequence": {
|
||||
"sequence_published": "Publiée",
|
||||
"sequence_waiting": "En cours de publication",
|
||||
"sequence_hidden": "Masquée",
|
||||
"title": "Séquence :",
|
||||
"hide_sequence_tooltip": "Masque la séquence sur la carte",
|
||||
"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",
|
||||
"delete_photo_tooltip": "Supprime définitivement les photo",
|
||||
"confirm_dialog": "Les photos sélectionnées vont être définitivement supprimées",
|
||||
"created": "Versement :",
|
||||
"taken": "Prise de vue :",
|
||||
"duration": "Durée :",
|
||||
"duration_begin": "Début :",
|
||||
"duration_end": "Fin :",
|
||||
"camera": "Matériel :",
|
||||
"button_delete": "Supprimer",
|
||||
"button_disable": "Masquer",
|
||||
"button_enable": "Afficher",
|
||||
"picture_selected": "{count} photo sélectionnée| {count} photos sélectionnées",
|
||||
"hours": "{count} heure| {count} heures",
|
||||
"minutes": "{count} minute| {count} minutes",
|
||||
"seconds": "{count} seconde| {count} secondes",
|
||||
@@ -71,10 +64,9 @@
|
||||
"sequence_waiting": "⌛ En cours de publication",
|
||||
"sequence_hidden": "❌ Masquée",
|
||||
"no_sequences_text": "Vous n'avez pas encore de photos publiées \uD83D\uDE22",
|
||||
"button_upload": "Partager vos photos",
|
||||
"sequence_deleted": "La séquence a bien été supprimée"
|
||||
"button_upload": "Partager vos photos"
|
||||
},
|
||||
"share_pictures": {
|
||||
"upload": {
|
||||
"title": "Partagez vos photos",
|
||||
"sub_title": "Un compte utilisateur est obligatoire pour partager des photos",
|
||||
"photo_type1": "Des lieux visibles depuis la voie publique",
|
||||
@@ -91,28 +83,8 @@
|
||||
"user_account_button": "Créer un compte",
|
||||
"description_terminal": "<a href='https://gitlab.com/geovisio/cli' target='_blank' style='color:black'>L'outil en ligne de commande</a> vous permet de partager de grands volumes de photos. La procédure est simple et vous devez disposer <a target='_blank' href='https://www.python.org/downloads/' style='color:black'>de python (au moins la version 3.8)</a>.\n\n1. Installer l’outil en ligne de commande geovisio\n2. Lancez la commande de versement d’images sur le dossier choisi. Après '--api-url', renseignez l'url de l'api de l'instance où partager les photos et le chemin vers votre dossier de photos sur votre machine. L’outil demandera vos informations de connexion avant l'import. Une fois les données chargées, un temps de traitement est nécessaire pour les rendre disponibles.",
|
||||
"terminal_install": "pip install geovisio_cli",
|
||||
"terminal_text": "geovisio upload --api-url {url} <DOSSIER_PHOTOS>",
|
||||
"terminal_text": "geovisio upload --api-url https://panoramax.ign.fr <DOSSIER_PHOTOS>",
|
||||
"button_copy": "Copier"
|
||||
},
|
||||
"upload": {
|
||||
"title": "Déposez vos photos",
|
||||
"input_label": "Déposez des photos dans la zone ou ",
|
||||
"import_word": "importez",
|
||||
"import_type": "Format JPEG uniquement",
|
||||
"sequence_title": "Séquence du ",
|
||||
"button_text": "Envoyer",
|
||||
"uploaded_files": "{count} fichier| {count} fichiers",
|
||||
"no_uploaded_files": "Aucun fichier sélectionné",
|
||||
"uploaded_word": " Image téléchargée",
|
||||
"import": "Imports",
|
||||
"error_word": "Images en erreur",
|
||||
"sequence_uploading_title": "Dernier import",
|
||||
"upload_pending": "Transfert en cours...",
|
||||
"upload_done": "Transfert terminé !",
|
||||
"upload_pending_pictures": "Envoi de {count} photo en cours |Envoi de {count} photos en cours",
|
||||
"sequence_link": "Voir la sequence",
|
||||
"button_new_upload": "Nouvel envoi",
|
||||
"leave_message": "⚠️ Attention, le téléchargement sera interrompu si vous quittez la page avant la fin."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,11 @@ import axios from 'axios'
|
||||
import VueAxios from 'vue-axios'
|
||||
import { globalCookiesConfig } from 'vue3-cookies'
|
||||
import { createMetaManager } from 'vue-meta'
|
||||
import { pinia } from './store'
|
||||
import fr from './locales/fr.json'
|
||||
import './assets/main.scss'
|
||||
import './assets/main.css'
|
||||
import 'bootstrap/dist/css/bootstrap.css'
|
||||
import 'bootstrap/dist/js/bootstrap.js'
|
||||
import 'bootstrap-icons/font/bootstrap-icons.css'
|
||||
import 'geovisio/build/index.css'
|
||||
|
||||
axios.defaults.baseURL = import.meta.env.VITE_API_URL
|
||||
axios.defaults.withCredentials = true
|
||||
@@ -33,7 +31,6 @@ globalCookiesConfig({
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(pinia)
|
||||
app.use(i18n)
|
||||
app.use(router)
|
||||
app.use(VueAxios, axios)
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { useCookies } from 'vue3-cookies'
|
||||
import type {
|
||||
RouteRecordRaw,
|
||||
NavigationGuardNext,
|
||||
RouteLocationNormalized
|
||||
} from 'vue-router'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import axios from 'axios'
|
||||
import { getAuthRoute } from '@/utils/auth'
|
||||
import HomeView from '../views/HomeView.vue'
|
||||
@@ -12,8 +8,7 @@ import MyInformationView from '../views/MyInformationView.vue'
|
||||
import MySettingsView from '../views/MySettingsView.vue'
|
||||
import MySequencesView from '../views/MySequencesView.vue'
|
||||
import MySequenceView from '../views/MySequenceView.vue'
|
||||
import SharePicturesView from '../views/SharePicturesView.vue'
|
||||
import UploadPicturesView from '../views/UploadPicturesView.vue'
|
||||
import UploadView from '../views/UploadView.vue'
|
||||
const { cookies } = useCookies()
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
@@ -39,60 +34,35 @@ const routes: Array<RouteRecordRaw> = [
|
||||
{ path: '/sequence/:id', name: 'sequence', component: MySequenceView },
|
||||
{
|
||||
path: '/partager-des-photos',
|
||||
name: 'share-pictures',
|
||||
component: SharePicturesView
|
||||
},
|
||||
{
|
||||
path: '/envoyer',
|
||||
name: 'upload-pictures',
|
||||
component: UploadPicturesView
|
||||
name: 'upload',
|
||||
component: UploadView
|
||||
}
|
||||
]
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
})
|
||||
router.beforeResolve(
|
||||
async (
|
||||
to: RouteLocationNormalized,
|
||||
from: RouteLocationNormalized,
|
||||
next: NavigationGuardNext
|
||||
) => {
|
||||
const siteLoggedRoutes =
|
||||
to.name === 'my-settings' ||
|
||||
to.name === 'my-sequences' ||
|
||||
to.name === 'sequence' ||
|
||||
to.name === 'upload-pictures'
|
||||
|
||||
if (siteLoggedRoutes) {
|
||||
if (!isSiteLogged()) goToLoginPage(to.path)
|
||||
else return next()
|
||||
}
|
||||
if (to.name === 'my-information') {
|
||||
try {
|
||||
const keycloakLogout = await isKeycloakLogout()
|
||||
if (keycloakLogout.status >= 300 || !isSiteLogged()) {
|
||||
return goToLoginPage(to.path)
|
||||
} else return next()
|
||||
} catch (e) {
|
||||
return goToLoginPage(to.path)
|
||||
router.beforeResolve(async (to, from, next) => {
|
||||
const loggedRoutes =
|
||||
to.name === 'my-information' ||
|
||||
to.name === 'my-settings' ||
|
||||
to.name === 'my-sequences' ||
|
||||
to.name === 'sequence'
|
||||
if (loggedRoutes) {
|
||||
try {
|
||||
const loginUrl = `/api/users/me`
|
||||
const isKeycloakLogout = await axios.get(loginUrl)
|
||||
const isSiteLogin = !!cookies.get('user_id')
|
||||
if (isKeycloakLogout.status >= 300 || !isSiteLogin) {
|
||||
window.location.replace(getAuthRoute('auth/login', to.path))
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
} catch (e) {
|
||||
window.location.replace(getAuthRoute('auth/login', to.path))
|
||||
}
|
||||
next()
|
||||
}
|
||||
)
|
||||
|
||||
function isSiteLogged(): boolean {
|
||||
return !!cookies.get('user_id')
|
||||
}
|
||||
|
||||
async function isKeycloakLogout(): Promise<{ status: number }> {
|
||||
const loginUrl = `/api/users/me`
|
||||
return await axios.get(loginUrl)
|
||||
}
|
||||
|
||||
function goToLoginPage(path: string): void {
|
||||
window.location.replace(getAuthRoute('auth/login', path))
|
||||
}
|
||||
} else next()
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
import { createPinia } from 'pinia'
|
||||
export const pinia = createPinia()
|
||||
@@ -1,17 +0,0 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useSequenceStore = defineStore('sequence', {
|
||||
state: () => ({
|
||||
toastText: <string>'',
|
||||
toastLook: <string>''
|
||||
}),
|
||||
actions: {
|
||||
addToastText(text: string, look: string): void {
|
||||
this.toastText = text
|
||||
this.toastLook = look
|
||||
setTimeout(() => {
|
||||
this.toastText = ''
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
describe('In the login page', () => {
|
||||
describe('In the upload page', () => {
|
||||
it('go to the login page', () => {
|
||||
cy.visit('partager-des-photos')
|
||||
cy.fixture('upload').then((uploadData) => {
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"addressToSearch": "97 boulevard Voltaire 75011 paris",
|
||||
"textAddressToSelect": "Boulevard Voltaire, Quartier de la Folie-Méricourt, Paris 11e Arrondissement, Paris, Île-de-France, France métropolitaine, 75011, France",
|
||||
"textLinkUpload": "À propos"
|
||||
"textLinkUpload": "Partager vos photos"
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
import { test, describe, expect } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import Button from '../../../components/Button.vue'
|
||||
import i18n from '../config'
|
||||
describe('Template', () => {
|
||||
describe('Props', () => {
|
||||
test('should have default props', () => {
|
||||
const wrapper = shallowMount(Button, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
}
|
||||
})
|
||||
expect(wrapper.vm.icon).toBe(null)
|
||||
expect(wrapper.vm.disabled).toBe(false)
|
||||
expect(wrapper.vm.isLoading).toBe(false)
|
||||
expect(wrapper.vm.text).toBe('')
|
||||
expect(wrapper.vm.tooltip).toBe('')
|
||||
expect(wrapper.vm.look).toBe('')
|
||||
expect(wrapper.vm.type).toBe('button')
|
||||
})
|
||||
})
|
||||
describe('When the component is disabled', () => {
|
||||
test('should render the button with disabled class', () => {
|
||||
const wrapper = shallowMount(Button, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
disabled: true
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="default disabled"')
|
||||
})
|
||||
})
|
||||
describe('When the component is loading', () => {
|
||||
test('should render the button with disabled class', () => {
|
||||
const wrapper = shallowMount(Button, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
isLoading: true
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="default disabled"')
|
||||
})
|
||||
})
|
||||
describe('When the component have an icon', () => {
|
||||
test('should render the button the icon displayed', () => {
|
||||
const wrapper = shallowMount(Button, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
icon: 'my-icon'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('<i')
|
||||
expect(wrapper.html()).contains('class="my-icon icon"')
|
||||
})
|
||||
})
|
||||
describe('When the component have a type submit', () => {
|
||||
test('should render the type to submit', () => {
|
||||
const wrapper = shallowMount(Button, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
type: 'submit'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('type="submit"')
|
||||
})
|
||||
})
|
||||
describe('When the component have tooltip', () => {
|
||||
test('should render the button with a tooltip', () => {
|
||||
const wrapper = shallowMount(Button, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
tooltip: 'my tooltip'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains(
|
||||
'class="tooltip-button">my tooltip</span>'
|
||||
)
|
||||
})
|
||||
})
|
||||
describe('When the component have a text', () => {
|
||||
test('should render the button with a text', () => {
|
||||
const wrapper = shallowMount(Button, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
text: 'My text'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="text">My text</span>')
|
||||
})
|
||||
})
|
||||
describe('When the component have specific look', () => {
|
||||
test('should render the button with a specific class', () => {
|
||||
const wrapper = shallowMount(Button, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
look: 'my--look'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="my--look default"')
|
||||
})
|
||||
})
|
||||
describe('When the button is trigger', () => {
|
||||
test('should emit', async () => {
|
||||
const wrapper = shallowMount(Button, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
}
|
||||
})
|
||||
await wrapper.trigger('click')
|
||||
expect(wrapper.emitted()).toHaveProperty('trigger')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -5,9 +5,6 @@ import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { useCookies } from 'vue3-cookies'
|
||||
import fr from '../../../locales/fr.json'
|
||||
import Header from '../../../components/Header.vue'
|
||||
import MyInformation from '../../../views/MyInformationView.vue'
|
||||
import MySettings from '../../../views/MySettingsView.vue'
|
||||
import SharePictures from '../../../views/SharePicturesView.vue'
|
||||
vi.mock('vue-router')
|
||||
vi.mock('vue3-cookies', () => {
|
||||
const mockCookies = {
|
||||
@@ -83,6 +80,26 @@ describe('Template', () => {
|
||||
expect(wrapper.html()).contains('general.header.sequences_text')
|
||||
expect(wrapper.html()).contains('general.header.my_settings_text')
|
||||
})
|
||||
it('should render the component with all links', async () => {
|
||||
vi.spyOn(useCookies().cookies, 'get').mockReturnValue('user_id=id')
|
||||
import.meta.env.VITE_API_URL = 'api-url/'
|
||||
const wrapper = shallowMount(Header, {
|
||||
props: {
|
||||
authEnabled: true,
|
||||
userProfileUrl: 'profil'
|
||||
},
|
||||
global: {
|
||||
plugins: [i18n, router],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('auth/logout')
|
||||
expect(wrapper.html()).contains('path="/mes-informations"')
|
||||
expect(wrapper.html()).contains('path="/mes-parametres"')
|
||||
expect(wrapper.html()).contains('path="/mes-sequences"')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
import { it, describe, expect } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import ImageItem from '../../../components/ImageItem.vue'
|
||||
|
||||
describe('Template', () => {
|
||||
describe('Props', () => {
|
||||
it('should have default props', () => {
|
||||
const wrapper = shallowMount(ImageItem, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.vm.created).toBe(null)
|
||||
expect(wrapper.vm.href).toBe(null)
|
||||
expect(wrapper.vm.hrefHd).toBe(null)
|
||||
expect(wrapper.vm.selected).toBe(false)
|
||||
expect(wrapper.vm.selectedOnMap).toBe(false)
|
||||
expect(wrapper.vm.status).toBe('')
|
||||
})
|
||||
})
|
||||
describe('When the component have default props filled', () => {
|
||||
it('should render the component with a href, a hrefHd and a date', () => {
|
||||
const wrapper = shallowMount(ImageItem, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
created: '10 mars 2023',
|
||||
href: 'my-url',
|
||||
hrefHd: 'my-url-hd',
|
||||
status: 'ready'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('<img')
|
||||
expect(wrapper.html()).contains('src="my-url"')
|
||||
expect(wrapper.html()).contains('<link-stub')
|
||||
expect(wrapper.html()).contains('10 mars 2023')
|
||||
})
|
||||
})
|
||||
describe('When the component is selected', () => {
|
||||
it('should render the component with the selected class', () => {
|
||||
const wrapper = shallowMount(ImageItem, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
selected: true
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="selected button-image-item"')
|
||||
})
|
||||
})
|
||||
describe('When the component is ready', () => {
|
||||
it('should render the component with the status class', () => {
|
||||
const wrapper = shallowMount(ImageItem, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
href: 'my-url',
|
||||
status: 'ready'
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.html()).contains('src="my-url"')
|
||||
expect(wrapper.html()).contains('class="ready"')
|
||||
})
|
||||
})
|
||||
describe('When the component is waiting-for-process', () => {
|
||||
it('should render the component with waiting-for-process classes and elements', () => {
|
||||
const wrapper = shallowMount(ImageItem, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
status: 'waiting-for-process',
|
||||
href: 'my-url'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="waiting-for-process"')
|
||||
expect(wrapper.html()).contains('pages.sequence.waiting_process')
|
||||
expect(wrapper.html()).contains('class="bi bi-card-image icon-waiting"')
|
||||
})
|
||||
})
|
||||
describe('When the component is selected on the map but not selected', () => {
|
||||
it('should render the component with the map pointer', () => {
|
||||
const wrapper = shallowMount(ImageItem, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
selectedOnMap: true,
|
||||
selected: false
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="icon-img pointer-map"')
|
||||
})
|
||||
})
|
||||
describe('When the component is selected but not selected on the map', () => {
|
||||
it('should render the component with the check icon', () => {
|
||||
const wrapper = shallowMount(ImageItem, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
selectedOnMap: false,
|
||||
selected: true
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="bi bi-check-lg"')
|
||||
expect(wrapper.html()).contains('class="icon-img button-check"')
|
||||
})
|
||||
})
|
||||
describe('When the component is selected and selected on the map', () => {
|
||||
it('should render the component with the check icon', () => {
|
||||
const wrapper = shallowMount(ImageItem, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
selectedOnMap: true,
|
||||
selected: true
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="icon-img button-check-pointer"')
|
||||
expect(wrapper.html()).contains('class="bi bi-check-lg"')
|
||||
})
|
||||
})
|
||||
describe('When the button is trigger', () => {
|
||||
it('should emit', () => {
|
||||
const wrapper = shallowMount(ImageItem, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
wrapper.vm.$emit('trigger')
|
||||
expect(wrapper.emitted().trigger).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,99 +0,0 @@
|
||||
import { it, describe, expect } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import InputCheckbox from '../../../components/InputCheckbox.vue'
|
||||
|
||||
describe('Template', () => {
|
||||
describe('Props', () => {
|
||||
it('should have default props', () => {
|
||||
const wrapper = shallowMount(InputCheckbox, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
expect(wrapper.vm.name).toBe(null)
|
||||
expect(wrapper.vm.label).toBe('')
|
||||
expect(wrapper.vm.isChecked).toBe(false)
|
||||
expect(wrapper.vm.isIndeterminate).toBe(false)
|
||||
})
|
||||
})
|
||||
describe('When the component have a label', () => {
|
||||
it('should render the component with a href, a hrefHd and a date', () => {
|
||||
const wrapper = shallowMount(InputCheckbox, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
label: 'my label'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="label"')
|
||||
expect(wrapper.html()).contains('my label')
|
||||
})
|
||||
})
|
||||
describe('When the component is checked', () => {
|
||||
it('should render the component with the checked icon', () => {
|
||||
const wrapper = shallowMount(InputCheckbox, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
isChecked: true,
|
||||
isIndeterminate: false
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="icon bi bi-check-square"')
|
||||
})
|
||||
})
|
||||
describe('When the component is not checked', () => {
|
||||
it('should render the component with the not checked icon', () => {
|
||||
const wrapper = shallowMount(InputCheckbox, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
isChecked: false,
|
||||
isIndeterminate: false
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="icon bi bi-square"')
|
||||
})
|
||||
})
|
||||
describe('When the component is not checked', () => {
|
||||
it('should render the component with the not checked icon', () => {
|
||||
const wrapper = shallowMount(InputCheckbox, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
isChecked: false,
|
||||
isIndeterminate: true
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="icon bi bi-dash-square"')
|
||||
})
|
||||
})
|
||||
describe('When the input is trigger', () => {
|
||||
it('should emit', () => {
|
||||
const wrapper = shallowMount(InputCheckbox, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
wrapper.vm.$emit('trigger', true)
|
||||
expect(wrapper.emitted().trigger).toBeTruthy()
|
||||
expect(wrapper.emitted().trigger[0][0]).toEqual(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -25,6 +25,35 @@ const stubs = {
|
||||
}
|
||||
}
|
||||
describe('Template', () => {
|
||||
describe('Snapshot', () => {
|
||||
test('Should match snapshot with external link', () => {
|
||||
const wrapper = mount(Link, {
|
||||
global: {
|
||||
stubs,
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
type: 'external',
|
||||
text: 'My-text',
|
||||
path: 'my-path',
|
||||
target: '_blank'
|
||||
}
|
||||
})
|
||||
expect(wrapper.element).toMatchSnapshot()
|
||||
})
|
||||
test('Should match snapshot with internal link', () => {
|
||||
const wrapper = shallowMount(Link, {
|
||||
global: {
|
||||
plugins: [i18n, router]
|
||||
},
|
||||
props: {
|
||||
text: 'My-text',
|
||||
path: 'my-path'
|
||||
}
|
||||
})
|
||||
expect(wrapper.element).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
describe('Props', () => {
|
||||
test('should have default props', () => {
|
||||
const wrapper = shallowMount(Link, {
|
||||
@@ -34,8 +63,7 @@ describe('Template', () => {
|
||||
})
|
||||
|
||||
expect(wrapper.vm.text).toBe(null)
|
||||
expect(wrapper.vm.route).toStrictEqual({})
|
||||
expect(wrapper.vm.pathExternal).toBe('')
|
||||
expect(wrapper.vm.path).toBe('')
|
||||
expect(wrapper.vm.look).toBe('')
|
||||
expect(wrapper.vm.type).toBe(null)
|
||||
expect(wrapper.vm.alt).toBe('')
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import { it, describe, expect } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import Loader from '../../../components/Loader.vue'
|
||||
import i18n from '../config'
|
||||
|
||||
describe('Template', () => {
|
||||
describe('Props', () => {
|
||||
it('should have default props', () => {
|
||||
const wrapper = shallowMount(Loader, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
}
|
||||
})
|
||||
expect(wrapper.vm.text).toBe(null)
|
||||
expect(wrapper.vm.look).toBe('sm')
|
||||
expect(wrapper.vm.isLoaded).toBe(true)
|
||||
})
|
||||
})
|
||||
describe('When the component is loading', () => {
|
||||
it('should not have the loaded class', () => {
|
||||
const wrapper = shallowMount(Loader, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
isLoaded: false
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="lds-ring sm"')
|
||||
})
|
||||
})
|
||||
describe('When the component have a look', () => {
|
||||
it('should have a look classe', () => {
|
||||
const wrapper = shallowMount(Loader, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
look: 'md'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="lds-ring md loaded"')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,75 +0,0 @@
|
||||
import { it, describe, expect } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import Pagination from '../../../components/Pagination.vue'
|
||||
|
||||
describe('Template', () => {
|
||||
describe('Props', () => {
|
||||
it('should have default props', () => {
|
||||
const wrapper = shallowMount(Pagination, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
expect(wrapper.vm.href).toBe(null)
|
||||
expect(wrapper.vm.selfLink).toStrictEqual({})
|
||||
expect(wrapper.vm.type).toBe('')
|
||||
})
|
||||
})
|
||||
describe('When the component is the last or the first page', () => {
|
||||
it('should render the component without border', () => {
|
||||
const wrapper = shallowMount(Pagination, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
type: 'double-left'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="wrapper-pagination no-border"')
|
||||
})
|
||||
})
|
||||
describe('When the component is disabled', () => {
|
||||
it('should render the component a disabled state', () => {
|
||||
const href = 'my-url'
|
||||
const wrapper = shallowMount(Pagination, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
type: 'double-left',
|
||||
href,
|
||||
selfLink: { href }
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="pagination-button disabled"')
|
||||
})
|
||||
})
|
||||
describe('When the button is trigger', () => {
|
||||
it('should emit', () => {
|
||||
const href = 'my-url'
|
||||
const wrapper = shallowMount(Pagination, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
type: 'left',
|
||||
href,
|
||||
selfLink: { href: 'self-link' }
|
||||
}
|
||||
})
|
||||
wrapper.vm.$nextTick()
|
||||
wrapper.vm.$emit('trigger', href)
|
||||
|
||||
expect(wrapper.emitted().trigger).toBeTruthy()
|
||||
expect(wrapper.emitted().trigger[0][0]).toEqual(href)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -26,7 +26,7 @@ describe('Template', () => {
|
||||
}
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('pages.share_pictures.button_copy')
|
||||
expect(wrapper.html()).contains('pages.upload.button_copy')
|
||||
})
|
||||
it('should render the view with the button', () => {
|
||||
const wrapper = shallowMount(Terminal, {
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import { it, describe, expect } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import Toast from '../../../components/Toast.vue'
|
||||
|
||||
describe('Template', () => {
|
||||
describe('Props', () => {
|
||||
it('should have default props', () => {
|
||||
const wrapper = shallowMount(Toast, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
expect(wrapper.vm.text).toBe('')
|
||||
expect(wrapper.vm.look).toStrictEqual('')
|
||||
})
|
||||
})
|
||||
describe('When the component have look and text filled', () => {
|
||||
it('should render the component with the error and the display class', () => {
|
||||
const wrapper = shallowMount(Toast, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: 'my text error',
|
||||
look: 'error'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="toast-wrapper error display"')
|
||||
expect(wrapper.html()).contains('class="bi bi-exclamation-triangle"')
|
||||
expect(wrapper.html()).contains('my text error')
|
||||
})
|
||||
|
||||
it('should render the component with the success and the display class', () => {
|
||||
const wrapper = shallowMount(Toast, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: 'my text success',
|
||||
look: 'success'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="toast-wrapper success display"')
|
||||
expect(wrapper.html()).contains('class="bi bi-check-circle"')
|
||||
expect(wrapper.html()).contains('my text success')
|
||||
})
|
||||
})
|
||||
describe('When the button is trigger', () => {
|
||||
it('should emit', () => {
|
||||
const wrapper = shallowMount(Toast, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
wrapper.vm.$emit('trigger')
|
||||
|
||||
expect(wrapper.emitted().trigger).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
32
src/tests/unit/components/__snapshots__/Link.spec.js.snap
Normal file
32
src/tests/unit/components/__snapshots__/Link.spec.js.snap
Normal file
@@ -0,0 +1,32 @@
|
||||
// Vitest Snapshot v1
|
||||
|
||||
exports[`Template > Snapshot > Should match snapshot with external link 1`] = `
|
||||
<a
|
||||
class="default"
|
||||
data-v-409c8661=""
|
||||
href="my-path"
|
||||
target="_blank"
|
||||
title=""
|
||||
>
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
<span
|
||||
class="text"
|
||||
data-v-409c8661=""
|
||||
>
|
||||
My-text
|
||||
</span>
|
||||
</a>
|
||||
`;
|
||||
|
||||
exports[`Template > Snapshot > Should match snapshot with internal link 1`] = `
|
||||
<router-link-stub
|
||||
ariacurrentvalue="page"
|
||||
class="default"
|
||||
custom="false"
|
||||
data-v-409c8661=""
|
||||
replace="false"
|
||||
title="My-text"
|
||||
to="my-path"
|
||||
/>
|
||||
`;
|
||||
@@ -1,111 +0,0 @@
|
||||
import { it, describe, expect } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import ImportedSection from '../../../../components/upload/ImportedSection.vue'
|
||||
import i18n from '../../config'
|
||||
|
||||
describe('Template', () => {
|
||||
describe('Props', () => {
|
||||
it('should have default props', () => {
|
||||
const wrapper = shallowMount(ImportedSection, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
}
|
||||
})
|
||||
expect(wrapper.vm.index).toBe(0)
|
||||
expect(wrapper.vm.sequence).toStrictEqual({})
|
||||
expect(wrapper.vm.picturesCount).toBe(null)
|
||||
expect(wrapper.vm.uploadErrors).toStrictEqual([])
|
||||
expect(wrapper.vm.uploadPictures).toStrictEqual([])
|
||||
})
|
||||
})
|
||||
describe('When the index is 0', () => {
|
||||
it('should have a specific class and wordings', () => {
|
||||
const wrapper = shallowMount(ImportedSection, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
index: 0
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains(
|
||||
'class="information-section first-sequence"'
|
||||
)
|
||||
expect(wrapper.html()).contains('Dernier impor')
|
||||
})
|
||||
})
|
||||
describe('When the sequence have pictures', () => {
|
||||
it('should have a title', () => {
|
||||
const wrapper = shallowMount(ImportedSection, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
sequence: {
|
||||
id: 'id132435',
|
||||
title: 'my title',
|
||||
pictureCount: 3,
|
||||
pictures: [{ id: 'id' }]
|
||||
}
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="uploaded-title"')
|
||||
expect(wrapper.html()).contains('Imports my title - 1/3')
|
||||
})
|
||||
it('should have a Link', () => {
|
||||
const wrapper = shallowMount(ImportedSection, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
sequence: {
|
||||
id: 'id132435',
|
||||
title: 'my title',
|
||||
pictureCount: 3,
|
||||
pictures: [{ id: 'id' }]
|
||||
}
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('<link-stub')
|
||||
expect(wrapper.html()).contains('text="Voir la sequence"')
|
||||
})
|
||||
})
|
||||
describe('When there are uploaded pictures', () => {
|
||||
it('should have a list of pictures', () => {
|
||||
const wrapper = shallowMount(ImportedSection, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
uploadPictures: [{ name: 'my name1' }, { name: 'my name2' }]
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('name="my name1"')
|
||||
expect(wrapper.html()).contains('name="my name2"')
|
||||
expect(wrapper.html()).contains('text=" Image téléchargée"')
|
||||
expect(wrapper.html()).contains('look="success"')
|
||||
expect(wrapper.html()).contains('<picture-item-stub')
|
||||
})
|
||||
})
|
||||
describe('When there are uploaded errors', () => {
|
||||
it('should have a list of errors', () => {
|
||||
const wrapper = shallowMount(ImportedSection, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
uploadErrors: [
|
||||
{ name: 'my name1', message: 'my message1' },
|
||||
{ name: 'my name2', message: 'my message2' }
|
||||
]
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('name="my name1"')
|
||||
expect(wrapper.html()).contains('name="my name2"')
|
||||
expect(wrapper.html()).contains('text="my message1"')
|
||||
expect(wrapper.html()).contains('text="my message2"')
|
||||
expect(wrapper.html()).contains('look="error"')
|
||||
expect(wrapper.html()).contains('<picture-item-stub')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,47 +0,0 @@
|
||||
import { it, describe, expect } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import PictureItem from '../../../../components/upload/PictureItem.vue'
|
||||
import i18n from '../../config'
|
||||
|
||||
describe('Template', () => {
|
||||
describe('Props', () => {
|
||||
it('should have default props', () => {
|
||||
const wrapper = shallowMount(PictureItem, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
}
|
||||
})
|
||||
expect(wrapper.vm.name).toBe('')
|
||||
expect(wrapper.vm.text).toStrictEqual('')
|
||||
expect(wrapper.vm.look).toBe('default')
|
||||
})
|
||||
})
|
||||
describe('When the component have a look', () => {
|
||||
it('should have a specific class', () => {
|
||||
const wrapper = shallowMount(PictureItem, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
look: 'success'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="uploaded-picture-item success"')
|
||||
})
|
||||
})
|
||||
describe('When the component have a name and a text', () => {
|
||||
it('should have a specific class', () => {
|
||||
const wrapper = shallowMount(PictureItem, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
name: 'name',
|
||||
text: 'text'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="uploaded-information"')
|
||||
expect(wrapper.html()).contains('name - text')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,68 +0,0 @@
|
||||
import { it, describe, expect } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import UploadLoader from '../../../../components/upload/UploadLoader.vue'
|
||||
import i18n from '../../config'
|
||||
|
||||
describe('Template', () => {
|
||||
describe('Props', () => {
|
||||
it('should have default props', () => {
|
||||
const wrapper = shallowMount(UploadLoader, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
}
|
||||
})
|
||||
expect(wrapper.vm.loadPercentage).toBe('0%')
|
||||
expect(wrapper.vm.loadTextSize).toBe('0 Mo')
|
||||
expect(wrapper.vm.isLoaded).toBe(false)
|
||||
expect(wrapper.vm.picturesCount).toBe(null)
|
||||
expect(wrapper.vm.uploadedSequences).toStrictEqual([])
|
||||
})
|
||||
})
|
||||
describe('When the component have a percentage equal to 100%', () => {
|
||||
it('should have a button to do a new upload', () => {
|
||||
const wrapper = shallowMount(UploadLoader, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
loadPercentage: '100%'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('button-stub')
|
||||
expect(wrapper.html()).contains('text="Nouvel envoi"')
|
||||
})
|
||||
})
|
||||
describe('When the component have an uploaded sequence', () => {
|
||||
it('should render the uploaded sequences information', () => {
|
||||
const wrapper = shallowMount(UploadLoader, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
uploadedSequences: [{ pictureSize: '2345 Mo' }],
|
||||
loadTextSize: '2345 Mo',
|
||||
loadPercentage: '97%'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('Transfert en cours...')
|
||||
expect(wrapper.html()).contains('2345 Mo/2345 Mo')
|
||||
})
|
||||
describe('When the loading is completed', () => {
|
||||
it('should render the loading ended information', () => {
|
||||
const wrapper = shallowMount(UploadLoader, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
uploadedSequences: [{ pictureSize: '2345 Mo' }],
|
||||
loadTextSize: '2345 Mo',
|
||||
loadPercentage: '100%'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('text="Nouvel envoi"')
|
||||
expect(wrapper.html()).contains('Transfert terminé !')
|
||||
expect(wrapper.html()).contains('2345 Mo/2345 Mo')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,15 +0,0 @@
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import fr from '../../locales/fr.json'
|
||||
|
||||
const i18n = createI18n({
|
||||
locale: 'fr',
|
||||
fallbackLocale: 'fr',
|
||||
globalInjection: true,
|
||||
legacy: false,
|
||||
warnHtmlMessage: false,
|
||||
messages: {
|
||||
fr
|
||||
}
|
||||
})
|
||||
|
||||
export default i18n
|
||||
@@ -1,217 +0,0 @@
|
||||
import { it, describe, expect } from 'vitest'
|
||||
import {
|
||||
imageStatus,
|
||||
photoToDeleteOrPatchSelected,
|
||||
spliceIntoChunks,
|
||||
formatPaginationItems
|
||||
} from '../../views/utils/sequence/index'
|
||||
import {
|
||||
formatPictureSize,
|
||||
formatTextSize,
|
||||
sortByName
|
||||
} from '../../views/utils/upload/index'
|
||||
import { getAuthRoute } from '../../utils/auth'
|
||||
import { img, getPicId } from '../../utils/image'
|
||||
import title from '../../utils/index'
|
||||
|
||||
describe('imageStatus', () => {
|
||||
it('should render the "status" value', () => {
|
||||
const sequenceStatus = 'hidden'
|
||||
const imgStatus = 'not hidden'
|
||||
expect(imageStatus(imgStatus, sequenceStatus)).toEqual('hidden')
|
||||
})
|
||||
it('should render the "sequenceStatus" value', () => {
|
||||
const sequenceStatus = 'not hidden'
|
||||
const status = 'hidden'
|
||||
expect(imageStatus(status, sequenceStatus)).toEqual('hidden')
|
||||
})
|
||||
})
|
||||
|
||||
describe('photoToDeleteOrPatchSelected', () => {
|
||||
it('should render true', () => {
|
||||
const imagesToDelete = ['1', '2']
|
||||
const item = {
|
||||
assets: { thumb: { href: '' }, hd: { href: '' } },
|
||||
properties: { created: new Date(), 'geovisio:status': '' },
|
||||
id: '1',
|
||||
bbox: [1, 3]
|
||||
}
|
||||
expect(photoToDeleteOrPatchSelected(item, imagesToDelete)).toEqual(true)
|
||||
})
|
||||
it('should render false', () => {
|
||||
const imagesToDelete = ['1', '2']
|
||||
const item = {
|
||||
assets: { thumb: { href: '' }, hd: { href: '' } },
|
||||
properties: { created: new Date(), 'geovisio:status': '' },
|
||||
id: '3',
|
||||
bbox: [1, 3]
|
||||
}
|
||||
expect(photoToDeleteOrPatchSelected(item, imagesToDelete)).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('spliceIntoChunks', () => {
|
||||
it('should render an chunked array of array with 4 elements max', () => {
|
||||
const array = ['123', '345', '6777', '0000', '66666', '222222', '9393888']
|
||||
const chunkSize = 4
|
||||
expect(spliceIntoChunks(array, chunkSize)).toEqual([
|
||||
['123', '345', '6777', '0000'],
|
||||
['66666', '222222', '9393888']
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatPaginationItems', () => {
|
||||
it('should render the "rel" links formated and without the "left" element', () => {
|
||||
const links = [
|
||||
{
|
||||
href: 'http://localhost:5000/api/',
|
||||
rel: 'root',
|
||||
title: 'Instance catalog',
|
||||
type: 'application/json'
|
||||
},
|
||||
{
|
||||
href: 'http://localhost:5000/api/collections/076e04c2-5ff5-4d70-88fa-6b2be3357709',
|
||||
rel: 'parent',
|
||||
type: 'application/json'
|
||||
},
|
||||
{
|
||||
href: 'http://localhost:5000/api/collections/076e04c2-5ff5-4d70-88fa-6b2be3357709/items?limit=100',
|
||||
rel: 'self',
|
||||
type: 'application/geo+json'
|
||||
},
|
||||
{
|
||||
href: 'http://localhost:5000/api/collections/076e04c2-5ff5-4d70-88fa-6b2be3357709/items?limit=100',
|
||||
rel: 'first',
|
||||
type: 'application/geo+json'
|
||||
},
|
||||
{
|
||||
href: 'http://localhost:5000/api/collections/076e04c2-5ff…a-6b2be3357709/items?limit=100&startAfterRank=100',
|
||||
rel: 'next',
|
||||
type: 'application/geo+json'
|
||||
},
|
||||
{
|
||||
href: 'http://localhost:5000/api/collections/076e04c2-5ff…-6b2be3357709/items?limit=100&startAfterRank=1023',
|
||||
rel: 'last',
|
||||
type: 'application/geo+json'
|
||||
}
|
||||
]
|
||||
expect(formatPaginationItems(links)).toEqual([
|
||||
{
|
||||
href: 'http://localhost:5000/api/collections/076e04c2-5ff5-4d70-88fa-6b2be3357709/items?limit=100',
|
||||
rel: 'double-left',
|
||||
type: 'application/geo+json'
|
||||
},
|
||||
{
|
||||
href: 'http://localhost:5000/api/collections/076e04c2-5ff…a-6b2be3357709/items?limit=100&startAfterRank=100',
|
||||
rel: 'right',
|
||||
type: 'application/geo+json'
|
||||
},
|
||||
{
|
||||
href: 'http://localhost:5000/api/collections/076e04c2-5ff…-6b2be3357709/items?limit=100&startAfterRank=1023',
|
||||
rel: 'double-right',
|
||||
type: 'application/geo+json'
|
||||
}
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatPictureSize', () => {
|
||||
it('should render the size number', () => {
|
||||
const size = 560673
|
||||
expect(formatPictureSize(size)).toEqual(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatTextSize', () => {
|
||||
const size = 2260121
|
||||
it('should render the size text formated in ko', () => {
|
||||
expect(formatTextSize(size, 1)).toEqual('2207.15 Ko')
|
||||
})
|
||||
it('should render the size text formated in mo', () => {
|
||||
expect(formatTextSize(size, 2)).toEqual('2.16 Mo')
|
||||
})
|
||||
it('should render the size text formated in go', () => {
|
||||
expect(formatTextSize(size, 3)).toEqual('0 Go')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getAuthRoute', () => {
|
||||
it('should render auth route', () => {
|
||||
import.meta.env.VITE_API_URL = 'my-url/'
|
||||
const authRoute = 'auth'
|
||||
const nextRoute = 'mes-sequences'
|
||||
const returnedRoute = `${
|
||||
import.meta.env.VITE_API_URL
|
||||
}api/${authRoute}?next_url=${encodeURIComponent(
|
||||
`${location.protocol}//${location.host}${nextRoute}`
|
||||
)}`
|
||||
expect(getAuthRoute(authRoute, nextRoute)).toEqual(returnedRoute)
|
||||
})
|
||||
})
|
||||
|
||||
describe('img', () => {
|
||||
it('should render the formated img path', () => {
|
||||
const name = 'my-img'
|
||||
expect(img(name)).contains('src/assets/images')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getPicId', () => {
|
||||
it('should return the Id of the picture in the url', () => {
|
||||
const url = 'http://dummy.com?pic=3205340583&test'
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: {
|
||||
href: url
|
||||
},
|
||||
writable: true // possibility to override
|
||||
})
|
||||
expect(getPicId()).toEqual('3205340583')
|
||||
})
|
||||
})
|
||||
|
||||
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 = [
|
||||
{ name: 'd_1_ct.jpg' },
|
||||
{ name: 'd_11_ct.jpg' },
|
||||
{ name: 'd_2_ct.jpg' }
|
||||
]
|
||||
expect(sortByName(list1)).toEqual([
|
||||
{ name: 'd_1_ct.jpg' },
|
||||
{ name: 'd_2_ct.jpg' },
|
||||
{ name: 'd_11_ct.jpg' }
|
||||
])
|
||||
|
||||
const list2 = [{ name: 'A.jpg' }, { name: 'Z.jpg' }, { name: 'B.jpg' }]
|
||||
expect(sortByName(list2)).toEqual([
|
||||
{ name: 'A.jpg' },
|
||||
{ name: 'B.jpg' },
|
||||
{ name: 'Z.jpg' }
|
||||
])
|
||||
|
||||
const list3 = [
|
||||
{ name: 'CAM1_001.jpg' },
|
||||
{ name: 'CAM2_002.jpg' },
|
||||
{ name: 'CAM1_011.jpg' }
|
||||
]
|
||||
expect(sortByName(list3)).toEqual([
|
||||
{ name: 'CAM1_001.jpg' },
|
||||
{ name: 'CAM1_011.jpg' },
|
||||
{ name: 'CAM2_002.jpg' }
|
||||
])
|
||||
})
|
||||
})
|
||||
34
src/tests/unit/views/HomeView.spec.js
Normal file
34
src/tests/unit/views/HomeView.spec.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import { it, describe, expect, vi } from 'vitest'
|
||||
import { flushPromises, shallowMount } from '@vue/test-utils'
|
||||
import HomeView from '../../../views/HomeView.vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import fr from '../../../locales/fr.json'
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
const i18n = createI18n({
|
||||
locale: 'fr',
|
||||
fallbackLocale: 'fr',
|
||||
globalInjection: true,
|
||||
legacy: false,
|
||||
messages: {
|
||||
fr
|
||||
}
|
||||
})
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: []
|
||||
})
|
||||
|
||||
describe('Template', () => {
|
||||
it('should render the view without pictures', async () => {
|
||||
const wrapper = shallowMount(HomeView, {
|
||||
global: {
|
||||
plugins: [i18n, router],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
console.log(wrapper.html())
|
||||
})
|
||||
})
|
||||
68
src/tests/unit/views/MySequence.spec.js
Normal file
68
src/tests/unit/views/MySequence.spec.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import { it, describe, expect, vi } from 'vitest'
|
||||
import { flushPromises, shallowMount } from '@vue/test-utils'
|
||||
import MySequenceView from '../../../views/MySequenceView.vue'
|
||||
import axios from 'axios'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import fr from '../../../locales/fr.json'
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { fetchMapAndViewer } from '../../../utils/mapAndViewer'
|
||||
|
||||
vi.mock('../../../utils/mapAndViewer', () => ({
|
||||
fetchMapAndViewer: vi
|
||||
.fn()
|
||||
.mockResolvedValue(/* mock the return value of fetchMapAndViewer */)
|
||||
}))
|
||||
vi.mock('../../../utils/dates', () => ({
|
||||
formatDate: vi.fn()
|
||||
}))
|
||||
vi.mock('axios')
|
||||
vi.mock('geovisio', () => ({
|
||||
__esModule: true,
|
||||
default: vi.fn().mockImplementation((container, apiUrl, params) => {
|
||||
return {
|
||||
container: '#viewer',
|
||||
_map: {
|
||||
once: (event, callback) => callback(),
|
||||
resize: vi.fn(),
|
||||
start: vi.fn(),
|
||||
stop: vi.fn()
|
||||
},
|
||||
psv: {
|
||||
// Mock the methods or properties of the `psv` object
|
||||
// ...
|
||||
},
|
||||
setWide: vi.fn()
|
||||
}
|
||||
})
|
||||
}))
|
||||
const i18n = createI18n({
|
||||
locale: 'fr',
|
||||
fallbackLocale: 'fr',
|
||||
globalInjection: true,
|
||||
legacy: false,
|
||||
messages: {
|
||||
fr
|
||||
}
|
||||
})
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: []
|
||||
})
|
||||
|
||||
describe('Template', () => {
|
||||
it('should render the view without pictures', async () => {
|
||||
const wrapper = shallowMount(MySequenceView, {
|
||||
global: {
|
||||
plugins: [i18n, router],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
await flushPromises()
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
console.log(wrapper)
|
||||
expect(fetchMapAndViewer).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -6,7 +6,6 @@ import { createI18n } from 'vue-i18n'
|
||||
import fr from '../../../locales/fr.json'
|
||||
import Button from '../../../components/Button.vue'
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
vi.mock('../../../utils/dates', () => ({
|
||||
formatDate: vi.fn()
|
||||
}))
|
||||
@@ -51,7 +50,7 @@ describe('Template', () => {
|
||||
vi.spyOn(axios, 'get').mockResolvedValue({ data: { links: [] } })
|
||||
const wrapper = shallowMount(MySequencesView, {
|
||||
global: {
|
||||
plugins: [i18n, router, createTestingPinia({ createSpy: vi.fn })],
|
||||
plugins: [i18n, router],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
@@ -60,22 +59,8 @@ describe('Template', () => {
|
||||
await flushPromises()
|
||||
expect(axios.get).toHaveBeenCalledWith('api/users/me/catalog')
|
||||
expect(wrapper.vm.userSequences).toEqual([])
|
||||
expect(wrapper.html()).contains('general.header.upload_text')
|
||||
})
|
||||
|
||||
it('should render the view with a loader loading', async () => {
|
||||
const wrapper = shallowMount(MySequencesView, {
|
||||
global: {
|
||||
plugins: [i18n, router, createTestingPinia({ createSpy: vi.fn })],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
await flushPromises()
|
||||
expect(axios.get).toHaveBeenCalledWith('api/users/me/catalog')
|
||||
expect(wrapper.vm.userSequences).toEqual([])
|
||||
expect(wrapper.html()).contains('general.header.upload_text')
|
||||
expect(wrapper.html()).contains('general.header.contribute_text')
|
||||
expect(wrapper.html()).contains('path="/partager-des-photos"')
|
||||
})
|
||||
|
||||
it('should render the view with a sequence in the list', async () => {
|
||||
@@ -84,56 +69,63 @@ describe('Template', () => {
|
||||
})
|
||||
const wrapper = shallowMount(MySequencesView, {
|
||||
global: {
|
||||
plugins: [i18n, createTestingPinia({ createSpy: vi.fn })],
|
||||
plugins: [i18n],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
wrapper.vm.isLoading = true
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.html()).contains('<loader')
|
||||
await flushPromises()
|
||||
expect(axios.get).toHaveBeenCalledWith('api/users/me/catalog')
|
||||
expect(wrapper.vm.userSequences).toEqual([mockResponseSequences[1]])
|
||||
expect(wrapper.html()).contains('src="https://my-link/thumb.jpg"')
|
||||
expect(wrapper.html()).contains('ma sequence 1')
|
||||
expect(wrapper.html()).contains('16')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Methods', () => {
|
||||
const mockResponseSequencesToSort = [
|
||||
{
|
||||
href: 'https://my-link',
|
||||
id: 'my-id',
|
||||
rel: 'child',
|
||||
'stats:items': { count: 16 },
|
||||
title: 'za sequence 1',
|
||||
extent: {
|
||||
temporal: {
|
||||
interval: [['2030-09-22T08:03:08+00:00', '2022-09-22T08:03:08+00:00']]
|
||||
describe('sortElements', () => {
|
||||
const mockResponseSequencesToSort = [
|
||||
{
|
||||
href: 'https://my-link',
|
||||
id: 'my-id',
|
||||
rel: 'child',
|
||||
'stats:items': { count: 16 },
|
||||
title: 'za sequence 1',
|
||||
extent: {
|
||||
temporal: {
|
||||
interval: [
|
||||
['2030-09-22T08:03:08+00:00', '2022-09-22T08:03:08+00:00']
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
href: 'https://my-link',
|
||||
id: 'my-id',
|
||||
rel: 'child',
|
||||
'stats:items': { count: 2 },
|
||||
title: 'ma sequence 1',
|
||||
extent: {
|
||||
temporal: {
|
||||
interval: [
|
||||
['2022-09-22T08:03:08+00:00', '2022-09-22T08:03:08+00:00']
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
href: 'https://my-link',
|
||||
id: 'my-id',
|
||||
rel: 'child',
|
||||
'stats:items': { count: 2 },
|
||||
title: 'ma sequence 1',
|
||||
extent: {
|
||||
temporal: {
|
||||
interval: [['2022-09-22T08:03:08+00:00', '2022-09-22T08:03:08+00:00']]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
beforeEach(async () => {
|
||||
await axios.get.mockReturnValue({
|
||||
data: { links: mockResponseSequencesToSort }
|
||||
]
|
||||
beforeEach(async () => {
|
||||
await axios.get.mockReturnValue({
|
||||
data: { links: mockResponseSequencesToSort }
|
||||
})
|
||||
await flushPromises()
|
||||
})
|
||||
await flushPromises()
|
||||
})
|
||||
describe('sortAlpha', () => {
|
||||
it('should should sort sequences by title', async () => {
|
||||
const wrapper = shallowMount(MySequencesView, {
|
||||
global: {
|
||||
plugins: [i18n, router, createTestingPinia({ createSpy: vi.fn })],
|
||||
plugins: [i18n, router],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
},
|
||||
@@ -142,24 +134,22 @@ describe('Methods', () => {
|
||||
}
|
||||
}
|
||||
})
|
||||
wrapper.vm.isLoading = false
|
||||
await wrapper.vm.$nextTick()
|
||||
const spy = vi.spyOn(wrapper.vm, 'sortAlpha')
|
||||
const spy = vi.spyOn(wrapper.vm, 'sortElements')
|
||||
const buttonWrapper = wrapper.findComponent(
|
||||
'[data-test="button-sort-title"]'
|
||||
)
|
||||
await buttonWrapper.vm.$emit('trigger')
|
||||
expect(spy).toHaveBeenCalledWith('title')
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('alpha')
|
||||
expect(wrapper.vm.userSequences[0]).toEqual(
|
||||
mockResponseSequencesToSort[1]
|
||||
)
|
||||
})
|
||||
})
|
||||
describe('sortNum', () => {
|
||||
it('should should sort sequences by number of pictures', async () => {
|
||||
const wrapper = shallowMount(MySequencesView, {
|
||||
global: {
|
||||
plugins: [i18n, router, createTestingPinia({ createSpy: vi.fn })],
|
||||
plugins: [i18n, router],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
},
|
||||
@@ -168,9 +158,8 @@ describe('Methods', () => {
|
||||
}
|
||||
}
|
||||
})
|
||||
wrapper.vm.isLoading = false
|
||||
await wrapper.vm.$nextTick()
|
||||
const spy = vi.spyOn(wrapper.vm, 'sortNum')
|
||||
const spy = vi.spyOn(wrapper.vm, 'sortElements')
|
||||
const buttonWrapper = wrapper.findComponent(
|
||||
'[data-test="button-sort-number"]'
|
||||
)
|
||||
@@ -184,7 +173,7 @@ describe('Methods', () => {
|
||||
it('should should sort sequences by date', async () => {
|
||||
const wrapper = shallowMount(MySequencesView, {
|
||||
global: {
|
||||
plugins: [i18n, router, createTestingPinia({ createSpy: vi.fn })],
|
||||
plugins: [i18n, router],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
},
|
||||
@@ -193,9 +182,8 @@ describe('Methods', () => {
|
||||
}
|
||||
}
|
||||
})
|
||||
wrapper.vm.isLoading = false
|
||||
await wrapper.vm.$nextTick()
|
||||
const spy = vi.spyOn(wrapper.vm, 'sortNum')
|
||||
const spy = vi.spyOn(wrapper.vm, 'sortElements')
|
||||
const buttonWrapper = wrapper.findComponent(
|
||||
'[data-test="button-sort-date"]'
|
||||
)
|
||||
@@ -46,13 +46,14 @@ describe('Template', () => {
|
||||
}
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
expect(axios.get).toHaveBeenCalledWith('api/users/me/tokens')
|
||||
expect(wrapper.vm.userTokens).toEqual(mockResponseTokens)
|
||||
expect(wrapper.html()).contains('•••••••••••••••••••••••••••••••')
|
||||
expect(wrapper.html()).contains('icon="bi bi-eye"')
|
||||
expect(wrapper.html()).contains('look="no-text"')
|
||||
expect(wrapper.html()).contains('look="button--rounded"')
|
||||
expect(wrapper.html()).contains(
|
||||
'icon="bi bi-clipboard-plus" disabled="false" isloading="false" text="pages.share_pictures.button_copy" tooltip="" look="button--white"'
|
||||
'icon="bi bi-clipboard-plus" disabled="false" isloading="false" text="pages.upload.button_copy" tooltip="" look="button--white"'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
import { it, describe, expect, vi } from 'vitest'
|
||||
import { shallowMount, mount, flushPromises } from '@vue/test-utils'
|
||||
import UploadPicturesView from '../../../views/UploadPicturesView.vue'
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import i18n from '../config'
|
||||
import InputUpload from '../../../components/InputUpload.vue'
|
||||
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'
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: []
|
||||
})
|
||||
describe('Template', () => {
|
||||
it('should render the view with the input upload and the good wordings', () => {
|
||||
const wrapper = shallowMount(UploadPicturesView, {
|
||||
global: {
|
||||
plugins: [i18n, router],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('pages.upload.title')
|
||||
expect(wrapper.html()).contains('<input-upload-stub')
|
||||
expect(wrapper.html()).contains('pages.upload.input_label')
|
||||
expect(wrapper.html()).contains('<button-stub')
|
||||
expect(wrapper.html()).contains('text="pages.upload.button_text"')
|
||||
})
|
||||
describe('trigger addPictures', () => {
|
||||
it('should trigger to add pictures', async () => {
|
||||
const wrapper = shallowMount(UploadPicturesView, {
|
||||
global: {
|
||||
plugins: [i18n, router],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
},
|
||||
components: {
|
||||
InputUpload
|
||||
}
|
||||
}
|
||||
})
|
||||
const spy = vi.spyOn(wrapper.vm, 'addPictures')
|
||||
const sortByNameMock = vi.spyOn(sortByName, 'sortByName')
|
||||
sortByNameMock.mockReturnValue([{}, {}])
|
||||
const wrapperInputUpload = await wrapper.findComponent(InputUpload)
|
||||
await wrapperInputUpload.trigger('trigger')
|
||||
await wrapperInputUpload.vm.$emit('trigger', [{}, {}])
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
expect(wrapper.html()).contains('2 fichiers')
|
||||
})
|
||||
describe('submit uploadPicture', () => {
|
||||
it('should trigger to uploadPictures', async () => {
|
||||
const wrapper = mount(UploadPicturesView, {
|
||||
global: {
|
||||
plugins: [i18n, router],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
},
|
||||
components: {
|
||||
InputUpload
|
||||
}
|
||||
}
|
||||
})
|
||||
const spy = vi.spyOn(wrapper.vm, 'uploadPicture')
|
||||
const wrapperInputUpload = wrapper.findComponent(InputUpload)
|
||||
await wrapperInputUpload.trigger('trigger')
|
||||
await wrapperInputUpload.vm.$emit('trigger', [{}, {}])
|
||||
const buttonWrapper = await wrapper.find('[data-test="button-upload"]')
|
||||
await buttonWrapper.trigger('submit.prevent')
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
expect(wrapper.html()).contains('class="loader-percentage"')
|
||||
expect(wrapper.html()).contains('class="lds-ring lg"')
|
||||
})
|
||||
})
|
||||
describe('one sequence has been imported', () => {
|
||||
it('should render a sequence with a list of uploaded pictures', async () => {
|
||||
const wrapper = mount(UploadPicturesView, {
|
||||
global: {
|
||||
plugins: [i18n, router],
|
||||
mocks: {
|
||||
$t: (msg) => msg
|
||||
}
|
||||
}
|
||||
})
|
||||
const picture = {
|
||||
lastModified: 1599133968750,
|
||||
name: '100MSDCF_DSC02790.JPG',
|
||||
size: 2345,
|
||||
type: 'image/jpeg'
|
||||
}
|
||||
const spyASequence = vi.spyOn(createASequence, 'createASequence')
|
||||
const spyPicture = vi.spyOn(
|
||||
createAPictureToASequence,
|
||||
'createAPictureToASequence'
|
||||
)
|
||||
const sortByNameMock = vi.spyOn(sortByName, 'sortByName')
|
||||
sortByNameMock.mockReturnValue([picture])
|
||||
const sequenceId = 'my-id'
|
||||
spyASequence.mockReturnValue({ data: { id: sequenceId } })
|
||||
spyPicture.mockReturnValue({ data: {} })
|
||||
const sequenceTitle = `Séquence du ${formatDate(
|
||||
new Date(),
|
||||
'Do MMMM YY, hh:mm:ss'
|
||||
)}`
|
||||
const body = new FormData()
|
||||
body.append('position', '1')
|
||||
body.append('picture', picture)
|
||||
const wrapperInputUpload = wrapper.findComponent(InputUpload)
|
||||
await wrapperInputUpload.trigger('trigger')
|
||||
await wrapperInputUpload.vm.$emit('trigger', [picture])
|
||||
const buttonWrapper = await wrapper.find('[data-test="button-upload"]')
|
||||
await buttonWrapper.trigger('submit.prevent')
|
||||
|
||||
expect(spyASequence).toHaveBeenCalledWith(sequenceTitle)
|
||||
expect(spyPicture).toHaveBeenCalledWith(sequenceId, body)
|
||||
|
||||
expect(wrapper.html()).contains('class="uploaded-picture-list"')
|
||||
expect(wrapper.html()).contains('class="uploaded-picture-item success"')
|
||||
expect(wrapper.html()).contains(
|
||||
'100MSDCF_DSC02790.JPG - pages.upload.uploaded_word'
|
||||
)
|
||||
expect(wrapper.html()).contains('<router-link')
|
||||
expect(wrapper.html()).contains(
|
||||
'class="default button button--white" title="pages.upload.sequence_link"'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,16 +1,26 @@
|
||||
import { it, describe, expect } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import SharePicturesView from '../../../views/SharePicturesView.vue'
|
||||
import i18n from '../config'
|
||||
import UploadView from '../../../views/UploadView.vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import fr from '../../../locales/fr.json'
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
const i18n = createI18n({
|
||||
locale: 'fr',
|
||||
fallbackLocale: 'fr',
|
||||
globalInjection: true,
|
||||
legacy: false,
|
||||
messages: {
|
||||
fr
|
||||
}
|
||||
})
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: []
|
||||
})
|
||||
describe('Template', () => {
|
||||
it('should render the view with the button link', async () => {
|
||||
const wrapper = shallowMount(SharePicturesView, {
|
||||
const wrapper = shallowMount(UploadView, {
|
||||
global: {
|
||||
plugins: [i18n, router],
|
||||
mocks: {
|
||||
@@ -24,12 +34,12 @@ describe('Template', () => {
|
||||
expect(wrapper.html()).contains('<link')
|
||||
expect(wrapper.html()).contains('path="')
|
||||
expect(wrapper.html()).contains('/auth/login')
|
||||
expect(wrapper.html()).contains('look="button button--blue"')
|
||||
expect(wrapper.html()).contains('look="button"')
|
||||
expect(wrapper.html()).contains('type="external"')
|
||||
})
|
||||
it('should render the view without the button link', async () => {
|
||||
import.meta.env.VITE_API_URL = 'api-url/'
|
||||
const wrapper = shallowMount(SharePicturesView, {
|
||||
const wrapper = shallowMount(UploadView, {
|
||||
global: {
|
||||
plugins: [i18n, router],
|
||||
mocks: {
|
||||
@@ -40,6 +50,6 @@ describe('Template', () => {
|
||||
}
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).not.toContain('pages.share_pictures.sub_title')
|
||||
expect(wrapper.html()).not.toContain('pages.upload.sub_title')
|
||||
})
|
||||
})
|
||||
@@ -1,8 +1,6 @@
|
||||
function getAuthRoute(authRoute: string, nextRoute: string): string {
|
||||
const next = `${location.protocol}//${location.host}${nextRoute}`
|
||||
return `${
|
||||
import.meta.env.VITE_API_URL
|
||||
}api/${authRoute}?next_url=${encodeURIComponent(`${next}`)}`
|
||||
return `/api/${authRoute}?next_url=${encodeURIComponent(`${next}`)}`
|
||||
}
|
||||
|
||||
export { getAuthRoute }
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import moment from 'moment'
|
||||
import 'moment/dist/locale/fr'
|
||||
|
||||
function formatDate(date: Date, formatType: string): string {
|
||||
const formatDate = moment(date)
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export default function title(title: string): string {
|
||||
const instanceName = import.meta.env.VITE_INSTANCE_NAME
|
||||
if (instanceName) return `${title} ${instanceName}`
|
||||
return title
|
||||
}
|
||||
@@ -1,41 +1,20 @@
|
||||
import axios from 'axios'
|
||||
import type {
|
||||
ViewerMapInterface,
|
||||
OptionalViewerMapInterface
|
||||
} from '@/views/interfaces/common'
|
||||
import type { ViewerMapInterface } from '@/views/interfaces/common'
|
||||
import GeoVisio from 'geovisio'
|
||||
|
||||
async function fetchMapAndViewer(params?: OptionalViewerMapInterface) {
|
||||
const tiles = import.meta.env.VITE_TILES
|
||||
let paramsGeovisio: ViewerMapInterface = {
|
||||
async function fetchMapAndViewer(picId?: string) {
|
||||
let params: ViewerMapInterface = {
|
||||
map: {
|
||||
startWide: true,
|
||||
style: await getIgnTiles(),
|
||||
maxZoom: 19
|
||||
}
|
||||
}
|
||||
if (tiles) {
|
||||
const style = tiles.includes('wxs.ign.fr') ? await getIgnTiles() : tiles
|
||||
paramsGeovisio = {
|
||||
map: {
|
||||
...paramsGeovisio.map,
|
||||
style
|
||||
}
|
||||
}
|
||||
}
|
||||
if (params) {
|
||||
const { picId } = params
|
||||
const { fetchOptions } = params
|
||||
if (picId) paramsGeovisio = { ...paramsGeovisio, picId: picId }
|
||||
if (fetchOptions) {
|
||||
paramsGeovisio = {
|
||||
...paramsGeovisio,
|
||||
fetchOptions: fetchOptions
|
||||
}
|
||||
}
|
||||
}
|
||||
if (picId) params = { ...params, picId: picId }
|
||||
return new GeoVisio(
|
||||
'viewer', // Div ID
|
||||
`${import.meta.env.VITE_API_URL}/api/search`,
|
||||
paramsGeovisio
|
||||
`${import.meta.env.VITE_API_URL}api/search`,
|
||||
params
|
||||
)
|
||||
}
|
||||
|
||||
@@ -45,15 +24,7 @@ async function getIgnTiles(): Promise<object> {
|
||||
)
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
41
src/utils/sequence.ts
Normal file
41
src/utils/sequence.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import axios from 'axios'
|
||||
import type {
|
||||
ResponseUserPhotoInterface,
|
||||
ResponseUserSequenceInterface
|
||||
} from '../views/interfaces/MySequenceView'
|
||||
|
||||
function deleteACollectionItem(
|
||||
collectionId: string | string[],
|
||||
itemId: string
|
||||
): Promise<unknown> {
|
||||
return axios.delete(`api/collections/${collectionId}/items/${itemId}`)
|
||||
}
|
||||
|
||||
function patchACollectionItem(
|
||||
isVisible: string,
|
||||
collectionId: string | string[],
|
||||
itemId: string
|
||||
): Promise<unknown> {
|
||||
return axios.patch(`api/collections/${collectionId}/items/${itemId}`, {
|
||||
visible: isVisible
|
||||
})
|
||||
}
|
||||
|
||||
async function fetchCollectionItems(collectionId: string | string[]): Promise<{
|
||||
data: { features: [ResponseUserPhotoInterface] }
|
||||
}> {
|
||||
return await axios.get(`api/collections/${collectionId}/items`)
|
||||
}
|
||||
|
||||
async function fetchCollection(collectionId: string | string[]): Promise<{
|
||||
data: ResponseUserSequenceInterface
|
||||
}> {
|
||||
return await axios.get(`api/collections/${collectionId}`)
|
||||
}
|
||||
|
||||
export {
|
||||
deleteACollectionItem,
|
||||
patchACollectionItem,
|
||||
fetchCollectionItems,
|
||||
fetchCollection
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
<template>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="https://cdn.jsdelivr.net/npm/geovisio@develop/build/index.css"
|
||||
/>
|
||||
<main class="entry-page">
|
||||
<section id="viewer" class="entry-viewer">
|
||||
<div v-if="mapIsLoaded" class="entry-report-button">
|
||||
@@ -18,7 +23,6 @@ import { onMounted, computed, ref } from 'vue'
|
||||
import Button from '@/components/Button.vue'
|
||||
import { fetchMapAndViewer } from '@/utils/mapAndViewer'
|
||||
import { getPicId } from '@/utils/image'
|
||||
import 'geovisio/build/index.css'
|
||||
|
||||
const { t } = useI18n()
|
||||
let mapIsLoaded = ref<boolean>(false)
|
||||
@@ -47,29 +51,28 @@ onMounted(async () => {
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
<style scoped>
|
||||
.entry-page {
|
||||
display: flex;
|
||||
}
|
||||
.entry-viewer {
|
||||
font-size: initial;
|
||||
width: 100vw;
|
||||
position: relative;
|
||||
min-height: calc(100vh - #{toRem(8)});
|
||||
min-height: calc(100vh - 8rem);
|
||||
}
|
||||
.gvs-has-map .entry-report-button {
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: toRem(12);
|
||||
top: toRem(2);
|
||||
right: 1rem;
|
||||
top: 2rem;
|
||||
z-index: 1;
|
||||
}
|
||||
.gvs-focus-map .entry-report-button {
|
||||
display: none;
|
||||
}
|
||||
@media (max-width: toRem(50)) {
|
||||
@media (max-width: 500px) {
|
||||
.entry-page {
|
||||
padding-top: toRem(11);
|
||||
padding-top: 11rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
@supports (-webkit-touch-callout: none) {
|
||||
@@ -83,7 +86,7 @@ onMounted(async () => {
|
||||
@supports not (-webkit-touch-callout: none) {
|
||||
/* CSS for other than iOS devices */
|
||||
.entry-viewer {
|
||||
min-height: calc(100vh - #{toRem(17)});
|
||||
min-height: calc(100vh - 17rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,30 +12,30 @@ const myAccountUrl = computed<string>(
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
<style scoped>
|
||||
.entry-page {
|
||||
padding-right: toRem(4);
|
||||
padding-left: toRem(8);
|
||||
padding-right: 4rem;
|
||||
padding-left: 8rem;
|
||||
}
|
||||
.iframe {
|
||||
width: 100%;
|
||||
min-height: calc(100vh - #{toRem(9)});
|
||||
min-height: calc(100vh - 9rem);
|
||||
}
|
||||
|
||||
@media (max-width: toRem(84.8)) {
|
||||
@media (max-width: 848px) {
|
||||
.entry-page {
|
||||
padding-top: toRem(4);
|
||||
padding-top: 4rem;
|
||||
}
|
||||
}
|
||||
@media (max-width: toRem(76.8)) {
|
||||
@media (max-width: 768px) {
|
||||
.entry-page {
|
||||
padding-right: toRem(2);
|
||||
padding-left: toRem(2);
|
||||
padding-right: 2rem;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
}
|
||||
@media (max-width: toRem(50)) {
|
||||
@media (max-width: 500px) {
|
||||
.entry-page {
|
||||
padding-top: toRem(14);
|
||||
padding-top: 14rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<main class="entry-page">
|
||||
<h1 class="sequences-title">{{ $t('pages.sequences.title') }}</h1>
|
||||
<ul v-if="!isLoading" class="sequence-list">
|
||||
<ul class="sequence-list">
|
||||
<li class="sequence-item">
|
||||
<div class="sequence-header-item"></div>
|
||||
<div class="sequence-header-item">
|
||||
@@ -10,7 +10,7 @@
|
||||
look="link--grey"
|
||||
icon="bi bi-arrow-down-up"
|
||||
data-test="button-sort-title"
|
||||
@trigger="sortAlpha('title')"
|
||||
@trigger="sortElements('alpha')"
|
||||
/>
|
||||
</div>
|
||||
<div class="sequence-header-item">
|
||||
@@ -19,7 +19,7 @@
|
||||
look="link--grey"
|
||||
icon="bi bi-arrow-down-up"
|
||||
data-test="button-sort-number"
|
||||
@trigger="sortNum('num')"
|
||||
@trigger="sortElements('num')"
|
||||
/>
|
||||
</div>
|
||||
<div class="sequence-header-item">
|
||||
@@ -28,7 +28,7 @@
|
||||
look="link--grey"
|
||||
icon="bi bi-arrow-down-up"
|
||||
data-test="button-sort-date"
|
||||
@trigger="sortNum('date')"
|
||||
@trigger="sortElements('date')"
|
||||
/>
|
||||
</div>
|
||||
<div class="sequence-header-item">
|
||||
@@ -37,12 +37,12 @@
|
||||
look="link--grey"
|
||||
icon="bi bi-arrow-down-up"
|
||||
data-test="button-sort-date"
|
||||
@trigger="sortAlpha('geovisio:status')"
|
||||
@trigger="sortElements('alpha')"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
v-if="userSequences.length"
|
||||
v-if="userSequences && userSequences.length"
|
||||
v-for="item in userSequences"
|
||||
class="sequence-item"
|
||||
>
|
||||
@@ -55,21 +55,11 @@
|
||||
>
|
||||
<div class="wrapper-thumb">
|
||||
<img
|
||||
v-if="item['stats:items'].count > 0"
|
||||
:src="`${item.href}/thumb.jpg`"
|
||||
lazy="loading"
|
||||
alt=""
|
||||
class="thumb"
|
||||
/>
|
||||
<div class="wrapper-thumb-hover">
|
||||
<img
|
||||
v-if="item['stats:items'].count > 0"
|
||||
:src="`${item.href}/thumb.jpg`"
|
||||
lazy="loading"
|
||||
alt=""
|
||||
class="thumb-hover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span>
|
||||
@@ -102,91 +92,90 @@
|
||||
{{ $t('pages.sequences.no_sequences_text') }}
|
||||
</p>
|
||||
<Link
|
||||
:text="$t('general.header.upload_text')"
|
||||
:text="$t('general.header.contribute_text')"
|
||||
look="button"
|
||||
:route="{ name: 'share-pictures' }"
|
||||
path="/partager-des-photos"
|
||||
/>
|
||||
</div>
|
||||
</ul>
|
||||
<div v-else class="loader">
|
||||
<Loader look="sm" :is-loaded="false" />
|
||||
</div>
|
||||
<Toast :text="toastText" :look="toastLook" @trigger="toastText = ''" />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useSequenceStore } from '@/store/sequence'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import axios from 'axios'
|
||||
import Button from '@/components/Button.vue'
|
||||
import Link from '@/components/Link.vue'
|
||||
import Toast from '@/components/Toast.vue'
|
||||
import Loader from '@/components/Loader.vue'
|
||||
import type {
|
||||
LinkInterface,
|
||||
ExtentLinkInterface
|
||||
ExtentInterface
|
||||
} from './interfaces/MySequencesView'
|
||||
import { formatDate } from '@/utils/dates'
|
||||
const { t } = useI18n()
|
||||
const sequenceStore = useSequenceStore()
|
||||
const { toastText, toastLook } = storeToRefs(sequenceStore)
|
||||
|
||||
let userSequences = ref<LinkInterface[]>([])
|
||||
let isSorted = ref<boolean>(false)
|
||||
let isLoading = ref<boolean>(false)
|
||||
|
||||
function sequenceStatus(status: string): string {
|
||||
if (status === 'ready') return t('pages.sequences.sequence_published')
|
||||
if (status === 'waiting-for-process')
|
||||
if (status === 'ready') {
|
||||
return t('pages.sequences.sequence_published')
|
||||
}
|
||||
if (status === 'waiting-for-process') {
|
||||
return t('pages.sequences.sequence_waiting')
|
||||
}
|
||||
return t('pages.sequences.sequence_hidden')
|
||||
}
|
||||
function sortAlpha<TKey extends keyof LinkInterface>(key: TKey): void {
|
||||
const sorted = userSequences.value.sort(
|
||||
(
|
||||
a: { [K in TKey]: LinkInterface[TKey] },
|
||||
b: { [K in TKey]: LinkInterface[TKey] }
|
||||
) => {
|
||||
if (a[key] < b[key]) return !isSorted.value ? -1 : 1
|
||||
if (a[key] > b[key]) return !isSorted.value ? 1 : -1
|
||||
return 0
|
||||
}
|
||||
)
|
||||
isSorted.value = !isSorted.value
|
||||
userSequences.value = sorted
|
||||
}
|
||||
function sortNum(type: string): void {
|
||||
let aa, bb: number
|
||||
const sorted = userSequences.value.sort(
|
||||
(a: ExtentLinkInterface, b: ExtentLinkInterface) => {
|
||||
aa = new Date(a.extent.temporal.interval[0][0]).getTime()
|
||||
bb = new Date(b.extent.temporal.interval[0][0]).getTime()
|
||||
if (type === 'num') {
|
||||
aa = Number(a['stats:items'].count)
|
||||
bb = Number(b['stats:items'].count)
|
||||
function sortElements(type: string): void {
|
||||
let aa: string | number
|
||||
let bb: string | number
|
||||
if (!isSorted.value) {
|
||||
const sorted = userSequences.value.sort(
|
||||
(a: ExtentInterface, b: ExtentInterface): number => {
|
||||
aa = new Date(a.extent.temporal.interval[0][0]).getTime()
|
||||
bb = new Date(b.extent.temporal.interval[0][0]).getTime()
|
||||
if (type === 'alpha') {
|
||||
aa = a.title
|
||||
bb = b.title
|
||||
}
|
||||
if (type === 'num') {
|
||||
aa = Number(a['stats:items'].count)
|
||||
bb = Number(b['stats:items'].count)
|
||||
}
|
||||
isSorted.value = true
|
||||
if (aa < bb) return -1
|
||||
return 0
|
||||
}
|
||||
if (aa < bb) return !isSorted.value ? -1 : 1
|
||||
if (aa > bb) return !isSorted.value ? 1 : -1
|
||||
return 0
|
||||
}
|
||||
)
|
||||
isSorted.value = !isSorted.value
|
||||
userSequences.value = sorted
|
||||
)
|
||||
userSequences.value = sorted
|
||||
} else {
|
||||
const sorted = userSequences.value.sort(
|
||||
(a: ExtentInterface, b: ExtentInterface): number => {
|
||||
aa = new Date(a.extent.temporal.interval[0][0]).getTime()
|
||||
bb = new Date(b.extent.temporal.interval[0][0]).getTime()
|
||||
if (type === 'alpha') {
|
||||
aa = a.title
|
||||
bb = b.title
|
||||
}
|
||||
if (type === 'num') {
|
||||
aa = Number(a['stats:items'].count)
|
||||
bb = Number(b['stats:items'].count)
|
||||
}
|
||||
isSorted.value = false
|
||||
if (aa > bb) return -1
|
||||
return 0
|
||||
}
|
||||
)
|
||||
userSequences.value = sorted
|
||||
}
|
||||
}
|
||||
onMounted(async () => {
|
||||
isLoading.value = true
|
||||
try {
|
||||
const { data } = await axios.get('api/users/me/catalog')
|
||||
const relChild = data.links.filter(
|
||||
(el: LinkInterface) => el.rel === 'child'
|
||||
)
|
||||
userSequences.value = relChild
|
||||
isLoading.value = false
|
||||
} catch (err) {
|
||||
isLoading.value = false
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
@@ -194,19 +183,19 @@ onMounted(async () => {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.entry-page {
|
||||
padding-right: toRem(8);
|
||||
padding-left: toRem(8);
|
||||
padding-top: toRem(11);
|
||||
min-height: calc(100vh - #{toRem(8)});
|
||||
padding-right: 8rem;
|
||||
padding-left: 8rem;
|
||||
padding-top: 11rem;
|
||||
min-height: calc(100vh - 8rem);
|
||||
}
|
||||
|
||||
.sequences-title {
|
||||
@include text(h1);
|
||||
margin-bottom: toRem(4);
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
.sequence-list {
|
||||
box-shadow: 0px 2px 30px 0px #0000000f;
|
||||
border-radius: toRem(2);
|
||||
border-radius: 2rem;
|
||||
padding: 0;
|
||||
}
|
||||
.sequence-item {
|
||||
@@ -217,18 +206,18 @@ onMounted(async () => {
|
||||
margin: auto;
|
||||
background-color: var(--blue-pale);
|
||||
&:last-child {
|
||||
border-bottom-right-radius: toRem(2);
|
||||
border-bottom-left-radius: toRem(2);
|
||||
border-bottom-right-radius: 2rem;
|
||||
border-bottom-left-radius: 2rem;
|
||||
.button-item {
|
||||
border-bottom-right-radius: toRem(2);
|
||||
border-bottom-left-radius: toRem(2);
|
||||
border-bottom-right-radius: 2rem;
|
||||
border-bottom-left-radius: 2rem;
|
||||
}
|
||||
}
|
||||
&:first-child {
|
||||
margin-bottom: toRem(1);
|
||||
padding: toRem(1) toRem(3);
|
||||
border-bottom: toRem(0.1) solid var(--grey);
|
||||
border-radius: toRem(2) toRem(2) 0rem 0rem;
|
||||
margin-bottom: 1rem;
|
||||
padding: 1rem 3rem;
|
||||
border-bottom: 0.1rem solid var(--grey);
|
||||
border-radius: 2rem 2rem 0rem 0rem;
|
||||
background-color: var(--white);
|
||||
}
|
||||
&:nth-child(2n) {
|
||||
@@ -239,54 +228,33 @@ onMounted(async () => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.wrapper-thumb {
|
||||
position: relative;
|
||||
}
|
||||
.wrapper-thumb-hover {
|
||||
display: none;
|
||||
border-radius: toRem(0.3);
|
||||
border: toRem(1) solid var(--grey);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
height: toRem(15);
|
||||
z-index: 1;
|
||||
}
|
||||
.thumb-hover {
|
||||
height: 100%;
|
||||
}
|
||||
.wrapper-thumb:hover {
|
||||
.wrapper-thumb-hover {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.thumb {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: toRem(0.5);
|
||||
position: relative;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
.button-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: toRem(2) toRem(3);
|
||||
padding: 2rem 3rem;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
& > * {
|
||||
padding: toRem(1);
|
||||
padding: 1rem;
|
||||
text-align: initial;
|
||||
width: 31%;
|
||||
color: var(--black);
|
||||
}
|
||||
> :first-child {
|
||||
color: var(--blue);
|
||||
width: toRem(6);
|
||||
width: 6rem;
|
||||
}
|
||||
& > :first-child {
|
||||
padding: 0;
|
||||
margin-right: toRem(2);
|
||||
margin-right: 2rem;
|
||||
}
|
||||
& > :nth-child(2) {
|
||||
color: var(--blue);
|
||||
@@ -299,49 +267,41 @@ onMounted(async () => {
|
||||
}
|
||||
}
|
||||
.bi-images {
|
||||
margin-right: toRem(0.5);
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.sequence-header-item {
|
||||
width: 31%;
|
||||
&:first-child {
|
||||
margin-right: toRem(2);
|
||||
margin-right: 2rem;
|
||||
}
|
||||
&:first-child {
|
||||
width: toRem(6);
|
||||
width: 6rem;
|
||||
}
|
||||
}
|
||||
.no-sequence {
|
||||
padding-top: toRem(2);
|
||||
padding-bottom: toRem(4);
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 4rem;
|
||||
margin: auto;
|
||||
width: fit-content;
|
||||
text-align: center;
|
||||
@include text(m-regular);
|
||||
}
|
||||
.no-sequence-text {
|
||||
margin-bottom: toRem(4);
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
.loader {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
margin-top: toRem(20);
|
||||
}
|
||||
@media (max-width: toRem(76.8)) {
|
||||
@media (max-width: 768px) {
|
||||
.entry-page {
|
||||
padding-right: toRem(2);
|
||||
padding-left: toRem(2);
|
||||
padding-top: toRem(14);
|
||||
min-height: calc(100vh - #{toRem(11)});
|
||||
padding-right: 2rem;
|
||||
padding-left: 2rem;
|
||||
padding-top: 14rem;
|
||||
min-height: calc(100vh - 11rem);
|
||||
}
|
||||
.button-item,
|
||||
.sequence-item:first-child {
|
||||
padding-right: toRem(1);
|
||||
padding-left: toRem(1);
|
||||
padding-right: 1rem;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
}
|
||||
@media (max-width: toRem(50)) {
|
||||
@media (max-width: 500px) {
|
||||
.button-item {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -357,8 +317,8 @@ onMounted(async () => {
|
||||
display: none;
|
||||
}
|
||||
.sequence-item {
|
||||
border-top-right-radius: toRem(1);
|
||||
border-top-left-radius: toRem(1);
|
||||
border-top-right-radius: 1rem;
|
||||
border-top-left-radius: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="entry-button">
|
||||
<Button
|
||||
:data-test="`button-eye-${i}`"
|
||||
look="no-text"
|
||||
look="button--rounded"
|
||||
:tooltip="$t('pages.settings.setting_tooltip')"
|
||||
:icon="
|
||||
!item.token || item.isHidden ? 'bi bi-eye' : 'bi bi-eye-slash'
|
||||
@@ -26,7 +26,7 @@
|
||||
<Button
|
||||
:data-test="`button-copy-${i}`"
|
||||
look="button--white"
|
||||
:text="$t('pages.share_pictures.button_copy')"
|
||||
:text="$t('pages.upload.button_copy')"
|
||||
:icon="
|
||||
item.copied
|
||||
? 'bi bi-clipboard-check-fill'
|
||||
@@ -86,31 +86,31 @@ onMounted(async () => {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.entry-page {
|
||||
padding-right: toRem(8);
|
||||
padding-left: toRem(8);
|
||||
padding-top: toRem(11);
|
||||
min-height: calc(100vh - #{toRem(8)});
|
||||
padding-right: 8rem;
|
||||
padding-left: 8rem;
|
||||
padding-top: 11rem;
|
||||
min-height: calc(100vh - 8rem);
|
||||
}
|
||||
.settings-title {
|
||||
@include text(h1);
|
||||
margin-bottom: toRem(4);
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
.settings-list {
|
||||
padding-left: 0;
|
||||
}
|
||||
.settings-item-title {
|
||||
font-size: toRem(1.8);
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
.settings-item {
|
||||
font-size: toRem(1.4);
|
||||
border: toRem(0.1) solid var(--grey);
|
||||
border-radius: toRem(0.5);
|
||||
padding: toRem(1.5);
|
||||
margin-bottom: toRem(2);
|
||||
font-size: 1.4rem;
|
||||
border: 0.1rem solid var(--grey);
|
||||
border-radius: 0.5rem;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.item-information {
|
||||
height: toRem(5);
|
||||
margin-top: toRem(1);
|
||||
height: 5rem;
|
||||
margin-top: 1rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
@@ -120,18 +120,17 @@ onMounted(async () => {
|
||||
width: 80%;
|
||||
}
|
||||
.token {
|
||||
margin-top: toRem(0.4);
|
||||
width: 100%;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.entry-button {
|
||||
margin-right: toRem(1);
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: toRem(102.4)) {
|
||||
@media (max-width: 1024px) {
|
||||
.settings-item {
|
||||
position: relative;
|
||||
height: toRem(14);
|
||||
height: 14rem;
|
||||
}
|
||||
.item-information {
|
||||
height: initial;
|
||||
@@ -139,21 +138,21 @@ onMounted(async () => {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.reveal-token {
|
||||
margin-right: toRem(2);
|
||||
margin-right: 2rem;
|
||||
}
|
||||
.entry-copy-button {
|
||||
position: absolute;
|
||||
bottom: toRem(1);
|
||||
right: toRem(1);
|
||||
bottom: 1rem;
|
||||
right: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: toRem(76.8)) {
|
||||
@media (max-width: 768px) {
|
||||
.entry-page {
|
||||
padding-right: toRem(2);
|
||||
padding-left: toRem(2);
|
||||
padding-top: toRem(14);
|
||||
min-height: calc(100vh - #{toRem(11)});
|
||||
padding-right: 2rem;
|
||||
padding-left: 2rem;
|
||||
padding-top: 14rem;
|
||||
min-height: calc(100vh - 11rem);
|
||||
}
|
||||
.settings-item {
|
||||
height: initial;
|
||||
@@ -163,7 +162,7 @@ onMounted(async () => {
|
||||
}
|
||||
.token {
|
||||
max-width: initial;
|
||||
width: calc(100% - #{toRem(3.5)});
|
||||
width: calc(100% - 3.5rem);
|
||||
}
|
||||
.entry-copy-button {
|
||||
display: none;
|
||||
|
||||
@@ -1,266 +0,0 @@
|
||||
<template>
|
||||
<main class="entry-page">
|
||||
<section class="upload-section">
|
||||
<h1 class="settings-title">{{ $t('pages.upload.title') }}</h1>
|
||||
<form v-if="!isLoading" @submit.prevent="uploadPicture">
|
||||
<InputUpload
|
||||
:text="$t('pages.upload.input_label')"
|
||||
:text-second-part="$t('pages.upload.import_word')"
|
||||
:text-picture-type="$t('pages.upload.import_type')"
|
||||
accept="image/jpeg"
|
||||
data-test="input-add-pictures"
|
||||
@trigger="addPictures"
|
||||
/>
|
||||
<div class="footer-form">
|
||||
<span v-if="fileUploaded" class="number-file-text">{{
|
||||
fileUploaded
|
||||
}}</span>
|
||||
<span v-else class="number-file-text">{{
|
||||
t('pages.upload.no_uploaded_files')
|
||||
}}</span>
|
||||
<Button
|
||||
:text="$t('pages.upload.button_text')"
|
||||
:disabled="!pictures || pictures.length < 1"
|
||||
type="submit"
|
||||
data-test="button-upload"
|
||||
look="button button--blue"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<UploadLoader
|
||||
v-else
|
||||
:load-percentage="loadPercentage"
|
||||
:load-text-size="loadTextSize"
|
||||
:is-loaded="isLoaded"
|
||||
:uploaded-sequences="uploadedSequences"
|
||||
:pictures-count="picturesCount"
|
||||
@triggerNewUpload="triggerNewUpload"
|
||||
/>
|
||||
</section>
|
||||
<ImportedSection
|
||||
v-for="(sequence, i) in uploadedSequences"
|
||||
:index="i"
|
||||
:sequence="sequence"
|
||||
:pictures-count="picturesCount"
|
||||
:upload-errors="sequence.picturesOnError"
|
||||
:upload-pictures="sequence.pictures"
|
||||
/>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch, onUnmounted } from 'vue'
|
||||
import { onBeforeRouteLeave } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import InputUpload from '@/components/InputUpload.vue'
|
||||
import Button from '@/components/Button.vue'
|
||||
import ImportedSection from '@/components/upload/ImportedSection.vue'
|
||||
import UploadLoader from '@/components/upload/UploadLoader.vue'
|
||||
import type { sequenceInterface } from './interfaces/UploadPicturesView'
|
||||
import { formatDate } from '@/utils/dates'
|
||||
import {
|
||||
createAPictureToASequence,
|
||||
createASequence
|
||||
} from '@/views/utils/upload/request'
|
||||
import {
|
||||
formatPictureSize,
|
||||
formatTextSize,
|
||||
sortByName
|
||||
} from '@/views/utils/upload/index'
|
||||
const { t } = useI18n()
|
||||
let pictures = ref<File[] | []>([])
|
||||
let picturesCount = ref<number>(0)
|
||||
let isLoading = ref<boolean>(false)
|
||||
let isLoaded = ref<boolean>(false)
|
||||
let uploadingPictures = ref<FileList | []>([])
|
||||
let uploadedSequences = ref<sequenceInterface[] | []>([])
|
||||
let picturesUploadingSize = ref<number>(0)
|
||||
let picturesToUploadSize = ref<number>(0)
|
||||
let loadPercentage = ref<string>('0%')
|
||||
let loadTextSize = ref<string>('0 Mo')
|
||||
|
||||
watch(isLoading, () => {
|
||||
if (isLoading.value) {
|
||||
window.onbeforeunload = function (e) {
|
||||
e = e || window.event
|
||||
if (e) e.returnValue = 'Sure?'
|
||||
return 'Sure?'
|
||||
}
|
||||
} else {
|
||||
window.onbeforeunload = null
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.onbeforeunload = null
|
||||
})
|
||||
onBeforeRouteLeave((to, from, next) => {
|
||||
if (isLoading.value) {
|
||||
const answer = window.confirm(t('pages.upload.leave_message'))
|
||||
if (answer) return next()
|
||||
return next(false)
|
||||
}
|
||||
next()
|
||||
})
|
||||
const fileUploaded = computed<string>(() => {
|
||||
return t('pages.upload.uploaded_files', { count: picturesCount.value })
|
||||
})
|
||||
|
||||
function picturesToUploadSizeText(): void {
|
||||
let fullSize = 0
|
||||
for (let i = 0; i < pictures.value.length; i++) {
|
||||
fullSize += pictures.value[i].size
|
||||
}
|
||||
picturesToUploadSize.value = fullSize
|
||||
loadTextSize.value = formatTextSize(fullSize, formatPictureSize(fullSize))
|
||||
}
|
||||
function calcPercentage(): void {
|
||||
if (picturesUploadingSize.value && picturesToUploadSize.value) {
|
||||
loadPercentage.value = `${(
|
||||
(picturesUploadingSize.value / picturesToUploadSize.value) *
|
||||
100
|
||||
).toFixed(0)}%`
|
||||
}
|
||||
}
|
||||
|
||||
function triggerNewUpload(): void {
|
||||
isLoading.value = false
|
||||
picturesCount.value = 0
|
||||
}
|
||||
function addPictures(value: FileList): void {
|
||||
const files = sortByName([].slice.call(value))
|
||||
pictures.value = files
|
||||
picturesCount.value = pictures.value.length
|
||||
picturesUploadingSize.value = 0
|
||||
picturesToUploadSize.value = 0
|
||||
loadPercentage.value = '0%'
|
||||
loadTextSize.value = '0 Mo'
|
||||
}
|
||||
function setPictureSizeValue(sequence: sequenceInterface, size: number): void {
|
||||
picturesUploadingSize.value = picturesUploadingSize.value + size
|
||||
sequence.pictureSize = formatTextSize(
|
||||
picturesUploadingSize.value,
|
||||
formatPictureSize(picturesUploadingSize.value)
|
||||
)
|
||||
}
|
||||
async function uploadPicture(): Promise<void> {
|
||||
if (!pictures.value || !pictures.value.length) {
|
||||
return
|
||||
}
|
||||
isLoaded.value = false
|
||||
isLoading.value = true
|
||||
picturesToUploadSizeText()
|
||||
const picturesToUpload = [...pictures.value]
|
||||
|
||||
const sequenceTitle = `${t('pages.upload.sequence_title')}${formatDate(
|
||||
new Date(),
|
||||
'Do MMMM YY, hh:mm:ss'
|
||||
)}`
|
||||
const { data } = await createASequence(sequenceTitle)
|
||||
const sequence: sequenceInterface = {
|
||||
title: sequenceTitle,
|
||||
id: data.id,
|
||||
pictures: [],
|
||||
picturesOnError: [],
|
||||
pictureCount: pictures.value.length,
|
||||
pictureSize: ''
|
||||
}
|
||||
uploadedSequences.value = [sequence, ...uploadedSequences.value]
|
||||
let i = 0
|
||||
for (let el of picturesToUpload) {
|
||||
const body = new FormData()
|
||||
i++
|
||||
body.append('position', i.toString())
|
||||
body.append('picture', el)
|
||||
try {
|
||||
const pictureUploaded = await createAPictureToASequence(data.id, body)
|
||||
const pictures = { ...pictureUploaded.data, name: el.name }
|
||||
setPictureSizeValue(sequence, el.size)
|
||||
sequence.pictures = [...sequence.pictures, pictures]
|
||||
const index = uploadedSequences.value.findIndex(
|
||||
(item: sequenceInterface) => item.id === data.id
|
||||
)
|
||||
uploadedSequences.value.splice(index, 1)
|
||||
uploadedSequences.value = [sequence, ...uploadedSequences.value]
|
||||
calcPercentage()
|
||||
} catch (err: any) {
|
||||
setPictureSizeValue(sequence, el.size)
|
||||
const picturesOnError = {
|
||||
message: err.response.data.message,
|
||||
name: el.name
|
||||
}
|
||||
sequence.picturesOnError = [...sequence.picturesOnError, picturesOnError]
|
||||
calcPercentage()
|
||||
}
|
||||
}
|
||||
isLoaded.value = true
|
||||
uploadingPictures.value = []
|
||||
pictures.value = []
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
ul {
|
||||
padding-left: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.entry-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.upload-section {
|
||||
width: 80%;
|
||||
padding: toRem(4);
|
||||
margin-top: toRem(6);
|
||||
margin-bottom: toRem(4);
|
||||
box-shadow: 0px 6.45694px 8.60925px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.settings-title {
|
||||
text-align: center;
|
||||
@include text(h1);
|
||||
margin-bottom: toRem(4);
|
||||
}
|
||||
.footer-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: toRem(3);
|
||||
}
|
||||
.number-file-text {
|
||||
margin-bottom: toRem(2);
|
||||
color: var(--blue-dark);
|
||||
@include text(s-regular);
|
||||
}
|
||||
::-webkit-scrollbar {
|
||||
width: toRem(2);
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--grey-pale);
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--blue);
|
||||
border-radius: toRem(5);
|
||||
}
|
||||
@media (max-width: toRem(76.8)) {
|
||||
.entry-page {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.information-section {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
@media (max-width: toRem(50)) {
|
||||
.upload-section {
|
||||
margin-top: toRem(15);
|
||||
width: 100%;
|
||||
padding-right: toRem(2);
|
||||
padding-left: toRem(2);
|
||||
}
|
||||
.information-section {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -3,7 +3,7 @@
|
||||
<section id="sec-1" class="section-upload">
|
||||
<div class="wrapper-upload">
|
||||
<div class="wrapper-upload-text">
|
||||
<h1 class="upload-title">{{ $t('pages.share_pictures.title') }}</h1>
|
||||
<h1 class="upload-title">{{ $t('pages.upload.title') }}</h1>
|
||||
<div class="wrapper-check">
|
||||
<div class="wrapper-img-icon">
|
||||
<i class="bi bi-images img-icon"></i>
|
||||
@@ -11,45 +11,41 @@
|
||||
<div class="element-check">
|
||||
<span class="block-check"
|
||||
><span class="check-border">✔</span
|
||||
>{{ $t('pages.share_pictures.photo_type1') }}</span
|
||||
>{{ $t('pages.upload.photo_type1') }}</span
|
||||
>
|
||||
<span class="block-check"
|
||||
><span class="check-border">✔</span
|
||||
>{{ $t('pages.share_pictures.photo_type2') }}</span
|
||||
>{{ $t('pages.upload.photo_type2') }}</span
|
||||
>
|
||||
<span class="block-check"
|
||||
><span class="check-border">✔</span
|
||||
>{{ $t('pages.share_pictures.photo_type3') }}</span
|
||||
>{{ $t('pages.upload.photo_type3') }}</span
|
||||
>
|
||||
<span class="block-check"
|
||||
><span class="check-border">✔</span
|
||||
>{{ $t('pages.share_pictures.photo_type4') }}</span
|
||||
>{{ $t('pages.upload.photo_type4') }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
class="upload-text"
|
||||
v-html="
|
||||
$t('pages.share_pictures.description', {
|
||||
$t('pages.upload.description', {
|
||||
check: checkImg
|
||||
})
|
||||
"
|
||||
/>
|
||||
<p class="upload-text">
|
||||
{{ $t('pages.share_pictures.footer_block') }}
|
||||
</p>
|
||||
<p class="upload-text">{{ $t('pages.upload.footer_block') }}</p>
|
||||
<div v-if="!isLogged && authConf.enabled" class="wrapper-account">
|
||||
<h4 class="account-subtitle">
|
||||
{{ $t('pages.share_pictures.sub_title') }}
|
||||
{{ $t('pages.upload.sub_title') }}
|
||||
</h4>
|
||||
<div class="entry-link">
|
||||
<Link
|
||||
:text="$t('pages.share_pictures.user_account_button')"
|
||||
:text="$t('pages.upload.user_account_button')"
|
||||
type="external"
|
||||
look="button button--blue"
|
||||
:path-external="
|
||||
getAuthRoute('auth/login', 'partager-des-photos')
|
||||
"
|
||||
look="button white"
|
||||
:path="getAuthRoute('auth/login', 'partager-des-photos')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -58,7 +54,7 @@
|
||||
<div class="image">
|
||||
<img
|
||||
src="@/assets/images/upload.png"
|
||||
:alt="$t('pages.share_pictures.alt_img_upload')"
|
||||
:alt="$t('pages.upload.alt_img_upload')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -78,32 +74,30 @@
|
||||
<div class="entry-terminal">
|
||||
<div class="terminal">
|
||||
<Terminal
|
||||
:text-upload="terminalText"
|
||||
:text-upload="$t('pages.upload.terminal_text')"
|
||||
:text-install="terminalTextInstall"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrapper-upload-text">
|
||||
<h2 class="upload-title">
|
||||
{{ $t('pages.share_pictures.title_terminal') }}
|
||||
</h2>
|
||||
<h2 class="upload-title">{{ $t('pages.upload.title_terminal') }}</h2>
|
||||
<p
|
||||
class="upload-text"
|
||||
v-html="$t('pages.share_pictures.description_terminal')"
|
||||
v-html="$t('pages.upload.description_terminal')"
|
||||
></p>
|
||||
<p
|
||||
class="upload-text grey"
|
||||
v-html="$t('pages.share_pictures.footer_description_terminal')"
|
||||
v-html="$t('pages.upload.footer_description_terminal')"
|
||||
></p>
|
||||
<div class="wrapper-account">
|
||||
<h4 class="account-subtitle">
|
||||
{{ $t('pages.share_pictures.cli_title') }}
|
||||
{{ $t('pages.upload.cli_title') }}
|
||||
</h4>
|
||||
<div class="entry-link">
|
||||
<Link
|
||||
:text="$t('pages.share_pictures.button')"
|
||||
:text="$t('pages.upload.button')"
|
||||
type="external"
|
||||
look="button button--blue"
|
||||
look="button"
|
||||
path="https://gitlab.com/geovisio/cli"
|
||||
/>
|
||||
</div>
|
||||
@@ -131,17 +125,11 @@ const route = useRoute()
|
||||
let hrefSection = ref<string>('#sec-2')
|
||||
let icon = ref<string>('bi bi-chevron-down')
|
||||
const checkImg =
|
||||
"<span style='background:white; padding: 5px 0.0525px;border-radius:50%; font-size:8px;'>✔</span>"
|
||||
"<span style='background:white; padding: 0.5rem 0.525rem;border-radius:50%; font-size:0.8rem;'>✔</span>"
|
||||
|
||||
const isLogged = computed((): boolean => !!cookies.get('user_id'))
|
||||
const terminalText = computed((): string => {
|
||||
const url = import.meta.env.VITE_API_URL
|
||||
? import.meta.env.VITE_API_URL
|
||||
: 'https://panoramax.ign.fr/'
|
||||
return t('pages.share_pictures.terminal_text', { url })
|
||||
})
|
||||
const terminalTextInstall = computed((): string =>
|
||||
t('pages.share_pictures.terminal_install')
|
||||
t('pages.upload.terminal_install')
|
||||
)
|
||||
function triggerHref(): void {
|
||||
icon.value =
|
||||
@@ -158,32 +146,32 @@ function triggerHref(): void {
|
||||
@include text(h4);
|
||||
}
|
||||
.entry-link {
|
||||
margin-top: toRem(1);
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.upload-text {
|
||||
@include text(m-regular);
|
||||
margin-top: toRem(5);
|
||||
margin-top: 5rem;
|
||||
}
|
||||
.wrapper-check {
|
||||
position: relative;
|
||||
width: fit-content;
|
||||
padding: toRem(3);
|
||||
padding: 3rem;
|
||||
background-color: var(--white);
|
||||
border-radius: toRem(1.4);
|
||||
font-size: toRem(1.6);
|
||||
margin-top: toRem(8);
|
||||
border-radius: 1.5rem;
|
||||
font-size: 1.6rem;
|
||||
margin-top: 8rem;
|
||||
}
|
||||
.wrapper-img-icon {
|
||||
background-color: var(--black);
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
height: toRem(5.5);
|
||||
width: toRem(5.5);
|
||||
height: 5.5rem;
|
||||
width: 5.5rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: toRem(3);
|
||||
top: toRem(-3);
|
||||
font-size: 3rem;
|
||||
top: -3rem;
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
}
|
||||
@@ -193,31 +181,31 @@ function triggerHref(): void {
|
||||
.element-check {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: toRem(3);
|
||||
margin-top: 3rem;
|
||||
}
|
||||
.block-check {
|
||||
margin-bottom: toRem(1);
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
}
|
||||
.check-border {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: toRem(2.2);
|
||||
height: toRem(2.2);
|
||||
width: 2.2rem;
|
||||
height: 2.2rem;
|
||||
border-radius: 50%;
|
||||
font-size: toRem(0.8);
|
||||
font-size: 0.8rem;
|
||||
background: var(--white);
|
||||
border: toRem(0.1) solid var(--black);
|
||||
margin-right: toRem(0.5);
|
||||
border: 0.1rem solid var(--black);
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.block-check:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.section-upload {
|
||||
height: 100%;
|
||||
padding-right: toRem(2);
|
||||
padding-left: toRem(2);
|
||||
padding-right: 2rem;
|
||||
padding-left: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
@@ -243,19 +231,19 @@ function triggerHref(): void {
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
margin-top: toRem(3);
|
||||
padding-top: toRem(1);
|
||||
border-top: toRem(0.1) solid #e6e6e6;
|
||||
margin-top: 3rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 0.1rem solid #e6e6e6;
|
||||
}
|
||||
.upload-button {
|
||||
display: flex;
|
||||
margin-top: toRem(3);
|
||||
margin-top: 3rem;
|
||||
}
|
||||
.image {
|
||||
background-color: var(--white);
|
||||
border-radius: toRem(1);
|
||||
padding: toRem(1);
|
||||
border: 1px solid var(--black);
|
||||
border-radius: 1rem;
|
||||
padding: 1rem;
|
||||
border: 1px solid black;
|
||||
width: 75%;
|
||||
height: fit-content;
|
||||
}
|
||||
@@ -264,30 +252,30 @@ function triggerHref(): void {
|
||||
justify-content: center;
|
||||
width: 35%;
|
||||
overflow: hidden;
|
||||
margin-left: toRem(6);
|
||||
margin-left: 6rem;
|
||||
}
|
||||
.entry-terminal {
|
||||
margin-left: 0;
|
||||
margin-right: toRem(6);
|
||||
margin-right: 6rem;
|
||||
}
|
||||
.entry-image img {
|
||||
width: 100%;
|
||||
border-radius: toRem(1);
|
||||
border-radius: 1rem;
|
||||
}
|
||||
.entry-button-down {
|
||||
z-index: 1;
|
||||
position: fixed;
|
||||
right: toRem(2);
|
||||
bottom: calc(20vh - #{toRem(10.5)});
|
||||
right: 2rem;
|
||||
bottom: calc(20vh - 10.5rem);
|
||||
}
|
||||
@media (max-width: toRem(102.4)) {
|
||||
@media (max-width: 1024px) {
|
||||
.section-upload {
|
||||
height: initial;
|
||||
}
|
||||
.wrapper-upload {
|
||||
flex-direction: column-reverse;
|
||||
padding-top: toRem(6);
|
||||
padding-bottom: toRem(6);
|
||||
padding-top: 6rem;
|
||||
padding-bottom: 6rem;
|
||||
}
|
||||
.wrapper-upload-text {
|
||||
width: 100%;
|
||||
@@ -301,12 +289,12 @@ function triggerHref(): void {
|
||||
}
|
||||
.terminal {
|
||||
width: 100%;
|
||||
margin-top: toRem(4);
|
||||
margin-top: 4rem;
|
||||
}
|
||||
}
|
||||
@media (max-width: toRem(76.8)) {
|
||||
@media (max-width: 768px) {
|
||||
.entry-page {
|
||||
padding-top: toRem(11);
|
||||
padding-top: 11rem;
|
||||
}
|
||||
.entry-image {
|
||||
width: 60%;
|
||||
@@ -321,10 +309,10 @@ function triggerHref(): void {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
@media (max-width: toRem(50)) {
|
||||
@media (max-width: 500px) {
|
||||
.entry-page {
|
||||
min-height: calc(100vh - #{toRem(11)});
|
||||
padding-top: toRem(11);
|
||||
min-height: calc(100vh - 11rem);
|
||||
padding-top: 11rem;
|
||||
}
|
||||
.entry-image {
|
||||
width: 100%;
|
||||
@@ -340,14 +328,14 @@ function triggerHref(): void {
|
||||
padding-left: 0;
|
||||
}
|
||||
.upload-text {
|
||||
margin-bottom: toRem(3);
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
.wrapper-account {
|
||||
padding-top: toRem(3);
|
||||
padding-top: 3rem;
|
||||
flex-direction: column;
|
||||
}
|
||||
.account-subtitle {
|
||||
margin-bottom: toRem(2);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.entry-button-down {
|
||||
display: none;
|
||||
@@ -1,19 +1,11 @@
|
||||
export interface ResponseUserPhotoInterface {
|
||||
assets: { thumb: { href: string }; hd: { href: string } }
|
||||
properties: { datetime: Date; 'geovisio:status': string }
|
||||
properties: { created: Date; 'geovisio:status': string }
|
||||
id: string
|
||||
bbox: number[]
|
||||
}
|
||||
|
||||
export interface ResponseUserPhotoLinksInterface {
|
||||
href: string
|
||||
rel: string
|
||||
title?: string
|
||||
type: string
|
||||
}
|
||||
|
||||
export interface UserSequenceInterface {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
license: string
|
||||
@@ -24,16 +16,14 @@ export interface UserSequenceInterface {
|
||||
duration: string
|
||||
camera: string
|
||||
cameraModel: string
|
||||
status: string
|
||||
extent: { temporal: { interval: [Date[]] }; spatial: { bbox: string[] } }
|
||||
}
|
||||
|
||||
export interface ResponseUserSequenceInterface extends UserSequenceInterface {
|
||||
extent: { temporal: { interval: [Date[]] }; spatial: { bbox: string[] } }
|
||||
['stats:items']: { count: number }
|
||||
summaries: {
|
||||
['pers:interior_orientation']: [{ make: string; model: string }]
|
||||
}
|
||||
'geovisio:status': string
|
||||
}
|
||||
|
||||
export interface CheckboxInterface {
|
||||
|
||||
@@ -9,7 +9,7 @@ export interface LinkInterface {
|
||||
['geovisio:status']: string
|
||||
}
|
||||
|
||||
export interface ExtentLinkInterface {
|
||||
export interface ExtentInterface {
|
||||
extent: { temporal: { interval: [Date[]] } }
|
||||
['stats:items']: { count: number }
|
||||
title: string
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
export interface sequenceInterface {
|
||||
title: string
|
||||
id: string
|
||||
pictures: any[]
|
||||
picturesOnError: uploadErrorInterface[] | []
|
||||
pictureCount: number
|
||||
pictureSize: string
|
||||
}
|
||||
export interface uploadErrorInterface {
|
||||
message: string
|
||||
name: string
|
||||
}
|
||||
@@ -1,14 +1,8 @@
|
||||
export interface OptionalViewerMapInterface {
|
||||
fetchOptions?: {
|
||||
credentials: string
|
||||
export interface ViewerMapInterface {
|
||||
map: {
|
||||
startWide: boolean
|
||||
style: object
|
||||
maxZoom: number
|
||||
}
|
||||
picId?: string
|
||||
}
|
||||
|
||||
export interface ViewerMapInterface extends OptionalViewerMapInterface {
|
||||
map: {
|
||||
startWide: boolean
|
||||
style?: object | string
|
||||
maxZoom: number
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import type {
|
||||
ResponseUserPhotoInterface,
|
||||
ResponseUserPhotoLinksInterface
|
||||
} from '@/views/interfaces/MySequenceView'
|
||||
|
||||
function imageStatus(imageStatus: string, sequenceStatus: string): string {
|
||||
if (sequenceStatus === 'hidden') return sequenceStatus
|
||||
return imageStatus
|
||||
}
|
||||
|
||||
function scrollIntoSelected(
|
||||
id: string,
|
||||
userPhotos: ResponseUserPhotoInterface[]
|
||||
): void {
|
||||
const itemPosition = userPhotos.map((el) => el.id).indexOf(id)
|
||||
const elementTarget = document.querySelector(`#photo${itemPosition}`)
|
||||
if (elementTarget) elementTarget.scrollIntoView()
|
||||
}
|
||||
|
||||
function photoToDeleteOrPatchSelected(
|
||||
item: ResponseUserPhotoInterface,
|
||||
imagesToDelete: string[]
|
||||
): boolean {
|
||||
return imagesToDelete.includes(item.id)
|
||||
}
|
||||
|
||||
function spliceIntoChunks(arr: string[], chunkSize: number) {
|
||||
const res = []
|
||||
arr = ([] as string[]).concat(...arr)
|
||||
while (arr.length) {
|
||||
res.push(arr.splice(0, chunkSize))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function formatPaginationItems(
|
||||
items: [ResponseUserPhotoLinksInterface]
|
||||
): ResponseUserPhotoLinksInterface[] {
|
||||
const filterItems = items.filter(
|
||||
(el: ResponseUserPhotoLinksInterface) =>
|
||||
el.rel === 'first' ||
|
||||
el.rel === 'last' ||
|
||||
el.rel === 'next' ||
|
||||
el.rel === 'prev'
|
||||
)
|
||||
return filterItems.map((el: ResponseUserPhotoLinksInterface) => {
|
||||
if (el.rel === 'first') return { ...el, rel: 'double-left' }
|
||||
if (el.rel === 'last') return { ...el, rel: 'double-right' }
|
||||
if (el.rel === 'next') return { ...el, rel: 'right' }
|
||||
if (el.rel === 'prev') return { ...el, rel: 'left' }
|
||||
return el
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
imageStatus,
|
||||
scrollIntoSelected,
|
||||
photoToDeleteOrPatchSelected,
|
||||
spliceIntoChunks,
|
||||
formatPaginationItems
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import axios from 'axios'
|
||||
import type {
|
||||
ResponseUserPhotoInterface,
|
||||
ResponseUserPhotoLinksInterface,
|
||||
ResponseUserSequenceInterface
|
||||
} from '@/views/interfaces/MySequenceView'
|
||||
|
||||
function deleteACollection(collectionId: string | string[]): Promise<unknown> {
|
||||
return axios.delete(`api/collections/${collectionId}`)
|
||||
}
|
||||
|
||||
function patchACollection(
|
||||
collectionId: string | string[],
|
||||
visible: string
|
||||
): Promise<unknown> {
|
||||
return axios.patch(`api/collections/${collectionId}`, { visible: visible })
|
||||
}
|
||||
|
||||
function deleteACollectionItem(
|
||||
collectionId: string | string[],
|
||||
itemId: string
|
||||
): Promise<unknown> {
|
||||
return axios.delete(`api/collections/${collectionId}/items/${itemId}`)
|
||||
}
|
||||
|
||||
function patchACollectionItem(
|
||||
isVisible: string,
|
||||
collectionId: string | string[],
|
||||
itemId: string
|
||||
): Promise<unknown> {
|
||||
return axios.patch(`api/collections/${collectionId}/items/${itemId}`, {
|
||||
visible: isVisible
|
||||
})
|
||||
}
|
||||
|
||||
async function fetchCollectionItems(
|
||||
collectionId: string | string[],
|
||||
limit: string,
|
||||
fullUrl?: string
|
||||
): Promise<{
|
||||
data: {
|
||||
features: [ResponseUserPhotoInterface]
|
||||
links: [ResponseUserPhotoLinksInterface]
|
||||
}
|
||||
}> {
|
||||
if (fullUrl) return await axios.get(fullUrl)
|
||||
return await axios.get(`api/collections/${collectionId}/items${limit}`)
|
||||
}
|
||||
|
||||
async function fetchCollectionItemsWithFullUrl(fullUrl: string): Promise<{
|
||||
data: {
|
||||
features: [ResponseUserPhotoInterface]
|
||||
links: [ResponseUserPhotoLinksInterface]
|
||||
}
|
||||
}> {
|
||||
return await axios.get(fullUrl)
|
||||
}
|
||||
|
||||
async function fetchCollection(collectionId: string | string[]): Promise<{
|
||||
data: ResponseUserSequenceInterface
|
||||
}> {
|
||||
return await axios.get(`api/collections/${collectionId}`)
|
||||
}
|
||||
|
||||
export {
|
||||
deleteACollectionItem,
|
||||
patchACollectionItem,
|
||||
fetchCollectionItems,
|
||||
fetchCollection,
|
||||
deleteACollection,
|
||||
patchACollection,
|
||||
fetchCollectionItemsWithFullUrl
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
function formatPictureSize(size: number): number {
|
||||
return Math.floor(Math.log(size) / Math.log(1024))
|
||||
}
|
||||
function formatTextSize(size: number, i: number): string {
|
||||
const sizes = ['0', 'Ko', 'Mo', 'Go', 'To', 'Po', 'Eo', 'Zo', 'o']
|
||||
return `${parseFloat((size / Math.pow(1024, i)).toFixed(2))} ${sizes[i]}`
|
||||
}
|
||||
function sortByName(fileList: File[]): File[] {
|
||||
return fileList.sort((a: File, b: File) =>
|
||||
a.name.localeCompare(b.name, navigator.languages[0] || navigator.language, {
|
||||
numeric: true,
|
||||
ignorePunctuation: true
|
||||
})
|
||||
)
|
||||
}
|
||||
export { formatPictureSize, formatTextSize, sortByName }
|
||||
@@ -1,11 +0,0 @@
|
||||
import axios from 'axios'
|
||||
|
||||
function createASequence(title: string): Promise<any> {
|
||||
return axios.post('api/collections', { title: title })
|
||||
}
|
||||
|
||||
async function createAPictureToASequence(id: string, body: any): Promise<any> {
|
||||
return await axios.post(`api/collections/${id}/items`, body)
|
||||
}
|
||||
|
||||
export { createASequence, createAPictureToASequence }
|
||||
@@ -8,10 +8,8 @@ export default defineConfig({
|
||||
server: {
|
||||
host: true,
|
||||
port: 5173,
|
||||
strictPort: true,
|
||||
hmr: {
|
||||
clientPort: 5173,
|
||||
overlay: false
|
||||
port: 9000
|
||||
}
|
||||
},
|
||||
base: '/',
|
||||
@@ -19,8 +17,7 @@ export default defineConfig({
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
additionalData:
|
||||
'@import "@/assets/font-size.scss"; @import "@/assets/rem-calc.scss";'
|
||||
additionalData: '@import "@/assets/font-size.scss";'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue({
|
||||
template: {
|
||||
compilerOptions: {
|
||||
isCustomElement: (tag) => {
|
||||
return tag.startsWith('router-link')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js'
|
||||
}
|
||||
},
|
||||
test: {
|
||||
deps: {
|
||||
inline: ['moment']
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user