Migrate to Vue 3 and Vite

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2022-07-12 10:55:28 +02:00
parent 8f4099ee33
commit ee20e03cc2
464 changed files with 31515 additions and 32758 deletions

View File

@@ -0,0 +1,117 @@
<template>
<div class="relative pt-10">
<div class="w-full flex flex-wrap gap-3 mb-2 items-center">
<h1
class="text-xl font-bold tracking-tight text-gray-900 dark:text-gray-100"
>
<slot name="title" />
</h1>
<button
v-if="suggestGeoloc && isIPLocation"
class="inline-flex bg-primary rounded text-white flex-initial px-4 py-2 justify-center w-full md:w-min whitespace-nowrap"
@click="$emit('doGeoLoc')"
>
{{ t("Geolocate me") }}
</button>
</div>
<div class="hidden sm:block" v-show="showScrollLeftButton">
<button
@click="scrollLeft"
class="absolute inset-y-0 my-auto z-10 rounded-full bg-white w-10 h-10 border border-shadowColor -left-5"
>
<div class="">&lt;</div>
</button>
</div>
<div class="overflow-hidden">
<div
class="relative w-full flex gap-6 snap-x overflow-x-auto pb-6"
ref="scrollContainer"
@scroll="scrollHandler"
>
<slot name="content" />
</div>
</div>
<div class="hidden sm:block" v-show="showScrollRightButton">
<button
@click="scrollRight"
class="absolute inset-y-0 my-auto z-10 rounded-full bg-white w-10 h-10 border border-shadowColor -right-5"
>
<div class="">&gt;</div>
</button>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, inject, onMounted, onUnmounted, ref } from "vue";
import { useI18n } from "vue-i18n";
import { LocationType } from "../../types/user-location.model";
withDefaults(
defineProps<{
suggestGeoloc?: boolean;
}>(),
{ suggestGeoloc: true }
);
const emit = defineEmits(["doGeoLoc"]);
const { t } = useI18n({ useScope: "global" });
const userLocationInjection = inject<{
userLocation: LocationType;
}>("userLocation");
const isIPLocation = computed(
() => userLocationInjection?.userLocation.isIPLocation
);
const showScrollRightButton = ref(true);
const showScrollLeftButton = ref(false);
const scrollContainer = ref<any>();
const scrollHandler = () => {
if (scrollContainer.value) {
showScrollRightButton.value =
scrollContainer.value.scrollLeft <
scrollContainer.value.scrollWidth - scrollContainer.value.clientWidth;
showScrollLeftButton.value = scrollContainer.value.scrollLeft > 0;
}
};
const doScroll = (e: Event, left: number) => {
e.preventDefault();
if (scrollContainer.value) {
scrollContainer.value.scrollBy({
left,
behavior: "smooth",
});
}
};
const scrollLeft = (e: Event) => {
doScroll(e, -300);
};
const scrollRight = (e: Event) => {
doScroll(e, 300);
};
const scrollHorizontalToVertical = (evt: WheelEvent) => {
evt.deltaY > 0 ? doScroll(evt, 600) : doScroll(evt, -600);
};
onMounted(async () => {
scrollContainer.value.addEventListener("wheel", scrollHorizontalToVertical);
});
onUnmounted(() => {
if (scrollContainer.value) {
scrollContainer.value.removeEventListener(
"wheel",
scrollHorizontalToVertical
);
}
});
</script>

View File

