editor
This commit is contained in:
1 parent
173d499bd4
commit
81db55d20f
9 files changed
+267
-78
No files matched your search
@@ -54,9 +54,7 @@ export default defineComponent({
|
|||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="content">
|
<RouterView :images="images" @delete="deleteFile" @updateImageList="updateImageList()" />
|
||||||
<RouterView :images="images" @delete="deleteFile" @updateImageList="updateImageList()" />
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.navi {
|
.navi {
|
||||||
@@ -80,10 +78,6 @@ ul>li+li {
|
|||||||
margin-left: 30px;
|
margin-left: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
|
||||||
margin-top: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul>li {
|
ul>li {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.flex {
|
.flex {
|
||||||
|
margin-top: 100px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Image from '@/components/Image.vue'
|
import Image from '@/components/Image.vue'
|
||||||
|
import HomeSelect from '@/components/HomeSelect.vue'
|
||||||
|
import HomeEditor from '@/components/HomeEditor.vue'
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
import { ImageType } from '@/image'
|
import { ImageClass, ImageType } from '@/image'
|
||||||
|
import { Filter } from '@/filter'
|
||||||
</script>
|
</script>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@@ -19,10 +22,9 @@ export default defineComponent({
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
imageSelectedId: -1,
|
imageSelectedId: -1,
|
||||||
sure: false,
|
|
||||||
timer: null as ReturnType<typeof setTimeout> | null,
|
timer: null as ReturnType<typeof setTimeout> | null,
|
||||||
open: false,
|
editor: false,
|
||||||
editor: false
|
filter: undefined as Filter | undefined,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@@ -36,23 +38,16 @@ export default defineComponent({
|
|||||||
images() {
|
images() {
|
||||||
this.updateImageSelectedId();
|
this.updateImageSelectedId();
|
||||||
},
|
},
|
||||||
imageSelectedId() {
|
|
||||||
this.sure = false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateImageSelectedId() {
|
updateImageSelectedId(id: number = -1) {
|
||||||
if (this.images.size === 0)
|
if (this.images.size === 0)
|
||||||
this.imageSelectedId = -1;
|
this.imageSelectedId = -1;
|
||||||
|
else if (id !== -1 && this.images.has(id))
|
||||||
|
this.imageSelectedId = id;
|
||||||
else
|
else
|
||||||
this.imageSelectedId = this.images.keys().next().value;
|
this.imageSelectedId = this.images.keys().next().value;
|
||||||
},
|
},
|
||||||
confirmDelete() {
|
|
||||||
if (this.sure)
|
|
||||||
this.$emit('delete', this.imageSelectedId);
|
|
||||||
else
|
|
||||||
this.sure = true;
|
|
||||||
},
|
|
||||||
startTimer() {
|
startTimer() {
|
||||||
if (this.timer !== null)
|
if (this.timer !== null)
|
||||||
clearTimeout(this.timer);
|
clearTimeout(this.timer);
|
||||||
@@ -71,39 +66,29 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="flex" v-if="images.size > 0">
|
<div class="flex" v-if="images.size > 0">
|
||||||
|
<h3 v-if="!editor">Restez sur l'image pour passer en mode éditeur</h3>
|
||||||
|
<h3 v-else>Restez sur l'image pour passer en mode selection</h3>
|
||||||
|
<a class="button" :href="images.get(imageSelectedId)?.data"
|
||||||
|
:download="images.get(imageSelectedId)?.name">Télécharger {{ images.get(imageSelectedId)?.name }}</a>
|
||||||
<Image @mouseleave="clearTimer()" @mouseenter="startTimer()" class="home"
|
<Image @mouseleave="clearTimer()" @mouseenter="startTimer()" class="home"
|
||||||
v-if="imageSelectedId !== undefined && imageSelectedId !== -1" :id="imageSelectedId" :images="images">
|
v-if="imageSelectedId !== undefined && imageSelectedId !== -1" :id="imageSelectedId" :images="images"
|
||||||
|
:filter="filter">
|
||||||
</Image>
|
</Image>
|
||||||
<div v-if="!editor" class="selector">
|
<HomeSelect v-if="!editor" :images="images" :image-selected-id="imageSelectedId" @delete="id => $emit('delete', id)"
|
||||||
<label>Selectioner une image: </label>
|
@updateImageSelectedId="id => updateImageSelectedId(id)"></HomeSelect>
|
||||||
<div class="custom-select" :tabindex="imageSelectedId" @blur="open = false">
|
<HomeEditor v-else @apply-filter="(newFilter) => filter = newFilter"></HomeEditor>
|
||||||
<div class="selected" :class="{ open: open }" @click="open = !open">
|
|
||||||
<span>{{ images.get(imageSelectedId)?.name }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="items" :class="{ selectHide: !open }">
|
|
||||||
<div v-for="[id, image] of images" :key="id" @click="imageSelectedId = image.id; open = false;">
|
|
||||||
<span>{{ image.name }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button class="button" @mouseleave="sure = false" @click="confirmDelete()" :class="{ confirm: sure }">
|
|
||||||
{{ sure ? "Confirmer" : "Supprimer" }}</button>
|
|
||||||
<a class="button" :href="images.get(imageSelectedId)?.data"
|
|
||||||
:download="images.get(imageSelectedId)?.name">Télécharger</a>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<h1 v-else>Aucune image trouvée</h1>
|
<h1 v-else>Aucune image trouvée</h1>
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@import url(@/customSelect.css);
|
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
@@ -112,35 +97,12 @@ html {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selector {
|
|
||||||
margin-bottom: 5%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-content: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector label {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex {
|
.flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm {
|
|
||||||
background-color: red;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
<style>
|
<style>
|
||||||
.home.imageContainer {
|
.home.imageContainer {
|
||||||
@@ -148,7 +110,7 @@ label {
|
|||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
max-height: 60vh;
|
max-height: 60vh;
|
||||||
margin: 0 0 2vw 0;
|
margin: 2vw 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.home.imageContainer:hover {
|
div.home.imageContainer:hover {
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { Filter, Arg } from '@/filter'
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<script lang="ts">
|
||||||
|
export default defineComponent({
|
||||||
|
emits: {
|
||||||
|
applyFilter(filter: Filter | undefined) {
|
||||||
|
return filter !== null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
filterSelectId: 0,
|
||||||
|
open: false,
|
||||||
|
filters: [
|
||||||
|
new Filter("Luminosité", "changeLuminosity", [new Arg("delta", -255, 255)]),
|
||||||
|
new Filter("Egalisation d'histogramme", "histogram", []),
|
||||||
|
new Filter("Filtre de couleur", "colorFilter", [new Arg("hue", 0, 359)]),
|
||||||
|
new Filter("Filter moyenneur", "meanFilter", [new Arg("size", 1, 100, 2)]),
|
||||||
|
new Filter("Filtre gaussien", "gaussianFilter", [new Arg("size", 1, 100, 2)]),
|
||||||
|
new Filter("Filtre de contours", "contours", []),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
applyFilter() {
|
||||||
|
if (this.filterSelectId === 0) {
|
||||||
|
this.$emit("applyFilter", undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const filter = this.filters[this.filterSelectId - 1];
|
||||||
|
filter.updated = true;
|
||||||
|
this.$emit("applyFilter", filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="editor">
|
||||||
|
<label>Selectioner un filtre: </label>
|
||||||
|
<div class="custom-select" @blur="open = false">
|
||||||
|
<div class="selected" :class="{ open: open }" @click="open = !open">
|
||||||
|
<span>{{ filterSelectId === 0 ? "Aucun filtre" : filters[filterSelectId - 1].name }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="items" :class="{ selectHide: !open }">
|
||||||
|
<div @click="open = false; filterSelectId = 0">
|
||||||
|
<span>Aucun filtre</span>
|
||||||
|
</div>
|
||||||
|
<div v-for="(filter, index) in filters" @click="open = false; filterSelectId = index + 1">
|
||||||
|
<span>{{ filter.name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="filterSelectId !== 0">
|
||||||
|
<div v-for="argument in filters[filterSelectId - 1].args">
|
||||||
|
<label>{{ argument.name }}: ({{ argument.value }}) </label>
|
||||||
|
<input type="range" :id="filters[filterSelectId - 1].name + '-' + argument.name" :min="argument.min"
|
||||||
|
:max="argument.max" :ref="filters[filterSelectId - 1].name + '-' + argument.name" :step="argument.step"
|
||||||
|
v-model.number="argument.value">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="button" @click="applyFilter()">Appliquer le filter</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@import url("@/customSelect.css");
|
||||||
|
|
||||||
|
label {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { ImageType } from '@/image'
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
emits: {
|
||||||
|
delete(id: number) {
|
||||||
|
return id >= 0;
|
||||||
|
},
|
||||||
|
updateImageSelectedId(id: number) {
|
||||||
|
return id >= 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
images: {
|
||||||
|
type: Map<number, ImageType>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
imageSelectedId: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
sure: false,
|
||||||
|
open: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
imageSelectedId() {
|
||||||
|
this.sure = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
confirmDelete() {
|
||||||
|
if (this.sure)
|
||||||
|
this.$emit('delete', this.imageSelectedId);
|
||||||
|
else
|
||||||
|
this.sure = true;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="selector">
|
||||||
|
<label>Selectioner une image: </label>
|
||||||
|
<div class="custom-select" @blur="open = false">
|
||||||
|
<div class="selected" :class="{ open: open }" @click="open = !open">
|
||||||
|
<span>{{ images.get(imageSelectedId)?.name }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="items" :class="{ selectHide: !open }">
|
||||||
|
<div v-for="[id, image] of images" :key="id"
|
||||||
|
@click="$emit('updateImageSelectedId', image.id); open = false;">
|
||||||
|
<span>{{ image.name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="button" @mouseleave="sure = false" @click="confirmDelete()" :class="{ confirm: sure }">
|
||||||
|
{{ sure ? "Confirmer" : "Supprimer" }}</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@import url(@/customSelect.css);
|
||||||
|
|
||||||
|
.selector {
|
||||||
|
margin-bottom: 5%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm {
|
||||||
|
background-color: red;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { api } from '@/http-api';
|
import { api } from '@/http-api';
|
||||||
import { ImageType } from '@/image';
|
import { ImageType } from '@/image';
|
||||||
|
import { Filter } from '@/filter';
|
||||||
</script>
|
</script>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@@ -16,23 +17,33 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
redirect: {
|
redirect: {
|
||||||
type: String
|
type: String
|
||||||
|
},
|
||||||
|
filter: {
|
||||||
|
type: Filter
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
data: "" as string
|
data: "" as string,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getImage(id: number) {
|
getImage(id: number, force: boolean = false) {
|
||||||
if (this.images.has(id) && this.images.get(id)!.data != null) {
|
if (!force && this.images.has(id) && this.images.get(id)!.data != null) {
|
||||||
this.data = this.images.get(id)!.data;
|
this.data = this.images.get(id)!.data;
|
||||||
} else {
|
} else {
|
||||||
this.downloadImage(id);
|
this.downloadImage(id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async downloadImage(id: number) {
|
async downloadImage(id: number) {
|
||||||
const data = await api.getImage(id);
|
let f = this.filter;
|
||||||
|
if (this.filter !== undefined) {
|
||||||
|
if (this.filter.updated)
|
||||||
|
this.filter.updated = false;
|
||||||
|
else
|
||||||
|
f = undefined;
|
||||||
|
}
|
||||||
|
const data = await api.getImage(id, f);
|
||||||
const reader = new window.FileReader();
|
const reader = new window.FileReader();
|
||||||
reader.readAsDataURL(data);
|
reader.readAsDataURL(data);
|
||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
@@ -46,7 +57,13 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
id() {
|
id() {
|
||||||
this.getImage(this.id);
|
this.getImage(this.id)
|
||||||
|
},
|
||||||
|
filter: {
|
||||||
|
handler() {
|
||||||
|
this.getImage(this.id, true);
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
export class Arg {
|
||||||
|
name: string
|
||||||
|
min: number
|
||||||
|
max: number
|
||||||
|
step: number
|
||||||
|
value: number | undefined
|
||||||
|
|
||||||
|
constructor(name: string, min: number, max: number, step: number = 1) {
|
||||||
|
this.name = name
|
||||||
|
this.min = min
|
||||||
|
this.max = max
|
||||||
|
this.value = undefined
|
||||||
|
this.step = step
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Filter {
|
||||||
|
name: string
|
||||||
|
path: string
|
||||||
|
args: Arg[]
|
||||||
|
updated: boolean = false
|
||||||
|
|
||||||
|
constructor(name: string, path: string, args: Arg[]) {
|
||||||
|
this.name = name
|
||||||
|
this.path = path
|
||||||
|
this.args = args
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getArgs
|
||||||
|
*/
|
||||||
|
public getArgs(): string {
|
||||||
|
return '&' + this.args.map((arg) => arg.name + "=" + arg.value).join("&");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import axios, { AxiosResponse, AxiosError, HttpStatusCode } from 'axios';
|
import axios, { AxiosResponse, AxiosError, HttpStatusCode } from 'axios';
|
||||||
import { ImageType } from '@/image';
|
import { ImageType } from '@/image';
|
||||||
|
import { Filter } from './filter';
|
||||||
|
|
||||||
const instance = axios.create({
|
const instance = axios.create({
|
||||||
baseURL: "/",
|
baseURL: "/",
|
||||||
@@ -18,7 +19,7 @@ const requests = {
|
|||||||
|
|
||||||
export const api = {
|
export const api = {
|
||||||
getImageList: (): Promise<ImageType[]> => requests.get('images', {}),
|
getImageList: (): Promise<ImageType[]> => requests.get('images', {}),
|
||||||
getImage: (id: number): Promise<Blob> => requests.get(`images/${id}`, { responseType: "blob" }),
|
getImage: (id: number, filter: Filter | undefined): Promise<Blob> => requests.get(`images/${id}${filter !== undefined ? "?algorithm=" + filter.path + filter.getArgs() : ""}`, { responseType: "blob" }),
|
||||||
createImage: (form: FormData): Promise<HttpStatusCode> => requests.post('images', form),
|
createImage: (form: FormData): Promise<HttpStatusCode> => requests.post('images', form),
|
||||||
deleteImage: (id: number): Promise<void> => requests.delete(`images/${id}`),
|
deleteImage: (id: number): Promise<void> => requests.delete(`images/${id}`),
|
||||||
};
|
};
|
||||||
@@ -2,18 +2,15 @@ export interface ImageType {
|
|||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
data: string;
|
data: string;
|
||||||
modifed: Map<string, string>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ImageClass implements ImageType {
|
export class ImageClass implements ImageType {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
data: string;
|
data: string;
|
||||||
modifed: Map<string, string>;
|
constructor(id: number, title: string, data: string = "") {
|
||||||
constructor(id: number, title: string, data: string = "", modifed: Map<string, string> = new Map<string, string>()) {
|
|
||||||
this.id = id
|
this.id = id
|
||||||
this.name = title
|
this.name = title
|
||||||
this.data = data
|
this.data = data
|
||||||
this.modifed = modifed
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user