forked from potsda.mn/mobilizon
Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
3c3206fd8a
|
@ -69,13 +69,31 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
|
|||
|
||||
@spec list_events(any(), map(), Absinthe.Resolution.t()) ::
|
||||
{:ok, Page.t(Event.t())} | {:error, :events_max_limit_reached}
|
||||
def list_events(
|
||||
_parent,
|
||||
%{
|
||||
page: page,
|
||||
limit: limit,
|
||||
order_by: order_by,
|
||||
direction: direction,
|
||||
longevents: longevents,
|
||||
location: location,
|
||||
radius: radius
|
||||
},
|
||||
_resolution
|
||||
)
|
||||
when limit < @event_max_limit do
|
||||
{:ok,
|
||||
Events.list_events(page, limit, order_by, direction, true, longevents, location, radius)}
|
||||
end
|
||||
|
||||
def list_events(
|
||||
_parent,
|
||||
%{page: page, limit: limit, order_by: order_by, direction: direction},
|
||||
_resolution
|
||||
)
|
||||
when limit < @event_max_limit do
|
||||
{:ok, Events.list_events(page, limit, order_by, direction)}
|
||||
{:ok, Events.list_events(page, limit, order_by, direction, true)}
|
||||
end
|
||||
|
||||
def list_events(_parent, %{page: _page, limit: _limit}, _resolution) do
|
||||
|
|
|
@ -375,6 +375,13 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
|
|||
object :event_queries do
|
||||
@desc "Get all events"
|
||||
field :events, :paginated_event_list do
|
||||
arg(:location, :string, default_value: nil, description: "A geohash for coordinates")
|
||||
|
||||
arg(:radius, :float,
|
||||
default_value: nil,
|
||||
description: "Radius around the location to search in"
|
||||
)
|
||||
|
||||
arg(:page, :integer, default_value: 1, description: "The page in the paginated event list")
|
||||
arg(:limit, :integer, default_value: 10, description: "The limit of events per page")
|
||||
|
||||
|
@ -388,6 +395,11 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
|
|||
description: "Direction for the sort"
|
||||
)
|
||||
|
||||
arg(:longevents, :boolean,
|
||||
default_value: nil,
|
||||
description: "if mention filter in or out long events"
|
||||
)
|
||||
|
||||
middleware(Rajska.QueryAuthorization, permit: :all)
|
||||
|
||||
resolve(&Event.list_events/3)
|
||||
|
|
|
@ -359,19 +359,34 @@ defmodule Mobilizon.Events do
|
|||
@doc """
|
||||
Returns the list of events.
|
||||
"""
|
||||
@spec list_events(integer | nil, integer | nil, atom, atom, boolean) :: Page.t(Event.t())
|
||||
@spec list_events(
|
||||
integer | nil,
|
||||
integer | nil,
|
||||
atom,
|
||||
atom,
|
||||
boolean,
|
||||
boolean | nil,
|
||||
string | nil,
|
||||
float | nil
|
||||
) :: Page.t(Event.t())
|
||||
def list_events(
|
||||
page \\ nil,
|
||||
limit \\ nil,
|
||||
sort \\ :begins_on,
|
||||
direction \\ :asc,
|
||||
is_future \\ true
|
||||
is_future \\ true,
|
||||
longevents \\ nil,
|
||||
location \\ nil,
|
||||
radius \\ nil
|
||||
) do
|
||||
Event
|
||||
|> distinct([e], [{^direction, ^sort}, asc: e.id])
|
||||
|> preload([:organizer_actor, :participants])
|
||||
|> sort(sort, direction)
|
||||
|> maybe_join_address(%{location: location, radius: radius})
|
||||
|> events_for_location(%{location: location, radius: radius})
|
||||
|> filter_future_events(is_future)
|
||||
|> events_for_longevents(longevents)
|
||||
|> filter_public_visibility()
|
||||
|> filter_draft()
|
||||
|> filter_cancelled_events()
|
||||
|
@ -572,7 +587,7 @@ defmodule Mobilizon.Events do
|
|||
|> events_for_search_query()
|
||||
|> events_for_begins_on(Map.get(args, :begins_on, DateTime.utc_now()))
|
||||
|> events_for_ends_on(Map.get(args, :ends_on))
|
||||
|> events_for_longevents(args)
|
||||
|> events_for_longevents(Map.get(args, :longevents))
|
||||
|> events_for_category(args)
|
||||
|> events_for_categories(args)
|
||||
|> events_for_languages(args)
|
||||
|
@ -1379,15 +1394,13 @@ defmodule Mobilizon.Events do
|
|||
end
|
||||
end
|
||||
|
||||
@spec events_for_longevents(Ecto.Queryable.t(), map()) :: Ecto.Query.t()
|
||||
defp events_for_longevents(query, args) do
|
||||
@spec events_for_longevents(Ecto.Queryable.t(), Boolean.t() | nil) :: Ecto.Query.t()
|
||||
defp events_for_longevents(query, longevents) do
|
||||
duration = Config.get([:instance, :duration_of_long_event], 0)
|
||||
|
||||
if duration <= 0 do
|
||||
query
|
||||
else
|
||||
longevents = Map.get(args, :longevents)
|
||||
|
||||
case longevents do
|
||||
nil ->
|
||||
query
|
||||
|
|
8
package-lock.json
generated
8
package-lock.json
generated
|
@ -15,7 +15,7 @@
|
|||
"@fullcalendar/daygrid": "^6.1.10",
|
||||
"@fullcalendar/interaction": "^6.1.10",
|
||||
"@fullcalendar/vue3": "^6.1.10",
|
||||
"@oruga-ui/oruga-next": "^0.8.10",
|
||||
"@oruga-ui/oruga-next": "0.8.12",
|
||||
"@oruga-ui/theme-oruga": "^0.2.0",
|
||||
"@sentry/tracing": "^7.1",
|
||||
"@sentry/vue": "^7.1",
|
||||
|
@ -3138,9 +3138,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/@oruga-ui/oruga-next": {
|
||||
"version": "0.8.10",
|
||||
"resolved": "https://registry.npmjs.org/@oruga-ui/oruga-next/-/oruga-next-0.8.10.tgz",
|
||||
"integrity": "sha512-ETPSoGZu1parbj8C3V2ZojQnN4ptQMiJEwS9Hx44NcaDzu4q/FDsYkKYiz6G9kx8cDceXXxvydfOUpZePVVdzw==",
|
||||
"version": "0.8.12",
|
||||
"resolved": "https://registry.npmjs.org/@oruga-ui/oruga-next/-/oruga-next-0.8.12.tgz",
|
||||
"integrity": "sha512-I1jcsTA4J6HQdNpSWgK4cNSqv1cHsghQGtJ12p0yXDSJseek0Y8f4vf9+tDRtfONzWHuRyWUGcHIfePsRKVbiQ==",
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.0"
|
||||
}
|
||||
|
|
|
@ -32,11 +32,7 @@
|
|||
"@apollo/client": "^3.9.5",
|
||||
"@framasoft/socket": "^1.0.0",
|
||||
"@framasoft/socket-apollo-link": "^1.0.0",
|
||||
"@fullcalendar/core": "^6.1.10",
|
||||
"@fullcalendar/daygrid": "^6.1.10",
|
||||
"@fullcalendar/interaction": "^6.1.10",
|
||||
"@fullcalendar/vue3": "^6.1.10",
|
||||
"@oruga-ui/oruga-next": "^0.8.10",
|
||||
"@oruga-ui/oruga-next": "0.8.12",
|
||||
"@oruga-ui/theme-oruga": "^0.2.0",
|
||||
"@fullcalendar/core": "^6.1.10",
|
||||
"@fullcalendar/daygrid": "^6.1.10",
|
||||
|
|
|
@ -3,17 +3,17 @@ msgstr ""
|
|||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-09-24 14:40+0000\n"
|
||||
"PO-Revision-Date: 2023-10-22 04:28+0000\n"
|
||||
"Last-Translator: Jakub Urbanowicz <tex@przeklad.pl>\n"
|
||||
"PO-Revision-Date: 2024-07-04 23:35+0000\n"
|
||||
"Last-Translator: ewm <gnu.ewm@protonmail.com>\n"
|
||||
"Language-Team: Polish <https://weblate.framasoft.org/projects/mobilizon/"
|
||||
"backend/pl/>\n"
|
||||
"Language: pl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
|
||||
"|| n%100>=20) ? 1 : 2;\n"
|
||||
"X-Generator: Weblate 5.0.1\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
|
||||
"|| n%100>=20) ? 1 : 2);\n"
|
||||
"X-Generator: Weblate 5.6.2\n"
|
||||
|
||||
#: lib/web/templates/email/password_reset.html.heex:66
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -2373,30 +2373,32 @@ msgstr "Data rejestracji uczestnika"
|
|||
#: lib/web/templates/email/event_participation_confirmed.html.heex:122
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Cancel my attendance"
|
||||
msgstr ""
|
||||
msgstr "Anuluj moją obecność"
|
||||
|
||||
#: lib/web/templates/email/anonymous_participation_confirmation.html.heex:90
|
||||
#: lib/web/templates/email/event_participation_confirmed.html.heex:99
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "If you wish to cancel your participation, simply click on the link below."
|
||||
msgstr ""
|
||||
msgstr "Jeśli chcesz anulować swój udział, kliknij poniższy link."
|
||||
|
||||
#: lib/web/email/admin.ex:142
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Email configuration test for %{instance}"
|
||||
msgstr ""
|
||||
msgstr "Test konfiguracji poczty e-mail dla %{instance}"
|
||||
|
||||
#: lib/web/templates/email/email_configuration_test.html.heex:47
|
||||
#: lib/web/templates/email/email_configuration_test.text.eex:3
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "If you received this email, the email configuration seems to be correct."
|
||||
msgstr ""
|
||||
"Jeśli otrzymano tę wiadomość e-mail, konfiguracja poczty wydaje się być "
|
||||
"poprawna."
|
||||
|
||||
#: lib/web/templates/email/email_configuration_test.html.heex:18
|
||||
#: lib/web/templates/email/email_configuration_test.text.eex:1
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Well done!"
|
||||
msgstr ""
|
||||
msgstr "Dobra robota!"
|
||||
|
||||
#: lib/web/templates/api/terms.html.heex:55
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
|
|
|
@ -8,16 +8,16 @@
|
|||
## to merge POT files into PO files.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"PO-Revision-Date: 2023-10-22 23:11+0000\n"
|
||||
"Last-Translator: Jakub Urbanowicz <tex@przeklad.pl>\n"
|
||||
"PO-Revision-Date: 2024-07-04 23:35+0000\n"
|
||||
"Last-Translator: ewm <gnu.ewm@protonmail.com>\n"
|
||||
"Language-Team: Polish <https://weblate.framasoft.org/projects/mobilizon/"
|
||||
"backend-errors/pl/>\n"
|
||||
"Language: pl\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
|
||||
"|| n%100>=20) ? 1 : 2;\n"
|
||||
"X-Generator: Weblate 5.0.1\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
|
||||
"|| n%100>=20) ? 1 : 2);\n"
|
||||
"X-Generator: Weblate 5.6.2\n"
|
||||
|
||||
#: lib/mobilizon/discussions/discussion.ex:68
|
||||
#, elixir-autogen
|
||||
|
@ -1457,9 +1457,9 @@ msgstr ""
|
|||
#: lib/graphql/resolvers/conversation.ex:164
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Conversation needs to mention at least one participant that's not yourself"
|
||||
msgstr ""
|
||||
msgstr "Konwersacja musi zawierać co najmniej jedną wzmiankę o innym uczestniku"
|
||||
|
||||
#: lib/graphql/resolvers/participant.ex:401
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "There are no participants matching the audience you've selected."
|
||||
msgstr ""
|
||||
msgstr "Nie ma uczestników pasujących do wybranej grupy odbiorców."
|
||||
|
|
|
@ -45,7 +45,7 @@ body {
|
|||
@apply border-2 border-mbz-success bg-transparent text-mbz-success hover:bg-mbz-success hover:text-white;
|
||||
}
|
||||
.btn-outlined-warning {
|
||||
@apply bg-transparent border dark:text-white hover:dark:text-slate-900 hover:bg-mbz-warning border-mbz-warning;
|
||||
@apply border-2 bg-transparent dark:text-white hover:dark:text-slate-900 hover:bg-mbz-warning border-mbz-warning;
|
||||
}
|
||||
.btn-outlined-danger {
|
||||
@apply border-2 bg-transparent border-mbz-danger text-mbz-danger hover:bg-mbz-danger hover:text-white;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<o-taginput
|
||||
:modelValue="modelValueWithDisplayName"
|
||||
@update:modelValue="(val: IActor[]) => $emit('update:modelValue', val)"
|
||||
@update:modelValue="updateTags"
|
||||
:data="availableActors"
|
||||
:allow-autocomplete="true"
|
||||
:allow-new="false"
|
||||
|
@ -25,12 +25,16 @@ import { computed, ref } from "vue";
|
|||
import ActorInline from "./ActorInline.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: IActor[];
|
||||
const emit = defineEmits<{
|
||||
"update:modelValue": [value: IActor[]];
|
||||
}>();
|
||||
|
||||
defineEmits<{
|
||||
"update:modelValue": [value: IActor[]];
|
||||
const updateTags = (val: IActor[]) => {
|
||||
emit("update:modelValue", val);
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: IActor[];
|
||||
}>();
|
||||
|
||||
const modelValue = computed(() => props.modelValue);
|
||||
|
|
|
@ -23,6 +23,11 @@
|
|||
<script lang="ts" setup>
|
||||
import { localeShortWeekDayNames } from "@/utils/datetime";
|
||||
import { computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { locale } = useI18n({ useScope: "global" });
|
||||
|
||||
const localeConverted = locale.replace("_", "-");
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
|
@ -35,15 +40,15 @@ const props = withDefaults(
|
|||
const dateObj = computed<Date>(() => new Date(props.date));
|
||||
|
||||
const month = computed<string>(() =>
|
||||
dateObj.value.toLocaleString(undefined, { month: "short" })
|
||||
dateObj.value.toLocaleString(localeConverted, { month: "short" })
|
||||
);
|
||||
|
||||
const day = computed<string>(() =>
|
||||
dateObj.value.toLocaleString(undefined, { day: "numeric" })
|
||||
dateObj.value.toLocaleString(localeConverted, { day: "numeric" })
|
||||
);
|
||||
|
||||
const weekday = computed<string>(() =>
|
||||
dateObj.value.toLocaleString(undefined, { weekday: "short" })
|
||||
dateObj.value.toLocaleString(localeConverted, { weekday: "short" })
|
||||
);
|
||||
|
||||
const smallStyle = computed<string>(() => (props.small ? "1.2" : "2"));
|
||||
|
|
|
@ -13,8 +13,14 @@
|
|||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
import Clock from "vue-material-design-icons/ClockTimeTenOutline.vue";
|
||||
|
||||
const { locale } = useI18n({ useScope: "global" });
|
||||
|
||||
const localeConverted = locale.replace("_", "-");
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
date: string;
|
||||
|
@ -26,7 +32,7 @@ const props = withDefaults(
|
|||
const dateObj = computed<Date>(() => new Date(props.date));
|
||||
|
||||
const time = computed<string>(() =>
|
||||
dateObj.value.toLocaleTimeString(undefined, {
|
||||
dateObj.value.toLocaleTimeString(localeConverted, {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<section
|
||||
v-if="promotedCategories.length > 1"
|
||||
class="mx-auto container flex flex-wrap items-center justify-center gap-3 md:gap-5 my-3"
|
||||
>
|
||||
<CategoryCard
|
||||
|
@ -10,7 +11,6 @@
|
|||
:imageLazy="false"
|
||||
/>
|
||||
<router-link
|
||||
v-if="promotedCategories.length > 0"
|
||||
:to="{ name: RouteName.CATEGORIES }"
|
||||
class="flex items-end brightness-85 h-36 w-36 md:h-52 md:w-52 rounded-lg font-semibold text-lg md:text-xl p-4 text-white bg-gradient-to-r from-pink-500 via-red-500 to-yellow-500 hover:text-slate-200"
|
||||
>
|
||||
|
@ -47,13 +47,17 @@ const eventCategoryLabel = (categoryId: string): string | undefined => {
|
|||
return eventCategories.value?.find(({ id }) => categoryId == id)?.label;
|
||||
};
|
||||
|
||||
const promotedCategories = computed((): CategoryStatsModel[] => {
|
||||
return shuffle(
|
||||
categoryStats.value.filter(
|
||||
({ key, number }) =>
|
||||
key !== "MEETING" && number >= 1 && categoriesWithPictures.includes(key)
|
||||
)
|
||||
)
|
||||
const promotedCategories = computed((): CategoryStatsModel[] | null => {
|
||||
const relevant_categories = categoryStats.value.filter(
|
||||
({ key, number }) =>
|
||||
key !== "MEETING" && number >= 1 && categoriesWithPictures.includes(key)
|
||||
);
|
||||
|
||||
if (relevant_categories.length <= 1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return shuffle(relevant_categories)
|
||||
.map(({ key, number }) => ({
|
||||
key,
|
||||
number,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<form
|
||||
id="search-anchor"
|
||||
class="container mx-auto my-3 px-2 flex flex-wrap flex-col sm:flex-row items-stretch gap-2 text-center items-center justify-center dark:text-slate-100"
|
||||
class="container mx-auto my-3 px-2 flex flex-wrap flex-col sm:flex-row items-stretch gap-2 text-center justify-center dark:text-slate-100"
|
||||
role="search"
|
||||
@submit.prevent="submit"
|
||||
>
|
||||
|
@ -25,6 +25,12 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { IAddress } from "@/types/address.model";
|
||||
import { AddressSearchType } from "@/types/enums";
|
||||
import {
|
||||
addressToLocation,
|
||||
getLocationFromLocal,
|
||||
storeLocationInLocal,
|
||||
} from "@/utils/location";
|
||||
import { computed, defineAsyncComponent } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
|
@ -38,6 +44,7 @@ const props = defineProps<{
|
|||
location: IAddress | null;
|
||||
locationDefaultText?: string | null;
|
||||
search: string;
|
||||
fromLocalStorage?: boolean | false;
|
||||
}>();
|
||||
|
||||
const router = useRouter();
|
||||
|
@ -51,10 +58,19 @@ const emit = defineEmits<{
|
|||
|
||||
const location = computed({
|
||||
get(): IAddress | null {
|
||||
return props.location;
|
||||
if (props.location) {
|
||||
return props.location;
|
||||
}
|
||||
if (props.fromLocalStorage) {
|
||||
return getLocationFromLocal();
|
||||
}
|
||||
return null;
|
||||
},
|
||||
set(newLocation: IAddress | null) {
|
||||
emit("update:location", newLocation);
|
||||
if (props.fromLocalStorage) {
|
||||
storeLocationInLocal(newLocation);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -69,12 +85,7 @@ const search = computed({
|
|||
|
||||
const submit = () => {
|
||||
emit("submit");
|
||||
const lat = location.value?.geom
|
||||
? parseFloat(location.value?.geom?.split(";")?.[1])
|
||||
: undefined;
|
||||
const lon = location.value?.geom
|
||||
? parseFloat(location.value?.geom?.split(";")?.[0])
|
||||
: undefined;
|
||||
const { lat, lon } = addressToLocation(location.value);
|
||||
router.push({
|
||||
name: RouteName.SEARCH,
|
||||
query: {
|
||||
|
|
|
@ -1,20 +1,36 @@
|
|||
<template>
|
||||
<close-content
|
||||
class="container mx-auto px-2"
|
||||
v-show="loading || (events && events.total > 0)"
|
||||
:suggestGeoloc="suggestGeoloc"
|
||||
:suggestGeoloc="false"
|
||||
v-on="attrs"
|
||||
@doGeoLoc="emit('doGeoLoc')"
|
||||
:doingGeoloc="doingGeoloc"
|
||||
>
|
||||
<template #title>
|
||||
<template v-if="userLocationName">
|
||||
{{ t("Events nearby {position}", { position: userLocationName }) }}
|
||||
<template v-if="userLocation?.name">
|
||||
{{
|
||||
t("Incoming events and activities nearby {position}", {
|
||||
position: userLocation?.name,
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ t("Events close to you") }}
|
||||
{{ t("Incoming events and activities") }}
|
||||
</template>
|
||||
</template>
|
||||
<template #subtitle>
|
||||
<div v-if="!loading && events.total == 0">
|
||||
<template v-if="userLocation?.name">
|
||||
{{
|
||||
t(
|
||||
"No events found nearby {position}. Try removing your position to see all events!",
|
||||
{ position: userLocation?.name }
|
||||
)
|
||||
}}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ t("No events found") }}
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<skeleton-event-result
|
||||
v-for="i in 6"
|
||||
|
@ -28,25 +44,36 @@
|
|||
:key="event.uuid"
|
||||
/>
|
||||
<more-content
|
||||
v-if="userLocationName && userLocation?.lat && userLocation?.lon"
|
||||
v-if="userLocation?.name && userLocation?.lat && userLocation?.lon"
|
||||
:to="{
|
||||
name: RouteName.SEARCH,
|
||||
query: {
|
||||
locationName: userLocationName,
|
||||
locationName: userLocation?.name,
|
||||
lat: userLocation.lat?.toString(),
|
||||
lon: userLocation.lon?.toString(),
|
||||
contentType: 'EVENTS',
|
||||
contentType: 'ALL',
|
||||
distance: `${distance}_km`,
|
||||
},
|
||||
}"
|
||||
:picture="userLocation?.picture"
|
||||
>
|
||||
{{
|
||||
t("View more events around {position}", {
|
||||
position: userLocationName,
|
||||
t("View more events and activities around {position}", {
|
||||
position: userLocation?.name,
|
||||
})
|
||||
}}
|
||||
</more-content>
|
||||
<more-content
|
||||
v-else
|
||||
:to="{
|
||||
name: RouteName.SEARCH,
|
||||
query: {
|
||||
contentType: 'ALL',
|
||||
},
|
||||
}"
|
||||
>
|
||||
{{ t("View more events and activities") }}
|
||||
</more-content>
|
||||
</template>
|
||||
</close-content>
|
||||
</template>
|
||||
|
@ -55,76 +82,55 @@
|
|||
import { LocationType } from "../../types/user-location.model";
|
||||
import MoreContent from "./MoreContent.vue";
|
||||
import CloseContent from "./CloseContent.vue";
|
||||
import { computed, onMounted, useAttrs } from "vue";
|
||||
import { SEARCH_EVENTS } from "@/graphql/search";
|
||||
import { watch, computed, useAttrs } from "vue";
|
||||
import { FETCH_EVENTS } from "@/graphql/event";
|
||||
import { IEvent } from "@/types/event.model";
|
||||
import { useLazyQuery } from "@vue/apollo-composable";
|
||||
import { useQuery } from "@vue/apollo-composable";
|
||||
import EventCard from "../Event/EventCard.vue";
|
||||
import { Paginate } from "@/types/paginate";
|
||||
import SkeletonEventResult from "../Event/SkeletonEventResult.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { coordsToGeoHash } from "@/utils/location";
|
||||
import { roundToNearestMinute } from "@/utils/datetime";
|
||||
import RouteName from "@/router/name";
|
||||
import { EventSortField, SortDirection } from "@/types/enums";
|
||||
|
||||
const props = defineProps<{
|
||||
userLocation: LocationType;
|
||||
doingGeoloc?: boolean;
|
||||
}>();
|
||||
const emit = defineEmits(["doGeoLoc"]);
|
||||
|
||||
const EVENT_PAGE_LIMIT = 12;
|
||||
defineEmits(["doGeoLoc"]);
|
||||
|
||||
const { t } = useI18n({ useScope: "global" });
|
||||
const attrs = useAttrs();
|
||||
|
||||
const userLocation = computed(() => props.userLocation);
|
||||
|
||||
const userLocationName = computed(() => {
|
||||
return userLocation.value?.name;
|
||||
const geoHash = computed(() => {
|
||||
console.debug("userLocation updated", userLocation.value);
|
||||
const geo = coordsToGeoHash(userLocation.value.lat, userLocation.value.lon);
|
||||
console.debug("geohash:", geo);
|
||||
return geo;
|
||||
});
|
||||
const suggestGeoloc = computed(() => userLocation.value?.isIPLocation);
|
||||
|
||||
const geoHash = computed(() =>
|
||||
coordsToGeoHash(props.userLocation.lat, props.userLocation.lon)
|
||||
const distance = computed<number>(() =>
|
||||
userLocation.value?.isIPLocation ? 150 : 25
|
||||
);
|
||||
|
||||
const distance = computed<number>(() => (suggestGeoloc.value ? 150 : 25));
|
||||
|
||||
const now = computed(() => roundToNearestMinute(new Date()));
|
||||
|
||||
const searchEnabled = computed(() => geoHash.value != undefined);
|
||||
|
||||
const {
|
||||
result: eventsResult,
|
||||
loading: loadingEvents,
|
||||
load: load,
|
||||
} = useLazyQuery<{
|
||||
const eventsQuery = useQuery<{
|
||||
searchEvents: Paginate<IEvent>;
|
||||
}>(
|
||||
SEARCH_EVENTS,
|
||||
() => ({
|
||||
location: geoHash.value,
|
||||
beginsOn: now.value,
|
||||
endsOn: undefined,
|
||||
radius: distance.value,
|
||||
eventPage: 1,
|
||||
limit: EVENT_PAGE_LIMIT,
|
||||
type: "IN_PERSON",
|
||||
}),
|
||||
() => ({
|
||||
enabled: searchEnabled.value,
|
||||
fetchPolicy: "cache-first",
|
||||
})
|
||||
);
|
||||
}>(FETCH_EVENTS, () => ({
|
||||
orderBy: EventSortField.BEGINS_ON,
|
||||
direction: SortDirection.ASC,
|
||||
longevents: false,
|
||||
location: geoHash.value,
|
||||
radius: distance.value,
|
||||
}));
|
||||
|
||||
const events = computed(
|
||||
() => eventsResult.value?.searchEvents ?? { elements: [], total: 0 }
|
||||
() => eventsQuery.result.value?.events ?? { elements: [], total: 0 }
|
||||
);
|
||||
watch(events, (e) => console.debug("events: ", e));
|
||||
|
||||
onMounted(async () => {
|
||||
await load();
|
||||
});
|
||||
|
||||
const loading = computed(() => props.doingGeoloc || loadingEvents.value);
|
||||
const loading = computed(() => props.doingGeoloc || eventsQuery.loading.value);
|
||||
watch(loading, (l) => console.debug("loading: ", l));
|
||||
</script>
|
||||
|
|
|
@ -60,6 +60,7 @@ const { result: resultEvents, loading: loadingEvents } = useQuery<{
|
|||
}>(FETCH_EVENTS, {
|
||||
orderBy: EventSortField.BEGINS_ON,
|
||||
direction: SortDirection.ASC,
|
||||
longevents: false,
|
||||
});
|
||||
const events = computed(
|
||||
() => resultEvents.value?.events ?? { total: 0, elements: [] }
|
||||
|
|
|
@ -209,7 +209,7 @@
|
|||
:to="{ name: RouteName.EVENT_CALENDAR }"
|
||||
class="block relative py-2 pr-4 pl-3 text-zinc-700 border-b border-gray-100 hover:bg-zinc-50 md:hover:bg-transparent md:border-0 md:hover:text-mbz-purple-700 md:p-0 dark:text-zinc-400 md:dark:hover:text-white dark:hover:bg-zinc-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
|
||||
>{{ t("Calendar")
|
||||
}}<span class="absolute right-0 text-sm"
|
||||
}}<span class="absolute right-0 text-xs"
|
||||
><br />(beta)</span
|
||||
></router-link
|
||||
>
|
||||
|
|
|
@ -2,6 +2,7 @@ import { IDENTITIES, REGISTER_PERSON } from "@/graphql/actor";
|
|||
import {
|
||||
CURRENT_USER_CLIENT,
|
||||
LOGGED_USER,
|
||||
LOGGED_USER_LOCATION,
|
||||
SET_USER_SETTINGS,
|
||||
UPDATE_USER_LOCALE,
|
||||
USER_SETTINGS,
|
||||
|
@ -51,6 +52,20 @@ export function useUserSettings() {
|
|||
return { loggedUser, error, loading };
|
||||
}
|
||||
|
||||
export function useUserLocation() {
|
||||
const {
|
||||
result: userSettingsResult,
|
||||
error,
|
||||
loading,
|
||||
onResult,
|
||||
} = useQuery<{ loggedUser: IUser }>(LOGGED_USER_LOCATION);
|
||||
|
||||
const location = computed(
|
||||
() => userSettingsResult.value?.loggedUser.settings.location
|
||||
);
|
||||
return { location, error, loading, onResult };
|
||||
}
|
||||
|
||||
export async function doUpdateSetting(
|
||||
variables: Record<string, unknown>
|
||||
): Promise<void> {
|
||||
|
|
|
@ -16,12 +16,15 @@ export const ACTOR_FRAGMENT = gql`
|
|||
}
|
||||
`;
|
||||
|
||||
// Do not request mediaSize here because mediaSize can only be accessed
|
||||
// by user_himself/moderator/administrator (can_get_actor_size? in media.ex)
|
||||
// - FETCH_PERSON is used by <NewConversation> and can be used by simple users here
|
||||
// - FETCH_PERSON is also used in <EditIdentity> but mediaSize is not used there
|
||||
export const FETCH_PERSON = gql`
|
||||
query FetchPerson($username: String!) {
|
||||
fetchPerson(preferredUsername: $username) {
|
||||
...ActorFragment
|
||||
suspended
|
||||
mediaSize
|
||||
avatar {
|
||||
id
|
||||
name
|
||||
|
|
|
@ -103,16 +103,22 @@ export const FETCH_EVENT_BASIC = gql`
|
|||
|
||||
export const FETCH_EVENTS = gql`
|
||||
query FetchEvents(
|
||||
$location: String
|
||||
$radius: Float
|
||||
$orderBy: EventOrderBy
|
||||
$direction: SortDirection
|
||||
$page: Int
|
||||
$limit: Int
|
||||
$longevents: Boolean
|
||||
) {
|
||||
events(
|
||||
location: $location
|
||||
radius: $radius
|
||||
orderBy: $orderBy
|
||||
direction: $direction
|
||||
page: $page
|
||||
limit: $limit
|
||||
longevents: $longevents
|
||||
) {
|
||||
total
|
||||
elements {
|
||||
|
|
|
@ -218,6 +218,7 @@ export const SEARCH_CALENDAR_EVENTS = gql`
|
|||
endsOn: $endsOn
|
||||
page: $eventPage
|
||||
limit: $limit
|
||||
longevents: false
|
||||
) {
|
||||
total
|
||||
elements {
|
||||
|
|
|
@ -138,6 +138,20 @@ export const USER_SETTINGS = gql`
|
|||
${USER_SETTINGS_FRAGMENT}
|
||||
`;
|
||||
|
||||
export const LOGGED_USER_LOCATION = gql`
|
||||
query LoggedUserLocation {
|
||||
loggedUser {
|
||||
settings {
|
||||
location {
|
||||
range
|
||||
geohash
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const LOGGED_USER_TIMEZONE = gql`
|
||||
query LoggedUserTimezone {
|
||||
loggedUser {
|
||||
|
|
|
@ -1626,7 +1626,7 @@
|
|||
"With {participants}": "With {participants}",
|
||||
"Conversations": "Conversations",
|
||||
"New private message": "New private message",
|
||||
"There's no conversations yet": "There's no conversations yet",
|
||||
"There's no conversations yet": "There are no conversations yet",
|
||||
"Open conversations": "Open conversations",
|
||||
"List of conversations": "List of conversations",
|
||||
"Conversation with {participants}": "Conversation with {participants}",
|
||||
|
|
|
@ -79,6 +79,7 @@
|
|||
"Add a contact": "Dodaj kontakt",
|
||||
"Add a new post": "Dodaj nowy wpis",
|
||||
"Add a note": "Dodaj notatkę",
|
||||
"Add a recipient": "Dodaj odbiorcę",
|
||||
"Add a todo": "Dodaj element listy do zrobienia",
|
||||
"Add an address": "Dodaj adres",
|
||||
"Add an instance": "Dodaj instancję",
|
||||
|
@ -118,6 +119,7 @@
|
|||
"And {number} comments": "Oraz {number} komentarzy",
|
||||
"Announcements": "Ogłoszenia",
|
||||
"Announcements and mentions notifications are always sent straight away.": "Ogłoszenia i powiadomienia o wzmiankach są zawsze wysyłane natychmiast.",
|
||||
"Announcements for {eventTitle}": "Ogłoszenia dla {eventTitle}",
|
||||
"Anonymous participant": "Anonimowy(-a) uczestnik(-czka)",
|
||||
"Anonymous participants will be asked to confirm their participation through e-mail.": "Anonimowi uczestnicy będą otrzymywać prośbę o potwierdzenie uczestnictwa przez e-mail.",
|
||||
"Anonymous participations": "Anonimowy udział",
|
||||
|
@ -146,6 +148,7 @@
|
|||
"Are you sure you want to cancel the event creation? You'll lose all modifications.": "Czy na pewno chcesz anulować tworzenie wydarzenia? Utracisz wszystkie zmiany.",
|
||||
"Are you sure you want to cancel the event edition? You'll lose all modifications.": "Czy na pewno chcesz usunąć edycję wydarzenia? Utracisz wszystkie zmiany.",
|
||||
"Are you sure you want to cancel your participation at event \"{title}\"?": "Czy na pewno chcesz wycofać swój udział w wydarzeniu „{title}”?",
|
||||
"Are you sure you want to delete this entire conversation?": "Czy na pewno chcesz usunąć całą konwersację?",
|
||||
"Are you sure you want to delete this entire discussion?": "Czy na pewno usunąć tę całą dyskusję?",
|
||||
"Are you sure you want to delete this event? This action cannot be reverted.": "Czy na pewno chcesz usunąć to wydarzenie? To działanie nie może zostać odwrócone.",
|
||||
"Are you sure you want to delete this post? This action cannot be reverted.": "Czy na pewno chcesz usunąć ten wpis? Tego działania nie można cofnąć.",
|
||||
|
@ -183,6 +186,7 @@
|
|||
"By transit": "Transportem publicznym",
|
||||
"By {group}": "Autorstwa {group}",
|
||||
"By {username}": "Od {username}",
|
||||
"Calendar": "Kalendarz",
|
||||
"Can be an email or a link, or just plain text.": "Może być adresem e-mail, odnośnikiem lub zwykłym tekstem.",
|
||||
"Cancel": "Anuluj",
|
||||
"Cancel anonymous participation": "Anuluj anonimowy udział",
|
||||
|
@ -193,6 +197,7 @@
|
|||
"Cancel membership request": "Anulowanie prośby o członkostwo",
|
||||
"Cancel my participation request…": "Anuluj moje zgłoszenie udziału…",
|
||||
"Cancel my participation…": "Anuluj mój udział…",
|
||||
"Cancel participation": "Anuluj swój udział",
|
||||
"Cancelled": "Anulowane",
|
||||
"Cancelled: Won't happen": "Anulowano: Nie odbędzie się",
|
||||
"Categories": "Kategorie",
|
||||
|
@ -229,6 +234,8 @@
|
|||
"Comment body": "Treść komentarza",
|
||||
"Comment deleted": "Usunięto komentarz",
|
||||
"Comment deleted and report resolved": "Komentarz usunięto i załatwiono zgłoszenie",
|
||||
"Comment from a private conversation": "Komentarz z prywatnej konwersacji",
|
||||
"Comment from an event announcement": "Komentarz z ogłoszenia o wydarzeniu",
|
||||
"Comment from {'@'}{username} reported": "Komentarz {'@'}{username} zgłoszony",
|
||||
"Comment text can't be empty": "Tekst komentarza nie morze być pusty",
|
||||
"Comment under event {eventTitle}": "Komentarz pod wydarzeniem {eventTitle}",
|
||||
|
@ -246,6 +253,8 @@
|
|||
"Contact": "Kontakt",
|
||||
"Continue": "Kontynuuj",
|
||||
"Continue editing": "Kontynuuj edycję",
|
||||
"Conversation with {participants}": "Konwersacja z {participants}",
|
||||
"Conversations": "Konwersacje",
|
||||
"Cookies and Local storage": "Pliki cookies i pamięć lokalna",
|
||||
"Copy URL to clipboard": "Kopiowanie adresu URL do schowka",
|
||||
"Copy details to clipboard": "Kopiowanie szczegółów do schowka",
|
||||
|
@ -299,6 +308,7 @@
|
|||
"Default": "Domyślne",
|
||||
"Default Mobilizon privacy policy": "Domyślna polityka prywatności Mobilizon",
|
||||
"Default Mobilizon terms": "Domyślne warunki użytkowania Mobilizon",
|
||||
"Default Picture": "Obraz domyślny",
|
||||
"Delete": "Usuń",
|
||||
"Delete account": "Usuń konto",
|
||||
"Delete comment": "Usuń komentarz",
|
||||
|
@ -318,6 +328,7 @@
|
|||
"Delete my account": "Usuń moje konto",
|
||||
"Delete post": "Usuń wpis",
|
||||
"Delete profiles": "Usuwanie profili",
|
||||
"Delete this conversation": "Usuń tę konwersację",
|
||||
"Delete this discussion": "Usunięcie tej dyskusji",
|
||||
"Delete this identity": "Usuń tę tożsamość",
|
||||
"Delete your identity": "Usuń swoją tożsamość",
|
||||
|
@ -349,6 +360,7 @@
|
|||
"Do you wish to {create_group} or {explore_groups}?": "Czy chcesz {create_group} lub {explore_groups}?",
|
||||
"Does the event needs to be confirmed later or is it cancelled?": "Czy wydarzenie wymaga jeszcze późniejszego potwierdzenia, czy też zostało odwołane?",
|
||||
"Domain": "Domen",
|
||||
"Domain or instance name": "Nazwa domeny lub instancji",
|
||||
"Draft": "Szkic",
|
||||
"Drafts": "Szkice",
|
||||
"Due on": "Zaplanowane na",
|
||||
|
@ -381,6 +393,7 @@
|
|||
"Error details copied!": "Szczegóły błędu zostały skopiowane!",
|
||||
"Error message": "Komunikat o błędzie",
|
||||
"Error stacktrace": "Ślad stosu błędów",
|
||||
"Error while cancelling your participation": "Błąd podczas anulowania uczestnictwa",
|
||||
"Error while changing email": "Wystąpił błąd podczas zmiany adresu e-mail",
|
||||
"Error while loading the preview": "Błąd podczas ładowania podglądu",
|
||||
"Error while login with {provider}. Retry or login another way.": "Błąd logowania z {provider}. Spróbuj ponownie lub zaloguj się w inny sposób.",
|
||||
|
@ -429,6 +442,7 @@
|
|||
"External registration": "Rejestracja zewnętrzna",
|
||||
"Failed to get location.": "Nie udało się uzyskać lokalizacji.",
|
||||
"Failed to save admin settings": "Nie udało się zapisać ustawień administratora (-ki)",
|
||||
"Favicon": "Favicon",
|
||||
"Featured events": "Wyróżnione wydarzenia",
|
||||
"Federated Group Name": "Sfederowana nazwa grupy",
|
||||
"Federation": "Federacja",
|
||||
|
@ -467,6 +481,7 @@
|
|||
"From the {startDate} at {startTime} to the {endDate}": "Od {startDate} o {startTime} do {endDate}",
|
||||
"From the {startDate} at {startTime} to the {endDate} at {endTime}": "Od {startDate} o {startTime} do {endDate} o {endTime}",
|
||||
"From the {startDate} to the {endDate}": "Od {startDate} do {endDate}",
|
||||
"From this instance only": "Tylko z tej instancji",
|
||||
"From yourself": "Od Ciebie",
|
||||
"Fully accessible with a wheelchair": "W pełni dostępne na wózku",
|
||||
"Gather ⋅ Organize ⋅ Mobilize": "Gromadźcie się ⋅ Organizujcie się⋅ Mobilizujcie się",
|
||||
|
@ -610,6 +625,7 @@
|
|||
"Light": "Jasny",
|
||||
"Limited number of places": "Ograniczona liczba miejsc",
|
||||
"List": "Lista",
|
||||
"List of conversations": "Lista konwersacji",
|
||||
"List title": "Tytuł listy",
|
||||
"Live": "Na żywo",
|
||||
"Load more": "Załaduj więcej",
|
||||
|
@ -627,6 +643,8 @@
|
|||
"Login on Mobilizon!": "Zaloguj się na Mobilizon!",
|
||||
"Login on {instance}": "Zaloguj się na {instance}",
|
||||
"Login status": "Stan logowania",
|
||||
"Logo": "Logo",
|
||||
"Logo of the instance. Defaults to the upstream Mobilizon logo.": "Logo instancji. Domyślnie jest to logo Mobilizon.",
|
||||
"Main languages you/your moderators speak": "Główne języki którymi posługujesz się Ty / moderatorzy (-ki)",
|
||||
"Make sure that all words are spelled correctly.": "Upewnij się, że wszystkie słowa są napisane poprawnie.",
|
||||
"Manage activity settings": "Zarządzanie ustawieniami aktywności",
|
||||
|
@ -684,6 +702,7 @@
|
|||
"Name": "Nazwa",
|
||||
"Navigated to {pageTitle}": "Przejście do {pageTitle}",
|
||||
"Never used": "Nigdy nie użyty",
|
||||
"New announcement": "Nowe ogłoszenie",
|
||||
"New discussion": "Nowa dyskusja",
|
||||
"New email": "Nowy adres e-mail",
|
||||
"New folder": "Nowy katalog",
|
||||
|
@ -692,11 +711,13 @@
|
|||
"New note": "Nowa notatka",
|
||||
"New password": "Nowe hasło",
|
||||
"New post": "Nowy wpis",
|
||||
"New private message": "Nowa wiadomość prywatna",
|
||||
"New profile": "Nowy profil",
|
||||
"Next": "Następny",
|
||||
"Next month": "W kolejnym miesiącu",
|
||||
"Next page": "Następna strona",
|
||||
"Next week": "W następnym tygodniu",
|
||||
"No activities found": "Nie znaleziono żadnych aktywności",
|
||||
"No address defined": "Nie określono adresu",
|
||||
"No apps authorized yet": "Żadne aplikacje nie zostały jeszcze autoryzowane",
|
||||
"No categories with public upcoming events on this instance were found.": "Nie znaleziono żadnych kategorii z publicznymi nadchodzącymi wydarzeniami w tej instancji.",
|
||||
|
@ -868,6 +889,7 @@
|
|||
"Previous month": "Poprzedni miesiąc",
|
||||
"Previous page": "Poprzednia strona",
|
||||
"Price sheet": "Cennik",
|
||||
"Primary Color": "Kolor podstawowy",
|
||||
"Privacy": "Prywatność",
|
||||
"Privacy Policy": "Polityka prywatności",
|
||||
"Privacy policy": "Polityka prywatności",
|
||||
|
@ -968,6 +990,7 @@
|
|||
"Resource provided is not an URL": "Podany zasób nie jest adresem URL",
|
||||
"Resources": "Zasoby",
|
||||
"Restricted": "Ograniczona",
|
||||
"Return to the event page": "Wróć do strony wydarzenia",
|
||||
"Return to the group page": "Powrót do strony grupy",
|
||||
"Revoke": "Cofnięcie uprawnień",
|
||||
"Right now": "Właśnie teraz",
|
||||
|
@ -982,6 +1005,7 @@
|
|||
"Search events, groups, etc.": "Szukaj wydarzeń, grup itp.",
|
||||
"Search target": "Cel wyszukiwania",
|
||||
"Searching…": "Wyszukiwanie…",
|
||||
"Secondary Color": "Kolor dodatkowy",
|
||||
"Select a category": "Wybierz kategorię",
|
||||
"Select a language": "Wybierz język",
|
||||
"Select a radius": "Wybierz promień",
|
||||
|
@ -1021,6 +1045,7 @@
|
|||
"Smoke free": "Strefa wolna od dymu tytoniowego",
|
||||
"Smoking allowed": "Palenie dozwolone",
|
||||
"Social": "Społeczne",
|
||||
"Software details: {software_details}": "Szczegóły oprogramowania: {software_details}",
|
||||
"Some terms, technical or otherwise, used in the text below may cover concepts that are difficult to grasp. We have provided a glossary here to help you understand them better:": "Część terminów z tekstu, technicznych lub innych, może być trudna do zrozumienia. Dlatego utworzyliśmy słownik który pomoże je lepiej zrozumieć:",
|
||||
"Sorry, we wen't able to save your feedback. Don't worry, we'll try to fix this issue anyway.": "Przepraszamy, nie mogliśmy zapisać Twoich informacji zwrotnych. Nie martw się, postaramy się naprawić ten błąd.",
|
||||
"Sort by": "Sortuj według",
|
||||
|
@ -1122,6 +1147,7 @@
|
|||
"There will be no way to recover your data.": "Nie będzie możliwości przywrócenia Twoich danych.",
|
||||
"There will be no way to restore the profile's data!": "Nie będzie możliwości przywrócenia danych profilu!",
|
||||
"There will be no way to restore the user's data!": "Nie będzie możliwości przywrócenia danych użytkownika!",
|
||||
"There's no announcements yet": "Nie ma jeszcze żadnych ogłoszeń",
|
||||
"There's no discussions yet": "Nie ma jeszcze dyskusji",
|
||||
"These apps can access your account through the API. If you see here apps that you don't recognize, that don't work as expected or that you don't use anymore, you can revoke their access.": "Aplikacje te mogą uzyskać dostęp do konta przez API. Jeśli widzisz tutaj aplikacje, których nie rozpoznajesz, które nie działają zgodnie z oczekiwaniami lub których już nie używasz, możesz odebrać im dostęp.",
|
||||
"These events may interest you": "Te wydarzenia mogą Cię zainteresować",
|
||||
|
@ -1312,6 +1338,7 @@
|
|||
"Visibility was set to public.": "Widoczność została ustawiona na publiczną.",
|
||||
"Visible everywhere on the web": "Widoczna w całej sieci",
|
||||
"Visible everywhere on the web (public)": "Widoczne w całym internecie (publiczne)",
|
||||
"Visit {instance_domain}": "Odwiedź {instance_domain}",
|
||||
"Waiting for organization team approval.": "Oczekiwanie na zatwierdzenie przez organizatorów.",
|
||||
"Warning": "Ostrzeżenie",
|
||||
"We collect your feedback and the error information in order to improve this service.": "Zbieramy informacje zwrotne i informacje o błędach w celu ulepszenia tej usługi.",
|
||||
|
@ -1346,6 +1373,8 @@
|
|||
"Why create an account?": "Dlaczego warto założyć konto?",
|
||||
"Will allow to display and manage your participation status on the event page when using this device. Uncheck if you're using a public device.": "Pozwoli na wyświetlenie i zarządzanie stanem uczestnictwa na stronie wydarzenia gdy używasz tego urządzenia. Odznacz, jeżeli korzystasz z ogólnodostępnego urządzenia.",
|
||||
"With the most participants": "Z największą liczbą uczestników(-czek)",
|
||||
"With unknown participants": "Z nieznanymi uczestnikami",
|
||||
"With {participants}": "Z {participants}",
|
||||
"Within {number} kilometers of {place}": "|W promieniu jednego kilometra od {place}|W promieniu {number} kilometrów od {place}",
|
||||
"Write a new comment": "Napisz nowy komentarz",
|
||||
"Write a new message": "Napisz nową wiadomość",
|
||||
|
@ -1410,6 +1439,7 @@
|
|||
"You moved the folder {resource} to the root folder.": "Katalog {resource} został przeniesiony do katalogu głównego przez Ciebie.",
|
||||
"You moved the resource {resource} into {new_path}.": "Zasób {resource} został przez Ciebie przeniesiony do {new_path}.",
|
||||
"You moved the resource {resource} to the root folder.": "Zasób {resource} został przez Ciebie przeniesiony do katalogu głównego.",
|
||||
"You need to enter a text": "Należy wprowadzić tekst",
|
||||
"You need to login.": "Musisz się zalogować.",
|
||||
"You need to provide the following code to your application. It will only be valid for a few minutes.": "Musisz dostarczyć następujący kod do swojej aplikacji. Będzie on ważny tylko przez kilka minut.",
|
||||
"You posted a comment on the event {event}.": "Twój komentarz do wydarzenia {event} został zamieszczony.",
|
||||
|
@ -1460,9 +1490,11 @@
|
|||
"Your federated identity": "Twoja sfederowana tożsamość",
|
||||
"Your membership is pending approval": "Twoje członkostwo oczekuje na zatwierdzenie",
|
||||
"Your membership was approved by {profile}.": "Twoje członkostwo zostało zatwierdzone przez {profile}.",
|
||||
"Your participation has been cancelled": "Twój udział został anulowany",
|
||||
"Your participation has been confirmed": "Twój udział został potwierdzony",
|
||||
"Your participation has been rejected": "Twoje uczestnictwo zostało odrzucone",
|
||||
"Your participation has been requested": "Poprosiłeś(-aś) o uczestnictwo",
|
||||
"Your participation is being cancelled": "Twój udział zostaje anulowany",
|
||||
"Your participation request has been validated": "Twoje uczestnictwo zostało zweryfikowane",
|
||||
"Your participation request is being validated": "Twoje uczestnictwo jest weryfikowane",
|
||||
"Your participation status has been changed": "Stan Twojego uczestnictwa został zmieniony",
|
||||
|
@ -1530,6 +1562,7 @@
|
|||
"{count} members or followers": "Brak członków(--iń) i osób obserwujących|Jeden członek / jedna członkini lub osoba obserwująca|{count} członków / członkinie(-ń) lub osób obserwujących",
|
||||
"{count} participants": "Jeszcze nie ma uczestników / uczestniczek|Jeden uczestnik / uczestniczka|{count} uczestników / uczestniczek",
|
||||
"{count} requests waiting": "{count} oczekujących zgłoszeń",
|
||||
"{eventsCount} activities found": "Nie znaleziono żadnych aktywności|Znaleziono jedną aktywność|Znaleziono {eventsCount} aktywności",
|
||||
"{eventsCount} events found": "Nie znaleziono wydarzeń|Znaleziono jedno wydarzenie|Znaleziono {eventsCount} wydarzenia/wydarzeń",
|
||||
"{folder} - Resources": "{folder} – Zasoby",
|
||||
"{groupsCount} groups found": "Nie znaleziono żadnych grup|Znaleziono jedną grupę|Znaleziono {groupsCount} grup",
|
||||
|
|
|
@ -1,7 +1,24 @@
|
|||
import ngeohash from "ngeohash";
|
||||
|
||||
import { IAddress, Address } from "@/types/address.model";
|
||||
import { LocationType } from "@/types/user-location.model";
|
||||
import { IUserPreferredLocation } from "@/types/current-user.model";
|
||||
|
||||
const GEOHASH_DEPTH = 9; // put enough accuracy, radius will be used anyway
|
||||
|
||||
export const addressToLocation = (
|
||||
address: IAddress
|
||||
): LocationType | undefined => {
|
||||
if (!address.geom) return undefined;
|
||||
const arr = address.geom.split(";");
|
||||
if (arr.length < 2) return undefined;
|
||||
return {
|
||||
lon: parseFloat(arr[0]),
|
||||
lat: parseFloat(arr[1]),
|
||||
name: address.description,
|
||||
};
|
||||
};
|
||||
|
||||
export const coordsToGeoHash = (
|
||||
lat: number | undefined,
|
||||
lon: number | undefined,
|
||||
|
@ -14,9 +31,72 @@ export const coordsToGeoHash = (
|
|||
};
|
||||
|
||||
export const geoHashToCoords = (
|
||||
geohash: string | undefined
|
||||
geohash: string | undefined | null
|
||||
): { latitude: number; longitude: number } | undefined => {
|
||||
if (!geohash) return undefined;
|
||||
const { latitude, longitude } = ngeohash.decode(geohash);
|
||||
return latitude && longitude ? { latitude, longitude } : undefined;
|
||||
};
|
||||
|
||||
export const storeLocationInLocal = (location: IAddress | null): undefined => {
|
||||
if (location) {
|
||||
window.localStorage.setItem("location", JSON.stringify(location));
|
||||
} else {
|
||||
window.localStorage.removeItem("location");
|
||||
}
|
||||
};
|
||||
|
||||
export const getLocationFromLocal = (): 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 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 = (
|
||||
location: IUserPreferredLocation | null
|
||||
): undefined => {
|
||||
if (location) {
|
||||
const latlon = geoHashToCoords(location.geohash);
|
||||
if (latlon) {
|
||||
storeLocationInLocal({
|
||||
...new Address(),
|
||||
geom: `${latlon.longitude};${latlon.latitude}`,
|
||||
description: location.name || "",
|
||||
type: "administrative",
|
||||
});
|
||||
}
|
||||
if (location.range) {
|
||||
storeRadiusInLocal(location.range);
|
||||
} else {
|
||||
console.debug("user has not set a radius");
|
||||
}
|
||||
} else {
|
||||
console.debug("user has not set a location");
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,6 +2,12 @@
|
|||
<!-- Unlogged introduction -->
|
||||
<unlogged-introduction :config="config" v-if="config && !isLoggedIn" />
|
||||
<!-- Search fields -->
|
||||
<search-fields
|
||||
v-model:search="search"
|
||||
v-model:location="location"
|
||||
:locationDefaultText="location?.description"
|
||||
:fromLocalStorage="true"
|
||||
/>
|
||||
<!-- Categories preview
|
||||
<categories-preview /> -->
|
||||
<!-- Welcome back -->
|
||||
|
@ -118,7 +124,6 @@
|
|||
/>
|
||||
<CloseGroups :userLocation="userLocation" @doGeoLoc="performGeoLocation()" />
|
||||
<OnlineEvents />
|
||||
<LastEvents v-if="instanceName" :instanceName="instanceName" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -135,7 +140,7 @@ import { IEvent } from "../types/event.model";
|
|||
// import { IFollowedGroupEvent } from "../types/followedGroupEvent.model";
|
||||
import CloseEvents from "@/components/Local/CloseEvents.vue";
|
||||
import CloseGroups from "@/components/Local/CloseGroups.vue";
|
||||
import LastEvents from "@/components/Local/LastEvents.vue";
|
||||
// import LastEvents from "@/components/Local/LastEvents.vue";
|
||||
import UpcomingEvents from "@/components/Local/UpcomingEvents.vue";
|
||||
import OnlineEvents from "@/components/Local/OnlineEvents.vue";
|
||||
import {
|
||||
|
@ -159,7 +164,7 @@ import CategoriesPreview from "@/components/Home/CategoriesPreview.vue";
|
|||
import UnloggedIntroduction from "@/components/Home/UnloggedIntroduction.vue";
|
||||
import SearchFields from "@/components/Home/SearchFields.vue";
|
||||
import { useHead } from "@unhead/vue";
|
||||
import { geoHashToCoords } from "@/utils/location";
|
||||
import { addressToLocation, geoHashToCoords } from "@/utils/location";
|
||||
import { useServerProvidedLocation } from "@/composition/apollo/config";
|
||||
import { ABOUT } from "@/graphql/config";
|
||||
import { IConfig } from "@/types/config.model";
|
||||
|
@ -215,6 +220,10 @@ const currentUserParticipations = computed(
|
|||
const location = ref(null);
|
||||
const search = ref("");
|
||||
|
||||
watch(location, (newLoc, oldLoc) =>
|
||||
console.debug("LOCATION UPDATED from", { ...oldLoc }, " to ", { ...newLoc })
|
||||
);
|
||||
|
||||
const isToday = (date: string): boolean => {
|
||||
return new Date(date).toDateString() === new Date().toDateString();
|
||||
};
|
||||
|
@ -359,11 +368,7 @@ const coords = computed(() => {
|
|||
userSettingsLocationGeoHash.value ?? undefined
|
||||
);
|
||||
|
||||
if (userSettingsCoords) {
|
||||
return { ...userSettingsCoords, isIPLocation: false };
|
||||
}
|
||||
|
||||
return { ...serverLocation.value, isIPLocation: true };
|
||||
return { ...serverLocation.value, isIPLocation: !userSettingsCoords };
|
||||
});
|
||||
|
||||
const { result: reverseGeocodeResult } = useQuery<{
|
||||
|
@ -404,6 +409,11 @@ const currentUserLocation = computed(() => {
|
|||
});
|
||||
|
||||
const userLocation = computed(() => {
|
||||
console.debug("new userLocation");
|
||||
if (location.value) {
|
||||
console.debug("userLocation is typed location");
|
||||
return addressToLocation(location.value);
|
||||
}
|
||||
if (
|
||||
!userSettingsLocation.value ||
|
||||
(userSettingsLocation.value?.isIPLocation &&
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
<template>
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<SearchFields
|
||||
<search-fields
|
||||
class="md:ml-10 mr-2"
|
||||
v-model:search="search"
|
||||
v-model:location="location"
|
||||
:locationDefaultText="locationName"
|
||||
:fromLocalStorage="true"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
@ -917,6 +918,9 @@ const groupPage = useRouteQuery("groupPage", 1, integerTransformer);
|
|||
const latitude = useRouteQuery("lat", 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 when = useRouteQuery("when", "any");
|
||||
const contentType = useRouteQuery(
|
||||
|
|
|
@ -127,15 +127,17 @@
|
|||
<script setup lang="ts">
|
||||
import { LOGIN } from "@/graphql/auth";
|
||||
import { LOGIN_CONFIG } from "@/graphql/config";
|
||||
import { LOGGED_USER_LOCATION } from "@/graphql/user";
|
||||
import { UPDATE_CURRENT_USER_CLIENT } from "@/graphql/user";
|
||||
import { IConfig } from "@/types/config.model";
|
||||
import { ILogin } from "@/types/login.model";
|
||||
import { IUser } from "@/types/current-user.model";
|
||||
import { saveUserData, SELECTED_PROVIDERS } from "@/utils/auth";
|
||||
import { storeUserLocationAndRadiusFromUserSettings } from "@/utils/location";
|
||||
import {
|
||||
initializeCurrentActor,
|
||||
NoIdentitiesException,
|
||||
} from "@/utils/identity";
|
||||
import { useMutation, useQuery } from "@vue/apollo-composable";
|
||||
import { useMutation, useLazyQuery, useQuery } from "@vue/apollo-composable";
|
||||
import { computed, reactive, ref, onMounted } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
@ -153,14 +155,14 @@ const route = useRoute();
|
|||
|
||||
const { currentUser } = useCurrentUserClient();
|
||||
|
||||
const { result: configResult } = useQuery<{
|
||||
const configQuery = useQuery<{
|
||||
config: Pick<
|
||||
IConfig,
|
||||
"auth" | "registrationsOpen" | "registrationsAllowlist"
|
||||
>;
|
||||
}>(LOGIN_CONFIG);
|
||||
|
||||
const config = computed(() => configResult.value?.config);
|
||||
const config = computed(() => configQuery.result.value?.config);
|
||||
|
||||
const canRegister = computed(() => {
|
||||
return (
|
||||
|
@ -181,85 +183,90 @@ const credentials = reactive({
|
|||
const redirect = useRouteQuery("redirect", "");
|
||||
const errorCode = useRouteQuery("code", null, enumTransformer(LoginErrorCode));
|
||||
|
||||
const {
|
||||
onDone: onLoginMutationDone,
|
||||
onError: onLoginMutationError,
|
||||
mutate: loginMutation,
|
||||
} = useMutation(LOGIN);
|
||||
// Login
|
||||
const loginMutation = useMutation(LOGIN);
|
||||
// Load user identities
|
||||
const currentUserIdentitiesQuery = useLazyCurrentUserIdentities();
|
||||
// Update user in cache
|
||||
const currentUserMutation = useMutation(UPDATE_CURRENT_USER_CLIENT);
|
||||
// Retrieve preferred location
|
||||
const loggedUserLocationQuery = useLazyQuery<{
|
||||
loggedUser: IUser;
|
||||
}>(LOGGED_USER_LOCATION);
|
||||
|
||||
onLoginMutationDone(async (result) => {
|
||||
const data = result.data;
|
||||
submitted.value = false;
|
||||
if (data == null) {
|
||||
throw new Error("Data is undefined");
|
||||
}
|
||||
|
||||
saveUserData(data.login);
|
||||
await setupClientUserAndActors(data.login);
|
||||
|
||||
if (redirect.value) {
|
||||
console.debug("We have a redirect", redirect.value);
|
||||
router.push(redirect.value);
|
||||
return;
|
||||
}
|
||||
console.debug("No redirect, going to homepage");
|
||||
if (window.localStorage) {
|
||||
console.debug("Has localstorage, setting welcome back");
|
||||
window.localStorage.setItem("welcome-back", "yes");
|
||||
}
|
||||
router.replace({ name: RouteName.HOME });
|
||||
return;
|
||||
});
|
||||
|
||||
onLoginMutationError((err) => {
|
||||
console.error(err);
|
||||
submitted.value = false;
|
||||
if (err.graphQLErrors) {
|
||||
err.graphQLErrors.forEach(({ message }: { message: string }) => {
|
||||
errors.value.push(message);
|
||||
});
|
||||
} else if (err.networkError) {
|
||||
errors.value.push(err.networkError.message);
|
||||
}
|
||||
});
|
||||
|
||||
const loginAction = (e: Event) => {
|
||||
// form submit action
|
||||
const loginAction = async (e: Event) => {
|
||||
e.preventDefault();
|
||||
if (submitted.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
submitted.value = true;
|
||||
errors.value = [];
|
||||
|
||||
loginMutation({
|
||||
email: credentials.email,
|
||||
password: credentials.password,
|
||||
});
|
||||
};
|
||||
|
||||
const { load: loadIdentities } = useLazyCurrentUserIdentities();
|
||||
|
||||
const { onDone: onCurrentUserMutationDone, mutate: updateCurrentUserMutation } =
|
||||
useMutation(UPDATE_CURRENT_USER_CLIENT);
|
||||
|
||||
onCurrentUserMutationDone(async () => {
|
||||
console.debug("Current user mutation done, now setuping actors…");
|
||||
// since we fail to refresh the navbar properly, we force a page reload.
|
||||
// see the explanation of the bug bellow
|
||||
window.location = redirect.value || "/";
|
||||
try {
|
||||
/* FIXME this promise never resolved the first time
|
||||
no idea why !
|
||||
this appends even with the last version of apollo-composable (4.0.2)
|
||||
may be related to that : https://github.com/vuejs/apollo/issues/1543
|
||||
*/
|
||||
const result = await loadIdentities();
|
||||
console.debug("login, loadIdentities resolved");
|
||||
if (!result) return;
|
||||
await initializeCurrentActor(result.loggedUser.actors);
|
||||
// Step 1: login the user
|
||||
const { data: loginData } = await loginMutation.mutate({
|
||||
email: credentials.email,
|
||||
password: credentials.password,
|
||||
});
|
||||
submitted.value = false;
|
||||
if (loginData == null) {
|
||||
throw new Error("Login: user's data is undefined");
|
||||
}
|
||||
|
||||
// Login saved to local storage
|
||||
saveUserData(loginData.login);
|
||||
|
||||
// Step 2: save login in apollo cache
|
||||
await currentUserMutation.mutate({
|
||||
id: loginData.login.user.id,
|
||||
email: credentials.email,
|
||||
isLoggedIn: true,
|
||||
role: loginData.login.user.role,
|
||||
});
|
||||
|
||||
// Step 3a: Retrieving user location
|
||||
const loggedUserLocationPromise = loggedUserLocationQuery.load();
|
||||
|
||||
// Step 3b: Setuping user's identities
|
||||
// FIXME this promise never resolved the first time
|
||||
// no idea why !
|
||||
// this appends even with the last version of apollo-composable (4.0.2)
|
||||
// may be related to that : https://github.com/vuejs/apollo/issues/1543
|
||||
// EDIT: now it works :shrug:
|
||||
const currentUserIdentitiesResult = await currentUserIdentitiesQuery.load();
|
||||
if (!currentUserIdentitiesResult) {
|
||||
throw new Error("Loading user's identities failed");
|
||||
}
|
||||
|
||||
await initializeCurrentActor(currentUserIdentitiesResult.loggedUser.actors);
|
||||
|
||||
// Step 3a following
|
||||
const loggedUserLocationResult = await loggedUserLocationPromise;
|
||||
storeUserLocationAndRadiusFromUserSettings(
|
||||
loggedUserLocationResult?.loggedUser?.settings?.location
|
||||
);
|
||||
|
||||
// Soft redirect
|
||||
if (redirect.value) {
|
||||
console.debug("We have a redirect", redirect.value);
|
||||
router.push(redirect.value);
|
||||
return;
|
||||
}
|
||||
console.debug("No redirect, going to homepage");
|
||||
if (window.localStorage) {
|
||||
console.debug("Has localstorage, setting welcome back");
|
||||
window.localStorage.setItem("welcome-back", "yes");
|
||||
}
|
||||
router.replace({ name: RouteName.HOME });
|
||||
|
||||
// Hard redirect
|
||||
// since we fail to refresh the navbar properly, we force a page reload.
|
||||
// see the explanation of the bug bellow
|
||||
// window.location = redirect.value || "/";
|
||||
} catch (err: any) {
|
||||
if (err instanceof NoIdentitiesException && currentUser.value) {
|
||||
console.debug("No identities, redirecting to profile registration");
|
||||
await router.push({
|
||||
name: RouteName.REGISTER_PROFILE,
|
||||
params: {
|
||||
|
@ -268,19 +275,17 @@ onCurrentUserMutationDone(async () => {
|
|||
},
|
||||
});
|
||||
} else {
|
||||
throw err;
|
||||
console.error(err);
|
||||
submitted.value = false;
|
||||
if (err.graphQLErrors) {
|
||||
err.graphQLErrors.forEach(({ message }: { message: string }) => {
|
||||
errors.value.push(message);
|
||||
});
|
||||
} else if (err.networkError) {
|
||||
errors.value.push(err.networkError.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const setupClientUserAndActors = async (login: ILogin): Promise<void> => {
|
||||
console.debug("Setuping client user and actors");
|
||||
updateCurrentUserMutation({
|
||||
id: login.user.id,
|
||||
email: credentials.email,
|
||||
isLoggedIn: true,
|
||||
role: login.user.role,
|
||||
});
|
||||
};
|
||||
|
||||
const hasCaseWarning = computed<boolean>(() => {
|
||||
|
|
Loading…
Reference in a new issue