feat: allow to add and remove effects

This commit is contained in:
Simon Vieille 2025-09-05 15:13:50 +02:00
commit 6b56e11119
Signed by: deblan
GPG key ID: 579388D585F70417
4 changed files with 74 additions and 20 deletions

View file

@ -2,4 +2,4 @@
Application for manipulating images (crop, resize, adujst brightness, ...).
![](./docs/screenshot_20250905.png)
![](./docs/app.png)

BIN
docs/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

View file

@ -10,6 +10,8 @@ const type = ref(null)
const result = ref(null)
const effects = ref([])
const resultLoading = ref(false)
const availableEffects = ref([])
const effectToAdd = ref(null)
const makeSetting = (label, value, min, max, step) => {
return {label, value, min, max, step}
@ -25,7 +27,7 @@ const openImageFile = () => {
source.value = info
effects.value = [
availableEffects.value = [
{
label: "Crop",
name: "crop",
@ -154,6 +156,29 @@ const applyEffects = () => {
})
}
function makeId() {
let result = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
for ( var i = 0; i < 20; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
const addEffect = () => {
if (!effectToAdd.value) {
return
}
let effect = JSON.parse(JSON.stringify(availableEffects.value.filter((e) => e.name === effectToAdd.value)[0]))
effect.id = makeId()
effects.value.push(effect)
}
const makeRenderStyle = () => {
return {
'align-items': source.value !== null ? 'top' : 'center'
@ -164,6 +189,10 @@ const makeImageSrc = (data) => {
return `data:image/${data.type};base64, ${data.base64}`
}
const deleteEffect = (index) => {
effects.value.splice(index, 1)
}
onMounted(openImageFile)
</script>
@ -176,20 +205,25 @@ onMounted(openImageFile)
:options="{handle: '.effect-move', animation: 400}"
v-model="effects"
@end="sortEnd"
item-key="name"
item-key="id"
tag="div"
>
<template #item="{element, index}">
<div class="effect" :key="index">
<div class="effect-move">
<div class="effect">
<div class="effect-actions">
<div class="effect-move">
</div>
<div class="effect-delete" @click="deleteEffect(index)">
🗑
</div>
</div>
<div class="effect-name" @click="element.enabled = !element.enabled">
<VueToggles :value="element.enabled" :width="40" />
{{ element.label }}
</div>
<div class="effect-settings" v-if="element.enabled">
<div v-for="setting in element.settings" :key="setting.name" class="effect-setting">
<div v-for="setting, key in element.settings" :key="element.id + key" class="effect-setting">
<input
type="range"
:min="setting.min"
@ -210,7 +244,20 @@ onMounted(openImageFile)
</template>
</Sortable>
<div class="text-center">
<button @click="applyEffects" :disabled="resultLoading">
<div>
<select v-model="effectToAdd">
<option
v-for="effect in availableEffects"
:key="effect.name"
:value="effect.name">
{{ effect.label }}
</option>
</select>
<button @click="addEffect">
Add
</button>
</div>
<button @click="applyEffects" :disabled="resultLoading" v-if="effects.length > 0">
<template v-if="resultLoading">
Loading...
</template>
@ -315,23 +362,30 @@ onMounted(openImageFile)
text-align: left;
border-bottom: 1px solid #999;
&-move {
cursor: pointer;
&-actions {
float: right;
display: inline-block;
background: #ffffff33;
padding: 3px;
width: 20px;
height: 20px;
text-align: center;
line-height: 20px;
border-radius: 2px;
cursor: pointer;
&:hover {
background: #ffffff99;
> div {
display: inline-block;
padding: 3px;
width: 20px;
height: 20px;
text-align: center;
line-height: 20px;
border-radius: 2px;
margin-left: 8px;
&:hover {
background: #ffffff99;
}
}
}
&-move {
background: #ffffff33;
}
&-name {
display: flex;
gap: 10px;