lien artiste dans concert

This commit is contained in:
2026-05-25 18:26:39 +02:00
parent c2a11f1b61
commit ba8b0d34e7
5 changed files with 363 additions and 12 deletions

View File

@@ -15,6 +15,7 @@
:title="c.titre_concert"
:dateISO="c.representation_concert?.[0]?.date_debut_representation"
:dateLabel="formatDateLong(c.representation_concert?.[0]?.date_debut_representation)"
:lien_billetterie_representation="c.representation_concert?.[0]?.lien_billetterie_representation"
:lieu="c.representation_concert?.[0]?.lieu_representation?.nom_lieu"
:adresse_lieu="c.representation_concert?.[0]?.lieu_representation?.nom_lieu"
:description="c.resume_concert"

View File

@@ -41,9 +41,11 @@
<DsText as="p" tone="default" size="lg" spacing="space-0" class="programme_compositeur">
{{ d.postes_artiste_ondif.map((p) => p.nom_poste).join(', ') }}
</DsText>
<DsText as="p" tone="default" size="lg" spacing="space-0" weight="semibold" class="programme_oeuvre">
{{ d.nom_artiste_ondif }}
</DsText>
<button type="button" class="programme_artist_button" @click="openArtistOverlay(d, 'ondif')">
<DsText as="span" tone="default" size="lg" spacing="space-0" weight="semibold" class="programme_oeuvre">
{{ d.nom_artiste_ondif }}
</DsText>
</button>
</div>
</template>
<template v-if="directionsInvite.length">
@@ -51,9 +53,11 @@
<DsText as="p" tone="default" size="lg" spacing="space-0" v-if="d.postes_artiste_invite?.length" class="programme_compositeur direction">
{{ d.postes_artiste_invite.map((p) => p.nom_poste).join(', ') }}
</DsText>
<DsText as="p" tone="default" size="lg" spacing="space-0" weight="semibold" class="programme_oeuvre">
{{ d.nom_artiste_invite }}
</DsText>
<button type="button" class="programme_artist_button" @click="openArtistOverlay(d, 'invite')">
<DsText as="span" tone="default" size="lg" spacing="space-0" weight="semibold" class="programme_oeuvre">
{{ d.nom_artiste_invite }}
</DsText>
</button>
</div>
</template>
<template v-if="artistesOndif.length">
@@ -61,9 +65,11 @@
<DsText as="p" tone="default" size="lg" spacing="space-0" v-if="d.postes_artiste_ondif?.length" class="programme_compositeur">
{{ d.postes_artiste_ondif.map((p) => p.nom_poste).join(', ') }}
</DsText>
<DsText as="p" tone="default" size="lg" spacing="space-0" weight="semibold" class="programme_oeuvre">
{{ d.nom_artiste_ondif }}
</DsText>
<button type="button" class="programme_artist_button" @click="openArtistOverlay(d, 'ondif')">
<DsText as="span" tone="default" size="lg" spacing="space-0" weight="semibold" class="programme_oeuvre">
{{ d.nom_artiste_ondif }}
</DsText>
</button>
</div>
</template>
<template v-if="artistesInvite.length">
@@ -71,9 +77,11 @@
<DsText as="p" tone="default" size="lg" spacing="space-0" v-if="d.postes_artiste_invite?.length" class="programme_compositeur">
{{ d.postes_artiste_invite.map((p) => p.nom_poste).join(', ') }}
</DsText>
<DsText as="p" tone="default" size="lg" spacing="space-0" weight="semibold" class="programme_oeuvre">
{{ d.nom_artiste_invite }}
</DsText>
<button type="button" class="programme_artist_button" @click="openArtistOverlay(d, 'invite')">
<DsText as="span" tone="default" size="lg" spacing="space-0" weight="semibold" class="programme_oeuvre">
{{ d.nom_artiste_invite }}
</DsText>
</button>
</div>
</template>
</div>
@@ -190,22 +198,117 @@
</div>
</section>
</PageSection>
<PageSection
v-if="selectedArtist"
padded_size=""
:content="false"
class="artist-overlay-section"
@click="closeArtistOverlay"
>
<aside class="actus-overlay artist-overlay" aria-label="Fiche artiste" @click.stop>
<button
type="button"
class="actus-overlay--close"
aria-label="Fermer la fiche artiste"
@click="closeArtistOverlay"
>
<svg class="actus-overlay--close-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M3 3h18v18H3V3Zm4.4 5.8 3.2 3.2-3.2 3.2 1.4 1.4 3.2-3.2 3.2 3.2 1.4-1.4-3.2-3.2 3.2-3.2-1.4-1.4-3.2 3.2-3.2-3.2-1.4 1.4Z" />
</svg>
<span class="sr-only">Fermer</span>
</button>
<div class="actus-overlay--content">
<div v-if="selectedArtist.postesLabel" class="actus-overlay--metas">
<DsText as="p" weight="bold" tone="default" class="uppercase">
{{ selectedArtist.postesLabel }}
</DsText>
</div>
<DsHeading ref="artistOverlayTitle" as="h1" tone="default" class="uppercase" tabindex="-1">
{{ selectedArtist.name }}
</DsHeading>
</div>
<section v-if="selectedArtist.illustration?.url" class="artist-overlay--image">
<DsMedia
:src="selectedArtist.illustration.url"
:alt="selectedArtist.illustration.alternativeText || selectedArtist.name"
ratio="3-4"
/>
</section>
<div class="actus-overlay--content">
<p v-if="selectedArtist.website">
<a :href="selectedArtist.website" target="_blank" rel="noopener noreferrer">Site internet</a>
</p>
<StrapiBlocksConvert v-if="selectedArtist.descriptionBlocks?.length" :blocks="selectedArtist.descriptionBlocks" />
</div>
<section v-if="selectedArtist.galleryImages.length" class="img-gallery_wp">
<div class="img-gallery">
<DsMedia
v-for="img in selectedArtist.galleryImages"
:key="img.id || img.url"
:src="img.url"
:alt="img.alternativeText || selectedArtist.name"
/>
</div>
</section>
<section v-if="selectedArtist.videos.length" class="youtube_wp">
<div class="youtube-list">
<div v-for="v in selectedArtist.videos" :key="v.id" class="youtube-item">
<iframe
:src="v.src"
title="Vidéo YouTube"
loading="lazy"
referrerpolicy="strict-origin-when-cross-origin"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
/>
</div>
</div>
</section>
<div class="actus-overlay--content">
<button
type="button"
class="actus-overlay--close actus-overlay--close-bottom"
aria-label="Fermer la fiche artiste"
@click="closeArtistOverlay"
>
<svg class="actus-overlay--close-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M3 3h18v18H3V3Zm4.4 5.8 3.2 3.2-3.2 3.2 1.4 1.4 3.2-3.2 3.2 3.2 1.4-1.4-3.2-3.2 3.2-3.2-1.4-1.4-3.2 3.2-3.2-3.2-1.4 1.4Z" />
</svg>
<span class="sr-only">Fermer</span>
</button>
</div>
</aside>
</PageSection>
</template>
</div>
</template>
<script setup>
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
import { formatDateLong } from "@/utils/dateFormat.js"
import DsMedia from '@root/design-system/primitives/DsMedia.vue'
import DsHeading from '@root/design-system/primitives/DsHeading.vue'
import DsText from '@root/design-system/primitives/DsText.vue'
const route = useRoute()
const selectedArtistRequest = ref(null)
const artistOverlayTitle = ref(null)
//////////////////////////////////////////////////////////////
// RÉCUPÉRATION DU CONTENU
//////////////////////////////////////////////////////////////
const concertSlug = computed(() => String(route.params.id || ''))
const artisteOndifPopulate = {
postes_artiste_ondif: true,
image_illustration_artiste_ondif: true,
}
const artisteInvitePopulate = {
postes_artiste_invite: true,
image_illustration_artiste_invite: true,
}
const populate = {
saison_concert: true,
genre_concert: true,
@@ -237,6 +340,31 @@
upcomingOnly: false,
})
const { items: artistesOndifItems } = useStrapi(
"/api/__strapi__/artistes",
{
locale: "fr-FR",
populate: artisteOndifPopulate,
fetchAll: true,
}
)
const { items: artistesInviteItems } = useStrapi(
"/api/__strapi__/artistesinvitees",
{
locale: "fr-FR",
populate: artisteInvitePopulate,
fetchAll: true,
}
)
const selectedArtist = computed(() => {
if (!selectedArtistRequest.value) return null
const { artist, type } = selectedArtistRequest.value
return normalizeArtist(findFullArtist(artist, type) || artist, type)
})
const concert = computed(() => concerts.value?.[0] || {})
useSeoMeta({
@@ -329,6 +457,116 @@
})
.filter(Boolean)
)
function openArtistOverlay(artist, type) {
selectedArtistRequest.value = { artist, type }
}
function closeArtistOverlay() {
selectedArtistRequest.value = null
}
function normalizeArtist(artist, type) {
const isInvite = type === 'invite'
const name = isInvite ? artist?.nom_artiste_invite : artist?.nom_artiste_ondif
const postes = extractStrapiList(isInvite ? artist?.postes_artiste_invite : artist?.postes_artiste_ondif)
const illustration = normalizeStrapiMedia(isInvite ? artist?.image_illustration_artiste_invite : artist?.image_illustration_artiste_ondif)
const galleryImages = extractStrapiList(isInvite ? artist?.images_artiste_invite : artist?.images_artiste_ondif)
.map(normalizeStrapiMedia)
.filter(Boolean)
const youtubeItems = extractStrapiList(isInvite ? artist?.liens_youtube_artiste_invite : artist?.liens_youtube_artiste_ondif)
return {
id: artist?.id || artist?.documentId || name,
type,
name: name || '',
postesLabel: postes.map((p) => p?.nom_poste).filter(Boolean).join(', '),
website: isInvite ? artist?.url_artiste_invite : artist?.url_artiste_ondif,
descriptionBlocks: isInvite ? artist?.description_artiste_invite || [] : artist?.description_artiste_ondif || [],
illustration,
galleryImages,
videos: youtubeItems
.map((item) => {
const id = getYoutubeId(item?.lien_youtube)
if (!id) return null
return {
id: item.id || id,
src: `https://www.youtube-nocookie.com/embed/${id}?rel=0&modestbranding=1&iv_load_policy=3&playsinline=1`,
}
})
.filter(Boolean),
}
}
function findFullArtist(artist, type) {
const rows = type === 'invite' ? artistesInviteItems.value : artistesOndifItems.value
const slug = type === 'invite' ? artist?.slug_artiste_invite : artist?.slug_artiste_ondif
const name = type === 'invite' ? artist?.nom_artiste_invite : artist?.nom_artiste_ondif
return rows.find((item) => {
if (artist?.id && item.id === artist.id) return true
if (slug && (type === 'invite' ? item.slug_artiste_invite : item.slug_artiste_ondif) === slug) return true
if (name && (type === 'invite' ? item.nom_artiste_invite : item.nom_artiste_ondif) === name) return true
return false
})
}
function extractStrapiList(value) {
if (!value) return []
if (Array.isArray(value)) return value.map(normalizeStrapiItem).filter(Boolean)
if (value?.data) {
const rows = Array.isArray(value.data) ? value.data : [value.data]
return rows.map(normalizeStrapiItem).filter(Boolean)
}
if (typeof value === 'object') return [normalizeStrapiItem(value)].filter(Boolean)
return []
}
function normalizeStrapiItem(item) {
if (!item || typeof item !== 'object') return null
if (item.attributes && typeof item.attributes === 'object') {
return { id: item.id, ...item.attributes }
}
return item
}
function normalizeStrapiMedia(media) {
if (media?.data) {
const mediaItem = Array.isArray(media.data) ? media.data[0] : media.data
return normalizeStrapiMedia(mediaItem)
}
const item = normalizeStrapiItem(media)
if (!item?.url) return null
return {
id: item.id || item.documentId || item.url,
url: item.url,
alternativeText: item.alternativeText || item.caption || '',
}
}
watch(
selectedArtist,
async (artist) => {
if (import.meta.client) {
document.body.style.overflow = artist ? 'hidden' : ''
}
if (!artist) return
await nextTick()
const titleElement = artistOverlayTitle.value?.$el || artistOverlayTitle.value
titleElement?.focus?.({ preventScroll: true })
}
)
onBeforeUnmount(() => {
if (import.meta.client) {
document.body.style.overflow = ''
}
})
</script>
<style lang="scss">
@@ -610,6 +848,26 @@
padding-left: 18px;
padding-bottom: 10px;
}
.programme_artist_button {
display: inline;
padding: 0;
border: 0;
background: transparent;
color: inherit;
text-align: left;
cursor: pointer;
&:hover,
&:focus-visible {
text-decoration: underline;
text-underline-offset: 3px;
}
&:focus-visible {
outline: 2px solid currentColor;
outline-offset: 3px;
}
}
}
.fiche_header_representation_wp {
//background-color: aqua;
@@ -678,6 +936,94 @@
}
}
.artist-overlay-section {
position: fixed;
inset: 0;
z-index: 1000;
display: flex;
justify-content: flex-end;
overflow: hidden;
background: linear-gradient(
to right,
rgb(0 0 0 / 71%) 0%,
rgb(0 0 0 / 71%) 20%,
transparent 20%,
transparent 100%
);
cursor: pointer;
}
.actus-overlay {
display: flex;
flex-direction: column;
width: 80%;
height: 100vh;
padding: 2rem;
overflow-y: auto;
background: var(--c-backgroud-brandreverse);
box-shadow: 0 0 40px rgb(0 0 0 / 18%);
cursor: auto;
&--close {
display: inline-flex;
align-items: center;
justify-content: center;
align-self: flex-end;
width: 2.5rem;
height: 2.5rem;
margin-bottom: 1.5rem;
padding: 0;
border: 0;
background: transparent;
color: inherit;
text-decoration: none;
cursor: pointer;
&-icon {
width: 4rem;
height: 4rem;
fill: currentColor;
flex: 0 0 auto;
}
}
&--close-bottom {
display: inline-flex;
margin-top: 2rem;
margin-bottom: 0;
}
&--content {
max-width: 680px;
}
&--metas {
display: flex;
column-gap: 25px;
padding-bottom: 10px;
}
}
.artist-overlay {
&--image {
max-width: 320px;
margin-top: 1rem;
margin-bottom: 1.5rem;
}
.img-gallery {
grid-template-columns: repeat(2, minmax(0, 300px));
justify-content: left;
}
}
@media (max-width: 700px) {
.actus-overlay {
width: 100%;
margin-left: 0;
}
}
/* ============================ */

View File

@@ -15,6 +15,7 @@
:title="c.titre_concert"
:dateISO="c.representation_concert?.[0]?.date_debut_representation"
:dateLabel="formatDateLong(c.representation_concert?.[0]?.date_debut_representation)"
:lien_billetterie_representation="c.representation_concert?.[0]?.lien_billetterie_representation"
:lieu="c.representation_concert?.[0]?.lieu_representation?.nom_lieu"
:adresse_lieu="c.representation_concert?.[0]?.lieu_representation?.nom_lieu"
:description="c.resume_concert"

View File

@@ -28,6 +28,7 @@
:title="c.titre_concert"
:dateISO="c.representation_concert?.[0]?.date_debut_representation"
:dateLabel="formatDateLong(c.representation_concert?.[0]?.date_debut_representation)"
:lien_billetterie_representation="c.representation_concert?.[0]?.lien_billetterie_representation"
:lieu="c.representation_concert?.[0]?.lieu_representation?.nom_lieu"
:adresse_lieu="c.representation_concert?.[0]?.lieu_representation?.nom_lieu"
:description="c.resume_concert"