merge-upstream-5.0.1 #66

Merged
778a69cd merged 80 commits from merge-upstream-5.0.1 into main 2024-12-26 12:55:41 +01:00
6 changed files with 162 additions and 268 deletions
Showing only changes of commit f2d7da7af7 - Show all commits

View file

@ -21,20 +21,17 @@
/> />
<full-address-auto-complete <full-address-auto-complete
:resultType="AddressSearchType.ADMINISTRATIVE" :resultType="AddressSearchType.ADMINISTRATIVE"
v-model="location" v-model="address"
:hide-map="true" :hide-map="true"
:hide-selected="true" :hide-selected="true"
:default-text="locationDefaultText" :default-text="addressDefaultText"
labelClass="sr-only" labelClass="sr-only"
:placeholder="t('e.g. Nantes, Berlin, Cork, …')" :placeholder="t('e.g. Nantes, Berlin, Cork, …')"
v-on:update:modelValue="modelValueUpdate" v-on:update:modelValue="modelValueUpdate"
> >
<o-dropdown v-model="distance" position="bottom-right" v-if="distance"> <o-dropdown v-model="distance" position="bottom-right" v-if="distance">
<template #trigger> <template #trigger>
<o-button <o-button :title="t('Select distance')">{{ distanceText }}</o-button>
icon-left="map-marker-distance"
:title="t('Select distance')"
/>
</template> </template>
<o-dropdown-item <o-dropdown-item
v-for="distance_item in distanceList" v-for="distance_item in distanceList"
@ -56,8 +53,8 @@ import { IAddress } from "@/types/address.model";
import { AddressSearchType } from "@/types/enums"; import { AddressSearchType } from "@/types/enums";
import { import {
addressToLocation, addressToLocation,
getLocationFromLocal, getAddressFromLocal,
storeLocationInLocal, storeAddressInLocal,
} from "@/utils/location"; } from "@/utils/location";
import { computed, defineAsyncComponent } from "vue"; import { computed, defineAsyncComponent } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
@ -69,8 +66,8 @@ const FullAddressAutoComplete = defineAsyncComponent(
); );
const props = defineProps<{ const props = defineProps<{
location: IAddress | null; address: IAddress | null;
locationDefaultText?: string | null; addressDefaultText?: string | null;
search: string; search: string;
distance: number | null; distance: number | null;
fromLocalStorage?: boolean | false; fromLocalStorage?: boolean | false;
@ -80,26 +77,27 @@ const router = useRouter();
const route = useRoute(); const route = useRoute();
const emit = defineEmits<{ const emit = defineEmits<{
(event: "update:location", location: IAddress | null): void; (event: "update:address", address: IAddress | null): void;
(event: "update:search", newSearch: string): void; (event: "update:search", newSearch: string): void;
(event: "update:distance", newDistance: number): void; (event: "update:distance", newDistance: number): void;
(event: "submit"): void; (event: "submit"): void;
}>(); }>();
const location = computed({ const address = computed({
get(): IAddress | null { get(): IAddress | null {
if (props.location) { console.debug("-- get address --", props);
return props.location; if (props.address) {
return props.address;
} }
if (props.fromLocalStorage) { if (props.fromLocalStorage) {
return getLocationFromLocal(); return getAddressFromLocal();
} }
return null; return null;
}, },
set(newLocation: IAddress | null) { set(newAddress: IAddress | null) {
emit("update:location", newLocation); emit("update:address", newAddress);
if (props.fromLocalStorage) { if (props.fromLocalStorage) {
storeLocationInLocal(newLocation); storeAddressInLocal(newAddress);
} }
}, },
}); });
@ -122,75 +120,31 @@ const distance = computed({
}, },
}); });
const distanceList = computed(() => { const distanceText = computed(() => {
return [ return distance.value + " km";
{
distance: 5,
label: t(
"{number} kilometers",
{
number: 5,
},
5
),
},
{
distance: 10,
label: t(
"{number} kilometers",
{
number: 10,
},
10
),
},
{
distance: 25,
label: t(
"{number} kilometers",
{
number: 25,
},
25
),
},
{
distance: 50,
label: t(
"{number} kilometers",
{
number: 50,
},
50
),
},
{
distance: 100,
label: t(
"{number} kilometers",
{
number: 100,
},
100
),
},
{
distance: 150,
label: t(
"{number} kilometers",
{
number: 150,
},
150
),
},
];
}); });
console.debug("initial", distance.value, search.value, location.value); const distanceList = computed(() => {
const distances = [];
[5, 10, 25, 50, 100, 150].forEach((value) => {
distances.push({
distance: value,
label: t(
"{number} kilometers",
{
number: value,
},
value
),
});
});
return distances;
});
const modelValueUpdate = (newlocation: IAddress | null) => { console.debug("initial", distance.value, search.value, address.value);
emit("update:location", newlocation);
const modelValueUpdate = (newaddress: IAddress | null) => {
emit("update:address", newaddress);
}; };
const submit = () => { const submit = () => {
@ -205,10 +159,9 @@ const submit = () => {
if (search.value != "") { if (search.value != "") {
search_query.search = search.value; search_query.search = search.value;
} }
if (location.value) { if (address.value) {
const { lat, lon } = addressToLocation(location.value); const { lat, lon } = addressToLocation(address.value);
search_query.locationName = search_query.locationName = address.value.locality ?? address.value.region;
location.value.locality ?? location.value.region;
search_query.lat = lat; search_query.lat = lat;
search_query.lon = lon; search_query.lon = lon;
if (distance.value != null) { if (distance.value != null) {

View file

@ -1360,6 +1360,7 @@
"Keyword, event title, group name, etc.": "Keyword, event title, group name, etc.", "Keyword, event title, group name, etc.": "Keyword, event title, group name, etc.",
"Go!": "Go!", "Go!": "Go!",
"Explore!": "Explore!", "Explore!": "Explore!",
"Select distance": "Select distance",
"Join {instance}, a Mobilizon instance": "Join {instance}, a Mobilizon instance", "Join {instance}, a Mobilizon instance": "Join {instance}, a Mobilizon instance",
"Open user menu": "Open user menu", "Open user menu": "Open user menu",
"Open main menu": "Open main menu", "Open main menu": "Open main menu",

View file

@ -1028,6 +1028,7 @@
"Select a radius": "Sélectionnez un rayon", "Select a radius": "Sélectionnez un rayon",
"Select a timezone": "Selectionnez un fuseau horaire", "Select a timezone": "Selectionnez un fuseau horaire",
"Select all resources": "Sélectionner toutes les ressources", "Select all resources": "Sélectionner toutes les ressources",
"Select distance": "Sélectionner la distance",
"Select languages": "Choisissez une langue", "Select languages": "Choisissez une langue",
"Select the activities for which you wish to receive an email or a push notification.": "Sélectionnez les activités pour lesquelles vous souhaitez recevoir un email ou une notification push.", "Select the activities for which you wish to receive an email or a push notification.": "Sélectionnez les activités pour lesquelles vous souhaitez recevoir un email ou une notification push.",
"Select this resource": "Sélectionner cette ressource", "Select this resource": "Sélectionner cette ressource",

View file

@ -9,9 +9,19 @@ const GEOHASH_DEPTH = 9; // put enough accuracy, radius will be used anyway
export const addressToLocation = ( export const addressToLocation = (
address: IAddress address: IAddress
): LocationType | undefined => { ): LocationType | undefined => {
if (!address.geom) return undefined; if (!address.geom)
return {
lon: undefined,
lat: undefined,
name: undefined,
};
const arr = address.geom.split(";"); const arr = address.geom.split(";");
if (arr.length < 2) return undefined; if (arr.length < 2)
return {
lon: undefined,
lat: undefined,
name: undefined,
};
return { return {
lon: parseFloat(arr[0]), lon: parseFloat(arr[0]),
lat: parseFloat(arr[1]), lat: parseFloat(arr[1]),
@ -19,6 +29,17 @@ export const addressToLocation = (
}; };
}; };
export const locationToAddress = (location: LocationType): IAddress | null => {
if (location.lon && location.lat) {
const new_add = new Address();
new_add.geom = location.lon.toString() + ";" + location.lat.toString();
new_add.description = location.name || "";
console.debug("locationToAddress", location, new_add);
return new_add;
}
return null;
};
export const coordsToGeoHash = ( export const coordsToGeoHash = (
lat: number | undefined, lat: number | undefined,
lon: number | undefined, lon: number | undefined,
@ -38,44 +59,24 @@ export const geoHashToCoords = (
return latitude && longitude ? { latitude, longitude } : undefined; return latitude && longitude ? { latitude, longitude } : undefined;
}; };
export const storeLocationInLocal = (location: IAddress | null): undefined => { export const storeAddressInLocal = (address: IAddress | null): undefined => {
if (location) { if (address) {
window.localStorage.setItem("location", JSON.stringify(location)); window.localStorage.setItem("address", JSON.stringify(address));
} else { } else {
window.localStorage.removeItem("location"); window.localStorage.removeItem("address");
} }
}; };
export const getLocationFromLocal = (): IAddress | null => { export const getAddressFromLocal = (): IAddress | null => {
const locationString = window.localStorage.getItem("location"); const addressString = window.localStorage.getItem("address");
if (!locationString) { if (!addressString) {
return null; return null;
} }
const location = JSON.parse(locationString) as IAddress; const address = JSON.parse(addressString) as IAddress;
if (!location.description || !location.geom) { if (!address.description || !address.geom) {
return null; return null;
} }
return location; return address;
};
export const storeRadiusInLocal = (radius: number | null): undefined => {
if (radius) {
window.localStorage.setItem("radius", radius.toString());
} else {
window.localStorage.removeItem("radius");
}
};
export const getRadiusFromLocal = (): IAddress | null => {
const locationString = window.localStorage.getItem("location");
if (!locationString) {
return null;
}
const location = JSON.parse(locationString) as IAddress;
if (!location.description || !location.geom) {
return null;
}
return location;
}; };
export const storeUserLocationAndRadiusFromUserSettings = ( export const storeUserLocationAndRadiusFromUserSettings = (
@ -84,18 +85,13 @@ export const storeUserLocationAndRadiusFromUserSettings = (
if (location) { if (location) {
const latlon = geoHashToCoords(location.geohash); const latlon = geoHashToCoords(location.geohash);
if (latlon) { if (latlon) {
storeLocationInLocal({ storeAddressInLocal({
...new Address(), ...new Address(),
geom: `${latlon.longitude};${latlon.latitude}`, geom: `${latlon.longitude};${latlon.latitude}`,
description: location.name || "", description: location.name || "",
type: "administrative", type: "administrative",
}); });
} }
if (location.range) {
storeRadiusInLocal(location.range);
} else {
console.debug("user has not set a radius");
}
} else { } else {
console.debug("user has not set a location"); console.debug("user has not set a location");
} }

View file

@ -28,11 +28,12 @@
<!-- Search fields --> <!-- Search fields -->
<search-fields <search-fields
v-model:search="search" v-model:search="search"
v-model:location="location" v-model:address="userAddress"
v-model:distance="distance" v-model:distance="distance"
:locationDefaultText="userLocation?.name" v-on:update:address="updateAddress"
v-on:update:location="updateLocation"
:fromLocalStorage="true" :fromLocalStorage="true"
:addressDefaultText="userLocation?.name"
:key="increated"
/> />
<!-- Categories preview --> <!-- Categories preview -->
<categories-preview /> <categories-preview />
@ -185,7 +186,13 @@ import CategoriesPreview from "@/components/Home/CategoriesPreview.vue";
import UnloggedIntroduction from "@/components/Home/UnloggedIntroduction.vue"; import UnloggedIntroduction from "@/components/Home/UnloggedIntroduction.vue";
import SearchFields from "@/components/Home/SearchFields.vue"; import SearchFields from "@/components/Home/SearchFields.vue";
import { useHead } from "@unhead/vue"; import { useHead } from "@unhead/vue";
import { addressToLocation, geoHashToCoords } from "@/utils/location"; import {
addressToLocation,
geoHashToCoords,
getAddressFromLocal,
locationToAddress,
storeAddressInLocal,
} from "@/utils/location";
import { useServerProvidedLocation } from "@/composition/apollo/config"; import { useServerProvidedLocation } from "@/composition/apollo/config";
import { ABOUT } from "@/graphql/config"; import { ABOUT } from "@/graphql/config";
import { IConfig } from "@/types/config.model"; import { IConfig } from "@/types/config.model";
@ -238,13 +245,14 @@ const currentUserParticipations = computed(
() => loggedUser.value?.participations.elements () => loggedUser.value?.participations.elements
); );
const location = ref(null); const increated = ref(0);
const address = ref(null);
const search = ref(""); const search = ref("");
const noLocation = ref(false); const noAddress = ref(false);
const current_distance = ref(null); const current_distance = ref(null);
watch(location, (newLoc, oldLoc) => watch(address, (newAdd, oldAdd) =>
console.debug("LOCATION UPDATED from", { ...oldLoc }, " to ", { ...newLoc }) console.debug("ADDRESS UPDATED from", { ...oldAdd }, " to ", { ...newAdd })
); );
const isToday = (date: string): boolean => { const isToday = (date: string): boolean => {
@ -401,13 +409,13 @@ const { result: reverseGeocodeResult } = useQuery<{
})); }));
const userSettingsLocation = computed(() => { const userSettingsLocation = computed(() => {
const address = reverseGeocodeResult.value?.reverseGeocode[0]; const location = reverseGeocodeResult.value?.reverseGeocode[0];
const placeName = address?.locality ?? address?.region ?? address?.country; const placeName = location?.locality ?? location?.region ?? location?.country;
return { return {
lat: coords.value?.latitude, lat: coords.value?.latitude,
lon: coords.value?.longitude, lon: coords.value?.longitude,
name: placeName, name: placeName,
picture: address?.pictureInfo, picture: location?.pictureInfo,
isIPLocation: coords.value?.isIPLocation, isIPLocation: coords.value?.isIPLocation,
}; };
}); });
@ -433,16 +441,20 @@ const currentUserLocation = computed(() => {
const userLocation = computed(() => { const userLocation = computed(() => {
console.debug("new userLocation"); console.debug("new userLocation");
if (noLocation.value) { if (noAddress.value) {
return { return {
lon: null, lon: null,
lat: null, lat: null,
name: null, name: null,
}; };
} }
if (location.value) { if (address.value) {
console.debug("userLocation is typed location"); console.debug("userLocation is typed location");
return addressToLocation(location.value); return addressToLocation(address.value);
}
const local_address = getAddressFromLocal();
if (local_address) {
return addressToLocation(local_address);
} }
if ( if (
!userSettingsLocation.value || !userSettingsLocation.value ||
@ -454,9 +466,36 @@ const userLocation = computed(() => {
return userSettingsLocation.value; return userSettingsLocation.value;
}); });
const userAddress = computed({
get(): IAddress | null {
if (noAddress.value) {
return null;
}
if (address.value) {
return address.value;
}
const local_address = getAddressFromLocal();
if (local_address) {
return local_address;
}
if (
!userSettingsLocation.value ||
(userSettingsLocation.value?.isIPLocation &&
currentUserLocation.value?.name)
) {
return locationToAddress(currentUserLocation.value);
}
return locationToAddress(userSettingsLocation.value);
},
set(newAddress: IAddress | null) {
address.value = newAddress;
noAddress.value = newAddress == null;
},
});
const distance = computed({ const distance = computed({
get(): number | null { get(): number | null {
if (noLocation.value || !userLocation.value?.name) { if (noAddress.value || !userLocation.value?.name) {
return null; return null;
} else if (current_distance.value == null) { } else if (current_distance.value == null) {
return userLocation.value?.isIPLocation ? 150 : 25; return userLocation.value?.isIPLocation ? 150 : 25;
@ -528,8 +567,13 @@ const performGeoLocation = () => {
); );
}; };
const updateLocation = (newlocation: IAddress | null) => { const updateAddress = (newAddress: IAddress | null) => {
noLocation.value = newlocation == null; if (address.value?.geom != newAddress?.geom || newAddress == null) {
increated.value += 1;
storeAddressInLocal(newAddress);
}
address.value = newAddress;
noAddress.value = newAddress == null;
}; };
/** /**

View file

@ -3,8 +3,9 @@
<search-fields <search-fields
class="md:ml-10 mr-2" class="md:ml-10 mr-2"
v-model:search="search" v-model:search="search"
v-model:location="location" v-model:address="address"
:locationDefaultText="locationName" v-model:distance="radius"
:addressDefaultText="addressName"
:fromLocalStorage="true" :fromLocalStorage="true"
/> />
</div> </div>
@ -155,43 +156,6 @@
</template> </template>
</filter-section> </filter-section>
<filter-section
v-show="!isOnline"
v-model:opened="searchFilterSectionsOpenStatus.eventDistance"
:title="t('Distance')"
>
<template #options>
<fieldset class="flex flex-col">
<legend class="sr-only">{{ t("Distance") }}</legend>
<div
v-for="distanceOption in eventDistance"
:key="distanceOption.id"
>
<input
:id="distanceOption.id"
v-model="distance"
type="radio"
name="eventDistance"
:value="distanceOption.id"
class="w-4 h-4 border-gray-300 focus:ring-2 focus:ring-blue-300 dark:focus:ring-blue-600 dark:focus:bg-blue-600 dark:bg-gray-700 dark:border-gray-600"
/>
<label
:for="distanceOption.id"
class="cursor-pointer ml-3 text-sm font-medium text-gray-900 dark:text-gray-300"
>{{ distanceOption.label }}</label
>
</div>
</fieldset>
</template>
<template #preview>
<span
class="bg-blue-100 text-blue-800 text-sm font-semibold p-0.5 rounded dark:bg-blue-200 dark:text-blue-800 grow-0"
>
{{ eventDistance.find(({ id }) => id === distance)?.label }}
</span>
</template>
</filter-section>
<filter-section <filter-section
v-show="contentType !== 'GROUPS'" v-show="contentType !== 'GROUPS'"
v-model:opened="searchFilterSectionsOpenStatus.eventCategory" v-model:opened="searchFilterSectionsOpenStatus.eventCategory"
@ -620,7 +584,7 @@
:contentType="contentType" :contentType="contentType"
:latitude="latitude" :latitude="latitude"
:longitude="longitude" :longitude="longitude"
:locationName="locationName" :locationName="addressName"
@map-updated="setBounds" @map-updated="setBounds"
:events="searchEvents" :events="searchEvents"
:groups="searchGroups" :groups="searchGroups"
@ -697,25 +661,25 @@ const EventMarkerMap = defineAsyncComponent(
const search = useRouteQuery("search", ""); const search = useRouteQuery("search", "");
const searchDebounced = refDebounced(search, 1000); const searchDebounced = refDebounced(search, 1000);
const locationName = useRouteQuery("locationName", null); const addressName = useRouteQuery("locationName", null);
const location = ref<IAddress | null>(null); const address = ref<IAddress | null>(null);
watch(location, (newLocation) => { watch(address, (newAddress: IAddress) => {
console.debug("location change", newLocation); console.debug("address change", newAddress);
if (newLocation?.geom) { if (newAddress?.geom) {
latitude.value = parseFloat(newLocation?.geom.split(";")[1]); latitude.value = parseFloat(newAddress?.geom.split(";")[1]);
longitude.value = parseFloat(newLocation?.geom.split(";")[0]); longitude.value = parseFloat(newAddress?.geom.split(";")[0]);
locationName.value = newLocation?.description; addressName.value = newAddress?.description;
console.debug("set location", [ console.debug("set address", [
latitude.value, latitude.value,
longitude.value, longitude.value,
locationName.value, addressName.value,
]); ]);
} else { } else {
console.debug("location emptied"); console.debug("address emptied");
latitude.value = undefined; latitude.value = undefined;
longitude.value = undefined; longitude.value = undefined;
locationName.value = null; addressName.value = null;
} }
}); });
@ -759,9 +723,6 @@ const groupPage = useRouteQuery("groupPage", 1, integerTransformer);
const latitude = useRouteQuery("lat", undefined, floatTransformer); const latitude = useRouteQuery("lat", undefined, floatTransformer);
const longitude = useRouteQuery("lon", undefined, floatTransformer); const longitude = useRouteQuery("lon", undefined, floatTransformer);
// TODO
// This should be updated with getRadiusFromLocal if we want to use user's
// preferences
const distance = useRouteQuery("distance", "10_km"); const distance = useRouteQuery("distance", "10_km");
const when = useRouteQuery("when", "any"); const when = useRouteQuery("when", "any");
const contentType = useRouteQuery( const contentType = useRouteQuery(
@ -946,75 +907,6 @@ const contentTypeMapping = computed(() => {
} }
}); });
const eventDistance = computed(() => {
return [
{
id: "anywhere",
label: t("Any distance"),
},
{
id: "5_km",
label: t(
"{number} kilometers",
{
number: 5,
},
5
),
},
{
id: "10_km",
label: t(
"{number} kilometers",
{
number: 10,
},
10
),
},
{
id: "25_km",
label: t(
"{number} kilometers",
{
number: 25,
},
25
),
},
{
id: "50_km",
label: t(
"{number} kilometers",
{
number: 50,
},
50
),
},
{
id: "100_km",
label: t(
"{number} kilometers",
{
number: 100,
},
100
),
},
{
id: "150_km",
label: t(
"{number} kilometers",
{
number: 150,
},
150
),
},
];
});
const eventStatuses = computed(() => { const eventStatuses = computed(() => {
return [ return [
{ {
@ -1049,7 +941,14 @@ const geoHashLocation = computed(() =>
coordsToGeoHash(latitude.value, longitude.value) coordsToGeoHash(latitude.value, longitude.value)
); );
const radius = computed(() => Number.parseInt(distance.value.slice(0, -3))); const radius = computed({
get(): number | null {
return Number.parseInt(distance.value.slice(0, -3));
},
set(newRadius: number) {
distance.value = newRadius.toString() + "_km";
},
});
const longEvents = computed(() => { const longEvents = computed(() => {
if (contentType.value === ContentType.EVENTS) { if (contentType.value === ContentType.EVENTS) {