forked from verdnatura/salix-front
364 lines
11 KiB
Vue
364 lines
11 KiB
Vue
<script setup>
|
|
import { reactive, computed, ref } from 'vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
|
import FetchData from 'components/FetchData.vue';
|
|
import VnRow from 'components/ui/VnRow.vue';
|
|
import VnInput from 'src/components/common/VnInput.vue';
|
|
|
|
import Croppie from 'croppie/croppie';
|
|
import 'croppie/croppie.css';
|
|
import useNotify from 'src/composables/useNotify.js';
|
|
import axios from 'axios';
|
|
|
|
const emit = defineEmits(['closeForm', 'onPhotoUploaded']);
|
|
|
|
const props = defineProps({
|
|
id: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
collection: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
});
|
|
|
|
const { t } = useI18n();
|
|
const { notify } = useNotify();
|
|
|
|
const uploadMethodsOptions = [
|
|
{ label: t('Select from computer'), value: 'computer' },
|
|
{ label: t('Import from external URL'), value: 'URL' },
|
|
];
|
|
|
|
const viewportTypes = [
|
|
{
|
|
code: 'normal',
|
|
description: t('Normal'),
|
|
viewport: {
|
|
width: 400,
|
|
height: 400,
|
|
},
|
|
output: {
|
|
width: 1200,
|
|
height: 1200,
|
|
},
|
|
},
|
|
{
|
|
code: 'panoramic',
|
|
description: t('Panoramic'),
|
|
viewport: {
|
|
width: 675,
|
|
height: 450,
|
|
},
|
|
output: {
|
|
width: 1350,
|
|
height: 900,
|
|
},
|
|
},
|
|
{
|
|
code: 'vertical',
|
|
description: t('Vertical'),
|
|
viewport: {
|
|
width: 306.66,
|
|
height: 533.33,
|
|
},
|
|
output: {
|
|
width: 460,
|
|
height: 800,
|
|
},
|
|
},
|
|
];
|
|
|
|
const uploadMethodSelected = ref('computer');
|
|
const viewPortTypeSelected = ref(viewportTypes[0]);
|
|
const inputFileRef = ref(null);
|
|
const allowedContentTypes = ref('');
|
|
const photoContainerRef = ref(null);
|
|
const editor = ref(null);
|
|
const newPhoto = reactive({
|
|
id: props.id,
|
|
collection: props.collection,
|
|
file: null,
|
|
url: null,
|
|
blob: null,
|
|
});
|
|
|
|
const openInputFile = () => {
|
|
inputFileRef.value.pickFiles();
|
|
};
|
|
|
|
const displayEditor = () => {
|
|
const viewportType = viewPortTypeSelected.value;
|
|
const viewport = viewportType.viewport;
|
|
const boundaryWidth = viewport.width + 200;
|
|
const boundaryHeight = viewport.height + 200;
|
|
|
|
if (editor.value) editor.value.destroy();
|
|
editor.value = new Croppie(photoContainerRef.value, {
|
|
viewport: { width: viewport.width, height: viewport.height },
|
|
boundary: { width: boundaryWidth, height: boundaryHeight },
|
|
enableOrientation: true,
|
|
showZoomer: true,
|
|
});
|
|
};
|
|
|
|
const viewportSelection = computed({
|
|
get() {
|
|
return viewPortTypeSelected.value;
|
|
},
|
|
set(val) {
|
|
viewPortTypeSelected.value = val;
|
|
|
|
const hasFile = newPhoto.files || newPhoto.url;
|
|
if (!val || !hasFile) return;
|
|
|
|
let file;
|
|
if (uploadMethodSelected.value == 'computer') file = newPhoto.files;
|
|
else if (uploadMethodSelected.value == 'URL') file = newPhoto.url;
|
|
|
|
updatePhotoPreview(file);
|
|
},
|
|
});
|
|
|
|
const updatePhotoPreview = (value) => {
|
|
if (value) {
|
|
displayEditor();
|
|
if (uploadMethodSelected.value == 'computer') {
|
|
newPhoto.files = value;
|
|
const reader = new FileReader();
|
|
reader.onload = (e) => editor.value.bind({ url: e.target.result });
|
|
reader.readAsDataURL(value);
|
|
} else if (uploadMethodSelected.value == 'URL') {
|
|
newPhoto.url = value;
|
|
const img = new Image();
|
|
img.crossOrigin = 'Anonymous';
|
|
img.src = value;
|
|
img.onload = () => editor.value.bind({ url: value });
|
|
img.onerror = () => {
|
|
notify(
|
|
t("This photo provider doesn't allow remote downloads"),
|
|
'negative'
|
|
);
|
|
};
|
|
}
|
|
}
|
|
};
|
|
|
|
const rotateLeft = () => {
|
|
editor.value.rotate(90);
|
|
};
|
|
|
|
const rotateRight = () => {
|
|
editor.value.rotate(-90);
|
|
};
|
|
|
|
const onUploadAccept = () => {
|
|
try {
|
|
if (!newPhoto.files && !newPhoto.url) {
|
|
notify(t('Select an image'), 'negative');
|
|
return;
|
|
}
|
|
|
|
const options = {
|
|
type: 'blob',
|
|
};
|
|
|
|
editor.value
|
|
.result(options)
|
|
.then((result) => {
|
|
const file = new File([result], newPhoto.files?.name || '');
|
|
newPhoto.blob = file;
|
|
})
|
|
.then(() => makeRequest());
|
|
} catch (err) {
|
|
console.error('Error uploading image');
|
|
}
|
|
};
|
|
|
|
const makeRequest = async () => {
|
|
const formData = new FormData();
|
|
const now = Date.vnNew();
|
|
const timestamp = now.getTime();
|
|
const fileName = `${newPhoto.files?.name}_${timestamp}`;
|
|
formData.append('blob', newPhoto.blob, fileName);
|
|
|
|
await axios.post('Images/upload', formData, {
|
|
params: newPhoto,
|
|
headers: {
|
|
'Content-Type': 'multipart/form-data',
|
|
},
|
|
});
|
|
|
|
emit('closeForm');
|
|
emit('onPhotoUploaded');
|
|
|
|
notify(t('globals.dataSaved'), 'positive');
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<FetchData
|
|
ref="allowTypesRef"
|
|
url="ImageContainers/allowedContentTypes"
|
|
@on-fetch="(data) => (allowedContentTypes = data.join(', '))"
|
|
auto-load
|
|
/>
|
|
<QForm @submit="onUploadAccept()" class="all-pointer-events">
|
|
<QCard class="q-pa-lg">
|
|
<span ref="closeButton" class="close-icon" v-close-popup>
|
|
<QIcon name="close" size="sm" />
|
|
</span>
|
|
<h1 class="title">{{ t('Edit photo') }}</h1>
|
|
<div class="row q-gutter-lg">
|
|
<div
|
|
v-show="newPhoto.files || newPhoto.url"
|
|
class="row q-gutter-lg items-center"
|
|
>
|
|
<QIcon
|
|
name="rotate_left"
|
|
size="sm"
|
|
color="primary"
|
|
class="cursor-pointer"
|
|
@click="rotateLeft()"
|
|
>
|
|
<!-- <QTooltip class="no-pointer-events">
|
|
{{ t('Rotate left') }}
|
|
</QTooltip> -->
|
|
</QIcon>
|
|
<div>
|
|
<div ref="photoContainerRef" />
|
|
</div>
|
|
<QIcon
|
|
name="rotate_right"
|
|
size="sm"
|
|
color="primary"
|
|
class="cursor-pointer"
|
|
@click="rotateRight()"
|
|
>
|
|
<!-- <QTooltip class="no-pointer-events">
|
|
{{ t('Rotate right') }}
|
|
</QTooltip> -->
|
|
</QIcon>
|
|
</div>
|
|
|
|
<div class="column">
|
|
<VnRow class="row q-gutter-md q-mb-md">
|
|
<div class="col">
|
|
<QOptionGroup
|
|
:options="uploadMethodsOptions"
|
|
type="radio"
|
|
v-model="uploadMethodSelected"
|
|
/>
|
|
</div>
|
|
</VnRow>
|
|
<VnRow class="row q-gutter-md q-mb-md">
|
|
<div class="col">
|
|
<QFile
|
|
v-if="uploadMethodSelected === 'computer'"
|
|
ref="inputFileRef"
|
|
:label="t('File')"
|
|
:multiple="false"
|
|
v-model="newPhoto.files"
|
|
@update:model-value="updatePhotoPreview($event)"
|
|
:accept="allowedContentTypes"
|
|
class="required cursor-pointer"
|
|
>
|
|
<template #append>
|
|
<QIcon
|
|
name="vn:attach"
|
|
class="cursor-pointer q-mr-sm"
|
|
@click="openInputFile()"
|
|
>
|
|
<!-- <QTooltip>{{ t('Select a file') }}</QTooltip> -->
|
|
</QIcon>
|
|
<QIcon name="info" class="cursor-pointer">
|
|
<QTooltip>{{
|
|
t(
|
|
'components.editPictureForm.allowedFilesText',
|
|
{
|
|
allowedContentTypes:
|
|
allowedContentTypes,
|
|
}
|
|
)
|
|
}}</QTooltip>
|
|
</QIcon>
|
|
</template>
|
|
</QFile>
|
|
<VnInput
|
|
v-if="uploadMethodSelected === 'URL'"
|
|
v-model="newPhoto.url"
|
|
@update:model-value="updatePhotoPreview($event)"
|
|
placeholder="https://"
|
|
/>
|
|
</div>
|
|
</VnRow>
|
|
<VnRow class="row q-gutter-md q-mb-md">
|
|
<div class="col">
|
|
<VnSelectFilter
|
|
:label="t('Orientation')"
|
|
:options="viewportTypes"
|
|
hide-selected
|
|
option-label="description"
|
|
v-model="viewportSelection"
|
|
/>
|
|
</div>
|
|
</VnRow>
|
|
<div class="q-mt-lg row justify-end">
|
|
<QBtn
|
|
:label="t('globals.save')"
|
|
type="submit"
|
|
color="primary"
|
|
:disabled="isLoading"
|
|
:loading="isLoading"
|
|
/>
|
|
<QBtn
|
|
:label="t('globals.cancel')"
|
|
type="reset"
|
|
color="primary"
|
|
flat
|
|
class="q-ml-sm"
|
|
:disabled="isLoading"
|
|
:loading="isLoading"
|
|
v-close-popup
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</QCard>
|
|
</QForm>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
.title {
|
|
font-size: 17px;
|
|
font-weight: bold;
|
|
line-height: 20px;
|
|
}
|
|
|
|
.close-icon {
|
|
position: absolute;
|
|
top: 20px;
|
|
right: 20px;
|
|
cursor: pointer;
|
|
}
|
|
</style>
|
|
|
|
<i18n>
|
|
es:
|
|
Edit photo: Editar foto
|
|
Select from computer: Seleccionar desde ordenador
|
|
Import from external URL: Importar desde URL externa
|
|
Vertical: Vertical
|
|
Normal: Normal
|
|
Panoramic: Panorámica
|
|
Orientation: Orientación
|
|
File: Fichero
|
|
This photo provider doesn't allow remote downloads: Este proveedor de fotos no permite descargas remotas
|
|
Rotate left: Girar a la izquierda
|
|
Rotate right: Girar a la derecha
|
|
Select an image: Selecciona una imagen
|
|
</i18n>
|