Studio lot 8e : lecteur audio dans la fiche d'un point de carte
Audio résolu par identifiant de ressource, portrait et nom du narrateur au-dessus du lecteur. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011VxSQeGQYUvPmSEoGdnidA
This commit is contained in:
parent
81c807cd77
commit
d371b447c6
@ -13,6 +13,8 @@ import { t, tPlain } from '@/lib/i18n'
|
|||||||
import type { SectionDTO, GeoPointDTO } from '@/lib/api/types'
|
import type { SectionDTO, GeoPointDTO } from '@/lib/api/types'
|
||||||
import { trackEvent } from '@/lib/stats'
|
import { trackEvent } from '@/lib/stats'
|
||||||
import { getGeoPointLatLng } from '@/lib/geo'
|
import { getGeoPointLatLng } from '@/lib/geo'
|
||||||
|
import NarratorAvatar from '@/components/ui/NarratorAvatar'
|
||||||
|
import { useAudioResource } from '@/hooks/useAudioResource'
|
||||||
import './map/map.css'
|
import './map/map.css'
|
||||||
|
|
||||||
const LeafletMap = dynamic(() => import('./map/LeafletMap'), {
|
const LeafletMap = dynamic(() => import('./map/LeafletMap'), {
|
||||||
@ -29,6 +31,7 @@ interface Props {
|
|||||||
slug: string
|
slug: string
|
||||||
configId: string
|
configId: string
|
||||||
languages: string[]
|
languages: string[]
|
||||||
|
apiKey: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mode = 'map' | 'list'
|
type Mode = 'map' | 'list'
|
||||||
@ -39,7 +42,7 @@ const DOCK_WIDTH = 340
|
|||||||
const DETAIL_WIDTH = 320
|
const DETAIL_WIDTH = 320
|
||||||
const PANEL_TOP = 60
|
const PANEL_TOP = 60
|
||||||
|
|
||||||
export default function MapSection({ section, configId, languages }: Props) {
|
export default function MapSection({ section, configId, languages, apiKey }: Props) {
|
||||||
const { language, setAvailableLanguages, instanceId } = useVisitor()
|
const { language, setAvailableLanguages, instanceId } = useVisitor()
|
||||||
const back = useBack()
|
const back = useBack()
|
||||||
|
|
||||||
@ -322,6 +325,7 @@ export default function MapSection({ section, configId, languages }: Props) {
|
|||||||
{/* Detail sheet (POI) */}
|
{/* Detail sheet (POI) */}
|
||||||
{selected && (
|
{selected && (
|
||||||
<PointDetail
|
<PointDetail
|
||||||
|
apiKey={apiKey}
|
||||||
point={selected}
|
point={selected}
|
||||||
language={language}
|
language={language}
|
||||||
variant={isLandscape ? 'card' : 'sheet'}
|
variant={isLandscape ? 'card' : 'sheet'}
|
||||||
@ -396,8 +400,9 @@ function PointList({
|
|||||||
|
|
||||||
// ── Detail sheet ────────────────────────────────────────────────────────────
|
// ── Detail sheet ────────────────────────────────────────────────────────────
|
||||||
function PointDetail({
|
function PointDetail({
|
||||||
point, language, variant, width, top, onClose,
|
apiKey, point, language, variant, width, top, onClose,
|
||||||
}: {
|
}: {
|
||||||
|
apiKey: string
|
||||||
point: GeoPointDTO
|
point: GeoPointDTO
|
||||||
language: string
|
language: string
|
||||||
/// `sheet` : feuille basse (portrait). `card` : colonne à droite (paysage),
|
/// `sheet` : feuille basse (portrait). `card` : colonne à droite (paysage),
|
||||||
@ -413,6 +418,10 @@ function PointDetail({
|
|||||||
const prices = t(point.prices, language)
|
const prices = t(point.prices, language)
|
||||||
const schedules = t(point.schedules, language)
|
const schedules = t(point.schedules, language)
|
||||||
const isCard = variant === 'card'
|
const isCard = variant === 'card'
|
||||||
|
const audio = useAudioResource(
|
||||||
|
point.audioIds?.find((a) => a.language === language)?.value ?? point.audioIds?.[0]?.value,
|
||||||
|
apiKey,
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -476,6 +485,15 @@ function PointDetail({
|
|||||||
className="overflow-y-auto px-5 py-4 flex flex-col gap-4"
|
className="overflow-y-auto px-5 py-4 flex flex-col gap-4"
|
||||||
style={isCard ? { flex: 1, minHeight: 0 } : { maxHeight: 'calc(80vh - 180px)' }}
|
style={isCard ? { flex: 1, minHeight: 0 } : { maxHeight: 'calc(80vh - 180px)' }}
|
||||||
>
|
>
|
||||||
|
{audio.url && (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
{audio.narratorName && (
|
||||||
|
<NarratorAvatar name={audio.narratorName} portraitUrl={audio.narratorPortraitUrl} color="var(--color-text)" />
|
||||||
|
)}
|
||||||
|
<audio controls src={audio.url} style={{ width: '100%' }} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{t(point.description, language) && (
|
{t(point.description, language) && (
|
||||||
<div
|
<div
|
||||||
className="text-sm leading-relaxed [&_p]:m-0 [&_p+p]:mt-2"
|
className="text-sm leading-relaxed [&_p]:m-0 [&_p+p]:mt-2"
|
||||||
|
|||||||
@ -265,6 +265,8 @@ export interface GeoPointDTO {
|
|||||||
site?: TranslationDTO[]
|
site?: TranslationDTO[]
|
||||||
geometry?: GeometryDTO
|
geometry?: GeometryDTO
|
||||||
polyColor?: string
|
polyColor?: string
|
||||||
|
/** Un audio par langue : identifiants de ressource (lot 8e). */
|
||||||
|
audioIds?: TranslationDTO[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CategorieDTO {
|
export interface CategorieDTO {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user