Slugy, webp, hromada refaktoringu
This commit is contained in:
@@ -5,6 +5,9 @@ import {setDefaultData} from "src/composables/form/formHandling";
|
||||
import FormWrapper from "components/FormWrapper.vue";
|
||||
import {commonProps} from "src/composables/inputs";
|
||||
import {isRequired} from "src/composables/form/rules";
|
||||
import SlugInput from "components/inputs/SlugInput.vue";
|
||||
import {storeToRefs} from "pinia";
|
||||
import {useLoggedUserStore} from "stores/LoggedUser";
|
||||
|
||||
const props = defineProps({
|
||||
copilot: {
|
||||
@@ -14,17 +17,22 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const formData = reactive({
|
||||
name: null,
|
||||
urlSlug: "",
|
||||
isPublic: false,
|
||||
});
|
||||
|
||||
const rules = {
|
||||
name: [isRequired]
|
||||
name: [isRequired],
|
||||
urlSlug: [],
|
||||
}
|
||||
|
||||
const loggedUser = storeToRefs(useLoggedUserStore()).user;
|
||||
|
||||
onMounted(() => {
|
||||
if (props.copilot) {
|
||||
setDefaultData(formData, props.copilot);
|
||||
@@ -41,5 +49,19 @@ onMounted(() => {
|
||||
:rules="rules.name"
|
||||
v-model="formData.name"
|
||||
/>
|
||||
|
||||
<template v-if="loggedUser?.publicUsername">
|
||||
<SlugInput
|
||||
v-if="formData.isPublic"
|
||||
v-bind="commonProps"
|
||||
v-model="formData.urlSlug"
|
||||
label="URL"
|
||||
:url="`/${loggedUser.publicUsername}/a/`"
|
||||
:slug-source="formData.name"
|
||||
:rules="rules.urlSlug"
|
||||
/>
|
||||
<q-checkbox v-model="formData.isPublic" label="Veřejný?"/>
|
||||
</template>
|
||||
|
||||
</FormWrapper>
|
||||
</template>
|
||||
|
||||
@@ -8,6 +8,9 @@ import {isRequired} from "src/composables/form/rules";
|
||||
import RichTextEditor from "components/inputs/RichTextEditor.vue";
|
||||
import DatetimePicker from "components/inputs/DatetimePicker.vue";
|
||||
import {FORMAT_DATE, FORMAT_DATETIME_LONG} from "src/composables/datetimeUtils";
|
||||
import SlugInput from "components/inputs/SlugInput.vue";
|
||||
import {storeToRefs} from "pinia";
|
||||
import {useLoggedUserStore} from "stores/LoggedUser";
|
||||
|
||||
const props = defineProps({
|
||||
event: {
|
||||
@@ -17,11 +20,12 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const formData = reactive({
|
||||
name: null,
|
||||
name: "",
|
||||
urlSlug: "",
|
||||
isPublic: false,
|
||||
description: "",
|
||||
dateFrom: null,
|
||||
@@ -38,6 +42,7 @@ onMounted(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const loggedUser = storeToRefs(useLoggedUserStore()).user;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -48,7 +53,15 @@ onMounted(() => {
|
||||
:rules="rules.name"
|
||||
v-model="formData.name"
|
||||
/>
|
||||
|
||||
<SlugInput
|
||||
v-if="formData.isPublic"
|
||||
v-bind="commonProps"
|
||||
v-model="formData.urlSlug"
|
||||
label="URL"
|
||||
:url="`/${loggedUser?.publicUsername}/udalosti/`"
|
||||
:slug-source="formData.name"
|
||||
/>
|
||||
<q-checkbox v-model="formData.isPublic" label="Veřejný?"/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col q-pr-sm">
|
||||
@@ -61,7 +74,7 @@ onMounted(() => {
|
||||
|
||||
|
||||
<RichTextEditor v-model="formData.description" v-bind="commonProps" label="Popis letu, poznámky, ..."/>
|
||||
<q-checkbox v-model="formData.isPublic" label="Veřejný?"/>
|
||||
|
||||
|
||||
</FormWrapper>
|
||||
</template>
|
||||
|
||||
@@ -8,8 +8,7 @@ const props = defineProps({
|
||||
},
|
||||
routeDetail: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({name: 'eventDetail', params: {}})
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -19,20 +18,20 @@ const getCoverPhotos = (item) => item.flights.filter(f => !!f.titlePhoto).map(f
|
||||
<template>
|
||||
<q-list bordered separator>
|
||||
<q-item
|
||||
v-for="event of items" :key="event"
|
||||
v-for="eventDetail of items" :key="eventDetail"
|
||||
clickable
|
||||
:to="{name: routeDetail.name, params: {...routeDetail.params, id: event.id}}"
|
||||
:to="{name: props.routeDetail.name, params: {...props.routeDetail.params, eventSlug: eventDetail.urlSlug, id: eventDetail.id}}"
|
||||
v-ripple
|
||||
>
|
||||
<q-item-section>
|
||||
<q-item-label>
|
||||
<q-avatar icon="event" color="primary" text-color="white" v-if="!event.flights || event.flights.length === 0"/>
|
||||
<AvatarsGroup :avatars="getCoverPhotos(event)" v-else/>
|
||||
<q-avatar icon="event" color="primary" text-color="white" v-if="!eventDetail.flights || eventDetail.flights.length === 0"/>
|
||||
<AvatarsGroup :avatars="getCoverPhotos(eventDetail)" v-else/>
|
||||
|
||||
{{ event.name }}
|
||||
{{ eventDetail.name }}
|
||||
</q-item-label>
|
||||
<q-item-label lines="4" caption>
|
||||
<article v-html="event.description"/>
|
||||
<article v-html="eventDetail.description"/>
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
@@ -22,7 +22,6 @@ const props = defineProps({
|
||||
required: false,
|
||||
default: null
|
||||
}
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -34,7 +33,7 @@ const props = defineProps({
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<router-link
|
||||
:to="{...aircraftDetailRoute, params: {...aircraftDetailRoute.params, id: flight.aircraft.id}}"
|
||||
:to="{...aircraftDetailRoute, params: {id: flight.aircraft.id, callSign: flight.aircraft.callSign}}"
|
||||
v-if="aircraftDetailRoute"
|
||||
>
|
||||
{{ flight.aircraft.callSign }}
|
||||
@@ -62,7 +61,9 @@ const props = defineProps({
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<div>
|
||||
<template v-if="flight.takeoffAirport">
|
||||
<strong>{{ flight.takeoffAirport.icaoCode }}</strong> ({{ flight.takeoffAirport.name }}),
|
||||
</template>
|
||||
čas vzletu: {{ formatDatetime(flight.takeoffDatetime, FORMAT_TIME) }}
|
||||
|
||||
<WeatherInfo class="q-mx-sm" v-if="flight.takeoffWeatherInfo" :weather="flight.takeoffWeatherInfo"/>
|
||||
@@ -78,7 +79,7 @@ const props = defineProps({
|
||||
<q-item-section>
|
||||
<template v-if="poi.pointOfInterest">
|
||||
<router-link
|
||||
:to="{name: poiDetailRoute.name, params: {...poiDetailRoute.params, id: poi.pointOfInterest.id}}"
|
||||
:to="{name: poiDetailRoute.name, params: {...poiDetailRoute.params, poiSlug: poi.pointOfInterest.urlSlug, id: poi.pointOfInterest.id}}"
|
||||
v-if="poi.pointOfInterest.isPublic"
|
||||
>
|
||||
{{ poi.pointOfInterest.name }}
|
||||
@@ -87,7 +88,11 @@ const props = defineProps({
|
||||
</template>
|
||||
<span v-if="poi.airport">
|
||||
<strong>{{ poi.airport.icaoCode }}</strong> ({{ poi.airport.name }}),
|
||||
<template v-if="poi.landingDuration > 0">
|
||||
délka mezipřistání {{ minutesToHumanReadable(poi.landingDuration) }}
|
||||
</template>
|
||||
<template v-else>touch and go</template>
|
||||
|
||||
</span>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
@@ -98,7 +103,9 @@ const props = defineProps({
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<div>
|
||||
<template v-if="flight.landingAirport">
|
||||
<strong>{{ flight.landingAirport.icaoCode }}</strong> ({{ flight.landingAirport.name }}),
|
||||
</template>
|
||||
čas přistání: {{ formatDatetime(flight.landingDatetime, FORMAT_TIME) }}
|
||||
<WeatherInfo class="q-mx-sm" v-if="flight.landingWeatherInfo" :weather="flight.landingWeatherInfo"/>
|
||||
</div>
|
||||
@@ -123,8 +130,8 @@ const props = defineProps({
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<router-link
|
||||
v-if="copilotDetailRoute"
|
||||
:to="{name: copilotDetailRoute.name, params: {...copilotDetailRoute.params, id: copilot.id}}"
|
||||
v-if="copilot.isPublic"
|
||||
:to="{name: copilotDetailRoute.name, params: {...copilotDetailRoute.params, copilotSlug: copilot.urlSlug, id: copilot.id}}"
|
||||
>
|
||||
{{ copilot.name }}
|
||||
</router-link>
|
||||
|
||||
@@ -20,7 +20,7 @@ const poisWithGPS = computed(() => props.flight.track.map(poi => poi.pointOfInte
|
||||
|
||||
const mapMarkers = [props.flight.takeoffAirport].concat(poisWithGPS.value).concat([props.flight.landingAirport]);
|
||||
const pointsOfInterests = computed(() => mapMarkers
|
||||
.filter(marker => marker.gpsLatitude && marker.gpsLongitude)
|
||||
.filter(marker => marker && marker.gpsLatitude && marker.gpsLongitude)
|
||||
.map(
|
||||
marker => ({label: marker.name, position: {lat: marker.gpsLatitude, lng: marker.gpsLongitude}})
|
||||
));
|
||||
|
||||
@@ -33,7 +33,7 @@ const props = defineProps({
|
||||
|
||||
const routeDetail = (flight) => {
|
||||
if (props.public)
|
||||
return {name: "publicFlightDetail", params: {username: flight.pilot.publicUsername, id: flight.id}}
|
||||
return {name: "publicFlightDetail", params: {username: flight.pilot.publicUsername, flightSlug: flight.urlSlug}}
|
||||
else
|
||||
return {name: "flightDetail", params: {id: flight.id}}
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const airportName = (airport) => `${airport.icaoCode} (${airport.name})`;
|
||||
@@ -32,8 +32,13 @@ const airportName = (airport) => `${airport.icaoCode} (${airport.name})`;
|
||||
const airports = computed(() => props.airports.map(a => ({id: a.id, name: airportName(a)})));
|
||||
const aircrafts = computed(() => props.aircrafts.map(a => ({id: a.id, name: a.callSign})));
|
||||
|
||||
|
||||
const manualInput = ref(false);
|
||||
const allowGPXUpload = ref(true);
|
||||
|
||||
const formData = reactive({
|
||||
aircraft: null,
|
||||
gpxTrackFile: null,
|
||||
takeoffAirport: null,
|
||||
landingAirport: null,
|
||||
takeoffDatetime: null,
|
||||
@@ -52,8 +57,14 @@ watch(() => props.flight, (newVal) => {
|
||||
if (newVal) {
|
||||
setDefaultData(formData, newVal);
|
||||
formData.aircraft = {id: newVal.aircraft.id, name: newVal.aircraft.callSign};
|
||||
|
||||
if (newVal.takeoffAirport)
|
||||
formData.takeoffAirport = {id: newVal.takeoffAirport.id, name: airportName(newVal.takeoffAirport)}
|
||||
|
||||
if (newVal.landingAirport)
|
||||
formData.landingAirport = {id: newVal.landingAirport.id, name: airportName(newVal.landingAirport)}
|
||||
allowGPXUpload.value = false;
|
||||
manualInput.value = true;
|
||||
}
|
||||
}, {immediate: true});
|
||||
watch(() => formData.takeoffDatetime, () => {
|
||||
@@ -67,18 +78,13 @@ watch(() => formData.takeoffAirport, () => {
|
||||
formData.landingAirport = {...formData.takeoffAirport};
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FormWrapper v-model="formData" :is-request-pending="props.isRequestPending">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<q-card class="q-my-sm">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Vyber letadlo</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section>
|
||||
<ComboboxInput
|
||||
:rules="rules.aircraft"
|
||||
v-bind="commonProps"
|
||||
@@ -86,17 +92,23 @@ watch(() => formData.takeoffAirport, () => {
|
||||
v-model="formData.aircraft"
|
||||
label="Letadlo"
|
||||
/>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-12 col-md-6">
|
||||
<q-card class="q-mr-md-sm q-my-sm">
|
||||
<q-card-section>
|
||||
|
||||
<div class="row" v-if="!manualInput">
|
||||
<div class="col">
|
||||
<div class="text-h6">Mám trasu v GPX, chci importovat data</div>
|
||||
<q-file v-bind="commonProps" v-model="formData.gpxTrackFile" label="Trasa v GPX" hint="Z trasy se načtou letiště a časy vzletu a přistání. Tyto hodnoty je možné později upravit."/>
|
||||
|
||||
<div class="text-center">
|
||||
<q-btn @click="manualInput = true" rounded flat>Zadat informace ručně</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" v-if="manualInput">
|
||||
<div class="col col-12 col-md-6 q-pr-md-sm">
|
||||
<div class="text-h6">Vzlet</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<DatetimePicker
|
||||
v-model="formData.takeoffDatetime"
|
||||
:rules="rules.takeoffDatetime"
|
||||
@@ -111,17 +123,10 @@ watch(() => formData.takeoffAirport, () => {
|
||||
label="Letiště vzletu"
|
||||
v-bind="commonProps"
|
||||
/>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</div>
|
||||
|
||||
<div class="col col-12 col-md-6">
|
||||
|
||||
<q-card class="q-ml-md-sm q-my-sm">
|
||||
<q-card-section>
|
||||
<div class="col col-12 col-md-6 q-pl-md-sm">
|
||||
<div class="text-h6">Přistání</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<DatetimePicker
|
||||
v-model="formData.landingDatetime"
|
||||
:min-date="formData.takeoffDatetime"
|
||||
@@ -137,9 +142,11 @@ watch(() => formData.takeoffAirport, () => {
|
||||
v-bind="commonProps"
|
||||
:rules="rules.landingAirport"
|
||||
/>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</div>
|
||||
<div class="col-12 text-center" v-if="allowGPXUpload">
|
||||
<q-btn @click="manualInput = false" rounded flat>Mám GPX, načíst informace z trasy</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</FormWrapper>
|
||||
</template>
|
||||
|
||||
@@ -5,6 +5,9 @@ import {setDefaultData} from "src/composables/form/formHandling";
|
||||
import RichTextEditor from "components/inputs/RichTextEditor.vue";
|
||||
import {commonProps} from "src/composables/inputs";
|
||||
import ComboboxInput from "components/inputs/ComboboxInput.vue";
|
||||
import SlugInput from "components/inputs/SlugInput.vue";
|
||||
import {storeToRefs} from "pinia";
|
||||
import {useLoggedUserStore} from "stores/LoggedUser";
|
||||
|
||||
const props = defineProps({
|
||||
flight: {
|
||||
@@ -25,9 +28,14 @@ const props = defineProps({
|
||||
const formData = reactive({
|
||||
name: "",
|
||||
event: null,
|
||||
description: ""
|
||||
description: "",
|
||||
urlSlug: "",
|
||||
isPublic: null
|
||||
})
|
||||
|
||||
|
||||
const loggedUser = storeToRefs(useLoggedUserStore()).user;
|
||||
|
||||
watch(() => props.flight, (newVal) => {
|
||||
if (newVal) {
|
||||
setDefaultData(formData, newVal);
|
||||
@@ -43,7 +51,18 @@ watch(() => props.flight, (newVal) => {
|
||||
<template>
|
||||
<FormWrapper v-model="formData" :is-request-pending="props.isRequestPending">
|
||||
<ComboboxInput :options="events" v-if="events" v-model="formData.event" label="Událost" v-bind="commonProps"/>
|
||||
<q-input type="text" v-model="formData.name" label="Jméno letu" v-bind="commonProps"/>
|
||||
<q-input type="text" v-model="formData.name" label="Jméno letu" v-bind="commonProps" />
|
||||
<template v-if="loggedUser?.publicUsername">
|
||||
<SlugInput
|
||||
v-if="formData.isPublic"
|
||||
v-bind="commonProps"
|
||||
v-model="formData.urlSlug"
|
||||
label="URL"
|
||||
:url="`/${loggedUser.publicUsername}/`"
|
||||
:slug-source="formData.name"
|
||||
/>
|
||||
<q-checkbox v-model="formData.isPublic" label="Veřejný?"/>
|
||||
</template>
|
||||
<RichTextEditor v-model="formData.description" v-bind="commonProps" label="Popis letu, poznámky, ..."/>
|
||||
</FormWrapper>
|
||||
</template>
|
||||
|
||||
@@ -3,6 +3,7 @@ import FormWrapper from "components/FormWrapper.vue";
|
||||
import {reactive, ref, watch} from "vue";
|
||||
import ComboboxInput from "components/inputs/ComboboxInput.vue";
|
||||
import {commonProps} from "src/composables/inputs";
|
||||
import {minutesToHumanReadable} from "src/composables/datetimeUtils";
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
@@ -24,7 +25,7 @@ const props = defineProps({
|
||||
|
||||
const formData = reactive({
|
||||
track: [],
|
||||
gpxTrack: null
|
||||
gpxTrackFile: null
|
||||
});
|
||||
const removeTrackItem = (index) => {
|
||||
formData.track.splice(index, 1);
|
||||
@@ -81,7 +82,7 @@ watch(() => props.flight.track, (newVal) => {
|
||||
|
||||
<q-file
|
||||
accept=".gpx"
|
||||
v-model="formData.gpxTrack"
|
||||
v-model="formData.gpxTrackFile"
|
||||
v-bind="commonProps"
|
||||
label="Nahrát trasu v GPX"
|
||||
hint="Slouží pro vykreslení kompletní a detailní trasy"
|
||||
@@ -91,6 +92,8 @@ watch(() => props.flight.track, (newVal) => {
|
||||
</template>
|
||||
</q-file>
|
||||
|
||||
<h5>Body na mapě</h5>
|
||||
|
||||
<ComboboxInput
|
||||
v-bind="commonProps"
|
||||
v-model="poi"
|
||||
@@ -103,7 +106,7 @@ watch(() => props.flight.track, (newVal) => {
|
||||
/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="col-12 col-md-8">
|
||||
<ComboboxInput
|
||||
v-bind="commonProps"
|
||||
v-model="airport"
|
||||
@@ -111,16 +114,16 @@ watch(() => props.flight.track, (newVal) => {
|
||||
:options="airports"
|
||||
clearable
|
||||
label="Letiště pro mezipřistání"
|
||||
hint="Nastav letiště a dobu mezispřistání"
|
||||
hint="Nastav letiště a dobu mezipřistání"
|
||||
/>
|
||||
</div>
|
||||
<div class="col" v-if="airport?.name">
|
||||
<div class="col-md-4 col-12" v-if="airport?.name">
|
||||
<q-input
|
||||
type="number"
|
||||
v-bind="commonProps"
|
||||
v-model="landingDuration"
|
||||
label="Délka mezipřistání (minuty)"
|
||||
class="q-mx-md"
|
||||
class="q-mx-md-md"
|
||||
>
|
||||
<template #after>
|
||||
<q-btn @click="addAirportToTrack(airport, landingDuration)" color="primary" icon="add"></q-btn>
|
||||
@@ -136,7 +139,7 @@ watch(() => props.flight.track, (newVal) => {
|
||||
<q-avatar color="primary" text-color="white" icon="flight_takeoff"/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
{{ flight.takeoffAirport.name }}
|
||||
{{ flight.takeoffAirport?.name || '???' }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
@@ -153,7 +156,11 @@ watch(() => props.flight.track, (newVal) => {
|
||||
</q-badge>
|
||||
</span>
|
||||
<span v-if="item.airport">
|
||||
{{ item.airport.name }}, mezipřistání: {{ item.landingDuration }} minut
|
||||
{{ item.airport.name }},
|
||||
<template v-if="item.landingDuration > 0">
|
||||
mezipřistání {{ minutesToHumanReadable(item.landingDuration) }}
|
||||
</template>
|
||||
<template v-else>touch and go</template>
|
||||
</span>
|
||||
|
||||
</q-item-section>
|
||||
@@ -167,7 +174,7 @@ watch(() => props.flight.track, (newVal) => {
|
||||
<q-avatar color="primary" text-color="white" icon="flight_land"/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
{{ flight.landingAirport.name }}
|
||||
{{ flight.landingAirport?.name || '???' }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<script setup>
|
||||
import {copyToClipboard, useQuasar} from "quasar";
|
||||
import {successObject} from "src/composables/dialog";
|
||||
import {useRouter} from "vue-router";
|
||||
|
||||
const props = defineProps({
|
||||
route: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const $router = useRouter();
|
||||
const $q = useQuasar();
|
||||
|
||||
|
||||
const copyLinkToClipboard = () => {
|
||||
const url = $router.resolve(props.route);
|
||||
copyToClipboard(window.location.origin + url.href).then(() => {
|
||||
$q.notify(successObject("Odkaz zkopírován"))
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-btn
|
||||
icon="link"
|
||||
@click="copyLinkToClipboard()"
|
||||
flat
|
||||
label="Získat odkaz"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,65 @@
|
||||
<script setup>
|
||||
import {useQuasar} from "quasar";
|
||||
import {ref, watch} from "vue";
|
||||
import {slugify} from "src/composables/slugify";
|
||||
|
||||
const props = defineProps({
|
||||
requireUrlSlug: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
isPublic: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
urlPrefix: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const emits = defineEmits(["changeVisibility"]);
|
||||
|
||||
const $q = useQuasar();
|
||||
const urlSlugModel = ref("");
|
||||
watch(() => urlSlugModel.value, (newVal) => {
|
||||
const slug = slugify(newVal);
|
||||
|
||||
if (urlSlugModel.value !== slug)
|
||||
urlSlugModel.value = slug;
|
||||
})
|
||||
const inputPrompt = () =>
|
||||
$q.dialog({
|
||||
title: 'URL',
|
||||
message: props.urlPrefix+"...",
|
||||
prompt: {
|
||||
model: urlSlugModel,
|
||||
placeholder: "URL fragment",
|
||||
isValid: val => val.length > 1,
|
||||
type: 'text' // optional
|
||||
},
|
||||
cancel: true,
|
||||
persistent: true
|
||||
})
|
||||
|
||||
|
||||
const togglePublicVisibility = () => {
|
||||
if (props.requireUrlSlug && !props.isPublic) {
|
||||
inputPrompt().onOk((data) => {
|
||||
console.log(data, urlSlugModel.value);
|
||||
emits("changeVisibility", {isPublic: !props.isPublic, urlSlug: slugify(urlSlugModel.value)});
|
||||
})
|
||||
} else {
|
||||
emits("changeVisibility", {isPublic: !props.isPublic});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-btn
|
||||
@click="togglePublicVisibility"
|
||||
:icon="isPublic ? 'visibility_off' : 'visibility'"
|
||||
flat
|
||||
:label="isPublic ? 'Skrýt' : 'Zveřejnit'"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,62 @@
|
||||
<script setup>
|
||||
import {ref, watch} from "vue";
|
||||
import {slugify} from "src/composables/slugify";
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String
|
||||
},
|
||||
slugSource: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
url : {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "URL"
|
||||
},
|
||||
rules: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => ([])
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
const model = ref("");
|
||||
|
||||
const emitValue = (val) => {
|
||||
emit("update:modelValue", val);
|
||||
}
|
||||
watch(() => model.value, (newVal) => {
|
||||
model.value = slugify(newVal);
|
||||
emitValue(newVal);
|
||||
})
|
||||
|
||||
|
||||
|
||||
watch(() => props.slugSource, (newVal, oldVal) => {
|
||||
if (model.value === "" || slugify(oldVal) === model.value) {
|
||||
// pokud puvodni vychozi hodnota je stejna jako aktualne nastaveny slug, zmenime model
|
||||
model.value = slugify(newVal);
|
||||
}
|
||||
}, {immediate: true});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-input
|
||||
v-model="model"
|
||||
:label="label"
|
||||
:rules="rules"
|
||||
hint="Veřejná URL adresa"
|
||||
>
|
||||
<template #prepend>
|
||||
{{ url }}
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
@@ -127,7 +127,7 @@ const showLarge = (displayedPhoto) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row" style="margin-bottom: 150px">
|
||||
<div class="row">
|
||||
<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">
|
||||
|
||||
@@ -8,6 +8,8 @@ import {errorObject} from "src/composables/dialog";
|
||||
import {commonProps} from "src/composables/inputs";
|
||||
import {isRequired} from "src/composables/form/rules";
|
||||
import ComboboxInput from "components/inputs/ComboboxInput.vue";
|
||||
import {useRouter} from "vue-router";
|
||||
import SlugInput from "components/inputs/SlugInput.vue";
|
||||
|
||||
const props = defineProps({
|
||||
poi: {
|
||||
@@ -23,7 +25,8 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const formData = reactive({
|
||||
name: null,
|
||||
name: "",
|
||||
urlSlug: "",
|
||||
description: "",
|
||||
gpsLatitude: null,
|
||||
gpsLongitude: null,
|
||||
@@ -47,7 +50,8 @@ const resetMap = () => {
|
||||
}
|
||||
|
||||
const rules = {
|
||||
name: [isRequired]
|
||||
name: [isRequired],
|
||||
urlSlug: []
|
||||
}
|
||||
|
||||
const zoom = ref(10);
|
||||
@@ -72,6 +76,18 @@ const handlePositionNotFound = () => {
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<SlugInput
|
||||
v-if="formData.isPublic"
|
||||
v-bind="commonProps"
|
||||
v-model="formData.urlSlug"
|
||||
label="URL"
|
||||
url="/mista/"
|
||||
:slug-source="formData.name"
|
||||
:rules="rules.urlSlug"
|
||||
/>
|
||||
<q-checkbox v-model="formData.isPublic" label="Veřejný?"/>
|
||||
|
||||
|
||||
<MapGPSInput
|
||||
v-if="poiNameForMap"
|
||||
:key="poiNameForMap"
|
||||
@@ -82,10 +98,8 @@ const handlePositionNotFound = () => {
|
||||
@not-found="handlePositionNotFound"
|
||||
/>
|
||||
|
||||
<q-input type="textarea" v-model="formData.description" v-bind="commonProps" label="Popis" />
|
||||
<q-input type="textarea" v-model="formData.description" v-bind="commonProps" label="Popis"/>
|
||||
|
||||
<ComboboxInput v-model="formData.type" :options="types" label="Kategorie" v-bind="commonProps" />
|
||||
|
||||
<q-checkbox v-model="formData.isPublic" label="Veřejný?"/>
|
||||
<ComboboxInput v-model="formData.type" :options="types" label="Kategorie" v-bind="commonProps"/>
|
||||
</FormWrapper>
|
||||
</template>
|
||||
|
||||
@@ -30,7 +30,7 @@ const props = defineProps({
|
||||
|
||||
const getRoute = (item) => {
|
||||
if (props.public)
|
||||
return {name: "publicPOIDetail", params: {id: item.id}}
|
||||
return {name: "publicPOIDetail", params: {slug: item.urlSlug}}
|
||||
else
|
||||
return {name: "poiDetail", params: {id: item.id}}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,6 @@ export const flightName = (flight) => {
|
||||
const track = flight.track ? flight.track.map(t => t.pointOfInterest?.name || t.airport?.icaoCode) : [];
|
||||
const trackStr = track.length > 0 ? ` - ${track.join(" - ")} - ` : ' - ';
|
||||
|
||||
return `${flight.takeoffAirport.icaoCode} ${trackStr} ${flight.landingAirport.icaoCode} (${date})`;
|
||||
return `${flight.takeoffAirport?.icaoCode || '???'} ${trackStr} ${flight.landingAirport?.icaoCode || "???"} (${date})`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,13 @@ export const setDefaultData = (formData, defaultData) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const toggleVisibility = ({store, $q, object}) => {
|
||||
console.log(object);
|
||||
export const toggleVisibility = ({store, $q, object, urlSlug = undefined}) => {
|
||||
const newVisibility = !object.isPublic;
|
||||
store.update(object.id, {isPublic: newVisibility})
|
||||
const variables = {isPublic: newVisibility};
|
||||
if (urlSlug) {
|
||||
variables['urlSlug'] = urlSlug;
|
||||
}
|
||||
store.update(object.id, variables)
|
||||
.then(() => {
|
||||
$q.notify(successObject(newVisibility ? "Úspěšně publikováno" : "Úspěšně skryto"));
|
||||
});
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
export const slugify = (text) => {
|
||||
// @see https://byby.dev/js-slugify-string
|
||||
return String(text)
|
||||
.normalize('NFKD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9 -]/g, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/-+/g, '-');
|
||||
}
|
||||
@@ -60,13 +60,8 @@ fragment aircraftFullPrivateInfo on Aircraft {
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
flights {
|
||||
...flightListInfo
|
||||
}
|
||||
}
|
||||
|
||||
${FRAGMENT_AIRCRAFT_FULL_INFO}
|
||||
${FRAGMENT_PHOTO_FULL_INFO}
|
||||
${FRAGMENT_FLIGHT_LIST_INFO}
|
||||
`;
|
||||
|
||||
@@ -15,6 +15,8 @@ export const FRAGMENT_COPILOT_FULL_INFO = gql`
|
||||
fragment copilotFullInfo on Copilot {
|
||||
id
|
||||
name
|
||||
isPublic
|
||||
urlSlug
|
||||
|
||||
titlePhoto {
|
||||
id
|
||||
@@ -25,13 +27,7 @@ fragment copilotFullInfo on Copilot {
|
||||
|
||||
export const FRAGMENT_COPILOT_FULL_PRIVATE_INFO = gql`
|
||||
fragment copilotFullPrivateInfo on Copilot {
|
||||
id
|
||||
name
|
||||
|
||||
titlePhoto {
|
||||
id
|
||||
url
|
||||
}
|
||||
...copilotFullInfo
|
||||
|
||||
photos {
|
||||
...photoFullInfo
|
||||
@@ -49,4 +45,5 @@ fragment copilotFullPrivateInfo on Copilot {
|
||||
}
|
||||
|
||||
${FRAGMENT_PHOTO_FULL_INFO}
|
||||
${FRAGMENT_COPILOT_FULL_INFO}
|
||||
`;
|
||||
|
||||
@@ -6,6 +6,7 @@ fragment eventListInfo on Event {
|
||||
name
|
||||
description
|
||||
isPublic
|
||||
urlSlug
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -15,6 +16,7 @@ fragment eventFullInfo on Event {
|
||||
name
|
||||
description
|
||||
isPublic
|
||||
urlSlug
|
||||
dateFrom
|
||||
dateTo
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ export const FRAGMENT_FLIGHT_FULL_INFO = gql`
|
||||
fragment flightFullInfo on Flight {
|
||||
id
|
||||
name
|
||||
urlSlug
|
||||
description
|
||||
takeoffDatetime
|
||||
landingDatetime
|
||||
@@ -53,6 +54,8 @@ fragment flightFullInfo on Flight {
|
||||
copilots {
|
||||
id
|
||||
name
|
||||
isPublic
|
||||
urlSlug
|
||||
}
|
||||
|
||||
event {
|
||||
|
||||
@@ -6,6 +6,7 @@ export const FRAGMENT_FLIGHT_LIST_INFO = gql`
|
||||
fragment flightListInfo on Flight {
|
||||
id
|
||||
name
|
||||
urlSlug
|
||||
takeoffDatetime
|
||||
landingDatetime
|
||||
durationMinCalculated
|
||||
|
||||
@@ -12,7 +12,6 @@ export const FRAGMENT_PHOTO_FULL_INFO = gql`
|
||||
aircraftId
|
||||
width
|
||||
height
|
||||
isFlightCover
|
||||
gpsLatitude
|
||||
gpsLongitude
|
||||
gpsAltitude
|
||||
|
||||
@@ -6,6 +6,7 @@ export const FRAGMENT_POI_LIST_INFO = gql`
|
||||
fragment pointOfInterestListInfo on PointOfInterest {
|
||||
id
|
||||
name
|
||||
urlSlug
|
||||
isPublic
|
||||
titlePhoto {
|
||||
thumbnailUrl
|
||||
@@ -21,6 +22,7 @@ export const FRAGMENT_POI_FULL_INFO = gql`
|
||||
fragment pointOfInterestFullInfo on PointOfInterest {
|
||||
id
|
||||
name
|
||||
urlSlug
|
||||
description
|
||||
isPublic
|
||||
gpsLatitude
|
||||
|
||||
@@ -25,8 +25,8 @@ ${FRAGMENT_AIRCRAFT_FULL_PRIVATE_INFO}
|
||||
`;
|
||||
|
||||
export const AIRCRAFT_PUBLIC_DETAIL = gql`
|
||||
query AircraftDetail($id: Int!) {
|
||||
aircraft(id: $id, public: true) {
|
||||
query AircraftDetail($callSign: String!) {
|
||||
aircraft(callSign: $callSign, public: true) {
|
||||
...aircraftFullInfo
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ ${FRAGMENT_FLIGHT_LIST_INFO}
|
||||
`;
|
||||
|
||||
export const COPILOT_PUBLIC_DETAIL = gql`
|
||||
query copilot($id: Int!, $pilotUsername: String) {
|
||||
copilot(id: $id, pilotUsername: $pilotUsername) {
|
||||
query copilot($urlSlug: String!, $pilotUsername: String) {
|
||||
copilot(urlSlug: $urlSlug, pilotUsername: $pilotUsername, public: true) {
|
||||
...copilotFullInfo
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +55,8 @@ ${FRAGMENT_EVENT_FULL_INFO}
|
||||
`;
|
||||
|
||||
export const EVENT_PUBLIC_DETAIL = gql`
|
||||
query eventPublicDetail($id: Int!, $username: String) {
|
||||
event(id: $id, username: $username, public: true) {
|
||||
query eventPublicDetail($urlSlug: String!, $username: String!) {
|
||||
event(urlSlug: $urlSlug, username: $username, public: true) {
|
||||
...eventFullInfo
|
||||
|
||||
flights {
|
||||
|
||||
@@ -4,8 +4,15 @@ import {FRAGMENT_FLIGHT_LIST_INFO} from "src/graphql/fragments/flight_list";
|
||||
import {FRAGMENT_PHOTO_FULL_INFO} from "src/graphql/fragments/photo";
|
||||
|
||||
export const FLIGHTS_LIST = gql`
|
||||
query FlightsList($limit: Int!, $offset: Int) {
|
||||
flights(limit: $limit, offset: $offset) {
|
||||
query FlightsList($eventId: Int, $aircraftId: Int, $copilotId: Int, $pointOfInterestId: Int, $limit: Int!, $offset: Int) {
|
||||
flights(
|
||||
eventId: $eventId,
|
||||
aircraftId: $aircraftId,
|
||||
copilotId: $copilotId,
|
||||
pointOfInterestId: $pointOfInterestId,
|
||||
limit: $limit,
|
||||
offset: $offset
|
||||
) {
|
||||
totalItemsCount
|
||||
items {
|
||||
...flightListInfo
|
||||
@@ -51,8 +58,8 @@ ${FRAGMENT_FLIGHT_FULL_PRIVATE_INFO}
|
||||
`;
|
||||
|
||||
export const FLIGHT_PUBLIC_DETAIL = gql`
|
||||
query FlightPublicDetail($id: Int!, $username: String) {
|
||||
flight(id: $id, username: $username, public: true) {
|
||||
query FlightPublicDetail($urlSlug: String!, $username: String) {
|
||||
flight(urlSlug: $urlSlug, username: $username, public: true) {
|
||||
...flightFullInfo
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@ ${FRAGMENT_POI_FULL_PRIVATE_INFO}
|
||||
`;
|
||||
|
||||
export const POI_PUBLIC_DETAIL = gql`
|
||||
query publicPOIDetail($id: Int!) {
|
||||
pointOfInterest(id: $id, public: true) {
|
||||
query publicPOIDetail($urlSlug: String!) {
|
||||
pointOfInterest(urlSlug: $urlSlug, public: true) {
|
||||
...pointOfInterestFullInfo
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
<q-page-container>
|
||||
<BreadcrumbsNavigation :items="breadcrumbs || []"/>
|
||||
<div class="q-mx-md q-my-md" style="margin-bottom: 150px">
|
||||
<div class="q-mx-md q-my-md" style="padding-bottom: 150px">
|
||||
<router-view/>
|
||||
</div>
|
||||
</q-page-container>
|
||||
@@ -56,7 +56,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import {ref} from 'vue'
|
||||
import {ref, watch} from 'vue'
|
||||
import {storeToRefs} from "pinia";
|
||||
import BreadcrumbsNavigation from "components/navigation/BreadcrumbsNavigation.vue";
|
||||
import {useAppStore} from "stores/App";
|
||||
@@ -71,9 +71,12 @@ const $q = useQuasar();
|
||||
const $router = useRouter();
|
||||
|
||||
const {pageTitle, breadcrumbs} = storeToRefs(useAppStore());
|
||||
useMeta(() => metaData({
|
||||
watch(() => pageTitle.value, () => {
|
||||
useMeta(() => metaData({
|
||||
title: pageTitle.value
|
||||
}));
|
||||
}));
|
||||
|
||||
})
|
||||
|
||||
const leftDrawerOpen = ref(false)
|
||||
const toggleLeftDrawer = () => {
|
||||
|
||||
@@ -11,14 +11,16 @@ const $router = useRouter();
|
||||
const url = computed(() => $route.query.url);
|
||||
|
||||
onMounted(() => {
|
||||
console.log(useAuthStore().getAccessToken());
|
||||
useAuthStore().refreshAccessToken().then(() => {
|
||||
console.log("refreshnuto", $route.query.url);
|
||||
$router.push($route.query.url)
|
||||
}).catch(e => {
|
||||
$router.push({name: "login"})
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
{{ url }}
|
||||
<q-spinner />
|
||||
</template>
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
<script setup>
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
import {copyToClipboard, useMeta, useQuasar} from "quasar";
|
||||
import {useMeta, useQuasar} from "quasar";
|
||||
import AircraftDetailSkeleton from "components/aircrafts/skeletons/AircraftDetailSkeleton.vue";
|
||||
import {confirmObject, successObject} from "src/composables/dialog";
|
||||
import {useAppStore} from "stores/App";
|
||||
import FlightsList from "components/flights/FlightsList.vue";
|
||||
import AircraftDetailCard from "components/aircrafts/AircraftInfo.vue";
|
||||
import {storeToRefs} from "pinia";
|
||||
import {useAircraftsStore} from "stores/Aircrafts";
|
||||
import {computed, watch} from "vue";
|
||||
import {watch} from "vue";
|
||||
import {metaData} from "src/composables/meta";
|
||||
import PhotoGallery from "components/photos/PhotoGallery.vue";
|
||||
import TitlePhotoWithGallery from "components/photos/TitlePhotoWithGallery.vue";
|
||||
import ContentCard from "components/ContentCard.vue";
|
||||
import AircraftInfo from "components/aircrafts/AircraftInfo.vue";
|
||||
import {useLoggedUserStore} from "stores/LoggedUser";
|
||||
import {useFlightsStore} from "stores/Flights";
|
||||
import {toggleVisibility} from "src/composables/form/formHandling";
|
||||
import ContentCardEditButton from "components/ContentCardEditButton.vue";
|
||||
|
||||
const $q = useQuasar();
|
||||
const $router = useRouter();
|
||||
import {loadPage, paginationLimit} from "src/composables/pagination";
|
||||
import BasicInfoBanner from "components/banners/BasicInfoBanner.vue";
|
||||
import FlightsListSkeleton from "components/flights/skeletons/FlightsListSkeleton.vue";
|
||||
import CopyLinkButton from "components/inputs/CopyLinkButton.vue";
|
||||
import PublishButton from "components/inputs/PublishButton.vue";
|
||||
|
||||
const aircraftId = parseInt(useRoute().params.id);
|
||||
|
||||
useAircraftsStore().fetchDetail(aircraftId);
|
||||
const loggedUser = storeToRefs(useLoggedUserStore()).user;
|
||||
const {aircraft, isQueryRunning} = storeToRefs(useAircraftsStore());
|
||||
watch(() => aircraft.value, (newVal) => {
|
||||
if (!newVal) return;
|
||||
@@ -37,6 +37,8 @@ watch(() => aircraft.value, (newVal) => {
|
||||
])
|
||||
});
|
||||
|
||||
const $q = useQuasar();
|
||||
const $router = useRouter();
|
||||
const deleteAircraft = (id) => {
|
||||
$q.dialog(confirmObject('Opravdu chceš smazat toto letadlo?'))
|
||||
.onOk(() => {
|
||||
@@ -48,20 +50,18 @@ const deleteAircraft = (id) => {
|
||||
};
|
||||
|
||||
const togglePublicVisibility = () => toggleVisibility({
|
||||
$q,
|
||||
store: useAircraftsStore(),
|
||||
object: aircraft.value
|
||||
$q, store: useAircraftsStore(), object: aircraft.value
|
||||
});
|
||||
|
||||
const copyLinkToClipboard = () => {
|
||||
const url = $router.resolve({
|
||||
name: "publicAircraftDetail",
|
||||
params: {id: aircraftId}
|
||||
});
|
||||
copyToClipboard(window.location.origin + url.href).then(() => {
|
||||
$q.notify(successObject("Odkaz zkopírován"))
|
||||
});
|
||||
};
|
||||
const loadNextFlightsPage = (appendItems) => loadPage({
|
||||
fetchFunc: useFlightsStore().fetch,
|
||||
filters: {aircraftId: aircraftId},
|
||||
appendItems
|
||||
});
|
||||
const {isQueryRunning: areFlightsLoading, totalItemsCount, flights} = storeToRefs(useFlightsStore());
|
||||
loadNextFlightsPage(false);
|
||||
|
||||
const loggedUser = storeToRefs(useLoggedUserStore()).user;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -71,18 +71,15 @@ const copyLinkToClipboard = () => {
|
||||
<ContentCard :title-photo="aircraft.titlePhoto" :title="aircraft.callSign">
|
||||
<template #actions v-if="aircraft.createdById === loggedUser?.id">
|
||||
<div class="col-grow">
|
||||
<q-btn
|
||||
@click="togglePublicVisibility"
|
||||
:icon="aircraft.isPublic ? 'visibility_off' : 'visibility'"
|
||||
flat
|
||||
:label="aircraft.isPublic ? 'Skrýt' : 'Zveřejnit'"
|
||||
<PublishButton
|
||||
url-prefix="/letadla"
|
||||
:is-public="aircraft.isPublic"
|
||||
:require-url-slug="false"
|
||||
@change-visibility="togglePublicVisibility"
|
||||
/>
|
||||
<q-btn
|
||||
icon="link"
|
||||
@click="copyLinkToClipboard()"
|
||||
flat
|
||||
<CopyLinkButton
|
||||
v-if="aircraft.isPublic"
|
||||
label="Získat odkaz"
|
||||
:route="{name: 'publicAircraftDetail', params: {callsign: aircraft.callSign}}"
|
||||
/>
|
||||
</div>
|
||||
<q-btn @click="deleteAircraft(aircraft.id)" icon="delete" flat text-color="red" label="Smazat"/>
|
||||
@@ -101,11 +98,21 @@ const copyLinkToClipboard = () => {
|
||||
label="Titulní fotografie"
|
||||
/>
|
||||
<PhotoGallery :photos="aircraft.photos"/>
|
||||
|
||||
<h3>Lety</h3>
|
||||
<q-banner v-if="aircraft.flights.length === 0" class="bg-light-blue text-white">
|
||||
<FlightsListSkeleton v-if="areFlightsLoading && flights.length === 0"/>
|
||||
<BasicInfoBanner v-if="flights.length === 0">
|
||||
Zatím žádné lety.
|
||||
</q-banner>
|
||||
<FlightsList :items="aircraft.flights" :public="false" v-else/>
|
||||
</BasicInfoBanner>
|
||||
<FlightsList
|
||||
v-else-if="flights.length > 0"
|
||||
:public="false"
|
||||
:items="flights"
|
||||
:total-items-count="totalItemsCount"
|
||||
:is-query-running="areFlightsLoading"
|
||||
:items-per-page="paginationLimit"
|
||||
@load-more="loadNextFlightsPage(true)"
|
||||
/>
|
||||
</q-card-section>
|
||||
</template>
|
||||
</ContentCard>
|
||||
|
||||
@@ -13,6 +13,7 @@ import {useLoggedUserStore} from "stores/LoggedUser";
|
||||
import PhotosEditorList from "components/photos/PhotosEditorList.vue";
|
||||
import CommonEditSwitcher from "components/navigation/CommonEditSwitcher.vue";
|
||||
import PhotoGallerySkeleton from "components/photos/skeletons/PhotoGallerySkeleton.vue";
|
||||
import BasicInfoBanner from "components/banners/BasicInfoBanner.vue";
|
||||
|
||||
const $router = useRouter();
|
||||
const $q = useQuasar();
|
||||
@@ -56,9 +57,10 @@ const formStep = computed(() => useRoute().params.step || "info");
|
||||
<template v-else>
|
||||
<CommonEditSwitcher :route="{name: 'aircraftEdit', params: {id: aircraft?.id}}"/>
|
||||
|
||||
<template v-if="formStep === 'basic'">
|
||||
<template v-if="formStep === 'info'">
|
||||
<AircraftEditSkeleton v-if="isQueryRunning || !aircraft"/>
|
||||
<AircraftForm
|
||||
v-else
|
||||
@save="handleSave"
|
||||
:default-values="aircraft"
|
||||
:organizations="loggedUser.organizations"
|
||||
@@ -68,6 +70,9 @@ const formStep = computed(() => useRoute().params.step || "info");
|
||||
|
||||
<template v-else-if="formStep === 'photo'">
|
||||
<PhotoGallerySkeleton v-if="isQueryRunning || !aircraft"/>
|
||||
<BasicInfoBanner v-else-if="aircraft && aircraft.photos.length === 0">
|
||||
U tohoto letadla nemáš žádnou fotku. Můžeš ji přidat v editaci fotky.
|
||||
</BasicInfoBanner>
|
||||
<PhotosEditorList
|
||||
v-else
|
||||
:photos="aircraft.photos"
|
||||
|
||||
@@ -8,27 +8,46 @@ import {useCopilotsStore} from "stores/Copilots";
|
||||
import {storeToRefs} from "pinia";
|
||||
import PhotoGallery from "components/photos/PhotoGallery.vue";
|
||||
import ContentCard from "components/ContentCard.vue";
|
||||
import {loadPage, paginationLimit} from "src/composables/pagination";
|
||||
import {useFlightsStore} from "stores/Flights";
|
||||
import FlightsListSkeleton from "components/flights/skeletons/FlightsListSkeleton.vue";
|
||||
import {useMeta, useQuasar} from "quasar";
|
||||
import {useLoggedUserStore} from "stores/LoggedUser";
|
||||
import PublishButton from "components/inputs/PublishButton.vue";
|
||||
import CopyLinkButton from "components/inputs/CopyLinkButton.vue";
|
||||
import {toggleVisibility} from "src/composables/form/formHandling";
|
||||
import {metaData} from "src/composables/meta";
|
||||
|
||||
const copilotId = parseInt(useRoute().params.id);
|
||||
onMounted(() => {
|
||||
useCopilotsStore().fetchDetail(copilotId).then(() => {
|
||||
useCopilotsStore().fetchDetail(copilotId).then(() => {
|
||||
useAppStore().setPageTitle(copilot.value.name);
|
||||
useAppStore().setBreadcrumbs([
|
||||
{to: {name: "copilots"}, label: "Kopiloti"},
|
||||
{label: copilot.value.name}
|
||||
])
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
const loadNextFlightsPage = (appendItems) => loadPage({
|
||||
fetchFunc: useFlightsStore().fetch,
|
||||
filters: {copilotId: copilotId},
|
||||
appendItems
|
||||
});
|
||||
loadNextFlightsPage(false);
|
||||
const {isQueryRunning: areFlightsLoading, totalItemsCount, flights} = storeToRefs(useFlightsStore());
|
||||
const {isQueryRunning, copilot} = storeToRefs(useCopilotsStore());
|
||||
const loggedUser = storeToRefs(useLoggedUserStore()).user;
|
||||
|
||||
const $q = useQuasar();
|
||||
const togglePublicVisibility = (data) => toggleVisibility({
|
||||
$q, store: useCopilotsStore(), object: copilot.value, urlSlug: data.urlSlug
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CopilotDetailSkeleton v-if="isQueryRunning || !copilot"/>
|
||||
|
||||
<template v-else>
|
||||
|
||||
<ContentCard :title-photo="copilot.titlePhoto" :title="copilot.name">
|
||||
<ContentCard :title-photo="copilot.titlePhoto" :title="copilot.name" v-else>
|
||||
<template #actions>
|
||||
<div class="col-grow">
|
||||
<q-btn
|
||||
@@ -38,6 +57,21 @@ const {isQueryRunning, copilot} = storeToRefs(useCopilotsStore());
|
||||
text-color="primary"
|
||||
label="Upravit"
|
||||
/>
|
||||
|
||||
<PublishButton
|
||||
:url-prefix="`/${loggedUser?.publicUsername}/a/`"
|
||||
:is-public="copilot.isPublic"
|
||||
:require-url-slug="copilot.urlSlug === ''"
|
||||
@change-visibility="togglePublicVisibility"
|
||||
/>
|
||||
<CopyLinkButton
|
||||
v-if="copilot.isPublic"
|
||||
:route="{
|
||||
name: 'publicCopilotDetail', params: {
|
||||
username: loggedUser.publicUsername,
|
||||
copilotSlug: copilot.urlSlug
|
||||
}}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
@@ -50,15 +84,20 @@ const {isQueryRunning, copilot} = storeToRefs(useCopilotsStore());
|
||||
|
||||
<h3>Společné lety</h3>
|
||||
|
||||
<q-banner v-if="copilot.flights.length === 0" class="bg-light-blue text-white">
|
||||
<FlightsListSkeleton v-if="areFlightsLoading && !flights.length"/>
|
||||
<FlightsList
|
||||
v-else-if="flights.length > 0"
|
||||
:public="false"
|
||||
:items="flights"
|
||||
:total-items-count="totalItemsCount"
|
||||
:is-query-running="areFlightsLoading"
|
||||
:items-per-page="paginationLimit"
|
||||
@load-more="loadNextFlightsPage(true)"
|
||||
/>
|
||||
<q-banner v-if="flights.length === 0" class="bg-light-blue text-white">
|
||||
Zatím tu nemáš uložené žádné společné lety.
|
||||
</q-banner>
|
||||
|
||||
<FlightsList :items="copilot.flights" :public="false" v-else/>
|
||||
</q-card-section>
|
||||
</template>
|
||||
|
||||
</ContentCard>
|
||||
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {storeToRefs} from "pinia";
|
||||
import PhotosEditorList from "components/photos/PhotosEditorList.vue";
|
||||
import CommonEditSwitcher from "components/navigation/CommonEditSwitcher.vue";
|
||||
import PhotoGallerySkeleton from "components/photos/skeletons/PhotoGallerySkeleton.vue";
|
||||
import BasicInfoBanner from "components/banners/BasicInfoBanner.vue";
|
||||
|
||||
const $q = useQuasar();
|
||||
const $router = useRouter();
|
||||
@@ -61,6 +62,9 @@ const setTitlePhoto = (titlePhoto) => {
|
||||
|
||||
<template v-else-if="formStep === 'photo'">
|
||||
<PhotoGallerySkeleton v-if="isQueryRunning || !copilot" />
|
||||
<BasicInfoBanner v-else-if="!isQueryRunning && copilot.photos.length === 0">
|
||||
U tohoto kopilota nemáš označenou žádnou fotku. Bez toho není možné zvolit úvodní fotografii.
|
||||
</BasicInfoBanner>
|
||||
<PhotosEditorList
|
||||
v-else
|
||||
:photos="copilot.photos"
|
||||
|
||||
@@ -8,6 +8,8 @@ import FlightsList from "components/flights/FlightsList.vue";
|
||||
import {FORMAT_DATE, formatDatetime} from "src/composables/datetimeUtils";
|
||||
import {useEventsStore} from "stores/Events";
|
||||
import {storeToRefs} from "pinia";
|
||||
import {loadPage, paginationLimit} from "src/composables/pagination";
|
||||
import {useFlightsStore} from "stores/Flights";
|
||||
|
||||
const eventId = parseInt(useRoute().params.id);
|
||||
onMounted(() => {
|
||||
@@ -18,9 +20,18 @@ onMounted(() => {
|
||||
{label: event.value.name}
|
||||
])
|
||||
});
|
||||
loadNextFlightsPage(false);
|
||||
})
|
||||
|
||||
const {isQueryRunning, event} = storeToRefs(useEventsStore());
|
||||
|
||||
const loadNextFlightsPage = (appendItems) => loadPage({
|
||||
fetchFunc: useFlightsStore().fetch,
|
||||
filters: {eventId: eventId},
|
||||
appendItems
|
||||
});
|
||||
const {isQueryRunning: areFlightsLoading, totalItemsCount, flights} = storeToRefs(useFlightsStore());
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -49,8 +60,15 @@ const {isQueryRunning, event} = storeToRefs(useEventsStore());
|
||||
Zatím tu nemáš uložené žádné lety.
|
||||
</q-banner>
|
||||
|
||||
<FlightsList :items="event.flights" :public="false" v-else/>
|
||||
|
||||
<FlightsList
|
||||
v-else-if="flights.length > 0"
|
||||
:public="false"
|
||||
:items="flights"
|
||||
:total-items-count="totalItemsCount"
|
||||
:is-query-running="areFlightsLoading"
|
||||
:items-per-page="paginationLimit"
|
||||
@load-more="loadNextFlightsPage(true)"
|
||||
/>
|
||||
<FAB mode="edit" :router-link="{name: 'eventEdit', params: {id: eventId}}"/>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -6,10 +6,14 @@ import BasicInfoBanner from "components/banners/BasicInfoBanner.vue";
|
||||
import EventsList from "components/event/EventsList.vue";
|
||||
import {storeToRefs} from "pinia";
|
||||
import {useEventsStore} from "stores/Events";
|
||||
import {onBeforeMount} from "vue";
|
||||
|
||||
useEventsStore().fetch();
|
||||
useAppStore().setPageTitle("Letecké události");
|
||||
|
||||
onBeforeMount(() => {
|
||||
useEventsStore().fetch({});
|
||||
});
|
||||
|
||||
const {isQueryRunning, events} = storeToRefs(useEventsStore());
|
||||
</script>
|
||||
|
||||
@@ -31,6 +35,7 @@ const {isQueryRunning, events} = storeToRefs(useEventsStore());
|
||||
<EventsList
|
||||
v-else
|
||||
:items="events"
|
||||
:route-detail="{name: 'eventDetail', params: {}}"
|
||||
/>
|
||||
|
||||
<FAB :router-link="{name: 'addEvent'}" mode="add"/>
|
||||
|
||||
@@ -208,6 +208,7 @@ const handleBatchPhotoEdit = async (flightId, data) => {
|
||||
<q-tab-panel name="basic">
|
||||
<FlightEditSkeleton :step="formStep" v-if="isQueryRunning || !flight"/>
|
||||
<FlightFormBasic
|
||||
v-else
|
||||
@save="handleFlightEdit"
|
||||
:flight="flight"
|
||||
:locations="pointsOfInterest"
|
||||
@@ -217,14 +218,18 @@ const handleBatchPhotoEdit = async (flightId, data) => {
|
||||
</q-tab-panel>
|
||||
|
||||
<q-tab-panel name="copilot">
|
||||
<FlightEditCopilotSkeleton :step="formStep" v-if="isQueryRunning || !flight"/>
|
||||
<FlightFormCopilot @save="handleFlightEdit" :flight="flight" :copilots="copilots"/>
|
||||
<FlightEditCopilotSkeleton v-if="isQueryRunning || !flight"/>
|
||||
<FlightFormCopilot
|
||||
v-else
|
||||
@save="handleFlightEdit"
|
||||
:flight="flight"
|
||||
:copilots="copilots"/>
|
||||
</q-tab-panel>
|
||||
|
||||
<q-tab-panel name="track">
|
||||
<FlightEditSkeleton :step="formStep" v-if="isQueryRunning || !flight"/>
|
||||
<FlightFormTrack
|
||||
v-if="pointsOfInterest"
|
||||
v-if="flight && pointsOfInterest"
|
||||
:flight="flight"
|
||||
:locations="pointsOfInterest"
|
||||
:airports="airports"
|
||||
@@ -239,6 +244,7 @@ const handleBatchPhotoEdit = async (flightId, data) => {
|
||||
@save="handlePhotosUpload"
|
||||
/>
|
||||
|
||||
<template v-if="flight">
|
||||
<PhotosEditorList
|
||||
:title-photo="flight.titlePhoto"
|
||||
:photos="flight.photos"
|
||||
@@ -269,6 +275,8 @@ const handleBatchPhotoEdit = async (flightId, data) => {
|
||||
@save="handleBatchPhotoEdit(flightId, $event)"
|
||||
@delete="deletePhoto"
|
||||
/>
|
||||
|
||||
</template>
|
||||
</q-tab-panel>
|
||||
|
||||
<q-tab-panel name="notes">
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script setup>
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
import {computed, onMounted, ref, watch} from "vue";
|
||||
import {watch} from "vue";
|
||||
import FlightDetailSkeleton from "components/flights/skeletons/FlightDetailSkeleton.vue";
|
||||
import {flightName} from "src/composables/flight";
|
||||
import {useAppStore} from "stores/App";
|
||||
import FlightBasicInfo from "components/flights/FlightBasicInfo.vue";
|
||||
import {copyToClipboard, useQuasar} from "quasar";
|
||||
import {useQuasar} from "quasar";
|
||||
import {confirmObject, successObject} from "src/composables/dialog";
|
||||
import {useFlightsStore} from "stores/Flights";
|
||||
import FlightTrackMap from "components/flights/FlightTrackMap.vue";
|
||||
@@ -13,13 +13,12 @@ import {useLoggedUserStore} from "stores/LoggedUser";
|
||||
import {storeToRefs} from "pinia";
|
||||
import BasicInfoBanner from "components/banners/BasicInfoBanner.vue";
|
||||
import PhotoGallery from "components/photos/PhotoGallery.vue";
|
||||
import TitlePhotoWithGallery from "components/photos/TitlePhotoWithGallery.vue";
|
||||
import ContentCard from "components/ContentCard.vue";
|
||||
import ContentCardEditButton from "components/ContentCardEditButton.vue";
|
||||
import {toggleVisibility} from "src/composables/form/formHandling";
|
||||
import PublishButton from "components/inputs/PublishButton.vue";
|
||||
import CopyLinkButton from "components/inputs/CopyLinkButton.vue";
|
||||
|
||||
const $q = useQuasar();
|
||||
const $router = useRouter();
|
||||
const flightId = parseInt(useRoute().params.id);
|
||||
const loggedUser = storeToRefs(useLoggedUserStore()).user;
|
||||
|
||||
@@ -35,6 +34,8 @@ watch(() => flight.value, (f) => {
|
||||
]);
|
||||
});
|
||||
|
||||
const $q = useQuasar();
|
||||
const $router = useRouter();
|
||||
const handleDeleteAndRedirect = () => {
|
||||
$q.dialog(confirmObject("Opravdu chceš smazat tento let?"))
|
||||
.onOk(() => {
|
||||
@@ -45,47 +46,36 @@ const handleDeleteAndRedirect = () => {
|
||||
});
|
||||
}
|
||||
|
||||
const togglePublicVisibility = () => toggleVisibility({
|
||||
const togglePublicVisibility = (data) => toggleVisibility({
|
||||
$q,
|
||||
store: useFlightsStore(),
|
||||
object: flight.value
|
||||
object: flight.value,
|
||||
urlSlug: data.isPublic ? data.urlSlug : undefined
|
||||
});
|
||||
|
||||
const copyLinkToClipboard = () => {
|
||||
const loggedUser = useLoggedUserStore().user;
|
||||
const url = $router.resolve({
|
||||
name: "publicFlightDetail",
|
||||
params: {id: flightId, username: loggedUser.publicUsername}
|
||||
});
|
||||
copyToClipboard(window.location.origin + url.href).then(() => {
|
||||
$q.notify(successObject("Odkaz zkopírován"))
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FlightDetailSkeleton v-if="isQueryRunning || !flight"/>
|
||||
|
||||
<ContentCard :title-photo="flight.titlePhoto" :title="flightName(flight)" v-else>
|
||||
|
||||
<template #actions>
|
||||
<template v-if="loggedUser.publicUsername">
|
||||
<div class="col-grow">
|
||||
<q-btn
|
||||
@click="togglePublicVisibility"
|
||||
:icon="flight.isPublic ? 'visibility_off' : 'visibility'"
|
||||
:disable="!loggedUser?.publicUsername"
|
||||
flat
|
||||
:label="flight.isPublic ? 'Skrýt' : 'Zveřejnit'"
|
||||
<PublishButton
|
||||
:url-prefix="`/${loggedUser.publicUsername}/`"
|
||||
:is-public="flight.isPublic"
|
||||
:require-url-slug="!flight.urlSlug"
|
||||
@change-visibility="togglePublicVisibility"
|
||||
/>
|
||||
|
||||
<q-btn
|
||||
icon="link"
|
||||
@click="copyLinkToClipboard()"
|
||||
flat
|
||||
<CopyLinkButton
|
||||
:route='{
|
||||
name: "publicFlightDetail",
|
||||
params: {flightSlug: flight.urlSlug, username: loggedUser.publicUsername}
|
||||
}'
|
||||
v-if="flight.isPublic"
|
||||
label="Získat odkaz"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<q-btn @click="handleDeleteAndRedirect" icon="delete" flat text-color="red" label="Smazat"/>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import {computed, onMounted, ref} from "vue";
|
||||
import {computed, onMounted} from "vue";
|
||||
import {useAircraftsStore} from "stores/Aircrafts";
|
||||
import {useFlightsStore} from "stores/Flights";
|
||||
import {useRouter} from "vue-router";
|
||||
|
||||
@@ -67,7 +67,11 @@ const {isQueryRunning, organization} = storeToRefs(useOrganizationsStore());
|
||||
|
||||
<BasicListSkeleton :items-count-min="1" :items-count-max="5" v-if="isQueryRunning || !organization"/>
|
||||
<template v-else>
|
||||
<AircraftsList :items="organization.aircrafts" v-if="organization.aircrafts.length > 0"/>
|
||||
<AircraftsList
|
||||
v-if="organization.aircrafts.length > 0"
|
||||
:items="organization.aircrafts"
|
||||
:total-items-count="organization.aircrafts.length"
|
||||
/>
|
||||
<BasicInfoBanner v-else>
|
||||
Žádné letadlo
|
||||
</BasicInfoBanner>
|
||||
|
||||
@@ -15,6 +15,7 @@ import PhotoEditorSkeleton from "components/photos/skeletons/PhotoEditorSkeleton
|
||||
import PhotosEditorList from "components/photos/PhotosEditorList.vue";
|
||||
import CommonEditSwitcher from "components/navigation/CommonEditSwitcher.vue";
|
||||
import PhotoGallerySkeleton from "components/photos/skeletons/PhotoGallerySkeleton.vue";
|
||||
import BasicInfoBanner from "components/banners/BasicInfoBanner.vue";
|
||||
|
||||
const poiId = parseInt(useRoute().params.id);
|
||||
|
||||
@@ -69,6 +70,9 @@ const formStep = computed(() => useRoute().params.step || "info");
|
||||
|
||||
<template v-else-if="formStep === 'photo'">
|
||||
<PhotoGallerySkeleton v-if="isQueryRunning || !pointOfInterest"/>
|
||||
<BasicInfoBanner v-else-if="!isQueryRunning && pointOfInterest.photos.length === 0">
|
||||
Toto místo není označené na žádné fotografii.
|
||||
</BasicInfoBanner>
|
||||
<PhotosEditorList
|
||||
v-else
|
||||
:photos="pointOfInterest.photos"
|
||||
|
||||
@@ -16,6 +16,9 @@ import {successObject} from "src/composables/dialog";
|
||||
import ContentCardEditButton from "components/ContentCardEditButton.vue";
|
||||
import {useFlightsStore} from "stores/Flights";
|
||||
import FlightsListSkeleton from "components/flights/skeletons/FlightsListSkeleton.vue";
|
||||
import {loadPage, paginationLimit} from "src/composables/pagination";
|
||||
import PublishButton from "components/inputs/PublishButton.vue";
|
||||
import CopyLinkButton from "components/inputs/CopyLinkButton.vue";
|
||||
|
||||
const loggedUser = storeToRefs(useLoggedUserStore()).user;
|
||||
const poiId = parseInt(useRoute().params.id);
|
||||
@@ -30,23 +33,19 @@ usePointsOfInterestStore().fetchDetail(poiId).then((p) => {
|
||||
])
|
||||
});
|
||||
|
||||
useFlightsStore().fetch({pointOfInterestId: poiId});
|
||||
|
||||
const loadNextFlightsPage = (appendItems) => loadPage({
|
||||
fetchFunc: useFlightsStore().fetch,
|
||||
filters: {pointOfInterestId: poiId},
|
||||
appendItems
|
||||
});
|
||||
loadNextFlightsPage(false);
|
||||
const {isQueryRunning: areFlightsLoading, totalItemsCount, flights} = storeToRefs(useFlightsStore());
|
||||
const {isQueryRunning, pointOfInterest} = storeToRefs(usePointsOfInterestStore());
|
||||
const {isQueryRunning: areFlightsLoading, flights} = storeToRefs(useFlightsStore());
|
||||
|
||||
const $q = useQuasar();
|
||||
const togglePublicVisibility = () => toggleVisibility({
|
||||
$q, store: usePointsOfInterestStore(), object: pointOfInterest.value
|
||||
const togglePublicVisibility = (data) => toggleVisibility({
|
||||
$q, store: usePointsOfInterestStore(), object: pointOfInterest.value, urlSlug: data.urlSlug
|
||||
});
|
||||
|
||||
const $router = useRouter();
|
||||
const copyLinkToClipboard = () => {
|
||||
const url = $router.resolve({name: "publicPOIDetail", params: {id: pointOfInterest.value.id}});
|
||||
copyToClipboard(window.location.origin + url.href).then(() => {
|
||||
$q.notify(successObject("Odkaz zkopírován"))
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -57,18 +56,15 @@ const copyLinkToClipboard = () => {
|
||||
<ContentCard :title-photo="pointOfInterest.titlePhoto" :title="pointOfInterest.name">
|
||||
<template #actions v-if="pointOfInterest.createdById === loggedUser.id">
|
||||
<div class="col-grow">
|
||||
<q-btn
|
||||
@click="togglePublicVisibility"
|
||||
:icon="pointOfInterest.isPublic ? 'visibility_off' : 'visibility'"
|
||||
flat
|
||||
:label="pointOfInterest.isPublic ? 'Skrýt' : 'Zveřejnit'"
|
||||
<PublishButton
|
||||
url-prefix="/mista/"
|
||||
:is-public="pointOfInterest.isPublic"
|
||||
:require-url-slug="pointOfInterest.urlSlug === ''"
|
||||
@change-visibility="togglePublicVisibility"
|
||||
/>
|
||||
<q-btn
|
||||
icon="link"
|
||||
@click="copyLinkToClipboard()"
|
||||
flat
|
||||
<CopyLinkButton
|
||||
v-if="pointOfInterest.isPublic"
|
||||
label="Získat odkaz"
|
||||
:route="{name: 'publicPOIDetail', params: {slug: pointOfInterest.urlSlug}}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -107,11 +103,15 @@ const copyLinkToClipboard = () => {
|
||||
|
||||
<h3>Tvoje lety do místa {{ pointOfInterest.name }}</h3>
|
||||
|
||||
<FlightsListSkeleton v-if="areFlightsLoading && !flights.length" />
|
||||
<FlightsListSkeleton v-if="areFlightsLoading && !flights.length"/>
|
||||
<FlightsList
|
||||
:items="flights"
|
||||
:public="false"
|
||||
v-else-if="flights.length > 0"
|
||||
:public="false"
|
||||
:items="flights"
|
||||
:total-items-count="totalItemsCount"
|
||||
:is-query-running="areFlightsLoading"
|
||||
:items-per-page="paginationLimit"
|
||||
@load-more="loadNextFlightsPage(true)"
|
||||
/>
|
||||
<BasicInfoBanner v-else>
|
||||
Na tomto místě jsi zatím nebyl/a.
|
||||
|
||||
@@ -3,7 +3,7 @@ import {useAircraftsStore} from "stores/Aircrafts";
|
||||
|
||||
export default {
|
||||
preFetch({currentRoute}) {
|
||||
return useAircraftsStore().fetchPublicDetail(currentRoute.params.id);
|
||||
return useAircraftsStore().fetchPublicDetail(currentRoute.params.callSign);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -48,6 +48,7 @@ useMeta(() => metaData({
|
||||
}));
|
||||
useAppStore().setBreadcrumbs([
|
||||
{label: "Polétání.cz", to: {name: "homepage"}},
|
||||
{label: "Letadla"},
|
||||
{label: aircraft.value.callSign}
|
||||
]);
|
||||
|
||||
@@ -88,8 +89,8 @@ onMounted(() => {
|
||||
:total-items-count="totalItemsCount"
|
||||
:is-query-running="areFlightsLoading"
|
||||
:items-per-page="paginationLimit"
|
||||
:public="true"
|
||||
@load-more="loadNextFlightsPage(true)"
|
||||
:public="true"
|
||||
/>
|
||||
</q-card-section>
|
||||
</template>
|
||||
|
||||
@@ -3,7 +3,7 @@ import {useCopilotsStore} from "stores/Copilots";
|
||||
|
||||
export default {
|
||||
preFetch({currentRoute}) {
|
||||
return useCopilotsStore().fetchPublicDetail(currentRoute.params.id, currentRoute.params.username);
|
||||
return useCopilotsStore().fetchPublicDetail(currentRoute.params.copilotSlug, currentRoute.params.username);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -3,7 +3,7 @@ import {useEventsStore} from "stores/Events";
|
||||
|
||||
export default {
|
||||
preFetch({currentRoute}) {
|
||||
return useEventsStore().fetchPublicDetail(currentRoute.params.id, currentRoute.params.username);
|
||||
return useEventsStore().fetchPublicDetail(currentRoute.params.eventSlug, currentRoute.params.username);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -3,7 +3,10 @@ import {useFlightsStore} from "stores/Flights";
|
||||
|
||||
export default {
|
||||
preFetch({store, currentRoute}) {
|
||||
return useFlightsStore(store).fetchPublicDetail(currentRoute.params.id, currentRoute.params.username)
|
||||
return useFlightsStore(store).fetchPublicDetail({
|
||||
urlSlug: currentRoute.params.flightSlug,
|
||||
username: currentRoute.params.username
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -3,7 +3,7 @@ import {usePointsOfInterestStore} from "stores/PointsOfInterest";
|
||||
|
||||
export default {
|
||||
preFetch({currentRoute}) {
|
||||
return usePointsOfInterestStore().fetchPublicDetail(currentRoute.params.id);
|
||||
return usePointsOfInterestStore().fetchPublicDetail(currentRoute.params.slug);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -13,7 +13,7 @@ const logUser = () => {
|
||||
useAuthStore().login(formData).then(() => {
|
||||
$router.push({name: "myFlights"});
|
||||
}).catch((err) => {
|
||||
$q.notify(errorObject("Přihlášení se nezdařilo."))
|
||||
$q.notify(errorObject(`Přihlášení se nezdařilo. ${err}`))
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
@@ -18,15 +18,15 @@ const routes = [
|
||||
component: () => import('pages/public/PilotProfileWrapper.vue'),
|
||||
children: [
|
||||
{path: '', name: "publicProfileDetail", component: () => import('pages/public/pilotProfile/FlightsListPage.vue')},
|
||||
{path: ':id', name: "publicFlightDetail", component: () => import('pages/public/pilotProfile/FlightDetailPage.vue')},
|
||||
{path: 'kopilot/:id', name: "publicCopilotDetail", component: () => import('pages/public/pilotProfile/CopilotDetailPage.vue')},
|
||||
{path: ':flightSlug', name: "publicFlightDetail", component: () => import('pages/public/pilotProfile/FlightDetailPage.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/:id', name: "publicEventDetail", component: () => import('pages/public/pilotProfile/EventDetailPage.vue')},
|
||||
{path: 'udalosti/:eventSlug', name: "publicEventDetail", component: () => import('pages/public/pilotProfile/EventDetailPage.vue')},
|
||||
]
|
||||
},
|
||||
{path: 'letadlo/:id', name: "publicAircraftDetail", component: () => import('pages/public/aircraft/AircraftDetailPage.vue')},
|
||||
{path: 'letadlo/:callSign', name: "publicAircraftDetail", component: () => import('pages/public/aircraft/AircraftDetailPage.vue')},
|
||||
{path: 'mista', name: "publicPOIs", component: () => import('pages/public/poi/PointsOfInterestPage.vue')},
|
||||
{path: 'mista/:id', name: "publicPOIDetail", component: () => import('pages/public/poi/PointOfInterestDetailPage.vue')},
|
||||
{path: 'mista/:slug', name: "publicPOIDetail", component: () => import('pages/public/poi/PointOfInterestDetailPage.vue')},
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -20,10 +20,10 @@ export const useAircraftsStore = defineStore('Aircrafts', () => {
|
||||
return item
|
||||
});
|
||||
|
||||
const fetchPublicDetail = (id) => query({
|
||||
const fetchPublicDetail = (callSign) => query({
|
||||
isQueryRunning,
|
||||
query: AIRCRAFT_PUBLIC_DETAIL,
|
||||
variables: {id},
|
||||
variables: {callSign},
|
||||
}).then(item => {
|
||||
aircraft.value = item;
|
||||
return item;
|
||||
|
||||
+6
-12
@@ -1,23 +1,17 @@
|
||||
import {defineStore} from 'pinia'
|
||||
import {api} from "boot/axios";
|
||||
import {ref} from "vue";
|
||||
import {cookies} from "boot/cookies";
|
||||
import {useLoggedUserStore} from "stores/LoggedUser";
|
||||
|
||||
export const useAuthStore = defineStore('Auth', () => {
|
||||
const isRefreshing = ref(false);
|
||||
|
||||
const login = async (formData) => {
|
||||
return api.post("/login", formData).then(({data}) => {
|
||||
return await api.post("/login", formData).then(({data}) => {
|
||||
setTokens(data);
|
||||
useLoggedUserStore().user.value = data.user;
|
||||
})
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
return api.post("/logout").then(response => {
|
||||
unsetTokens();
|
||||
useLoggedUserStore().user.value = data.user;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -26,7 +20,10 @@ export const useAuthStore = defineStore('Auth', () => {
|
||||
};
|
||||
|
||||
const setTokens = ({access_token, access_token_validity}) => {
|
||||
cookies.set("accessToken", access_token, {expires: `${access_token_validity}s`})
|
||||
cookies.set("accessToken", access_token, {
|
||||
expires: `${access_token_validity}s`,
|
||||
sameSite: 'Strict'
|
||||
});
|
||||
};
|
||||
|
||||
const getAccessToken = () => {
|
||||
@@ -44,8 +41,5 @@ export const useAuthStore = defineStore('Auth', () => {
|
||||
return Promise.reject(e);
|
||||
})
|
||||
|
||||
return {
|
||||
login, logout, getAccessToken, hasAccessToken, refreshAccessToken,
|
||||
isRefreshing
|
||||
}
|
||||
return {login, logout, getAccessToken, hasAccessToken, refreshAccessToken}
|
||||
});
|
||||
|
||||
@@ -18,11 +18,11 @@ export const useCopilotsStore = defineStore('Copilots', () => {
|
||||
}).then(item => copilot.value = item);
|
||||
};
|
||||
|
||||
const fetchPublicDetail = (copilotId, username) => {
|
||||
const fetchPublicDetail = (urlSlug, username) => {
|
||||
return query({
|
||||
isQueryRunning,
|
||||
query: COPILOT_PUBLIC_DETAIL,
|
||||
variables: {id: copilotId, pilotUsername: username},
|
||||
variables: {urlSlug, pilotUsername: username},
|
||||
}).then(item => {
|
||||
copilot.value = item;
|
||||
return item;
|
||||
|
||||
@@ -19,11 +19,11 @@ export const useEventsStore = defineStore('Events', () => {
|
||||
}).then(item => event.value = item);
|
||||
};
|
||||
|
||||
const fetchPublicDetail = (id, username) => {
|
||||
const fetchPublicDetail = (urlSlug, username) => {
|
||||
return query({
|
||||
isQueryRunning,
|
||||
query: EVENT_PUBLIC_DETAIL,
|
||||
variables: {id, username},
|
||||
variables: {urlSlug, username},
|
||||
}).then(item => event.value = item);
|
||||
};
|
||||
|
||||
|
||||
@@ -20,10 +20,10 @@ export const useFlightsStore = defineStore('Flights', () => {
|
||||
return item;
|
||||
});
|
||||
|
||||
const fetchPublicDetail = (id, username) => query({
|
||||
const fetchPublicDetail = ({urlSlug, username}) => query({
|
||||
isQueryRunning,
|
||||
query: FLIGHT_PUBLIC_DETAIL,
|
||||
variables: {username, id}
|
||||
variables: {username, urlSlug}
|
||||
}).then(item => {
|
||||
flight.value = item;
|
||||
return item;
|
||||
@@ -31,6 +31,7 @@ export const useFlightsStore = defineStore('Flights', () => {
|
||||
|
||||
const fetch = (
|
||||
{
|
||||
eventId,
|
||||
aircraftId,
|
||||
copilotId,
|
||||
pointOfInterestId,
|
||||
@@ -41,6 +42,7 @@ export const useFlightsStore = defineStore('Flights', () => {
|
||||
isQueryRunning,
|
||||
query: FLIGHTS_LIST,
|
||||
variables: {
|
||||
eventId,
|
||||
aircraftId,
|
||||
copilotId,
|
||||
pointOfInterestId,
|
||||
|
||||
@@ -21,10 +21,10 @@ export const usePointsOfInterestStore = defineStore('PointsOfInterest', () => {
|
||||
return item;
|
||||
});
|
||||
|
||||
const fetchPublicDetail = (id) => query({
|
||||
const fetchPublicDetail = (urlSlug) => query({
|
||||
isQueryRunning,
|
||||
query: POI_PUBLIC_DETAIL,
|
||||
variables: {id},
|
||||
variables: {urlSlug},
|
||||
}).then(item => {
|
||||
pointOfInterest.value = item;
|
||||
return item;
|
||||
|
||||
Reference in New Issue
Block a user