Moznost nahravat fotky copilotem
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script setup>
|
||||
import {FORMAT_DATE, FORMAT_TIME, formatDatetime, minutesToHumanReadable} from "src/composables/datetimeUtils";
|
||||
import WeatherInfo from "components/weather/WeatherInfo.vue";
|
||||
import CopyLinkButton from "components/inputs/CopyLinkButton.vue";
|
||||
|
||||
const props = defineProps({
|
||||
flight: {
|
||||
@@ -26,6 +27,11 @@ const props = defineProps({
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
loggedUser: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -144,14 +150,31 @@ const props = defineProps({
|
||||
<q-item v-for="copilot in flight.copilots" :key="copilot.id">
|
||||
<q-item-section avatar>
|
||||
<q-icon name="people"/>
|
||||
</q-item-section>
|
||||
</q-item-section>3
|
||||
<q-item-section>
|
||||
<router-link
|
||||
v-if="copilot.isPublic"
|
||||
:to="{name: copilotDetailRoute.name, params: {...copilotDetailRoute.params, copilotSlug: copilot.urlSlug, id: copilot.id}}"
|
||||
>
|
||||
{{ copilot.name }}
|
||||
</router-link>
|
||||
<template v-if="copilot.isPublic">
|
||||
<div>
|
||||
<router-link
|
||||
:to="{name: copilotDetailRoute.name, params: {...copilotDetailRoute.params, copilotSlug: copilot.urlSlug, id: copilot.id}}"
|
||||
>
|
||||
{{ copilot.name }}
|
||||
</router-link>
|
||||
|
||||
<CopyLinkButton
|
||||
:route='{
|
||||
name: "publicCopilotUploadPhoto",
|
||||
params: {flightSlug: flight.urlSlug, username: loggedUser.publicUsername},
|
||||
query: {token: copilot.token}
|
||||
}'
|
||||
icon="cloud_upload"
|
||||
title="Získat odkaz na nahrání fotek od kopilota"
|
||||
label=""
|
||||
round
|
||||
class="q-mx-3"
|
||||
v-if="loggedUser && copilot.token"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else> {{ copilot.name }}</template>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<script setup>
|
||||
import FormWrapper from "components/FormWrapper.vue";
|
||||
import {reactive} from "vue";
|
||||
import ComboboxInput from "components/inputs/ComboboxInput.vue";
|
||||
import {roundWithPrecision} from "../../../composables/utils";
|
||||
|
||||
const props = defineProps({
|
||||
flight: {
|
||||
|
||||
@@ -7,6 +7,16 @@ const props = defineProps({
|
||||
route: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "link"
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "Získat odkaz"
|
||||
}
|
||||
})
|
||||
|
||||
@@ -24,9 +34,10 @@ const copyLinkToClipboard = () => {
|
||||
|
||||
<template>
|
||||
<q-btn
|
||||
icon="link"
|
||||
@click="copyLinkToClipboard()"
|
||||
:icon="icon"
|
||||
:label="label"
|
||||
v-bind="$attrs"
|
||||
flat
|
||||
label="Získat odkaz"
|
||||
@click="copyLinkToClipboard()"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import {computed, onMounted, reactive, ref, watch} from "vue";
|
||||
import {useQuasar} from "quasar";
|
||||
import {confirmObject} from "src/composables/dialog";
|
||||
import {usePhotoBatchManagement} from "src/composables/photos/batch";
|
||||
|
||||
const props = defineProps({
|
||||
photos: {
|
||||
@@ -19,100 +20,23 @@ const props = defineProps({
|
||||
|
||||
const $q = useQuasar();
|
||||
|
||||
|
||||
const batchEdit = ref({});
|
||||
for (const photo of props.photos) {
|
||||
batchEdit.value[photo.id] = false;
|
||||
}
|
||||
|
||||
const emits = defineEmits(["edit", "delete", "set-title-photo", "batch-edit", "batch-rotate"])
|
||||
|
||||
const openEdit = (photo) => {
|
||||
emits("edit", photo);
|
||||
};
|
||||
|
||||
|
||||
const confirmPhotoDelete = (id) => {
|
||||
$q.dialog(confirmObject("Opravdu chces smazat tuto fotku?"))
|
||||
.onOk(() => {
|
||||
emits("delete", id);
|
||||
});
|
||||
.onOk(() => {
|
||||
emits("delete", id);
|
||||
});
|
||||
}
|
||||
|
||||
const confirmBatchPhotoDelete = () => {
|
||||
$q.dialog(confirmObject("Opravdu chces smazat vybrané fotky?"))
|
||||
.onOk(() => {
|
||||
for (const id in batchEdit.value) {
|
||||
if (!batchEdit.value[id]) continue;
|
||||
emits("delete", parseInt(id));
|
||||
batchEdit.value[id] = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const handleImageClick = (photo, idx) => {
|
||||
batchEdit.value[photo.id] = !batchEdit.value[photo.id];
|
||||
}
|
||||
|
||||
const selectedPhotos = computed(() => {
|
||||
const photos = [];
|
||||
for (const i in batchEdit.value) {
|
||||
const photo = props.photos.find(p => p.id === parseInt(i));
|
||||
if (photo && batchEdit.value[i]) photos.push(photo);
|
||||
}
|
||||
|
||||
return photos;
|
||||
});
|
||||
|
||||
const openBatchEdit = () => {
|
||||
const photos = selectedPhotos.value;
|
||||
for (const i in batchEdit.value) {
|
||||
batchEdit.value[i] = false;
|
||||
}
|
||||
emits('batch-edit', photos);
|
||||
}
|
||||
|
||||
const batchRotate = (direction) => {
|
||||
const photos = selectedPhotos.value;
|
||||
emits("batch-rotate", {photos, direction});
|
||||
}
|
||||
|
||||
const isBatchEditAllowed = computed(() => Object.values(batchEdit.value).filter(v => v).length > 0);
|
||||
|
||||
const checkAll = ref(false);
|
||||
watch(() => checkAll.value, (newVal) => {
|
||||
if (newVal === null) return;
|
||||
|
||||
for (const photo of props.photos) {
|
||||
batchEdit.value[photo.id] = newVal;
|
||||
}
|
||||
});
|
||||
watch(() => batchEdit.value, () => {
|
||||
let checkedAll = true;
|
||||
let uncheckedAll = true;
|
||||
|
||||
for (const i of Object.keys(batchEdit.value)) {
|
||||
if (typeof batchEdit.value[i] === 'undefined') continue;
|
||||
|
||||
if (batchEdit.value[i]) uncheckedAll = false;
|
||||
if (!batchEdit.value[i]) checkedAll = false;
|
||||
}
|
||||
|
||||
if (checkedAll) {
|
||||
checkAll.value = true;
|
||||
} else if (uncheckedAll) {
|
||||
checkAll.value = false;
|
||||
} else {
|
||||
checkAll.value = null;
|
||||
}
|
||||
}, {deep: true});
|
||||
|
||||
const batchOptionsProps = computed(() => ({
|
||||
round: true,
|
||||
flat: true,
|
||||
class: "q-ml-lg",
|
||||
disable: !isBatchEditAllowed.value
|
||||
}));
|
||||
const {
|
||||
openBatchEdit, batchEdit, handleImageClick, confirmBatchPhotoDelete, batchOptionsProps,
|
||||
isBatchEditAllowed, batchRotate, checkAll
|
||||
} = usePhotoBatchManagement({photos: props.photos, emits});
|
||||
|
||||
const setFlightCover = (titlePhoto) => {
|
||||
emits("set-title-photo", titlePhoto);
|
||||
@@ -136,10 +60,10 @@ const showLarge = (displayedPhoto) => {
|
||||
<q-btn icon="fullscreen" color="grey-6" round @click.stop.prevent="showLarge(photo)"/>
|
||||
</div>
|
||||
<q-btn
|
||||
icon="star"
|
||||
:color="titlePhoto?.id === photo.id ? 'amber-9' : 'grey-6'"
|
||||
round
|
||||
@click.stop.prevent="setFlightCover(photo)"
|
||||
icon="star"
|
||||
:color="titlePhoto?.id === photo.id ? 'amber-9' : 'grey-6'"
|
||||
round
|
||||
@click.stop.prevent="setFlightCover(photo)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -183,7 +107,7 @@ const showLarge = (displayedPhoto) => {
|
||||
<q-dialog v-model="openPhoto" full-width>
|
||||
<q-img :src="photo.url">
|
||||
<div class="absolute-top bg-transparent text-right">
|
||||
<q-btn icon="close" round color="primary" @click="openPhoto = false" />
|
||||
<q-btn icon="close" round color="primary" @click="openPhoto = false"/>
|
||||
</div>
|
||||
</q-img>
|
||||
</q-dialog>
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<script setup>
|
||||
import {useQuasar} from "quasar";
|
||||
import {confirmObject} from "src/composables/dialog";
|
||||
import {usePhotoBatchManagement} from "src/composables/photos/batch";
|
||||
|
||||
const props = defineProps({
|
||||
photos: {
|
||||
type: Array
|
||||
},
|
||||
titlePhoto: {
|
||||
type: Object,
|
||||
},
|
||||
showActions: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
const $q = useQuasar();
|
||||
|
||||
|
||||
const emits = defineEmits(["delete", "batch-edit", "batch-rotate"])
|
||||
|
||||
const confirmPhotoDelete = (id) => {
|
||||
$q.dialog(confirmObject("Opravdu chces smazat tuto fotku?"))
|
||||
.onOk(() => {
|
||||
emits("delete", id);
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
batchEdit, handleImageClick, confirmBatchPhotoDelete, batchOptionsProps, isBatchEditAllowed, batchRotate, checkAll
|
||||
} = usePhotoBatchManagement({photos: props.photos, emits});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row" v-bind="$attrs">
|
||||
<div class="col-6 col-sm-4 col-md-4 col-lg-3" v-for="(photo, idx) in photos" :key="photo.id">
|
||||
<q-card :class='{"q-ma-sm": true, "shadow-1": !batchEdit[photo.id], "shadow-10": batchEdit[photo.id]}'>
|
||||
<q-img :src="photo.thumbnailUrl" @click="handleImageClick(photo, idx)" style="cursor: pointer"/>
|
||||
<q-card-actions align="right" :class="{'bg-primary': batchEdit[photo.id], 'text-white': batchEdit[photo.id]}" v-if="showActions">
|
||||
<div class="col-grow">
|
||||
<q-checkbox v-model="batchEdit[photo.id]"/>
|
||||
</div>
|
||||
<q-btn flat icon="delete" color="red" @click="confirmPhotoDelete(photo.id)"/>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<q-page-sticky :offset="[50, 50]" position="bottom" v-if="photos.length > 0 && showActions">
|
||||
<q-card>
|
||||
<q-card-actions>
|
||||
<q-checkbox v-model="checkAll" label="Vybrat vše"/>
|
||||
<q-btn v-bind="batchOptionsProps" icon="rotate_90_degrees_ccw" :color="isBatchEditAllowed ? 'secondary' : 'grey'" @click="batchRotate('counterClockwise')"/>
|
||||
<q-btn v-bind="batchOptionsProps" icon="rotate_90_degrees_cw" :color="isBatchEditAllowed ? 'secondary' : 'grey'" @click="batchRotate('clockwise')"/>
|
||||
<q-btn v-bind="batchOptionsProps" icon="delete" :color="isBatchEditAllowed ? 'red' : 'grey'" @click="confirmBatchPhotoDelete"/>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-page-sticky>
|
||||
</template>
|
||||
@@ -0,0 +1,44 @@
|
||||
import {flightName} from "src/composables/flight";
|
||||
|
||||
const homepageBreadcrumbs = () => {
|
||||
return [{to: {name: 'homepage'}, label: "Polétání.cz"}]
|
||||
}
|
||||
|
||||
|
||||
// TODO: dodelat
|
||||
|
||||
const publicProfileBreadcrumbs = (pilot) => {
|
||||
return [
|
||||
...homepageBreadcrumbs(),
|
||||
{to: {name: "publicProfileDetail", params: {username: pilot.publicUsername}}, label: `@${pilot.publicUsername}`},
|
||||
]
|
||||
}
|
||||
|
||||
const publicEventBreadcrumbs = (pilot, event) => {
|
||||
|
||||
}
|
||||
|
||||
export const flightBreadcrumbs = (pilot, flight, event) => {
|
||||
const breadcrumbs = publicProfileBreadcrumbs(pilot)
|
||||
|
||||
if (event && event.isPublic) {
|
||||
breadcrumbs.push({to: {name: 'publicEventsList', username: pilot.publicUsername}, label: "Události"});
|
||||
breadcrumbs.push({
|
||||
to: {
|
||||
name: 'publicEventDetail',
|
||||
params: {eventSlug: event.urlSlug, username: pilot.publicUsername}
|
||||
}, label: event.name
|
||||
});
|
||||
}
|
||||
|
||||
breadcrumbs.push({label: flightName(flight)})
|
||||
|
||||
return breadcrumbs;
|
||||
}
|
||||
|
||||
export const copilotBreadcrumbs = (pilot, flight, event, copilot) => {
|
||||
return [
|
||||
...flightBreadcrumbs(pilot, flight, event),
|
||||
{label: copilot.name}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import {ref} from "vue";
|
||||
import {usePhotosStore} from "stores/Photos";
|
||||
import {errorObject, successObject, warningObject} from "src/composables/dialog";
|
||||
import {useQuasar} from "quasar";
|
||||
import {usePointsOfInterestStore} from "stores/PointsOfInterest";
|
||||
import {storeToRefs} from "pinia";
|
||||
import {useFlightsStore} from "stores/Flights";
|
||||
|
||||
export const useBasicPhotoOperations = ({copilotUploadToken}) => {
|
||||
const $q = useQuasar();
|
||||
|
||||
const {flight} = storeToRefs(useFlightsStore());
|
||||
const {photos} = storeToRefs(usePhotosStore());
|
||||
|
||||
// TODO: V PRIVATNI CASTI JE TO ZATIM ROZBITE!!!
|
||||
|
||||
|
||||
const uploadInProgress = ref(false);
|
||||
const handlePhotosUpload = (data) => {
|
||||
const promises = [];
|
||||
uploadInProgress.value = true;
|
||||
|
||||
for (const photo of data.photos) {
|
||||
promises.push(usePhotosStore().upload(flight.value.id, {photo, copilotUploadToken}).then((data) => {
|
||||
photos.value.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) => photos.value.findIndex((object) => object.id === photoId);
|
||||
const deletePhoto = (id) => {
|
||||
usePhotosStore().destroy(id, copilotUploadToken).then((data) => {
|
||||
photos.value.splice(_findPhotoIndex(data.id), 1);
|
||||
});
|
||||
}
|
||||
|
||||
const batchRotatePhotos = (data) => {
|
||||
const selectedPhotos = data.photos;
|
||||
const direction = data.direction;
|
||||
|
||||
const promises = [];
|
||||
for (const photo of selectedPhotos) {
|
||||
promises.push(usePhotosStore().changeOrientation(photo.id, direction, copilotUploadToken).then(data => {
|
||||
const idx = _findPhotoIndex(data.id);
|
||||
data.thumbnailUrl += "?t=" + (new Date()).getTime();
|
||||
photos.value[idx] = data;
|
||||
}));
|
||||
}
|
||||
|
||||
let success = 0;
|
||||
Promise.allSettled(promises).then(result => {
|
||||
for (const item of result) {
|
||||
if (item.status === 'fulfilled') success++;
|
||||
else console.error(item.reason)
|
||||
}
|
||||
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);
|
||||
photos.value[idx] = data;
|
||||
usePointsOfInterestStore().fetch({});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
uploadInProgress, handlePhotosUpload, batchRotatePhotos, deletePhoto, updateSinglePhoto
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import {computed, ref, watch} from "vue";
|
||||
import {confirmObject} from "src/composables/dialog";
|
||||
import {useQuasar} from "quasar";
|
||||
|
||||
|
||||
export const usePhotoBatchManagement = ({photos, emits}) => {
|
||||
const batchEdit = ref({});
|
||||
const $q = useQuasar();
|
||||
|
||||
// console.log(photos);
|
||||
for (const photo of photos) {
|
||||
batchEdit.value[photo.id] = false;
|
||||
}
|
||||
|
||||
const handleImageClick = (photo, idx) => {
|
||||
batchEdit.value[photo.id] = !batchEdit.value[photo.id];
|
||||
}
|
||||
|
||||
const selectedPhotos = computed(() => {
|
||||
const selected = [];
|
||||
for (const i in batchEdit.value) {
|
||||
const photo = photos.find(p => p.id === parseInt(i));
|
||||
if (photo && batchEdit.value[i]) selected.push(photo);
|
||||
}
|
||||
|
||||
return selected;
|
||||
});
|
||||
|
||||
const confirmBatchPhotoDelete = () => {
|
||||
$q.dialog(confirmObject("Opravdu chces smazat vybrané fotky?"))
|
||||
.onOk(() => {
|
||||
for (const id in batchEdit.value) {
|
||||
if (!batchEdit.value[id]) continue;
|
||||
emits("delete", parseInt(id));
|
||||
batchEdit.value[id] = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const openBatchEdit = () => {
|
||||
const photos = selectedPhotos.value;
|
||||
for (const i in batchEdit.value) {
|
||||
batchEdit.value[i] = false;
|
||||
}
|
||||
emits('batch-edit', photos);
|
||||
}
|
||||
|
||||
const batchRotate = (direction) => {
|
||||
const photos = selectedPhotos.value;
|
||||
emits("batch-rotate", {photos, direction});
|
||||
}
|
||||
|
||||
const isBatchEditAllowed = computed(() => Object.values(batchEdit.value).filter(v => v).length > 0);
|
||||
|
||||
const checkAll = ref(false);
|
||||
watch(() => checkAll.value, (newVal) => {
|
||||
if (newVal === null) return;
|
||||
|
||||
for (const photo of photos) {
|
||||
batchEdit.value[photo.id] = newVal;
|
||||
}
|
||||
});
|
||||
|
||||
const batchOptionsProps = computed(() => ({
|
||||
round: true,
|
||||
flat: true,
|
||||
class: "q-ml-lg",
|
||||
disable: !isBatchEditAllowed.value
|
||||
}));
|
||||
|
||||
watch(() => batchEdit.value, () => {
|
||||
let checkedAll = true;
|
||||
let uncheckedAll = true;
|
||||
|
||||
for (const i of Object.keys(batchEdit.value)) {
|
||||
if (typeof batchEdit.value[i] === 'undefined') continue;
|
||||
|
||||
if (batchEdit.value[i]) uncheckedAll = false;
|
||||
if (!batchEdit.value[i]) checkedAll = false;
|
||||
}
|
||||
|
||||
if (checkedAll) {
|
||||
checkAll.value = true;
|
||||
} else if (uncheckedAll) {
|
||||
checkAll.value = false;
|
||||
} else {
|
||||
checkAll.value = null;
|
||||
}
|
||||
}, {deep: true});
|
||||
|
||||
return {
|
||||
batchEdit, handleImageClick, selectedPhotos, confirmBatchPhotoDelete,
|
||||
openBatchEdit, batchRotate, checkAll, isBatchEditAllowed, batchOptionsProps
|
||||
}
|
||||
};
|
||||
@@ -11,18 +11,26 @@ fragment copilotListInfo on Copilot {
|
||||
}
|
||||
`;
|
||||
|
||||
export const FRAGMENT_COPILOT_FULL_INFO = gql`
|
||||
fragment copilotFullInfo on Copilot {
|
||||
export const FRAGMENT_COPILOT_PUBLIC_INFO = gql`
|
||||
fragment copilotPublicInfo on Copilot {
|
||||
id
|
||||
name
|
||||
isPublic
|
||||
urlSlug
|
||||
}
|
||||
`
|
||||
|
||||
export const FRAGMENT_COPILOT_FULL_INFO = gql`
|
||||
fragment copilotFullInfo on Copilot {
|
||||
...copilotPublicInfo
|
||||
|
||||
titlePhoto {
|
||||
id
|
||||
url
|
||||
}
|
||||
}
|
||||
|
||||
${FRAGMENT_COPILOT_PUBLIC_INFO}
|
||||
`;
|
||||
|
||||
export const FRAGMENT_COPILOT_FULL_PRIVATE_INFO = gql`
|
||||
|
||||
@@ -3,6 +3,7 @@ import {FRAGMENT_AIRCRAFT_FULL_INFO} from "src/graphql/fragments/aircraft";
|
||||
import {FRAGMENT_AIRPORT_INFO} from "src/graphql/fragments/airport";
|
||||
import {FRAGMENT_PHOTO_FULL_INFO} from "src/graphql/fragments/photo";
|
||||
import {FRAGMENT_POI_FULL_INFO} from "src/graphql/fragments/poi";
|
||||
import {FRAGMENT_COPILOT_PUBLIC_INFO} from "src/graphql/fragments/copilot";
|
||||
|
||||
export const FRAGMENT_WEATHER_FULL_INFO = gql`
|
||||
fragment weatherFullInfo on WeatherInfo {
|
||||
@@ -51,13 +52,6 @@ fragment flightFullInfo on Flight {
|
||||
...weatherFullInfo
|
||||
}
|
||||
|
||||
copilots {
|
||||
id
|
||||
name
|
||||
isPublic
|
||||
urlSlug
|
||||
}
|
||||
|
||||
event {
|
||||
id
|
||||
isPublic
|
||||
@@ -107,6 +101,11 @@ fragment flightFullPrivateInfo on Flight {
|
||||
...flightFullInfo
|
||||
debriefingNotes
|
||||
|
||||
copilots {
|
||||
...copilotPublicInfo
|
||||
token
|
||||
}
|
||||
|
||||
photos {
|
||||
...photoFullInfo
|
||||
|
||||
@@ -124,4 +123,5 @@ fragment flightFullPrivateInfo on Flight {
|
||||
|
||||
${FRAGMENT_FLIGHT_FULL_INFO}
|
||||
${FRAGMENT_PHOTO_FULL_INFO}
|
||||
${FRAGMENT_COPILOT_PUBLIC_INFO}
|
||||
`;
|
||||
|
||||
@@ -23,8 +23,8 @@ mutation editPhoto($id: Int!, $input: EditPhotoInput!) {
|
||||
`;
|
||||
|
||||
export const CHANGE_PHOTO_ORIENTATION = gql`
|
||||
mutation changeOrientation($id: Int!, $direction: String!) {
|
||||
changeOrientation(id: $id, direction: $direction) {
|
||||
mutation changeOrientation($id: Int!, $direction: String!, $copilotUploadToken: String) {
|
||||
changeOrientation(id: $id, direction: $direction, copilotUploadToken: $copilotUploadToken) {
|
||||
...photoFullInfo
|
||||
|
||||
flight {
|
||||
@@ -67,8 +67,8 @@ mutation adjustPhoto($id: Int!, $adjustment: AdjustmentInput!) {
|
||||
// TODO: ^^ udelat z toho asi jeden fragment a pouzit i v query GET_PHOTO
|
||||
|
||||
export const DELETE_PHOTO = gql`
|
||||
mutation deletePhoto($id: Int!) {
|
||||
deletePhoto(id: $id) {
|
||||
mutation deletePhoto($id: Int!, $copilotUploadToken: String) {
|
||||
deletePhoto(id: $id, copilotUploadToken: $copilotUploadToken) {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,14 @@ ${FRAGMENT_FLIGHT_LIST_INFO}
|
||||
`;
|
||||
|
||||
export const COPILOT_PUBLIC_DETAIL = gql`
|
||||
query copilot($urlSlug: String!, $pilotUsername: String) {
|
||||
copilot(urlSlug: $urlSlug, pilotUsername: $pilotUsername, public: true) {
|
||||
query copilot($urlSlug: String, $uploadToken: String, $uploadFlightSlug: String, $pilotUsername: String!) {
|
||||
copilot(
|
||||
urlSlug: $urlSlug,
|
||||
uploadToken: $uploadToken,
|
||||
uploadFlightSlug: $uploadFlightSlug,
|
||||
pilotUsername: $pilotUsername,
|
||||
public: true
|
||||
) {
|
||||
...copilotFullInfo
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import gql from "graphql-tag";
|
||||
import {FRAGMENT_FLIGHT_FULL_INFO, FRAGMENT_FLIGHT_FULL_PRIVATE_INFO} from "src/graphql/fragments/flight";
|
||||
import {FRAGMENT_FLIGHT_LIST_INFO} from "src/graphql/fragments/flight_list";
|
||||
import {FRAGMENT_PHOTO_FULL_INFO} from "src/graphql/fragments/photo";
|
||||
import {FRAGMENT_COPILOT_PUBLIC_INFO} from "src/graphql/fragments/copilot";
|
||||
|
||||
export const FLIGHTS_LIST = gql`
|
||||
query FlightsList($eventId: Int, $aircraftId: Int, $copilotId: Int, $pointOfInterestId: Int, $limit: Int!, $offset: Int) {
|
||||
@@ -58,12 +59,17 @@ ${FRAGMENT_FLIGHT_FULL_PRIVATE_INFO}
|
||||
`;
|
||||
|
||||
export const FLIGHT_PUBLIC_DETAIL = gql`
|
||||
query FlightPublicDetail($urlSlug: String!, $username: String) {
|
||||
query FlightPublicDetail($urlSlug: String, $username: String) {
|
||||
flight(urlSlug: $urlSlug, username: $username, public: true) {
|
||||
...flightFullInfo
|
||||
|
||||
copilots {
|
||||
...copilotPublicInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${FRAGMENT_FLIGHT_FULL_INFO}
|
||||
${FRAGMENT_COPILOT_PUBLIC_INFO}
|
||||
`;
|
||||
|
||||
|
||||
@@ -39,8 +39,21 @@ ${FRAGMENT_FLIGHT_LIST_INFO}
|
||||
`;
|
||||
|
||||
export const PHOTOS_PUBLIC_LIST = gql`
|
||||
query publicPhotos($flightId: Int, $pointOfInterestId: Int, $aircraftId: Int, $copilotId: Int) {
|
||||
photos(public: true, flightId: $flightId, pointOfInterestId: $pointOfInterestId, aircraftId: $aircraftId, copilotId: $copilotId) {
|
||||
query publicPhotos(
|
||||
$flightId: Int,
|
||||
$pointOfInterestId: Int,
|
||||
$aircraftId: Int,
|
||||
$copilotId: Int,
|
||||
$uploadedByCopilotId: Int
|
||||
) {
|
||||
photos(
|
||||
public: true,
|
||||
flightId: $flightId,
|
||||
pointOfInterestId: $pointOfInterestId,
|
||||
aircraftId: $aircraftId,
|
||||
copilotId: $copilotId,
|
||||
uploadedByCopilotId: $uploadedByCopilotId
|
||||
) {
|
||||
...photoFullInfo
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}}"
|
||||
/>
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ const routes = [
|
||||
children: [
|
||||
{path: '', name: "publicProfileDetail", component: () => import('pages/public/pilotProfile/FlightsListPage.vue')},
|
||||
{path: ':flightSlug', name: "publicFlightDetail", component: () => import('pages/public/pilotProfile/FlightDetailPage.vue')},
|
||||
{path: ':flightSlug/nahraj', name: "publicCopilotUploadPhoto", component: () => import('pages/public/pilotProfile/CopilotUploadPhotoPage.vue')},
|
||||
{path: 'a/:copilotSlug', name: "publicCopilotDetail", component: () => import('pages/public/pilotProfile/CopilotDetailPage.vue')},
|
||||
{path: 'udalosti', name: "publicEventsList", component: () => import('pages/public/pilotProfile/EventsListPage.vue')},
|
||||
{path: 'udalosti/:eventSlug', name: "publicEventDetail", component: () => import('pages/public/pilotProfile/EventDetailPage.vue')},
|
||||
|
||||
@@ -18,11 +18,11 @@ export const useCopilotsStore = defineStore('Copilots', () => {
|
||||
}).then(item => copilot.value = item);
|
||||
};
|
||||
|
||||
const fetchPublicDetail = (urlSlug, username) => {
|
||||
const fetchPublicDetail = ({urlSlug, username, uploadToken, uploadFlightSlug}) => {
|
||||
return query({
|
||||
isQueryRunning,
|
||||
query: COPILOT_PUBLIC_DETAIL,
|
||||
variables: {urlSlug, pilotUsername: username},
|
||||
variables: {urlSlug, pilotUsername: username, uploadToken, uploadFlightSlug},
|
||||
}).then(item => {
|
||||
copilot.value = item;
|
||||
return item;
|
||||
|
||||
@@ -21,10 +21,10 @@ export const usePhotosStore = defineStore('Photos', () => {
|
||||
variables: {id}
|
||||
}).then(item => photo.value = item);
|
||||
|
||||
const fetchPublic = ({flightId, pointOfInterestId, aircraftId, copilotId}) => query({
|
||||
const fetchPublic = ({flightId, pointOfInterestId, aircraftId, copilotId, uploadedByCopilotId}) => query({
|
||||
isQueryRunning,
|
||||
query: PHOTOS_PUBLIC_LIST,
|
||||
variables: {flightId, pointOfInterestId, aircraftId, copilotId}
|
||||
variables: {flightId, pointOfInterestId, aircraftId, copilotId, uploadedByCopilotId}
|
||||
}).then(items => photos.value = items);
|
||||
|
||||
const upload = (flightId, input) => {
|
||||
@@ -61,10 +61,10 @@ export const usePhotosStore = defineStore('Photos', () => {
|
||||
return item;
|
||||
})
|
||||
|
||||
const changeOrientation = (id, direction) => mutate({
|
||||
const changeOrientation = (id, direction, copilotUploadToken) => mutate({
|
||||
isMutationRunning,
|
||||
mutation: CHANGE_PHOTO_ORIENTATION,
|
||||
variables: {id, direction}
|
||||
variables: {id, direction, copilotUploadToken}
|
||||
}).then(item => {
|
||||
photo.value = item;
|
||||
return item;
|
||||
@@ -79,10 +79,10 @@ export const usePhotosStore = defineStore('Photos', () => {
|
||||
return item;
|
||||
});
|
||||
|
||||
const destroy = (id) => mutate({
|
||||
const destroy = (id, copilotUploadToken) => mutate({
|
||||
isMutationRunning,
|
||||
mutation: DELETE_PHOTO,
|
||||
variables: {id}
|
||||
variables: {id, copilotUploadToken}
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user