forked from Ivasoft/geovisio-website
Feat/my sequences resize
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
describe('In the login page', () => {
|
||||
it('type in the form to login', () => {
|
||||
cy.visit('https://geovisio-proxy-dev.osc-fr1.scalingo.io/api/auth/login')
|
||||
cy.get('#password').type('coucouc')
|
||||
cy.visit('/')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -25,11 +25,12 @@
|
||||
"axios": "^1.2.3",
|
||||
"bootstrap": "^5.2.3",
|
||||
"bootstrap-icons": "^1.10.3",
|
||||
"geovisio": "2.2.1",
|
||||
"geovisio": "2.2.1-develop-acb9989e",
|
||||
"moment": "^2.29.4",
|
||||
"pinia": "^2.1.4",
|
||||
"vue": "^3.2.45",
|
||||
"vue-axios": "^3.5.2",
|
||||
"vue-draggable-resizable-vue3": "^2.3.1-beta.13",
|
||||
"vue-eslint-parser": "^9.1.0",
|
||||
"vue-i18n": "9.2.2",
|
||||
"vue-meta": "^3.0.0-alpha.10",
|
||||
@@ -57,6 +58,8 @@
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-vue": "^9.8.0",
|
||||
"jsdom": "^20.0.3",
|
||||
"less": "^4.2.0",
|
||||
"less-loader": "^11.1.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "2.8.1",
|
||||
"sass": "^1.62.0",
|
||||
|
||||
@@ -46,7 +46,8 @@ defineProps({
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
border-radius: toRem(1);
|
||||
padding: toRem(1.3) toRem(2) toRem(1.3);
|
||||
padding: toRem(1.3);
|
||||
|
||||
.icon {
|
||||
font-size: toRem(2);
|
||||
}
|
||||
@@ -103,11 +104,15 @@ defineProps({
|
||||
width: toRem(3);
|
||||
padding: 0;
|
||||
.icon {
|
||||
color: var(---black);
|
||||
color: var(--black);
|
||||
font-size: toRem(1.8);
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
.no-text-white .icon {
|
||||
color: var(--white);
|
||||
margin-right: 0;
|
||||
}
|
||||
.background-white {
|
||||
background-color: var(--white);
|
||||
}
|
||||
@@ -155,7 +160,7 @@ defineProps({
|
||||
position: absolute;
|
||||
bottom: -100%;
|
||||
visibility: hidden;
|
||||
width: toRem(18);
|
||||
width: toRem(20);
|
||||
right: 0;
|
||||
@include text(xss-regular);
|
||||
}
|
||||
|
||||
131
src/components/EditText.vue
Normal file
131
src/components/EditText.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<div class="entry-edit">
|
||||
<form
|
||||
v-if="isEditTitle"
|
||||
@submit.prevent="isEditTitle = false"
|
||||
class="edit-form"
|
||||
>
|
||||
<div class="wrapper-input">
|
||||
<Input
|
||||
:text="text || ''"
|
||||
:placeholder="$t('pages.upload.edit_placeholder_input')"
|
||||
@input="changeTextValue"
|
||||
/>
|
||||
<div class="close-button">
|
||||
<Button
|
||||
id="close-button"
|
||||
look="no-text-white"
|
||||
icon="bi bi-x"
|
||||
@trigger="closeEdition"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
id="valid-button"
|
||||
:text="$t('pages.upload.ok_button')"
|
||||
type="submit"
|
||||
look="button button--blue"
|
||||
@trigger="validNewName"
|
||||
/>
|
||||
</form>
|
||||
<span v-else class="title">{{ text }}</span>
|
||||
<div v-if="!isEditTitle" class="edit-button">
|
||||
<Button
|
||||
look="no-text"
|
||||
icon="bi bi-pen"
|
||||
:tooltip="$t('pages.upload.edit_title_tooltip')"
|
||||
@trigger="goToEditMode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import Button from '@/components/Button.vue'
|
||||
import Input from '@/components/Input.vue'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'triggerNewText', value: string | null): void
|
||||
}>()
|
||||
const props = defineProps({
|
||||
defaultText: { type: String, default: null }
|
||||
})
|
||||
let titleToEdit = ref<string | null>(null)
|
||||
let isEditTitle = ref<boolean>(false)
|
||||
|
||||
function changeTextValue(value: string): void {
|
||||
titleToEdit.value = value
|
||||
}
|
||||
function closeEdition(): void {
|
||||
isEditTitle.value = false
|
||||
titleToEdit.value = null
|
||||
}
|
||||
function validNewName(): void {
|
||||
emit('triggerNewText', titleToEdit.value)
|
||||
isEditTitle.value = false
|
||||
}
|
||||
function goToEditMode(): void {
|
||||
isEditTitle.value = true
|
||||
titleToEdit.value = props.defaultText
|
||||
}
|
||||
|
||||
const text = computed<string | null>(() => {
|
||||
if (isEditTitle.value) return titleToEdit.value
|
||||
if (props.defaultText) return props.defaultText
|
||||
return null
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.title {
|
||||
color: var(--blue-dark);
|
||||
}
|
||||
.entry-edit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: toRem(2);
|
||||
width: 100%;
|
||||
height: toRem(4.7);
|
||||
}
|
||||
.wrapper-edit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: toRem(1);
|
||||
width: 100%;
|
||||
}
|
||||
.edit-button {
|
||||
background-color: var(--grey);
|
||||
border-radius: 50%;
|
||||
height: toRem(3.5);
|
||||
width: toRem(3.5);
|
||||
padding: toRem(1);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-left: toRem(1.5);
|
||||
}
|
||||
.wrapper-input {
|
||||
position: relative;
|
||||
margin-right: toRem(1.5);
|
||||
width: 100%;
|
||||
}
|
||||
.edit-form {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.close-button {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: toRem(2);
|
||||
width: toRem(2);
|
||||
top: toRem(-1);
|
||||
right: toRem(-1);
|
||||
background-color: var(--blue-dark);
|
||||
color: var(--white);
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
@@ -14,9 +14,9 @@
|
||||
<i v-if="status === 'hidden'" class="bi bi-eye-slash icon-hidden"></i>
|
||||
<img
|
||||
v-if="href"
|
||||
loading="lazy"
|
||||
:src="href"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
class="photo-img"
|
||||
/>
|
||||
</div>
|
||||
@@ -141,7 +141,6 @@ defineProps({
|
||||
}
|
||||
.waiting-info {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
color: var(--black);
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
33
src/components/Input.vue
Normal file
33
src/components/Input.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<input
|
||||
:value="text"
|
||||
:required="true"
|
||||
:placeholder="placeholder"
|
||||
class="input"
|
||||
type="text"
|
||||
@input="emitValue"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const emit = defineEmits<{ (e: 'input', value: string): void }>()
|
||||
const props = defineProps({
|
||||
text: { type: String, default: null },
|
||||
placeholder: { type: String, default: '' }
|
||||
})
|
||||
|
||||
function emitValue(event: Event): void {
|
||||
const inputValue = (event.target as HTMLInputElement).value
|
||||
emit('input', inputValue)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.input {
|
||||
padding: toRem(1);
|
||||
border-radius: toRem(0.5);
|
||||
border: toRem(0.1) solid var(--blue-dark);
|
||||
color: var(--blue-dark);
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -10,8 +10,8 @@
|
||||
type="file"
|
||||
multiple
|
||||
:accept="accept"
|
||||
class="input-file"
|
||||
@change="changeFile"
|
||||
class="input-file"
|
||||
/>
|
||||
<i class="bi bi-cloud-upload-fill"></i>
|
||||
<span v-if="text" class="input-text">
|
||||
|
||||
@@ -157,6 +157,7 @@ ul {
|
||||
}
|
||||
.logged-link {
|
||||
padding: toRem(0);
|
||||
margin-bottom: toRem(2);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -121,7 +121,7 @@ defineProps({
|
||||
margin-bottom: toRem(1);
|
||||
}
|
||||
.sequence-button {
|
||||
width: fit-content;
|
||||
width: toRem(22);
|
||||
}
|
||||
.text-information {
|
||||
@include text(s-r-regular);
|
||||
@@ -167,7 +167,6 @@ defineProps({
|
||||
margin-bottom: toRem(2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: toRem(102.4)) {
|
||||
.loader-title {
|
||||
flex-direction: column;
|
||||
|
||||
@@ -133,6 +133,8 @@
|
||||
"import_word": "upload",
|
||||
"import_type": "JPEG format only",
|
||||
"subtitle_import": "Picture upload",
|
||||
"title_sequence": "Séquence title",
|
||||
"description_title_sequence": "By default, the sequence title will be the date of the day. You can, if you want, edit the title here.",
|
||||
"text_import": "Upload your jpg files here. Each picture or series of pictures constitutes a \"sequence\". You can then find them in the \"my pictures\" section and choose to hide, show or delete them.",
|
||||
"subtitle_process": "Upload processing",
|
||||
"uploading_process": "Upload in progress...",
|
||||
@@ -143,6 +145,9 @@
|
||||
"no_img_text": "no picture upload so far",
|
||||
"upload_done": "Sequence upload done",
|
||||
"sequence_link": "Show this sequence",
|
||||
"edit_title_tooltip": "Edit the sequence's title",
|
||||
"edit_placeholder_input": "Edit the sequence's title",
|
||||
"ok_button": "OK",
|
||||
"pictures_error": "{count} picture could not be uploaded| {count} pictures could not be uploaded",
|
||||
"sequence_loading_information": "Once uploaded, the sequence will be processed then published on Panoramax (usually within a couple of minutes).",
|
||||
"sequence_loaded_information": "The sequences has been uploaded and is under processing. It should be publicly available on Panoramax within a couple of minutes.",
|
||||
|
||||
@@ -133,7 +133,9 @@
|
||||
"import_word": "importer",
|
||||
"import_type": "Format JPEG uniquement",
|
||||
"subtitle_import": "Dépôt des images",
|
||||
"text_import": "Déposez ici vos fichiers jpg. Chaque image ou série d’images constitue une “séquence”. Vous pourrez ensuite les retrouver dans la section « mes images » et choisir de les masquer, les afficher ou les supprimer.",
|
||||
"title_sequence": "Titre de ma séquence",
|
||||
"description_title_sequence": "Le titre d'une séquence est par défaut la date du jour. Vous pouvez, si vous le souhaitez le modifier ci-dessous.",
|
||||
"text_import": "Déposez ici vos fichiers jpg. Chaque image ou série d’images constitue une « séquence ». Vous pourrez ensuite les retrouver dans la section « mes images » et choisir de les masquer, les afficher ou les supprimer.",
|
||||
"subtitle_process": "Traitements de l'import",
|
||||
"uploading_process": "Téléchargement en cours...",
|
||||
"sequence_title": "Séquence du ",
|
||||
@@ -143,6 +145,9 @@
|
||||
"no_img_text": "aucune image chargée actuellement",
|
||||
"upload_done": "Le chargement de la séquence est terminé",
|
||||
"sequence_link": "Accéder à cette séquence",
|
||||
"edit_title_tooltip": "Modifier le titre de la séquence",
|
||||
"edit_placeholder_input": "Modifier le titre de la séquence",
|
||||
"ok_button": "Valider",
|
||||
"pictures_error": "{count} image n'a pas pu être chargée| {count} image n'ont pas pu être chargées",
|
||||
"sequence_loading_information": "Une fois chargée, la séquence sera en traitement et accessible sur Panoramax dans les prochaines minutes.",
|
||||
"sequence_loaded_information": "La séquence est chargée et est en cours de traitement. Elle sera accessible sur Panoramax dans quelques minutes.",
|
||||
|
||||
@@ -6,6 +6,7 @@ import axios from 'axios'
|
||||
import VueAxios from 'vue-axios'
|
||||
import { globalCookiesConfig } from 'vue3-cookies'
|
||||
import { createMetaManager } from 'vue-meta'
|
||||
import { VueDraggableResizable } from 'vue-draggable-resizable-vue3'
|
||||
import { pinia } from './store'
|
||||
import fr from './locales/fr.json'
|
||||
import en from './locales/en.json'
|
||||
@@ -40,4 +41,5 @@ app.use(router)
|
||||
app.use(VueAxios, axios)
|
||||
app.provide('axios', app.config.globalProperties.axios)
|
||||
app.use(createMetaManager())
|
||||
app.use(VueDraggableResizable)
|
||||
app.mount('#app')
|
||||
|
||||
88
src/tests/unit/components/EditText.spec.js
Normal file
88
src/tests/unit/components/EditText.spec.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import { test, describe, expect, vi } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import EditText from '../../../components/EditText.vue'
|
||||
import Button from '../../../components/Button.vue'
|
||||
import i18n from '../config'
|
||||
describe('Template', () => {
|
||||
describe('Props', () => {
|
||||
test('should have default props', () => {
|
||||
const wrapper = shallowMount(EditText, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
}
|
||||
})
|
||||
expect(wrapper.vm.defaultText).toBe(null)
|
||||
})
|
||||
})
|
||||
describe('When the props are filled', () => {
|
||||
test('should render the component with the defaultText', () => {
|
||||
const wrapper = shallowMount(EditText, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
defaultText: 'My default text'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('class="title">My default text</span>')
|
||||
expect(wrapper.html()).contains('icon="bi bi-pen"')
|
||||
})
|
||||
})
|
||||
describe('When the component is in editable mode', () => {
|
||||
test('should render the component with the element to edit the title', async () => {
|
||||
const wrapper = shallowMount(EditText, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
}
|
||||
})
|
||||
wrapper.vm.isEditTitle = true
|
||||
wrapper.vm.titleToEdit = 'title to edit'
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.html()).contains('text="title to edit"')
|
||||
expect(wrapper.html()).contains('icon="bi bi-x"')
|
||||
expect(wrapper.html()).contains('text="Valider"')
|
||||
})
|
||||
test('should valid the name and emit', async () => {
|
||||
const wrapper = shallowMount(EditText, {
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
components: {
|
||||
Button
|
||||
}
|
||||
}
|
||||
})
|
||||
wrapper.vm.isEditTitle = true
|
||||
wrapper.vm.titleToEdit = 'title to edit'
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const validationButton = wrapper.findComponent('#valid-button')
|
||||
await validationButton.vm.$emit('trigger')
|
||||
|
||||
expect(wrapper.emitted()).toHaveProperty('triggerNewText')
|
||||
expect(wrapper.emitted().triggerNewText[0][0]).toEqual(
|
||||
wrapper.vm.titleToEdit
|
||||
)
|
||||
})
|
||||
test('should close the edit mode', async () => {
|
||||
const wrapper = shallowMount(EditText, {
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
components: {
|
||||
Button
|
||||
}
|
||||
}
|
||||
})
|
||||
wrapper.vm.isEditTitle = true
|
||||
wrapper.vm.titleToEdit = 'title to edit'
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const closeButton = wrapper.findComponent('#close-button')
|
||||
await closeButton.vm.$emit('trigger')
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.vm.isEditTitle).toEqual(false)
|
||||
expect(wrapper.vm.titleToEdit).toEqual(null)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -28,7 +28,7 @@ const i18n = createI18n({
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: []
|
||||
routes: [{ path: '/', component: { template: '<div></div>' } }]
|
||||
})
|
||||
|
||||
describe('Template', () => {
|
||||
|
||||
43
src/tests/unit/components/Input.spec.js
Normal file
43
src/tests/unit/components/Input.spec.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { test, describe, expect } from 'vitest'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import Input from '../../../components/Input.vue'
|
||||
import i18n from '../config'
|
||||
describe('Template', () => {
|
||||
describe('Props', () => {
|
||||
test('should have default props', () => {
|
||||
const wrapper = shallowMount(Input, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
}
|
||||
})
|
||||
expect(wrapper.vm.text).toBe(null)
|
||||
expect(wrapper.vm.placeholder).toBe('')
|
||||
})
|
||||
})
|
||||
describe('When the props are filled', () => {
|
||||
test('should render the button with the placeholder', () => {
|
||||
const wrapper = shallowMount(Input, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
placeholder: 'My placeholder'
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).contains('placeholder="My placeholder"')
|
||||
})
|
||||
test('should emit an input event with the prop value', async () => {
|
||||
const wrapper = shallowMount(Input, {
|
||||
global: {
|
||||
plugins: [i18n]
|
||||
},
|
||||
props: {
|
||||
text: 'My text'
|
||||
}
|
||||
})
|
||||
await wrapper.trigger('input')
|
||||
expect(wrapper.emitted()).toHaveProperty('input')
|
||||
expect(wrapper.emitted().input[0][0]).toEqual(wrapper.vm.text)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -17,7 +17,7 @@ const i18n = createI18n({
|
||||
})
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: []
|
||||
routes: [{ path: '/', component: { template: '<div></div>' } }]
|
||||
})
|
||||
const stubs = {
|
||||
'router-link': {
|
||||
|
||||
@@ -7,7 +7,7 @@ vi.mock('vue-router')
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: []
|
||||
routes: [{ path: '/', component: { template: '<div></div>' } }]
|
||||
})
|
||||
describe('Template', () => {
|
||||
describe('Props', () => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: []
|
||||
routes: [{ path: '/', component: { template: '<div></div>' } }]
|
||||
})
|
||||
describe('Template', () => {
|
||||
it('should render the view with the button link', async () => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import * as createAPictureToASequence from '@/views/utils/upload/request'
|
||||
import * as createASequence from '@/views/utils/upload/request'
|
||||
import { formatDate } from '../../../utils/dates'
|
||||
import * as sortByName from '../../../views/utils/upload/index'
|
||||
|
||||
describe('Template', () => {
|
||||
it('should render the view with the input upload and the good wordings', () => {
|
||||
const wrapper = shallowMount(UploadPicturesView, {
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import Viewer from '@/components/Viewer.vue'
|
||||
const emit = defineEmits<{ (e: 'trigger', value: string): void }>()
|
||||
const viewerRef = ref<unknown>(null)
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -1,20 +1,32 @@
|
||||
<template>
|
||||
<main class="entry-page">
|
||||
<section class="section-viewer">
|
||||
<Viewer
|
||||
v-if="collectionBbox.length"
|
||||
:fetch-options="{
|
||||
fetchOptions: {
|
||||
credentials: 'include'
|
||||
}
|
||||
}"
|
||||
:geovisio-viewer="false"
|
||||
:user-id="getUserId"
|
||||
:bbox="collectionBbox"
|
||||
ref="viewerRef"
|
||||
/>
|
||||
<section :style="{ width: `${mapWidth}px` }" class="section-viewer">
|
||||
<vue-draggable-resizable
|
||||
:style="{ width: `${mapWidth}px` }"
|
||||
:w="mapWidth"
|
||||
:h="windowHeight"
|
||||
:max-width="windowWidth"
|
||||
:handles="['mr']"
|
||||
:draggable="false"
|
||||
class-name-active="resize-active-map"
|
||||
class-name-handle="resize-handle-map"
|
||||
@resizing="onResizeMap"
|
||||
>
|
||||
<Viewer
|
||||
v-if="collectionBbox.length"
|
||||
:fetch-options="{
|
||||
fetchOptions: {
|
||||
credentials: 'include'
|
||||
}
|
||||
}"
|
||||
:geovisio-viewer="false"
|
||||
:user-id="getUserId"
|
||||
:bbox="collectionBbox"
|
||||
ref="viewerRef"
|
||||
/>
|
||||
</vue-draggable-resizable>
|
||||
</section>
|
||||
<section class="section-sequence">
|
||||
<section :style="{ width: `${listWidth}px` }" class="section-sequence">
|
||||
<h1 class="sequences-title">{{ $t('pages.sequences.title') }}</h1>
|
||||
<ul v-if="!isLoading" class="sequence-list">
|
||||
<li class="sequence-item sequence-item-head">
|
||||
@@ -86,16 +98,16 @@
|
||||
<div class="wrapper-thumb">
|
||||
<img
|
||||
v-if="item['stats:items'].count > 0"
|
||||
:src="`${item.href}/thumb.jpg`"
|
||||
loading="lazy"
|
||||
:src="`${item.href}/thumb.jpg`"
|
||||
alt=""
|
||||
class="thumb"
|
||||
/>
|
||||
<div class="wrapper-thumb-hover">
|
||||
<img
|
||||
v-if="item['stats:items'].count > 0"
|
||||
:src="`${item.href}/thumb.jpg`"
|
||||
loading="lazy"
|
||||
:src="`${item.href}/thumb.jpg`"
|
||||
alt=""
|
||||
class="thumb-hover"
|
||||
/>
|
||||
@@ -133,6 +145,25 @@
|
||||
}}</span>
|
||||
</div>
|
||||
</router-link>
|
||||
<div class="wrapper-button">
|
||||
<Button
|
||||
:tooltip="$t('pages.sequence.hide_sequence_tooltip')"
|
||||
look="button--white background-white"
|
||||
:icon="
|
||||
item['geovisio:status'] === 'ready'
|
||||
? 'bi bi-eye-slash'
|
||||
: 'bi bi-eye'
|
||||
"
|
||||
class="disable-button"
|
||||
@trigger="patchCollection(item)"
|
||||
/>
|
||||
<Button
|
||||
:tooltip="$t('pages.sequence.delete_sequence_tooltip')"
|
||||
look="button--red background-white"
|
||||
icon="bi bi-trash"
|
||||
@trigger="deleteCollection(item)"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
<div v-else class="no-sequence">
|
||||
<p class="no-sequence-text">
|
||||
@@ -148,8 +179,8 @@
|
||||
<div v-else class="loader">
|
||||
<Loader look="sm" :is-loaded="false" />
|
||||
</div>
|
||||
<Toast :text="toastText" :look="toastLook" @trigger="toastText = ''" />
|
||||
</section>
|
||||
<Toast :text="toastText" :look="toastLook" @trigger="toastText = ''" />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
@@ -171,18 +202,53 @@ import type {
|
||||
ExtentSequenceLinkInterface
|
||||
} from './interfaces/MySequencesView'
|
||||
import { formatDate } from '@/utils/dates'
|
||||
import {
|
||||
deleteACollection,
|
||||
patchACollection
|
||||
} from '@/views/utils/sequence/request'
|
||||
const { t } = useI18n()
|
||||
const { cookies } = useCookies()
|
||||
const sequenceStore = useSequenceStore()
|
||||
const { toastText, toastLook } = storeToRefs(sequenceStore)
|
||||
|
||||
let userSequences = ref<SequenceLinkInterface[]>([])
|
||||
let collectionBbox = ref<number[]>([])
|
||||
let isSorted = ref<boolean>(false)
|
||||
let isLoading = ref<boolean>(false)
|
||||
let seqId = ref<string>('')
|
||||
let width = ref<number>(0)
|
||||
let mapWidth = ref<number>(window.innerWidth / 3)
|
||||
let listWidth = ref<number>(window.innerWidth / 1.5)
|
||||
const windowWidth = ref<number>(window.innerWidth)
|
||||
const windowHeight = ref<number>(window.innerHeight - 80)
|
||||
const viewerRef = ref<any>(null)
|
||||
|
||||
async function fetchAndFormatSequence(): Promise<void> {
|
||||
const { data } = await axios.get('api/users/me/collection')
|
||||
collectionBbox.value = data.extent.spatial.bbox[0]
|
||||
userSequences.value = getRelChild(data.links)
|
||||
}
|
||||
|
||||
async function patchCollection(sequence: SequenceLinkInterface): Promise<void> {
|
||||
isLoading.value = true
|
||||
let visible
|
||||
if (sequence['geovisio:status'] === 'ready') visible = 'false'
|
||||
else visible = 'true'
|
||||
await patchACollection(sequence.id, visible)
|
||||
await fetchAndFormatSequence()
|
||||
viewerRef.value.viewer.reloadVectorTiles()
|
||||
isLoading.value = false
|
||||
}
|
||||
async function deleteCollection(
|
||||
sequence: SequenceLinkInterface
|
||||
): Promise<void> {
|
||||
isLoading.value = true
|
||||
if (confirm(t('pages.sequence.confirm_sequence_dialog'))) {
|
||||
await deleteACollection(sequence.id)
|
||||
await fetchAndFormatSequence()
|
||||
viewerRef.value.viewer.reloadVectorTiles()
|
||||
}
|
||||
isLoading.value = false
|
||||
}
|
||||
function sequenceStatus(status: string): string {
|
||||
if (status === 'ready') return t('pages.sequences.sequence_published')
|
||||
if (status === 'hidden') return t('pages.sequences.sequence_hidden')
|
||||
@@ -202,6 +268,13 @@ function sortAlpha<TKey extends keyof SequenceLinkInterface>(key: TKey): void {
|
||||
isSorted.value = !isSorted.value
|
||||
userSequences.value = sorted
|
||||
}
|
||||
function onResizeMap(width: any) {
|
||||
if (width) {
|
||||
width.value = width
|
||||
mapWidth.value = width.value.width
|
||||
listWidth.value = window.innerWidth - width.value.width
|
||||
}
|
||||
}
|
||||
function sortNum(type: string, dateToSort?: string): void {
|
||||
let aa, bb: number
|
||||
const sorted = userSequences.value.sort(
|
||||
@@ -235,18 +308,9 @@ const getUserId = computed<string>(() => cookies.get('user_id'))
|
||||
onMounted(async () => {
|
||||
isLoading.value = true
|
||||
try {
|
||||
const { data } = await axios.get('api/users/me/catalog')
|
||||
const collectionData = await axios.get('api/users/me/collection')
|
||||
collectionBbox.value = collectionData.data.extent.spatial.bbox[0]
|
||||
const sequences = getRelChild(data.links)
|
||||
const sequencesCollection = getRelChild(collectionData.data.links)
|
||||
sequencesCollection.map((el: SequenceLinkInterface) => {
|
||||
let index = sequences.findIndex(
|
||||
(elem: SequenceLinkInterface) => elem.id === el.id
|
||||
)
|
||||
sequences[index] = el
|
||||
})
|
||||
userSequences.value = sequencesCollection
|
||||
const { data } = await axios.get('api/users/me/collection')
|
||||
collectionBbox.value = data.extent.spatial.bbox[0]
|
||||
userSequences.value = getRelChild(data.links)
|
||||
isLoading.value = false
|
||||
} catch (err) {
|
||||
isLoading.value = false
|
||||
@@ -266,21 +330,41 @@ watchEffect(() => {
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.resize-handle-map-mr {
|
||||
top: 0;
|
||||
right: toRem(0);
|
||||
cursor: e-resize;
|
||||
background-color: var(--black);
|
||||
display: block !important;
|
||||
}
|
||||
.resize-handle-map {
|
||||
z-index: 999999;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: toRem(0.5);
|
||||
&:hover {
|
||||
cursor: col-resize;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped>
|
||||
.entry-page {
|
||||
display: flex;
|
||||
height: calc(100vh - #{toRem(8)});
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.section-viewer {
|
||||
width: 40%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.section-sequence {
|
||||
overflow-y: auto;
|
||||
width: 60%;
|
||||
overflow-x: hidden;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.sequences-title {
|
||||
@include text(h1);
|
||||
@@ -295,9 +379,11 @@ watchEffect(() => {
|
||||
}
|
||||
.sequence-item {
|
||||
@include text(s-regular);
|
||||
position: relative;
|
||||
border: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: auto;
|
||||
background-color: var(--blue-pale);
|
||||
padding: toRem(2);
|
||||
@@ -312,6 +398,16 @@ watchEffect(() => {
|
||||
background-color: var(--white);
|
||||
}
|
||||
}
|
||||
.wrapper-button {
|
||||
position: absolute;
|
||||
right: toRem(2);
|
||||
bottom: toRem(2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.button--white:first-child {
|
||||
margin-right: toRem(1);
|
||||
}
|
||||
}
|
||||
.button-item-hover {
|
||||
background-color: var(--blue);
|
||||
&:nth-child(2n) {
|
||||
@@ -431,7 +527,6 @@ watchEffect(() => {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
margin-top: toRem(20);
|
||||
}
|
||||
.ay11-link {
|
||||
padding: toRem(2);
|
||||
@@ -457,18 +552,16 @@ watchEffect(() => {
|
||||
display: none;
|
||||
}
|
||||
.section-sequence {
|
||||
width: 100%;
|
||||
width: 100% !important;
|
||||
}
|
||||
.button-item,
|
||||
.sequence-item:first-child {
|
||||
padding-right: toRem(1);
|
||||
padding-left: toRem(1);
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@media (max-width: toRem(50)) {
|
||||
.button-item {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-right: toRem(1);
|
||||
padding-left: toRem(1);
|
||||
& > * {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
@@ -477,12 +570,17 @@ watchEffect(() => {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
.sequence-item:first-child {
|
||||
display: none;
|
||||
}
|
||||
.sequence-item {
|
||||
border-top-right-radius: toRem(1);
|
||||
border-top-left-radius: toRem(1);
|
||||
flex-direction: column;
|
||||
}
|
||||
.wrapper-button {
|
||||
position: relative;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
justify-content: center;
|
||||
margin-top: toRem(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -35,6 +35,14 @@
|
||||
:text="authConf.license.id"
|
||||
class="entry-license"
|
||||
/>
|
||||
<h2 class="subtitle">{{ $t('pages.upload.title_sequence') }}</h2>
|
||||
<p class="import-text">
|
||||
{{ $t('pages.upload.description_title_sequence') }}
|
||||
</p>
|
||||
<EditText
|
||||
:default-text="newSequenceTitle || sequenceTitle"
|
||||
@triggerNewText="setNewSequenceTitle"
|
||||
/>
|
||||
<form>
|
||||
<div class="wrapper-form">
|
||||
<InputUpload
|
||||
@@ -85,11 +93,13 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch, onUnmounted } from 'vue'
|
||||
import { ref, computed, watch, onUnmounted, onMounted } from 'vue'
|
||||
import { onBeforeRouteLeave } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import InputUpload from '@/components/InputUpload.vue'
|
||||
import Button from '@/components/Button.vue'
|
||||
import Link from '@/components/Link.vue'
|
||||
import EditText from '@/components/EditText.vue'
|
||||
import Modal from '@/components/Modal.vue'
|
||||
import InformationCard from '@/components/InformationCard.vue'
|
||||
import UploadLoader from '@/components/upload/UploadLoader.vue'
|
||||
@@ -102,7 +112,6 @@ import {
|
||||
} from '@/views/utils/upload/request'
|
||||
import { sortByName } from '@/views/utils/upload/index'
|
||||
import authConfig from '../composables/auth'
|
||||
import Link from '@/components/Link.vue'
|
||||
|
||||
const { authConf } = authConfig()
|
||||
const { t } = useI18n()
|
||||
@@ -115,6 +124,8 @@ let uploadedSequence = ref<SequenceInterface | null>(null)
|
||||
let picturesUploadingSize = ref<number>(0)
|
||||
let picturesToUploadSize = ref<number>(0)
|
||||
let loadPercentage = ref<string>('0%')
|
||||
let sequenceTitle = ref<string>(formatSequenceTitle())
|
||||
let newSequenceTitle = ref<string | null>(null)
|
||||
let modal = ref()
|
||||
|
||||
watch(isLoading, () => {
|
||||
@@ -128,6 +139,11 @@ watch(isLoading, () => {
|
||||
window.onbeforeunload = null
|
||||
}
|
||||
})
|
||||
onMounted(() => {
|
||||
setInterval(() => {
|
||||
sequenceTitle.value = formatSequenceTitle()
|
||||
}, 1000)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
window.onbeforeunload = null
|
||||
})
|
||||
@@ -145,7 +161,15 @@ const inputIsDisplayed = computed<boolean | null>(
|
||||
isLoaded.value ||
|
||||
(uploadedSequence.value && !uploadedSequence.value.pictures)
|
||||
)
|
||||
|
||||
function setNewSequenceTitle(value: string | null): void {
|
||||
newSequenceTitle.value = value
|
||||
}
|
||||
function formatSequenceTitle(): string {
|
||||
return `${t('pages.upload.sequence_title')}${formatDate(
|
||||
new Date(),
|
||||
'Do MMMM YYYY, hh:mm:ss'
|
||||
)}`
|
||||
}
|
||||
function picturesToUploadSizeText(): void {
|
||||
let fullSize = 0
|
||||
for (let i = 0; i < pictures.value.length; i++) {
|
||||
@@ -183,14 +207,12 @@ async function uploadPicture(): Promise<void> {
|
||||
picturesToUploadSizeText()
|
||||
uploadedSequence.value = null
|
||||
const picturesToUpload = [...pictures.value]
|
||||
|
||||
const sequenceTitle = `${t('pages.upload.sequence_title')}${formatDate(
|
||||
new Date(),
|
||||
'Do MMMM YYYY, hh:mm:ss'
|
||||
)}`
|
||||
const { data } = await createASequence(sequenceTitle)
|
||||
const title = newSequenceTitle.value
|
||||
? newSequenceTitle.value
|
||||
: sequenceTitle.value
|
||||
const { data } = await createASequence(title)
|
||||
uploadedSequence.value = {
|
||||
title: sequenceTitle,
|
||||
title: title,
|
||||
id: data.id,
|
||||
pictures: [],
|
||||
picturesOnError: [],
|
||||
@@ -228,6 +250,8 @@ async function uploadPicture(): Promise<void> {
|
||||
isLoaded.value = true
|
||||
pictures.value = []
|
||||
picturesCount.value = 0
|
||||
newSequenceTitle.value = null
|
||||
sequenceTitle.value = formatSequenceTitle()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -248,7 +272,7 @@ h3 {
|
||||
color: var(--grey-dark);
|
||||
}
|
||||
.logged {
|
||||
height: calc(100vh - #{toRem(8)});
|
||||
min-height: calc(100vh - #{toRem(8)});
|
||||
}
|
||||
.section {
|
||||
width: 100%;
|
||||
|
||||
1
vue-draggable-resizable-vue3.d.ts
vendored
Normal file
1
vue-draggable-resizable-vue3.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
declare module 'vue-draggable-resizable-vue3'
|
||||
Reference in New Issue
Block a user