@@ -0,0 +1,176 @@
<template>
<close-content
v-show="loadingEvents || (events && events.total > 0)"
:suggestGeoloc="suggestGeoloc"
v-on="attrs"
>
<template #title>
{{ t("Events nearby {position}", { position: userLocationName }) }}
</template>
<template #content>
<skeleton-event-result
v-for="i in 6"
class="scroll-ml-6 snap-center shrink-0 w-[18rem] my-4"
:key="i"
v-show="loadingEvents"
/>
<event-card
v-for="event in events.elements"
:event="event"
:key="event.uuid"
/>
<more-content
v-if="
userLocation &&
userLocationName &&
userLocation.lat &&
userLocation.lon &&
userLocation.picture
"
:to="{
name: 'SEARCH',
query: {
locationName: userLocationName,
lat: userLocation.lat?.toString(),
lon: userLocation.lon?.toString(),
contentType: 'EVENTS',
distance: '25',
},
}"
:picture="userLocation.picture"
>
{{
t("View more events around {position}", {
position: userLocationName,
})
}}
</more-content>
</template>
</close-content>
</template>
<script lang="ts" setup>
import { LocationType } from "../../types/user-location.model";
import MoreContent from "./MoreContent.vue";
import CloseContent from "./CloseContent.vue";
import { computed, useAttrs } from "vue";
import { SEARCH_EVENTS } from "@/graphql/search";
import { IEvent } from "@/types/event.model";
import { useQuery } from "@vue/apollo-composable";
import EventCard from "../Event/EventCard.vue";
import { Paginate } from "@/types/paginate";
import SkeletonEventResult from "../Event/SkeletonEventResult.vue";
import { CURRENT_USER_LOCATION_CLIENT } from "@/graphql/location";
import { ICurrentUser, IUser } from "@/types/current-user.model";
import { CURRENT_USER_CLIENT, USER_SETTINGS } from "@/graphql/user";
import { coordsToGeoHash, geoHashToCoords } from "@/utils/location";
import { REVERSE_GEOCODE } from "@/graphql/address";
import { IAddress } from "@/types/address.model";
import { CONFIG } from "@/graphql/config";
import { IConfig } from "@/types/config.model";
import { useI18n } from "vue-i18n";
const EVENT_PAGE_LIMIT = 12;
const { result: currentUserResult } = useQuery<{
currentUser: ICurrentUser;
}>(CURRENT_USER_CLIENT);
const currentUser = computed(() => currentUserResult.value?.currentUser);
const { result: userResult } = useQuery<{ loggedUser: IUser }>(
USER_SETTINGS,
{},
() => ({
enabled: currentUser.value?.isLoggedIn,
})
);
const loggedUser = computed(() => userResult.value?.loggedUser);
const { result: configResult } = useQuery<{ config: IConfig }>(CONFIG);
const config = computed<IConfig | undefined>(() => configResult.value?.config);
const serverLocation = computed(() => config.value?.location);
const { t } = useI18n({ useScope: "global" });
const attrs = useAttrs();
const coords = computed(() => {
const userSettingsGeoHash =
loggedUser.value?.settings?.location?.geohash ?? undefined;
const userSettingsCoords = geoHashToCoords(userSettingsGeoHash);
if (userSettingsCoords) {
return { ...userSettingsCoords, isIPLocation: false };
}
return { ...serverLocation.value, isIPLocation: true };
});
const { result: reverseGeocodeResult } = useQuery<{
reverseGeocode: IAddress[];
}>(REVERSE_GEOCODE, coords, () => ({
enabled: coords.value?.longitude != undefined,
}));
const userSettingsLocation = computed(() => {
const address = reverseGeocodeResult.value?.reverseGeocode[0];
const placeName = address?.locality ?? address?.region ?? address?.country;
return {
lat: coords.value?.latitude,
lon: coords.value?.longitude,
name: placeName,
picture: address?.pictureInfo,
isIPLocation: coords.value?.isIPLocation,
};
});
const { result: currentUserLocationResult } = useQuery<{
currentUserLocation: LocationType;
}>(CURRENT_USER_LOCATION_CLIENT);
const currentUserLocation = computed(() => {
return {
...currentUserLocationResult.value?.currentUserLocation,
isIPLocation: false,
};
});
const geohash = computed(() => {
return coordsToGeoHash(userLocation.value?.lat, userLocation.value?.lon);
});
const userLocationName = computed(() => {
return userLocation.value?.name;
});
const userLocation = computed(() => {
if (
!userSettingsLocation.value ||
(userSettingsLocation.value?.isIPLocation &&
currentUserLocation.value?.name)
) {
return currentUserLocation.value;
}
return userSettingsLocation.value;
});
const suggestGeoloc = computed(() => userLocation.value?.isIPLocation);
const { result: eventsResult, loading: loadingEvents } = useQuery<{
searchEvents: Paginate<IEvent>;
}>(SEARCH_EVENTS, {
location: geohash,
beginsOn: new Date(),
endsOn: undefined,
radius: 2500,
eventPage: 1,
limit: EVENT_PAGE_LIMIT,
type: "IN_PERSON",
});
const events = computed(
() => eventsResult.value?.searchEvents ?? { elements: [], total: 0 }
);
</script>

View File

@@ -0,0 +1,95 @@
<template>
<close-content v-show="loadingGroups || selectedGroups.length > 0">
<template #title>
{{
$t("Popular groups nearby {position}", {
position: userLocationName,
})
}}
</template>
<template #content>
<!-- <skeleton-group-result
v-for="i in [...Array(6).keys()]"
class="scroll-ml-6 snap-center shrink-0 w-[18rem] my-4"
:key="i"
v-show="loadingGroups"
/> -->
<group-result
v-for="group in selectedGroups"
:key="group.id"
class="scroll-ml-6 snap-center shrink-0 first:pl-8 last:pr-8 w-[18rem]"
:group="group"
:view-mode="'column'"
:minimal="true"
:has-border="true"
/>
<more-content
v-if="userLocation"
:to="{
name: 'SEARCH',
query: {
locationName: userLocationName,
lat: userLocation.lat,
lon: userLocation.lon,
contentType: 'GROUPS',
distance: currentDistance,
},
}"
:picture="userLocation.picture"
>
{{
$t("View more groups around {position}", {
position: userLocationName,
})
}}
</more-content>
</template>
</close-content>
</template>
<script lang="ts">
import { defineComponent, inject, reactive, computed, ref } from "vue";
// import SkeletonGroupResult from "../../components/result/SkeletonGroupResult.vue";
import sampleSize from "lodash/sampleSize";
import { LocationType } from "../../types/user-location.model";
import MoreContent from "./MoreContent.vue";
import CloseContent from "./CloseContent.vue";
import { IGroup } from "@/types/actor";
import { SEARCH_GROUPS } from "@/graphql/search";
export default defineComponent({
components: {
MoreContent,
CloseContent,
// SkeletonGroupResult,
},
apollo: {
groups: {
query: SEARCH_GROUPS,
},
},
setup() {
const userLocationInjection = inject<{
userLocation: LocationType;
}>("userLocation");
const groups = reactive<IGroup[]>([]);
const selectedGroups = computed(() => sampleSize(groups, 5));
const currentDistance = ref("25_km");
const userLocationName = computed(
() => userLocationInjection?.userLocation?.name
);
return {
selectedGroups,
userLocation: userLocationInjection?.userLocation,
userLocationName,
currentDistance,
};
},
});
</script>

