mobilizon/js/src/components/Event/OrganizerPickerWrapper.vue
Thomas Citharel 4f9e0911e7
OrganizerPicker improvements
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2022-08-12 16:46:04 +02:00

318 lines
9.2 KiB
Vue

<template>
<div
class="bg-white dark:bg-violet-3 border border-gray-300 rounded-lg cursor-pointer"
v-if="selectedActor"
>
<!-- If we have a current actor (inline) -->
<div
v-if="inline && selectedActor.id"
class=""
dir="auto"
@click="isComponentModalActive = true"
>
<div class="flex gap-1 p-4">
<div class="">
<figure class="" v-if="selectedActor.avatar">
<img
class="rounded"
:src="selectedActor.avatar.url"
:alt="selectedActor.avatar.alt ?? ''"
height="48"
width="48"
/>
</figure>
<AccountCircle v-else :size="48" />
</div>
<div class="flex-1" v-if="selectedActor.name">
<p class="">{{ selectedActor.name }}</p>
<p class="">
{{ `@${selectedActor.preferredUsername}` }}
</p>
</div>
<div class="flex-1" v-else>
{{ `@${selectedActor.preferredUsername}` }}
</div>
<o-button type="text" @click="isComponentModalActive = true">
{{ $t("Change") }}
</o-button>
</div>
</div>
<!-- If we have a current actor -->
<span
v-else-if="selectedActor.id"
class="block"
@click="isComponentModalActive = true"
>
<img
class="rounded"
v-if="selectedActor.avatar"
:src="selectedActor.avatar.url"
:alt="selectedActor.avatar.alt"
width="48"
height="48"
/>
<AccountCircle v-else :size="48" />
</span>
<o-modal
v-model:active="isComponentModalActive"
has-modal-card
:close-button-aria-label="$t('Close')"
>
<div class="p-2 rounded">
<header class="">
<h2 class="">{{ $t("Pick a profile or a group") }}</h2>
</header>
<section class="">
<div class="flex flex-wrap gap-2 items-center">
<div class="max-h-[400px] overflow-y-auto flex-1">
<organizer-picker
v-if="currentActor"
:current-actor="currentActor"
:identities="identities ?? []"
v-model="selectedActor"
@update:model-value="relay"
:restrict-moderator-level="true"
:group-memberships="groupMemberships"
v-model:actorFilter="actorFilter"
/>
</div>
<div class="max-h-[400px] overflow-y-auto">
<div v-if="isSelectedActorAGroup">
<p>{{ $t("Add a contact") }}</p>
<o-input
:placeholder="$t('Filter by name')"
:value="contactFilter"
@input="debounceSetFilterByName"
dir="auto"
/>
<div v-if="actorMembers.length > 0">
<p
class="field"
v-for="actor in filteredActorMembers"
:key="actor.id"
>
<o-checkbox
v-model="actualContacts"
:native-value="actor.id"
>
<div class="flex gap-1">
<div class="">
<figure class="" v-if="actor.avatar">
<img
class="rounded"
:src="actor.avatar.url"
:alt="actor.avatar.alt"
width="48"
height="48"
/>
</figure>
<AccountCircle v-else :size="48" />
</div>
<div class="" v-if="actor.name">
<p class="">{{ actor.name }}</p>
<p class="">
{{ `@${usernameWithDomain(actor)}` }}
</p>
</div>
<div class="" v-else>
{{ `@${usernameWithDomain(actor)}` }}
</div>
</div>
</o-checkbox>
</p>
</div>
<div
v-else-if="
actorMembers.length === 0 && contactFilter.length > 0
"
>
<empty-content icon="account-multiple" :inline="true">
{{ $t("No group member found") }}
</empty-content>
</div>
</div>
<div v-else class="">
<p>{{ $t("Your profile will be shown as contact.") }}</p>
</div>
</div>
</div>
</section>
<footer class="my-2">
<o-button variant="primary" @click="pickActor">
{{ $t("Pick") }}
</o-button>
</footer>
</div>
</o-modal>
</div>
</template>
<script lang="ts" setup>
import { IActor, IGroup, usernameWithDomain } from "../../types/actor";
import OrganizerPicker from "./OrganizerPicker.vue";
import EmptyContent from "../Utils/EmptyContent.vue";
import {
LOGGED_USER_MEMBERSHIPS,
PERSON_GROUP_MEMBERSHIPS,
} from "../../graphql/actor";
import { GROUP_MEMBERS } from "@/graphql/member";
import { ActorType, MemberRole } from "@/types/enums";
import { useQuery } from "@vue/apollo-composable";
import { computed, ref, watch } from "vue";
import {
useCurrentActorClient,
useCurrentUserIdentities,
} from "@/composition/apollo/actor";
import { useRoute } from "vue-router";
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
import debounce from "lodash/debounce";
import { IUser } from "@/types/current-user.model";
const MEMBER_ROLES = [
MemberRole.CREATOR,
MemberRole.ADMINISTRATOR,
MemberRole.MODERATOR,
MemberRole.MEMBER,
];
const { currentActor } = useCurrentActorClient();
const route = useRoute();
const { result: personMembershipsResult } = useQuery(
PERSON_GROUP_MEMBERSHIPS,
() => ({
id: currentActor.value?.id,
page: 1,
limit: 10,
groupId: route.query?.actorId,
})
);
const personMemberships = computed(
() =>
personMembershipsResult.value?.person.memberships ?? {
elements: [],
total: 0,
}
);
const { identities } = useCurrentUserIdentities();
const props = withDefaults(
defineProps<{
modelValue?: IActor;
inline?: boolean;
contacts?: IActor[];
}>(),
{ inline: true, contacts: () => [] }
);
const emit = defineEmits(["update:modelValue", "update:Contacts"]);
const selectedActor = computed({
get(): IActor | undefined {
if (props.modelValue?.id) {
return props.modelValue;
}
if (currentActor.value) {
return (identities.value ?? []).find(
(identity) => identity.id === currentActor.value?.id
);
}
return undefined;
},
set(newSelectedActor: IActor | undefined) {
emit("update:modelValue", newSelectedActor);
},
});
const isComponentModalActive = ref(false);
const contactFilter = ref("");
const membersPage = ref(1);
const { result: membersResult } = useQuery(
GROUP_MEMBERS,
() => ({
groupName: usernameWithDomain(selectedActor.value),
page: membersPage.value,
limit: 10,
roles: MEMBER_ROLES.join(","),
name: contactFilter.value,
}),
() => ({ enabled: selectedActor.value?.type === ActorType.GROUP })
);
const members = computed(
() => membersResult.value?.members ?? { elements: [], total: 0 }
);
const actualContacts = computed({
get(): (string | undefined)[] {
return props.contacts.map(({ id }) => id);
},
set(contactsIds: (string | undefined)[]) {
emit(
"update:Contacts",
actorMembers.value.filter(({ id }) => contactsIds.includes(id))
);
},
});
const setContactFilter = (newContactFilter: string) => {
contactFilter.value = newContactFilter;
};
const debounceSetFilterByName = debounce(setContactFilter, 1000);
watch(personMemberships, () => {
if (
personMemberships.value?.elements[0]?.parent?.id === route.query?.actorId
) {
selectedActor.value = personMemberships.value?.elements[0]?.parent;
}
});
const relay = async (group: IGroup): Promise<void> => {
actualContacts.value = [];
selectedActor.value = group;
};
const pickActor = (): void => {
isComponentModalActive.value = false;
};
const actorMembers = computed((): IActor[] => {
if (isSelectedActorAGroup.value) {
return members.value.elements.map(({ actor }: { actor: IActor }) => actor);
}
return [];
});
const filteredActorMembers = computed((): IActor[] => {
return actorMembers.value.filter((actor) => {
return [
actor.preferredUsername.toLowerCase(),
actor.name?.toLowerCase(),
actor.domain?.toLowerCase(),
];
});
});
const isSelectedActorAGroup = computed((): boolean => {
return selectedActor.value?.type === ActorType.GROUP;
});
const actorFilter = ref("");
const { result: groupMembershipsResult } = useQuery<{
loggedUser: Pick<IUser, "memberships">;
}>(LOGGED_USER_MEMBERSHIPS, () => ({
page: 1,
limit: 10,
membershipName: actorFilter.value,
}));
const groupMemberships = computed(
() => groupMembershipsResult.value?.loggedUser.memberships.elements ?? []
);
</script>