Aktualizace balicku, strnakovani, uprava hlavni stranky

This commit is contained in:
Michal Kváček
2023-12-12 23:42:16 +01:00
parent 8c7e237053
commit c8ea17c1ec
67 changed files with 1372 additions and 774 deletions
+38
View File
@@ -0,0 +1,38 @@
<script setup></script>
<template>
<q-footer>
<div class="row justify-between">
<div class="col-4">
<q-list>
<q-item><router-link :to="{name: 'about'}">O projektu</router-link></q-item>
<q-item><router-link :to="{name: 'contact'}">Kontakt</router-link></q-item>
<q-item><router-link :to="{name: 'login'}">Login</router-link></q-item>
<q-item><router-link :to="{name: 'registration'}">Registrace</router-link></q-item>
</q-list>
</div>
<div class="col-4">
<q-list>
<q-item><router-link :to="{name: 'about'}">Podmínky použití</router-link></q-item>
<q-item><router-link :to="{name: 'about'}">Správa osobních údajů</router-link></q-item>
</q-list>
</div>
<!-- <div class="col">-->
<!-- &copy; 2023 Polétání.cz-->
<!-- </div>-->
</div>
<div class="row">
<div class="col text-center q-my-lg">
Vytvořeno s &hearts; s využitím následujících technologií: Quasar, GraphQL, Strawberry, FastAPI
</div>
</div>
</q-footer>
</template>
<style scoped>
a {
color: white;
font-size: 110%;
}
</style>
+41
View File
@@ -0,0 +1,41 @@
<script setup>
const props = defineProps({
totalItemsCount: {
type: Number,
required: true
},
itemsLength: {
type: Number,
required: true,
},
itemsPerPage: {
type: Number,
required: false,
default: 5,
},
isQueryRunning: {
type: Boolean,
required: false,
default: false
}
})
const emits = defineEmits(["load-more"]);
const loadMore = () => {
emits("load-more", props.itemsPerPage);
}
</script>
<template>
<div>
<slot name="items"/>
<slot name="skeleton" v-if="isQueryRunning"/>
<div class="text-center q-my-md">
<q-btn color="primary" :loading="isQueryRunning" rounded size="lg" icon="expand_more" @click="loadMore()" v-if="totalItemsCount > itemsLength">
<slot name="loadMoreText"/>
</q-btn>
</div>
</div>
</template>
+56 -34
View File
@@ -1,46 +1,68 @@
<script setup>
import {useAppStore} from "stores/App";
import LoadMoreList from "components/LoadMoreList.vue";
import FlightsListSkeleton from "components/flights/skeletons/FlightsListSkeleton.vue";
import BasicListSkeleton from "components/basicSkeletons/BasicListSkeleton.vue";
const props = defineProps({
items: {
type: Array,
required: true
},
totalItemsCount: {
type: Number,
required: true
},
itemsPerPage: {
type: Number,
required: false,
default: 5,
},
isQueryRunning: {
type: Boolean,
required: false,
default: false
}
});
useAppStore().setPageTitle("Letadla");
</script>
<template>
<q-list bordered separator>
<q-item
clickable
v-ripple
:to="{name: 'aircraftDetail', params: {id: aircraft.id }}"
v-for="aircraft of props.items" :key="aircraft"
>
<q-item-section avatar>
<q-avatar v-if="aircraft.titlePhoto">
<img :src="aircraft.titlePhoto.thumbnailUrl" alt=""/>
</q-avatar>
<q-avatar color="teal" text-color="white" icon="flight" v-else/>
</q-item-section>
<q-item-section>
<q-item-label label>
{{ aircraft.callSign }}
</q-item-label>
<q-item-label caption>
{{ aircraft.model }}
<template v-if="aircraft.manufacturer">
({{ aircraft.manufacturer }})
</template>
</q-item-label>
<q-item-label caption v-if="aircraft.organization">
Provozovatel: {{ aircraft.organization.name }}
</q-item-label>
</q-item-section>
</q-item>
</q-list>
<LoadMoreList :total-items-count="totalItemsCount" :items-length="items.length">
<template #items>
<q-list bordered separator>
<q-item
clickable
v-ripple
:to="{name: 'aircraftDetail', params: {id: aircraft.id }}"
v-for="aircraft of props.items" :key="aircraft"
>
<q-item-section avatar>
<q-avatar v-if="aircraft.titlePhoto">
<img :src="aircraft.titlePhoto.thumbnailUrl" alt=""/>
</q-avatar>
<q-avatar color="teal" text-color="white" icon="flight" v-else/>
</q-item-section>
<q-item-section>
<q-item-label label>
{{ aircraft.callSign }}
</q-item-label>
<q-item-label caption>
{{ aircraft.model }}
<template v-if="aircraft.manufacturer">
({{ aircraft.manufacturer }})
</template>
</q-item-label>
<q-item-label caption v-if="aircraft.organization">
Provozovatel: {{ aircraft.organization.name }}
</q-item-label>
</q-item-section>
</q-item>
</q-list>
</template>
<template #skeleton>
<BasicListSkeleton :items="itemsPerPage"/>
</template>
<template #loadMoreText>
Další
</template>
</LoadMoreList>
</template>
+80 -40
View File
@@ -2,6 +2,9 @@
import {minutesToHumanReadable} from "src/composables/datetimeUtils";
import {flightName} from "src/composables/flight";
import AvatarFromPhoto from "components/avatars/AvatarFromPhoto.vue";
import FlightsListSkeleton from "components/flights/skeletons/FlightsListSkeleton.vue";
import {computed} from "vue";
import LoadMoreList from "components/LoadMoreList.vue";
const props = defineProps({
items: {
@@ -12,6 +15,20 @@ const props = defineProps({
type: Boolean,
required: true
},
totalItemsCount: {
type: Number,
required: true
},
itemsPerPage: {
type: Number,
required: false,
default: 5,
},
isQueryRunning: {
type: Boolean,
required: false,
default: false
}
});
const routeDetail = (flight) => {
@@ -20,52 +37,75 @@ const routeDetail = (flight) => {
else
return {name: "flightDetail", params: {id: flight.id}}
};
const pluralize = (count, singular, plural, pluralMore) => {
if (count === 1) return singular;
else if (count < 5) return plural;
else return pluralMore;
}
const loadMoreCount = computed(() => {
const remaining = props.totalItemsCount - props.items.length;
return Math.min(props.itemsPerPage, remaining);
});
</script>
<template>
<q-list bordered separator>
<q-item
v-for="flight of items" :key="flight"
ripple
clickable
:to="routeDetail(flight)"
>
<q-item-section avatar>
<AvatarFromPhoto :size="120" v-if="flight.titlePhoto" :src="flight.titlePhoto.thumbnailUrl"/>
</q-item-section>
<q-item-section>
<div class="text-h5 q-mb-md">{{ flightName(flight) }}</div>
<LoadMoreList :total-items-count="totalItemsCount" :items-length="items.length">
<template #items>
<q-list bordered separator>
<q-item
v-for="flight of items" :key="flight"
ripple
clickable
:to="routeDetail(flight)"
>
<q-item-section avatar>
<AvatarFromPhoto :size="120" v-if="flight.titlePhoto" :src="flight.titlePhoto.thumbnailUrl"/>
</q-item-section>
<q-item-section>
<div class="text-h5 q-mb-md">{{ flightName(flight) }}</div>
<div class="text-secondary" v-if="props.public">
<q-icon name="support_agent"/>
@{{flight.pilot.publicUsername}}
</div>
<div class="text-secondary" v-if="props.public">
<q-icon name="support_agent"/>
@{{ flight.pilot.publicUsername }}
</div>
<div class="text-secondary" v-if="flight.copilot">
<q-icon name="person"/>
{{ flight.pilot.name }}
{{ flight.copilot.name }}
</div>
<div class="text-secondary" v-if="flight.copilots">
<q-icon name="person"/>
{{ flight.pilot.name }}
{{ flight.copilots }}
</div>
<div class="text-secondary">
<q-icon name="schedule"/>
{{ minutesToHumanReadable(flight.durationMinCalculated) }}
</div>
<div class="text-secondary">
<q-icon name="flight"/>
{{ flight.aircraft.callSign }}
</div>
<div class="text-secondary">
<q-icon name="schedule"/>
{{ minutesToHumanReadable(flight.durationMinCalculated) }}
</div>
<div class="text-secondary">
<q-icon name="flight"/>
{{ flight.aircraft.callSign }}
</div>
<div class="text-secondary" v-if="flight.event">
<q-icon name="event"/>
{{ flight.event.name }}
</div>
</q-item-section>
<div class="text-secondary" v-if="flight.event">
<q-icon name="event"/>
{{ flight.event.name }}
</div>
</q-item-section>
<q-item-section side top v-if="flight.isPublic && !props.public">
<!-- Zobraz jen v adminu -->
<q-icon name="visibility"></q-icon>
</q-item-section>
</q-item>
</q-list>
</template>
<template #skeleton>
<FlightsListSkeleton :items="itemsPerPage"/>
</template>
<template #loadMoreText>
Načíst {{ pluralize(loadMoreCount, 'jeden let', `další ${loadMoreCount} lety`, `dalších ${loadMoreCount} letů`) }}
</template>
</LoadMoreList>
<q-item-section side top v-if="flight.isPublic && !props.public">
<!-- Zobraz jen v adminu -->
<q-icon name="visibility"></q-icon>
</q-item-section>
</q-item>
</q-list>
</template>
@@ -1,10 +1,18 @@
<script setup>
import {randomInt} from "src/composables/random";
const props = defineProps({
items: {
type: Number,
required: false,
default: 10,
}
})
</script>
<template>
<q-list bordered separator>
<q-item v-for="i in 10" :key="i" ripple>
<q-item v-for="i in items" :key="i" ripple>
<q-item-section avatar>
<q-avatar size="100px">
<q-skeleton type="QAvatar" size="100px"></q-skeleton>
@@ -12,9 +20,9 @@ import {randomInt} from "src/composables/random";
</q-item-section>
<q-item-section>
<q-skeleton :width="randomInt(30,50)+'%'"/>
<q-skeleton type="text" :width="randomInt(10,15)+'%'"/>
<q-skeleton type="text" :width="randomInt(5,10)+'%'"/>
<q-skeleton type="text" width="8%"/>
<q-skeleton type="text" :width="randomInt(10,15)+'%'"/>
<q-skeleton type="text" :width="randomInt(5,10)+'%'"/>
<q-skeleton type="text" width="8%"/>
</q-item-section>
</q-item>
</q-list>
@@ -0,0 +1,26 @@
<script setup>
import {flightName} from "src/composables/flight";
const props = defineProps({
flight: {
type: Object,
required: true
},
textColor: {
type: String,
required: false,
default: "white"
}
})
</script>
<template>
<div class="text-h4">
<router-link :class="`text-${props.textColor}`" :to="{name: 'publicFlightDetail', params: {username: flight.pilot.publicUsername, id: flight.id}}">
{{ flightName(flight) }}
</router-link>
</div>
<router-link :class="`text-${props.textColor}`" :to="{name: 'publicProfileDetail', params: {username: flight.pilot.publicUsername}}">
@{{ flight.pilot.publicUsername }}
</router-link>
</template>
@@ -0,0 +1,40 @@
<script setup>
import {ref} from "vue";
import {flightName} from "src/composables/flight";
import LatestFlightSliderInfo from "components/flights/slider/LatestFlightSliderInfo.vue";
const props = defineProps({
items: {
type: Array,
required: true
}
});
const slide = ref(0);
</script>
<template>
<q-carousel
v-model="slide"
transition-prev="scale"
transition-next="scale"
swipeable
animated
control-color="primary"
navigation
padding
arrows
infinite
height="500px"
class="bg-blue-1 text-white shadow-1 "
>
<q-carousel-slide :name="idx" v-for="(flight, idx) in items" :key="idx" class="column no-wrap flex-center text-center">
<q-img :src="flight.titlePhoto.url" fit="cover" ratio="16/9" height="400px" v-if="flight.titlePhoto">
<div class="absolute-bottom">
<LatestFlightSliderInfo :flight="flight"/>
</div>
</q-img>
<LatestFlightSliderInfo v-else :flight="flight" text-color="primary"/>
</q-carousel-slide>
</q-carousel>
</template>
@@ -0,0 +1,36 @@
<script setup></script>
<template>
<q-timeline color="primary" class="q-my-lg">
<q-timeline-entry title="Zaznamenej si let">
<div>
Zapiš si odkud, kdy a čím jsi letěl. Aplikace sama stáhne informace o počasí při vzletu a přistání.
</div>
</q-timeline-entry>
<q-timeline-entry title="Vyber a nahraj fotky">
<div>
Máš nějaké fotky z letu? Nahraj je ke svému letu! A pokud mají v sobě uloženou informaci o poloze, polétání.cz ti zobrazí i místo kde fotka vznikla.
</div>
</q-timeline-entry>
<q-timeline-entry title="Ulož trasu">
<div>
Polétání.cz umí pracovat s trasou ve formátu GPX. Pokud ale tento záznam nemáš, můžeš si naklikat trasu ručně.
</div>
</q-timeline-entry>
<q-timeline-entry title="Popiš zážitek">
<div>
Určitě se po ceste stalo něco vzrušujícího. Proč o tom nenapsat pár vět?
</div>
</q-timeline-entry>
<q-timeline-entry title="Sdílej">
<div>
Létat v sóle je super, ale ještě lepší je se o zážitek podělit. Jedním kliknutím můžeš svůj let zveřejnit a sdílet odkaz kde chceš. Třeba s tvým kopilotem.
</div>
</q-timeline-entry>
</q-timeline>
</template>
+47 -19
View File
@@ -1,4 +1,8 @@
<script setup>
import FlightsListSkeleton from "components/flights/skeletons/FlightsListSkeleton.vue";
import LoadMoreList from "components/LoadMoreList.vue";
import BasicListSkeleton from "components/basicSkeletons/BasicListSkeleton.vue";
const props = defineProps({
items: {
type: Array,
@@ -7,6 +11,20 @@ const props = defineProps({
public: {
type: Boolean,
required: true
},
totalItemsCount: {
type: Number,
required: true
},
itemsPerPage: {
type: Number,
required: false,
default: 5,
},
isQueryRunning: {
type: Boolean,
required: false,
default: false
}
});
@@ -18,23 +36,33 @@ const getRoute = (item) => {
}
</script>
<template>
<q-list bordered separator>
<q-item clickable v-ripple v-for="poi of items" :key="poi" :to="getRoute(poi)">
<q-item-section avatar>
<q-avatar v-if="poi.titlePhoto">
<img :src="poi.titlePhoto.thumbnailUrl" alt=""/>
</q-avatar>
<q-avatar v-else :icon="poi.type?.icon || 'place'" color="primary" text-color="white"/>
</q-item-section>
<q-item-section>
{{ poi.name }}
<div class="text-secondary" v-if="poi.type">
{{ poi.type.name }}
</div>
</q-item-section>
<q-item-section side top v-if="poi.isPublic">
<q-icon name="visibility"></q-icon>
</q-item-section>
</q-item>
</q-list>
<LoadMoreList :total-items-count="totalItemsCount" :items-length="items.length">
<template #items>
<q-list bordered separator>
<q-item clickable v-ripple v-for="poi of items" :key="poi" :to="getRoute(poi)">
<q-item-section avatar>
<q-avatar v-if="poi.titlePhoto">
<img :src="poi.titlePhoto.thumbnailUrl" alt=""/>
</q-avatar>
<q-avatar v-else :icon="poi.type?.icon || 'place'" color="primary" text-color="white"/>
</q-item-section>
<q-item-section>
{{ poi.name }}
<div class="text-secondary" v-if="poi.type">
{{ poi.type.name }}
</div>
</q-item-section>
<q-item-section side top v-if="!props.public && poi.isPublic">
<q-icon name="visibility"></q-icon>
</q-item-section>
</q-item>
</q-list>
</template>
<template #skeleton>
<BasicListSkeleton :items="itemsPerPage"/>
</template>
<template #loadMoreText>
Další
</template>
</LoadMoreList>
</template>
+1 -1
View File
@@ -1,7 +1,7 @@
<script setup>
import {onMounted, reactive, watch} from "vue";
import {useRoute} from "vue-router";
import {useUserStore} from "stores/User";
import {useLoggedUserStore} from "stores/LoggedUser";
import {commonProps} from "src/composables/inputs";
import RichTextEditor from "components/inputs/RichTextEditor.vue";
import FormWrapper from "components/FormWrapper.vue";