View File

@@ -0,0 +1,91 @@
<template>
<router-link
:to="to"
class="mbz-card flex flex-col items-center dark:border-gray-700 shadow-md md:flex-col my-4 snap-center shrink-0 first:pl-8 w-[18rem]"
>
<div class="relative w-full group">
<img
v-if="picture"
class="object-cover h-40 w-full rounded-t-lg"
:src="picture.url"
width="350"
alt=""
/>
<div class="absolute top-0 left-0 h-full w-full">
<div
class="custom-overlay opacity-0 invisible group-hover:opacity-100 group-hover:visible absolute left-0 h-full w-full transition-opacity"
/>
<div
class="opacity-0 invisible group-hover:opacity-100 group-hover:visible absolute left-0 h-full w-full"
>
<p class="absolute text-sm text-white left-1 bottom-1.5 px-1">
<svg
xmlns="http://www.w3.org/2000/svg"
class="inline h-5 w-5 pr-1"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span
@click="(e) => e.stopPropagation()"
class="align-bottom"
v-html="
$t('Photo by {author} on {source}', {
author: imageAuthor,
source: imageSource,
})
"
/>
</p>
</div>
</div>
</div>
<div
class="flex h-full justify-between self-end flex-col p-2 pt-1 md:p-4 md:pt-2 leading-normal w-full"
>
<h2
class="mb-2 text-xl font-bold tracking-tight text-violet-title dark:text-white"
>
<slot name="default" />
</h2>
</div>
</router-link>
</template>
<script lang="ts" setup>
import { computed } from "vue";
import { RouteLocationNormalizedLoaded } from "vue-router";
import { CategoryPictureLicencing } from "../Categories/constants";
const props = defineProps<{
to: { name: string; query: Record<string, string> };
picture: CategoryPictureLicencing & { url: string };
}>();
const imageAuthor = computed(
() =>
`<a target="_blank" class="underline font-medium" href="${props.picture?.author?.url}">${props.picture?.author?.name}</a>`
);
const imageSource = computed(
() =>
`<a target="_blank" class="underline font-medium" href="${props.picture?.source?.url}">${props.picture?.source?.name}</a>`
);
</script>
<style>
.custom-overlay {
background-image: linear-gradient(
180deg,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0) 25%,
rgba(2, 0, 36, 0.75) 90%,
rgba(2, 0, 36, 0.85) 100%
);
transition: opacity 0.1s ease-in-out, visibility 0.1s ease-in-out;
}
</style>

View File

@@ -0,0 +1,74 @@
<template>
<close-content
:suggest-geoloc="false"
v-show="loadingEvents || events.length > 0"
>
<template #title>
{{ $t("Online upcoming events") }}
</template>
<template #content>
<skeleton-event-result
v-for="i in [...Array(6).keys()]"
class="scroll-ml-6 snap-center shrink-0 w-[18rem] my-4"
:key="i"
v-show="loadingEvents"
/>
<event-card
class="scroll-ml-6 snap-center shrink-0 first:pl-8 last:pr-8 w-[18rem]"
v-for="event in events"
:key="event.id"
:event="event"
view-mode="column"
:has-border="true"
:minimal="true"
/>
<more-content
:to="{
name: 'SEARCH',
query: {
contentType: 'EVENTS',
isOnline: 'true',
},
}"
:picture="{
url: '/img/online-event.jpg',
author: {
name: 'Chris Montgomery',
url: 'https://unsplash.com/@cwmonty',
},
source: {
name: 'Unsplash',
url: 'https://unsplash.com/?utm_source=Mobilizon&utm_medium=referral',
},
}"
>
{{ $t("View more online events") }}
</more-content>
</template>
</close-content>
</template>
<script lang="ts" setup>
import { computed } from "vue";
import SkeletonEventResult from "../result/SkeletonEventResult.vue";
import MoreContent from "./MoreContent.vue";
import CloseContent from "./CloseContent.vue";
import { SEARCH_EVENTS } from "@/graphql/search";
import EventCard from "../../components/Event/EventCard.vue";
import { useQuery } from "@vue/apollo-composable";
const EVENT_PAGE_LIMIT = 12;
const { result: searchEventResult, loading: loadingEvents } = useQuery(
SEARCH_EVENTS,
() => ({
beginsOn: new Date(),
endsOn: undefined,
eventPage: 1,
limit: EVENT_PAGE_LIMIT,
type: "ONLINE",
})
);
const events = computed(() => searchEventResult.value.searchEvents);
</script>