305 lines
10 KiB
Vue
305 lines
10 KiB
Vue
<script setup>
|
|
import {useRoute} from "vue-router";
|
|
import {onMounted, ref, watch} from "vue";
|
|
import {usePhotosStore} from "stores/Photos";
|
|
import {useAppStore} from "stores/App";
|
|
import {flightName} from "src/composables/flight";
|
|
import VuePictureCropper from 'vue-picture-cropper';
|
|
import {croppedAreaForPreview, getPreviewUrl} from "src/composables/photos/editor/preview";
|
|
import {compare, onResize, splitter, splitterWidth} from "src/composables/photos/editor/splitter";
|
|
import {aspectRatioOptions, croppedArea, cropperReady, photoAspectRatio, selectedAspectRatio, setCropData} from "src/composables/photos/editor/crop";
|
|
import {debounce, useQuasar} from "quasar";
|
|
import {successObject} from "src/composables/dialog";
|
|
import PhotoEditorSkeleton from "components/photos/PhotoEditorSkeleton.vue";
|
|
import {storeToRefs} from "pinia";
|
|
import FormSubmitButton from "components/FormSubmitButton.vue";
|
|
import FormWrapper from "components/FormWrapper.vue";
|
|
|
|
const $route = useRoute();
|
|
const $q = useQuasar();
|
|
const photoId = parseInt($route.params.id);
|
|
const noCacheTimestamp = ref((new Date()).getTime());
|
|
const editorTab = ref("colors");
|
|
|
|
// adjustments
|
|
const defaultAdjustments = {
|
|
saturation: 1,
|
|
brightness: 1,
|
|
contrast: 1,
|
|
sharpness: 1,
|
|
rotate: 0,
|
|
};
|
|
const adjustment = ref({...defaultAdjustments});
|
|
const croppedAdjusted = ref("");
|
|
const croppedAdjustedUnrotated = ref("");
|
|
const uncroppedAdjusted = ref("");
|
|
const croppedUnadjusted = ref("");
|
|
|
|
watch(() => [adjustment.value, croppedAreaForPreview.value], debounce(() => {
|
|
croppedAdjusted.value = getPreviewUrl(photoId, {...adjustment.value, ...croppedAreaForPreview.value});
|
|
croppedAdjustedUnrotated.value = getPreviewUrl(photoId, {...adjustment.value, rotate: 0, ...croppedAreaForPreview.value});
|
|
uncroppedAdjusted.value = getPreviewUrl(photoId, {...adjustment.value});
|
|
croppedUnadjusted.value = getPreviewUrl(photoId, {rotate: adjustment.value.rotate, ...croppedAreaForPreview.value});
|
|
}, 200), {deep: true, immediate: true});
|
|
|
|
const savePhotoAdjustment = (photoId) => {
|
|
const adjustmentInput = {...adjustment.value};
|
|
|
|
if (croppedArea.value && croppedArea.value.left !== null) {
|
|
adjustmentInput.crop = {...croppedArea.value}
|
|
}
|
|
|
|
usePhotosStore().adjust(photoId, adjustmentInput).then((data) => {
|
|
noCacheTimestamp.value = (new Date()).getTime();
|
|
$q.notify(successObject("Fotka úspěšně upravena"));
|
|
});
|
|
};
|
|
|
|
onMounted(() => {
|
|
usePhotosStore().fetchDetail(photoId).then((p) => {
|
|
const flightNameFormatted = flightName(p.flight);
|
|
|
|
photoAspectRatio.value = p.width / p.height;
|
|
|
|
if (p.adjustment) {
|
|
const photoAdjustment = p.adjustment;
|
|
const src = photoAdjustment || defaultAdjustments;
|
|
for (const i in defaultAdjustments) {
|
|
adjustment.value[i] = src[i];
|
|
}
|
|
|
|
if (photoAdjustment) {
|
|
croppedArea.value = {
|
|
left: photoAdjustment.cropLeft,
|
|
top: photoAdjustment.cropTop,
|
|
width: photoAdjustment.cropWidth,
|
|
height: photoAdjustment.cropHeight
|
|
};
|
|
}
|
|
}
|
|
useAppStore().setBreadcrumbs([
|
|
{to: {name: "myFlights"}, label: "Moje lety"},
|
|
{to: {name: "flightDetail", params: {id: p.flightId}}, label: flightNameFormatted},
|
|
{to: {name: "flightEdit", params: {id: p.flightId, step: 'photos'}}, label: "Fotografie"},
|
|
{label: "Foto editor"}
|
|
]);
|
|
})
|
|
|
|
useAppStore().setPageTitle("Foto editor");
|
|
});
|
|
|
|
const {isQueryRunning, isMutationRunning, photo} = storeToRefs(usePhotosStore());
|
|
</script>
|
|
|
|
<template>
|
|
<q-resize-observer @resize="onResize" :debounce="100"/>
|
|
|
|
<PhotoEditorSkeleton v-if="isQueryRunning"/>
|
|
|
|
<template v-if="!isQueryRunning">
|
|
<q-card :style="`width: ${splitterWidth}px; margin: 0 auto`">
|
|
<VuePictureCropper
|
|
v-if="editorTab === 'crop'"
|
|
:img="uncroppedAdjusted"
|
|
:boxStyle="{
|
|
width: '100%',
|
|
height: '100%',
|
|
backgroundColor: '#f8f8f8',
|
|
margin: 'auto',
|
|
}"
|
|
:options="{
|
|
viewMode: 1,
|
|
autoCropArea: 1,
|
|
dragMode: 'none',
|
|
zoomable: false,
|
|
draggable: false,
|
|
rotatable: false,
|
|
}"
|
|
@cropend="setCropData"
|
|
@ready="cropperReady"
|
|
/>
|
|
|
|
<div class="overflow-hidden relative-position" v-else-if="editorTab === 'rotate'">
|
|
<div class="rotate-overlay"></div>
|
|
<q-img :src="croppedAdjustedUnrotated" fit="contain" :style="`transform: rotate(${adjustment.rotate}deg)`"/>
|
|
</div>
|
|
|
|
<div class="overflow-hidden" v-else>
|
|
<q-img fit="contain" :src="croppedAdjusted" v-show="!compare"/>
|
|
<q-splitter
|
|
v-show="compare"
|
|
v-model="splitter"
|
|
:limits="[0, 100]"
|
|
:style="{
|
|
height: Math.round(splitterWidth / photoAspectRatio) + 'px',
|
|
width: splitterWidth.value + 'px'
|
|
}"
|
|
before-class="overflow-hidden"
|
|
after-class="overflow-hidden">
|
|
<template #before>
|
|
<img
|
|
:width="splitterWidth"
|
|
class="absolute-top-left"
|
|
:src="croppedUnadjusted"
|
|
alt="before"
|
|
/>
|
|
</template>
|
|
<template #after>
|
|
<img
|
|
:width="splitterWidth"
|
|
class="absolute-top-right"
|
|
:src="croppedAdjusted"
|
|
alt="after"
|
|
/>
|
|
</template>
|
|
</q-splitter>
|
|
|
|
<div class="text-right q-pt-sm q-pr-sm">
|
|
<q-btn flat icon="compare" @click="compare = !compare" :text-color="compare ? 'primary' : 'grey-5'">
|
|
<q-tooltip>Klikni pro porovnání</q-tooltip>
|
|
</q-btn>
|
|
</div>
|
|
</div>
|
|
|
|
<q-card-section>
|
|
<q-separator/>
|
|
<q-tabs v-model="editorTab">
|
|
<q-tab name="colors" icon="palette" label="Barvy"/>
|
|
<q-tab name="details" icon="deblur" label="Detialy"/>
|
|
<q-tab name="rotate" icon="loop" label="Otočit"/>
|
|
<q-tab name="crop" icon="crop" label="Oříznout"/>
|
|
</q-tabs>
|
|
<q-separator/>
|
|
|
|
|
|
<FormWrapper :is-request-pending="isMutationRunning" @save="savePhotoAdjustment(photoId)">
|
|
<q-tab-panels v-model="editorTab" animated>
|
|
<q-tab-panel name="colors">
|
|
<q-list>
|
|
<q-item>
|
|
<q-item-section avatar>
|
|
<q-icon name="contrast"/>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-slider
|
|
v-model="adjustment.contrast"
|
|
:min="0.5"
|
|
:max="1.5"
|
|
:step="0.01"
|
|
label-always
|
|
@dblclick="adjustment.contrast=1"
|
|
:label="true"
|
|
/>
|
|
</q-item-section>
|
|
</q-item>
|
|
|
|
<q-item>
|
|
<q-item-section avatar>
|
|
<q-icon name="brightness_6"/>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-slider
|
|
v-model="adjustment.brightness"
|
|
:min="0.5"
|
|
:max="1.5"
|
|
:step="0.01"
|
|
:label="true"
|
|
label-always
|
|
@dblclick="adjustment.brightness = 1"
|
|
/>
|
|
</q-item-section>
|
|
</q-item>
|
|
|
|
<q-item>
|
|
<q-item-section avatar>
|
|
<q-icon name="palette"/>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-slider
|
|
v-model="adjustment.saturation"
|
|
:min="0"
|
|
:max="2"
|
|
:step="0.05"
|
|
:label="true"
|
|
label-always
|
|
@dblclick="adjustment.saturation = 1"
|
|
/>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</q-tab-panel>
|
|
<q-tab-panel name="details">
|
|
<q-list>
|
|
<q-item>
|
|
<q-item-section avatar>
|
|
<q-icon name="deblur"/>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-slider
|
|
v-model="adjustment.sharpness"
|
|
:min="-3"
|
|
:max="8"
|
|
:step="1"
|
|
:label="true"
|
|
label-always
|
|
snap
|
|
@dblclick="adjustment.sharpness=1"
|
|
/>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</q-tab-panel>
|
|
<q-tab-panel name="rotate">
|
|
<q-list>
|
|
<q-item>
|
|
<q-item-section avatar>
|
|
<q-btn round flat icon="rotate_left" @click="adjustment.rotate -= 0.1"/>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-slider
|
|
v-model="adjustment.rotate"
|
|
:min="-45"
|
|
:max="45"
|
|
:step="0.1"
|
|
:label="true"
|
|
label-always
|
|
@dblclick="adjustment.rotate=0"
|
|
/>
|
|
</q-item-section>
|
|
<q-item-section avatar>
|
|
<q-btn round flat icon="rotate_right" @click="adjustment.rotate += 0.1"/>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</q-tab-panel>
|
|
<q-tab-panel name="crop">
|
|
<q-btn-toggle
|
|
no-caps
|
|
spread
|
|
v-model="selectedAspectRatio"
|
|
toggle-color="primary"
|
|
:options="aspectRatioOptions"
|
|
class="q-ma-lg"
|
|
/>
|
|
</q-tab-panel>
|
|
</q-tab-panels>
|
|
</FormWrapper>
|
|
</q-card-section>
|
|
</q-card>
|
|
</template>
|
|
</template>
|
|
|
|
|
|
<style>
|
|
.rotate-overlay {
|
|
position: absolute;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 100%;
|
|
z-index: 888;
|
|
background-size: 60px 60px;
|
|
background-image: linear-gradient(to right, rgba(61, 4, 4, 0.5) 1px, transparent 1px),
|
|
linear-gradient(to bottom, rgba(61, 4, 4, 0.5) 1px, transparent 1px);
|
|
}
|
|
</style>
|