Moznost nahravat fotky copilotem

This commit is contained in:
Michal Kváček
2024-03-07 06:45:22 +01:00
parent f0a4c439dc
commit cd9c7351e9
22 changed files with 550 additions and 216 deletions
+38 -87
View File
@@ -1,29 +1,39 @@
<script>
import {useFlightsStore} from "stores/Flights";
export default {
preFetch({currentRoute}) {
const flightId = currentRoute.params.id
return useFlightsStore().fetchDetail(flightId);
}
};
</script>
<script setup>
import FlightFormSteps from "components/flights/form/FlightFormSteps.vue";
import {computed, onMounted, ref, watch} from "vue";
import {onMounted, ref, toRefs, watch} from "vue";
import {useRoute} from "vue-router";
import {useFlightsStore} from "stores/Flights";
import FlightFormBasic from "components/flights/form/FlightFormBasic.vue";
import {useAircraftsStore} from "stores/Aircrafts";
import {usePointsOfInterestStore} from "stores/PointsOfInterest";
import {useAirportsStore} from "stores/Airports";
import {useQuasar} from "quasar";
import {storeToRefs} from "pinia";
import {flightName} from "src/composables/flight";
import FlightFormCopilot from "components/flights/form/FlightFormCopilot.vue";
import {useCopilotsStore} from "stores/Copilots";
import FlightFormNotes from "components/flights/form/FlightFormNotes.vue";
import FlightFormPhotos from "components/flights/form/FlightFormPhotos.vue";
import {usePhotosStore} from "stores/Photos";
import PhotosEditorList from "components/photos/PhotosEditorList.vue";
import {errorObject, successObject, warningObject} from "src/composables/dialog";
import PhotoFormDialog from "components/photos/PhotoFormDialog.vue";
import FlightFormTrack from "components/flights/form/FlightFormTrack.vue";
import FlightEditSkeleton from "components/flights/skeletons/FlightEditSkeleton.vue";
import BatchPhotoFormDialog from "components/photos/BatchPhotoFormDialog.vue";
import {useAppStore} from "stores/App";
import {useEventsStore} from "stores/Events";
import FlightEditCopilotSkeleton from "components/flights/skeletons/FlightEditCopilotSkeleton.vue";
import {useBasicPhotoOperations} from "src/composables/photos/basicOperations";
import {usePhotosStore} from "stores/Photos";
const $q = useQuasar();
const $route = useRoute();
@@ -34,29 +44,33 @@ const {pointsOfInterest} = storeToRefs(usePointsOfInterestStore());
const {airports} = storeToRefs(useAirportsStore());
const {copilots} = storeToRefs(useCopilotsStore());
const {events} = storeToRefs(useEventsStore());
const {flight, isQueryRunning, isMutationRunning} = storeToRefs(useFlightsStore());
const {photos} = storeToRefs(usePhotosStore());
const {flight, isQueryRunning, isRequestPending: isMutationRunning} = storeToRefs(useFlightsStore());
const formStep = ref("basic");
watch(() => $route.params.step, (val) => formStep.value = val);
onMounted(() => {
formStep.value = $route.params.step || "basic";
import {flightName} from "src/composables/flight";
import {useAppStore} from "stores/App";
const flightNameFormatted = flightName(flight.value);
useAppStore().setPageTitle(`Editace - ${flightNameFormatted}`);
useAppStore().setBreadcrumbs([
{to: {name: "myFlights"}, label: "Moje lety"},
{to: {name: "flightDetail", params: {id: flight.id}}, label: flightNameFormatted},
{label: "Editace"}
]);
onMounted(() => {
photos.value = flight.value.photos;
formStep.value = $route.params.step || "basic";
useAircraftsStore().fetch({});
usePointsOfInterestStore().fetch({});
useAirportsStore().fetch();
useCopilotsStore().fetch();
useEventsStore().fetch({});
useFlightsStore().fetchDetail(flightId).then((f) => {
const flightNameFormatted = flightName(f);
useAppStore().setPageTitle(`Editace - ${flightNameFormatted}`);
useAppStore().setBreadcrumbs([
{to: {name: "myFlights"}, label: "Moje lety"},
{to: {name: "flightDetail", params: {id: flightId}}, label: flightNameFormatted},
{label: "Editace"}
]);
})
});
const handleFlightEdit = (data) => {
@@ -64,40 +78,9 @@ const handleFlightEdit = (data) => {
$q.notify(successObject("Úspěšně upraveno"));
})
}
const isRequestPending = storeToRefs(useFlightsStore()).isMutationRunning;
const uploadInProgress = ref(false);
const handlePhotosUpload = (data) => {
const promises = [];
uploadInProgress.value = true;
for (const photo of data.photos) {
promises.push(usePhotosStore().upload(flightId, {photo}).then((data) => {
flight.value.photos.push(data);
photo._uploaded = true;
}));
}
Promise.allSettled(promises).then((values) => {
const success = values.filter(v => v.status === 'fulfilled');
const failed = values.filter(v => v.status === 'rejected'); // TODO: nebo vse - success?
if (success.length > 0)
$q.notify(successObject(`Úspěšně nahráno ${success.length} fotek`));
if (failed.length > 0)
$q.notify(warningObject(`Nepovedlo se nahrát ${failed.length} fotek. Zde jsou důvody: ${failed.map(f => f.reason)}`));
data.photos = [];
}).finally(() => uploadInProgress.value = false);
}
const _findPhotoIndex = (photoId) => flight.value.photos.findIndex((object) => object.id === photoId);
const deletePhoto = (id) => {
usePhotosStore().destroy(id).then((data) => {
flight.value.photos.splice(_findPhotoIndex(data.id), 1);
});
}
const {
uploadInProgress, handlePhotosUpload, batchRotatePhotos, deletePhoto, updateSinglePhoto
} = useBasicPhotoOperations({});
const setTitlePhoto = (photo) => {
useFlightsStore().update(flight.value.id, {titlePhotoId: photo.id}).then(f => {
@@ -105,38 +88,6 @@ const setTitlePhoto = (photo) => {
});
}
const rotatePhotos = ({photos, direction}) => {
const promises = [];
for (const photo of photos) {
promises.push(usePhotosStore().changeOrientation(photo.id, direction).then(data => {
const idx = _findPhotoIndex(data.id);
data.thumbnailUrl += "?t=" + (new Date()).getTime();
flight.value.photos[idx] = data;
}));
}
let success = 0;
Promise.allSettled(promises).then(result => {
for (const item of result) {
if (item.status === 'fulfilled') success++;
}
const error = result.length - success;
if (success > 0) $q.notify(successObject("Otočeno " + success + " fotografií"));
if (error > 0) $q.notify(errorObject(`Nepovedlo se otočit ${error} fotografi`))
});
}
const updateSinglePhoto = (id, formData) => {
formData.copilots = formData.copilots.map(c => ({id: c.id, name: c.name}));
return usePhotosStore().update(id, formData).then((data) => {
const idx = _findPhotoIndex(data.id);
flight.value.photos[idx] = data;
usePointsOfInterestStore().fetch({});
});
}
const handlePhotoUpdate = (flightId, formData) => {
const photoId = formData.id;
delete formData.id;
@@ -240,18 +191,18 @@ const handleBatchPhotoEdit = async (flightId, data) => {
<q-tab-panel name="photos">
<FlightFormPhotos
:flight="flight"
:is-request-pending="isRequestPending || uploadInProgress"
:is-request-pending="uploadInProgress"
@save="handlePhotosUpload"
/>
<template v-if="flight">
<PhotosEditorList
:title-photo="flight.titlePhoto"
:photos="flight.photos"
:photos="photos"
:show-actions="true"
@edit="openPhotoEdit"
@batch-edit="openBatchPhotoForm"
@batch-rotate="rotatePhotos"
@batch-rotate="batchRotatePhotos"
@delete="deletePhoto"
@set-title-photo="setTitlePhoto"
/>
@@ -97,6 +97,7 @@ const togglePublicVisibility = (variables) => toggleVisibility({
:event-detail-route="flight.event ? {name: 'eventDetail', params: {id: flight.event.id}} : null"
:aircraft-detail-route="{name: 'aircraftDetail'}"
:copilot-detail-route="{name: 'copilotDetail'}"
:logged-user="loggedUser"
/>
<ContentCardEditButton
@@ -3,7 +3,7 @@ import {useCopilotsStore} from "stores/Copilots";
export default {
preFetch({currentRoute}) {
return useCopilotsStore().fetchPublicDetail(currentRoute.params.copilotSlug, currentRoute.params.username);
return useCopilotsStore().fetchPublicDetail({urlSlug: currentRoute.params.copilotSlug, username: currentRoute.params.username});
}
}
</script>
@@ -0,0 +1,100 @@
<script>
import {useCopilotsStore} from "stores/Copilots";
import {useFlightsStore} from "stores/Flights";
export default {
preFetch({currentRoute}) {
return Promise.allSettled([
useFlightsStore().fetchPublicDetail({
username: currentRoute.params.username,
urlSlug: currentRoute.params.flightSlug
}),
useCopilotsStore().fetchPublicDetail({
username: currentRoute.params.username,
uploadToken: currentRoute.query.token,
uploadFlightSlug: currentRoute.params.flightSlug
})
]);
}
}
</script>
<script setup>
import {storeToRefs} from "pinia";
import ContentCard from "components/ContentCard.vue";
import {useAppStore} from "stores/App";
import {useMeta, useQuasar} from "quasar";
import {metaData} from "src/composables/meta";
import {flightName} from "src/composables/flight";
import {usePhotosStore} from "stores/Photos";
import {onMounted, ref} from "vue";
import PhotosEditorListSimple from "components/photos/PhotosEditorListSimple.vue";
import {copilotBreadcrumbs} from "src/composables/breadcrumbs";
import FlightFormPhotos from "components/flights/form/FlightFormPhotos.vue";
import BasicInfoBanner from "components/banners/BasicInfoBanner.vue";
import {useRoute} from "vue-router";
import {useBasicPhotoOperations} from "src/composables/photos/basicOperations";
const props = defineProps({
pilot: {
type: Object,
required: false
},
});
const {copilot} = storeToRefs(useCopilotsStore());
const {flight} = storeToRefs(useFlightsStore());
const {photos, isQueryRunning: arePhotosLoading} = storeToRefs(usePhotosStore())
useAppStore().setPageTitle(`Nahraj fotky - ${props.pilot.name} - ${flightName(flight.value)}`);
useAppStore().setBreadcrumbs([
...copilotBreadcrumbs(props.pilot, flight.value, flight.value.event, copilot.value),
{label: "Nahraj fotky"},
]);
useMeta(() => metaData({
title: `Nahraj fotky: ${copilot.value.name}`,
index: false,
image: copilot.value.titleImage?.url
}));
onMounted(() => {
usePhotosStore().fetchPublic({
flightId: flight.value.id,
uploadedByCopilotId: copilot.value.id
});
});
const $route = useRoute();
const copilotUploadToken = $route.query.token;
const {
uploadInProgress, handlePhotosUpload, batchRotatePhotos, deletePhoto
} = useBasicPhotoOperations({copilotUploadToken});
</script>
<template>
<ContentCard :title="copilot.name">
<template #content>
<q-card-section>
Nahraj fotky k letu {{ flightName(flight) }}
<FlightFormPhotos
:is-request-pending="uploadInProgress"
:flight="flight"
@save="handlePhotosUpload"
/>
<BasicInfoBanner v-if="photos.length === 0 && !arePhotosLoading">
Zatím tu od tebe nejsou nahrané žádné fotky.
</BasicInfoBanner>
<PhotosEditorListSimple
v-else
style="margin-bottom: 150px"
:photos="photos"
@batch-rotate="batchRotatePhotos"
@delete="deletePhoto"
/>
</q-card-section>
</template>
</ContentCard>
</template>
@@ -81,7 +81,7 @@ useMeta(() => metaData({
:flight="flight"
:poi-detail-route="{name: 'publicPOIDetail'}"
:aircraft-detail-route="{name: 'publicAircraftDetail'}"
:event-detail-route="event.isPublic ? {name: 'publicEventDetail', params: {username: pilot.publicUsername, eventSlug: event.urlSlug}} : null"
:event-detail-route="event && event.isPublic ? {name: 'publicEventDetail', params: {username: pilot.publicUsername, eventSlug: event.urlSlug}} : null"
:copilot-detail-route="{name: 'publicCopilotDetail', params: {username: pilot.publicUsername}}"
/>