Merge branch 'Client' into 'main'
besoin 9 besoin 12 besoin 14 finis See merge request pdl-l3/teams/2023/v1/v1d!8
This commit is contained in:
commit
20559a029e
18 files changed
+530
-198
No files matched your search
+2
-2
@@ -3,9 +3,9 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>PDL L3 - Galerie</title>
|
<title>PDL L3 - Projet</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs><style>.cls-1{fill:#e4e6ea;}.cls-2{fill:#ffb85a;}.cls-3{opacity:0.12;}.cls-4{fill:#2b374f;}</style></defs><title>magic-wand-filled</title><path class="cls-1" d="M20.28,491.72h0a25.6,25.6,0,0,1,0-36.2L288.91,186.88l36.21,36.21L56.48,491.72A25.6,25.6,0,0,1,20.28,491.72Z"/><path class="cls-2" d="M445.95,213.51l-59.07-1.28-33.79,48.13a12.81,12.81,0,0,1-22.66-3.46l-18.18-57.15-57.15-18.18a12.81,12.81,0,0,1-3.46-22.66l48.13-33.79-1.28-59.07A12.82,12.82,0,0,1,319,55.55L366.78,91.4,423.1,72.71A12.78,12.78,0,0,1,439.29,88.9L420.6,145.22,456.45,193A12.82,12.82,0,0,1,445.95,213.51Z"/><g class="cls-3"><path d="M445.95,213.51l-59.07-1.28-33.79,48.13a12.81,12.81,0,0,1-22.66-3.46l-18.18-57.15,124-124a12.75,12.75,0,0,1,3.07,13.12L420.6,145.22,456.45,193A12.82,12.82,0,0,1,445.95,213.51Z"/></g><path class="cls-4" d="M434.87,142.92l16.57-50a25.6,25.6,0,0,0-32.35-32.35l-50,16.57L326.66,45.32a25.6,25.6,0,0,0-41,21l1.12,52.2-42.54,29.9a25.6,25.6,0,0,0,7,45.34l9.62,3.06L11.22,446.46a38.46,38.46,0,0,0,0,54.31h0a38.45,38.45,0,0,0,54.31,0L315.15,251.15l3.06,9.62a25.45,25.45,0,0,0,20.51,17.54,26.06,26.06,0,0,0,4,.3,25.43,25.43,0,0,0,20.88-10.89l29.9-42.54,52.2,1.12a25.6,25.6,0,0,0,21-41ZM47.44,482.67a12.8,12.8,0,1,1-18.11-18.1l259-259,1.8.57,15.74,15.74.57,1.79ZM387.18,199.44a12.81,12.81,0,0,0-10.75,5.44L342.61,253,324.41,195.9a12.81,12.81,0,0,0-8.31-8.31L259,169.39l48.12-33.82a12.8,12.8,0,0,0,5.44-10.75l-1.27-59,47.77,35.83a12.79,12.79,0,0,0,11.71,1.91l56.39-18.77,0,.08-18.68,56.36a12.81,12.81,0,0,0,1.91,11.71l35.84,47.78Z"/><circle class="cls-4" cx="486.4" cy="121.6" r="12.8"/><circle class="cls-4" cx="492.8" cy="19.2" r="19.2"/><circle class="cls-4" cx="364.79" cy="12.8" r="12.8"/><circle class="cls-4" cx="422.4" cy="281.61" r="19.2"/><circle class="cls-4" cx="223.99" cy="89.6" r="12.8"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
+140
-53
@@ -1,60 +1,147 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import Home from './components/Home.vue'
|
||||||
|
import Gallery from './components/Gallery.vue';
|
||||||
|
import NotFound from './components/NotFound.vue'
|
||||||
|
import Upload from './components/Upload.vue'
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { api } from '@/http-api'
|
||||||
|
import { ImageType, ImageClass } from './image';
|
||||||
</script>
|
</script>
|
||||||
|
<script lang="ts">
|
||||||
<template>
|
const routes: { [key: string]: string } = {
|
||||||
<div id="app">
|
'/': "Home",
|
||||||
<nav>
|
'Gallery': "Gallery",
|
||||||
<ul>
|
'Upload': "Upload"
|
||||||
<li>
|
|
||||||
<router-link to="/">Home</router-link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<router-link to="/gallery">Gallery</router-link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<router-link to="/upload">Upload</router-link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h1>PDL - L3</h1>
|
|
||||||
<router-view />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
#app {
|
|
||||||
text-align: center;
|
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
|
||||||
color: #2c3e50;
|
|
||||||
}
|
}
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
Home,
|
||||||
|
Gallery,
|
||||||
|
NotFound,
|
||||||
|
Upload
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
images: new Map<Number, ImageType>(),
|
||||||
|
currentPath: window.location.hash,
|
||||||
|
sent: false,
|
||||||
|
file: null as FileList | null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentView(): string {
|
||||||
|
return routes[this.currentPath.slice(1) || '/'] || "notFound";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async updateImageList(sleep: number) {
|
||||||
|
setTimeout(() => {
|
||||||
|
api.getImageList().then((newImages) => {
|
||||||
|
const newImageList = new Map<number, ImageType>();
|
||||||
|
for (const image of newImages.values())
|
||||||
|
newImageList.set(image.id, image);
|
||||||
|
this.images = newImageList;
|
||||||
|
})
|
||||||
|
}, sleep);
|
||||||
|
},
|
||||||
|
deleteFile(id: number) {
|
||||||
|
api.deleteImage(id).then(() => {
|
||||||
|
this.updateImageList(100);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
isActive(route: string) {
|
||||||
|
if (route === undefined) {
|
||||||
|
if (this.currentPath === "")
|
||||||
|
return 'router active';
|
||||||
|
route = "/";
|
||||||
|
}
|
||||||
|
return this.currentPath.endsWith(route) ? 'router active' : 'router';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.updateImageList(0);
|
||||||
|
window.addEventListener('hashchange', () => {
|
||||||
|
this.currentPath = window.location.hash;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<header>
|
||||||
|
<div class="navi">
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li v-for="route in routes" :class="isActive(routes[route])">
|
||||||
|
<a :href="'#' + (routes[route] || '/')"> <span>{{ route }}</span></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<component :is="currentView" :key="sent" :images="images" @delete="deleteFile"
|
||||||
|
@updateImageList="updateImageList(100)" />
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.navi {
|
||||||
|
/* reduce size of children */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
padding-top: 30px;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul>li+li {
|
||||||
|
margin-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul>li {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 5px 15px;
|
||||||
|
border-radius: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
background-color: #09c7aa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active>a {
|
||||||
|
color: rgb(205, 201, 194);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: rgb(172, 165, 154);
|
||||||
|
text-decoration-color: initial;
|
||||||
|
font-family: "Aeonik", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding-left: 20px;
|
||||||
|
height: 60px;
|
||||||
|
padding-right: 20px;
|
||||||
|
border-radius: 60px;
|
||||||
|
background-color: rgb(24, 26, 27);
|
||||||
|
margin-right: 20px;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
list-style-type: none;
|
display: flex;
|
||||||
margin: 0;
|
list-style: none;
|
||||||
padding: 0;
|
padding-inline-start: 0;
|
||||||
overflow: hidden;
|
|
||||||
background-color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
li a {
|
|
||||||
display: block;
|
|
||||||
color: white;
|
|
||||||
text-align: center;
|
|
||||||
padding: 14px 16px;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
color: red;
|
|
||||||
font-weight: bold;
|
|
||||||
list-style-type: none;
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.7 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 496 B |
@@ -1,25 +1,55 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
import { api } from '@/http-api';
|
|
||||||
import { ImageType } from '@/image'
|
import { ImageType } from '@/image'
|
||||||
import Image from './Image.vue';
|
import Image from '@/components/Image.vue'
|
||||||
|
</script>
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
const imageList = ref<ImageType[]>([]);
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
api.getImageList()
|
images: {
|
||||||
.then((data) => {
|
type: Map<number, ImageType>,
|
||||||
imageList.value = data;
|
required: true
|
||||||
})
|
}
|
||||||
.catch(e => {
|
}
|
||||||
console.log(e.message);
|
})
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="flex" v-if="images.keys !== null">
|
||||||
<h3>Gallery</h3>
|
<div v-for="[id, image] in images" :key="id" class="image">
|
||||||
<Image v-for="image in imageList" :id="image.id" />
|
<label>{{ image.name }}</label>
|
||||||
|
<Image class="gallery" :id="id" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<h1 v-else>No image found</h1>
|
||||||
</template>
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
<style scoped></style>
|
.image {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: 1.5em;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style>
|
||||||
|
.gallery>img {
|
||||||
|
margin: 5px;
|
||||||
|
max-width: max(33vw, calc(90vw/v-bind('images.size')));
|
||||||
|
max-height: max(33vh, calc(90vh/v-bind('images.size')));
|
||||||
|
margin: 2vw;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,35 +1,81 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import Image from '@/components/Image.vue'
|
||||||
import router from "@/router";
|
import { defineComponent } from 'vue'
|
||||||
import { api } from '@/http-api';
|
|
||||||
import { ImageType } from '@/image'
|
import { ImageType } from '@/image'
|
||||||
|
|
||||||
const selectedId = ref(-1);
|
|
||||||
const imageList = ref<ImageType[]>([]);
|
|
||||||
getImageList();
|
|
||||||
|
|
||||||
function getImageList() {
|
|
||||||
api.getImageList().then((data) => {
|
|
||||||
imageList.value = data;
|
|
||||||
}).catch(e => {
|
|
||||||
console.log(e.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function showImage() {
|
|
||||||
router.push({ name: 'image', params: { id: selectedId.value } })
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
<script lang="ts">
|
||||||
|
export default defineComponent({
|
||||||
|
emits: {
|
||||||
|
delete(id: number) {
|
||||||
|
return id >= 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
images: {
|
||||||
|
type: Map<number, ImageType>,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
imageSelectedId: 0 as number,
|
||||||
|
sure: false as boolean
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.imageSelectedId = this.images.keys().next().value;
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
images() {
|
||||||
|
this.imageSelectedId = this.images.keys().next().value;
|
||||||
|
},
|
||||||
|
imageSelectedId() {
|
||||||
|
this.sure = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
confirmDelete() {
|
||||||
|
if (this.sure)
|
||||||
|
this.$emit('delete', this.imageSelectedId);
|
||||||
|
else
|
||||||
|
this.sure = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="flex" v-if="images.keys != null">
|
||||||
<h3>Choose an image</h3>
|
|
||||||
<div>
|
<div>
|
||||||
<select v-model="selectedId" @change="showImage">
|
<label>Selectioner une image: </label>
|
||||||
<option v-for="image in imageList" :value="image.id" :key="image.id">{{ image.name }}</option>
|
<select v-model="imageSelectedId" style="width: min-content;margin: 15px;">
|
||||||
|
<option v-for="[id, image] in images" :key="id" :value="id">{{ image.name }}</option>
|
||||||
</select>
|
</select>
|
||||||
|
<button @mouseleave="sure = false" @click="confirmDelete()" :class="sure ? 'confirm' : ''">
|
||||||
|
{{ sure ? "Confirmer" : "Supprimer" }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
<Image class="home" v-if="imageSelectedId !== undefined" :id="imageSelectedId"></Image>
|
||||||
</div>
|
</div>
|
||||||
|
<h1 v-else>Aucune image trouvée</h1>
|
||||||
</template>
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
<style scoped></style>
|
.confirm {
|
||||||
|
background-color: red;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style>
|
||||||
|
.home>img {
|
||||||
|
max-width: 60%;
|
||||||
|
max-height: 60vh;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,31 +1,49 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineProps } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { api } from '@/http-api';
|
import { api } from '@/http-api';
|
||||||
|
|
||||||
const props = defineProps<{ id: number }>()
|
|
||||||
|
|
||||||
api.getImage(props.id)
|
|
||||||
.then((data: Blob) => {
|
|
||||||
const reader = new window.FileReader();
|
|
||||||
reader.readAsDataURL(data);
|
|
||||||
reader.onload = () => {
|
|
||||||
const galleryElt = document.getElementById("gallery-" + props.id);
|
|
||||||
if (galleryElt !== null) {
|
|
||||||
const imgElt = document.createElement("img");
|
|
||||||
galleryElt.appendChild(imgElt);
|
|
||||||
if (imgElt !== null && reader.result as string) {
|
|
||||||
imgElt.setAttribute("src", (reader.result as string));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
console.log(e.message);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
<script lang="ts">
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
id: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async getImage(id: number) {
|
||||||
|
const data = await api.getImage(id);
|
||||||
|
const reader = new window.FileReader();
|
||||||
|
reader.readAsDataURL(data);
|
||||||
|
reader.onload = () => {
|
||||||
|
const imgElt = document.getElementById("img-" + id) as HTMLImageElement;
|
||||||
|
if (imgElt !== null) {
|
||||||
|
if (reader.result as string) {
|
||||||
|
imgElt.setAttribute("src", (reader.result as string));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getImage(this.id);
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
id() {
|
||||||
|
this.getImage(this.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<figure :id="'gallery-' + id"></figure>
|
<a :href="'./images/' + id" target="_blank">
|
||||||
|
<img :id="'img-' + id" />
|
||||||
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style></style>
|
<style scoped>
|
||||||
|
img:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
transition: transform 0.5s;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>404 NOT FOUND</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,40 +1,137 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
import { api } from '@/http-api';
|
import { api } from '@/http-api';
|
||||||
|
import { ImageType, ImageClass } from '@/image';
|
||||||
|
|
||||||
const target = ref<HTMLInputElement>();
|
|
||||||
|
|
||||||
function submitFile() {
|
|
||||||
if (target.value !== null && target.value !== undefined && target.value.files !== null) {
|
|
||||||
const file = target.value.files[0];
|
|
||||||
if (file === undefined)
|
|
||||||
return;
|
|
||||||
let formData = new FormData();
|
|
||||||
formData.append("file", file);
|
|
||||||
api.createImage(formData).then(() => {
|
|
||||||
if (target.value !== undefined)
|
|
||||||
target.value.value = '';
|
|
||||||
}).catch(e => {
|
|
||||||
console.log(e.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleFileUpload(event: Event) {
|
|
||||||
target.value = (event.target as HTMLInputElement);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
interface InputFileEvent extends Event {
|
||||||
|
target: HTMLInputElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
emits: ['updateImageList', 'delete'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
sent: false,
|
||||||
|
file: null as FileList | null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
images: {
|
||||||
|
type: Map<number, ImageType>,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async submitImage() {
|
||||||
|
if (this.sent === true || this.file === null)
|
||||||
|
return;
|
||||||
|
Array.from(this.file).forEach((file, id) => {
|
||||||
|
|
||||||
|
let formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
api.createImage(formData);
|
||||||
|
});
|
||||||
|
this.sent = true;
|
||||||
|
this.$emit('updateImageList');
|
||||||
|
this.resetFile();
|
||||||
|
},
|
||||||
|
handleFilesUpload(event: InputFileEvent) {
|
||||||
|
this.file = event.target.files;
|
||||||
|
this.sent = false;
|
||||||
|
},
|
||||||
|
resetFile() {
|
||||||
|
this.file = null;
|
||||||
|
(document.getElementById('fileUpload') as HTMLInputElement).value = "";
|
||||||
|
},
|
||||||
|
titleSendButton() {
|
||||||
|
if (this.sent === true)
|
||||||
|
return "Envoyé";
|
||||||
|
if (this.file === null)
|
||||||
|
return "Aucune image sélectionnée";
|
||||||
|
return "Envoyer (" + this.file.length + " image(s) sélectionnée(s))";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
file() {
|
||||||
|
if (this.file === null)
|
||||||
|
return;
|
||||||
|
Array.from(this.file).forEach((file, id) => {
|
||||||
|
const reader = new window.FileReader();
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
reader.onload = () => {
|
||||||
|
const imgElt = document.getElementById("preview-" + id) as HTMLImageElement;
|
||||||
|
if (imgElt !== null) {
|
||||||
|
if (reader.result as string) {
|
||||||
|
imgElt.setAttribute("src", (reader.result as string));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h3>Upload an image</h3>
|
<label>Ajouter une ou plusieurs image(s):
|
||||||
<div>
|
<input id="fileUpload" type="file" @change="handleFilesUpload($event as InputFileEvent)" accept="image/jpeg"
|
||||||
<input type="file" id="file" ref="file" @change="handleFileUpload" />
|
multiple />
|
||||||
</div>
|
</label>
|
||||||
<div>
|
<button :class="sent ? 'green' : 'normal'" @click="submitImage()">{{ titleSendButton() }}</button>
|
||||||
<button @click="submitFile">Submit</button>
|
<button v-if="file !== null" @click="resetFile()">Reinitialiser</button>
|
||||||
</div>
|
</div>
|
||||||
|
<h2 v-if="file !== null">Preview :</h2>
|
||||||
|
<div class="previewBox">
|
||||||
|
<label v-for="(f, id) in (file as FileList)" :key="id" class="imageLabel">{{ (f as File).name }}:
|
||||||
|
<img :id="'preview-' + id" />
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 90%;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
<style scoped></style>
|
label {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.imageLabel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
max-width: 33%;
|
||||||
|
height: 33vh;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.previewBox {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green {
|
||||||
|
color: green;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Vendored
-8
@@ -1,8 +0,0 @@
|
|||||||
/// <reference types="vite/client" />
|
|
||||||
|
|
||||||
declare module '*.vue' {
|
|
||||||
import { DefineComponent } from 'vue'
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
|
||||||
const component: DefineComponent<{}, {}, any>
|
|
||||||
export default component
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,13 @@
|
|||||||
export interface ImageType {
|
export interface ImageType {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ImageClass implements ImageType {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
constructor(id: number, title: string) {
|
||||||
|
this.id = id
|
||||||
|
this.name = title
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
|
import './style.css'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router';
|
|
||||||
|
|
||||||
createApp(App).use(router).mount('#app')
|
createApp(App).mount('#app')
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import { createWebHistory, createRouter } from "vue-router";
|
|
||||||
import { RouteRecordRaw } from "vue-router";
|
|
||||||
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
|
||||||
{
|
|
||||||
path: "/",
|
|
||||||
name: "home",
|
|
||||||
component: () => import("./components/Home.vue"),
|
|
||||||
props: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/gallery",
|
|
||||||
name: "gallery",
|
|
||||||
component: () => import("./components/Gallery.vue"),
|
|
||||||
props: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/image/:id",
|
|
||||||
name: "image",
|
|
||||||
component: () => import("./components/Image.vue"),
|
|
||||||
props: ({ params }) => ({ id: Number(params.id) || 0 })
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/upload",
|
|
||||||
name: "upload",
|
|
||||||
component: () => import("./components/Upload.vue"),
|
|
||||||
props: true
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const router = createRouter({
|
|
||||||
history: createWebHistory(),
|
|
||||||
routes,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
:root {
|
||||||
|
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
color-scheme: light dark;
|
||||||
|
color: rgba(255, 255, 255, 0.87);
|
||||||
|
background-color: #242424;
|
||||||
|
|
||||||
|
font-synthesis: none;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
min-width: 320px;
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #242424;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 3.2em;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
padding: 0.6em 1.2em;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: inherit;
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
border-color: #646cff;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus,
|
||||||
|
button:focus-visible {
|
||||||
|
outline: 4px auto -webkit-focus-ring-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
:root {
|
||||||
|
color: #213547;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
@@ -24,5 +24,5 @@
|
|||||||
"src/**/*.d.ts",
|
"src/**/*.d.ts",
|
||||||
"src/**/*.tsx",
|
"src/**/*.tsx",
|
||||||
"src/**/*.vue"
|
"src/**/*.vue"
|
||||||
],
|
]
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user