mirror of
https://github.com/IliesAllali/parenthese.git
synced 2026-11-06 23:32:08 +00:00
Merge pull request #173 from IliesAllali/feat/visages-discrets
Visages sur la photo : rien par défaut, ronds fins à la demande
This commit is contained in:
3 files changed
+40
-15
No files matched your search
@@ -3,19 +3,27 @@
|
||||
.mp-avatar { width: 24px; height: 24px; flex: none; border-radius: 50%; object-fit: cover; }
|
||||
.mp-avatar--initial { display: grid; place-items: center; background: var(--pz-a100); color: var(--pz-a700); font: 400 12px/1 var(--pz-serif); }
|
||||
|
||||
/* Point sur un visage : rond blanc cerclé, le prénom apparaît au survol ou au focus (toujours en identification) */
|
||||
/* Rond sur un visage : fil fin, assez large pour entourer le visage sans le couvrir. Caché par défaut, la photo d'abord
|
||||
(Patch, 27/10) : visible au survol de la photo, quand on la touche (.show-names), quand son prénom est survolé
|
||||
dessous (.is-hot), et toujours en identification. Apparition d'un coup, sans fondu. */
|
||||
.mp-marker {
|
||||
position: absolute;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
margin: -13px 0 0 -13px;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
margin: -17px 0 0 -17px;
|
||||
padding: 0;
|
||||
border: 2px solid #fff;
|
||||
border: 1.5px solid rgba(255, 255, 255, .9);
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
box-shadow: 0 0 0 1px rgba(42, 38, 34, .3), 0 2px 8px rgba(42, 38, 34, .25);
|
||||
box-shadow: 0 0 0 1px rgba(42, 38, 34, .18);
|
||||
cursor: pointer;
|
||||
visibility: hidden;
|
||||
}
|
||||
@media (hover: hover) and (pointer: fine) { .mv-photo-box:hover .mp-marker { visibility: visible; } }
|
||||
.mv-photo-box.is-tagging .mp-marker,
|
||||
.mv-photo-box.show-names .mp-marker,
|
||||
.mp-marker.is-hot { visibility: visible; }
|
||||
.mp-marker.is-hot { border-color: #fff; box-shadow: 0 0 0 1px rgba(42, 38, 34, .25), 0 0 0 4px rgba(255, 255, 255, .25); }
|
||||
.mp-marker.is-active { border-color: var(--pz-a500); box-shadow: 0 0 0 3px rgba(210, 105, 74, .35), 0 2px 8px rgba(42, 38, 34, .25); }
|
||||
.mp-marker:focus-visible { outline: 2px solid var(--pz-a700); outline-offset: 3px; }
|
||||
.mp-marker-name {
|
||||
@@ -33,10 +41,9 @@
|
||||
visibility: hidden;
|
||||
}
|
||||
.mp-marker:hover .mp-marker-name,
|
||||
.mp-marker:focus-visible .mp-marker-name,
|
||||
.mp-marker.is-hot .mp-marker-name,
|
||||
.mv-photo-box.is-tagging .mp-marker-name,
|
||||
.mv-photo-box.show-names .mp-marker-name { visibility: visible; }
|
||||
@media (hover: none) { .mp-marker-name { visibility: visible; } }
|
||||
|
||||
/* Photo cliquable pendant le placement d'un point */
|
||||
.mv-photo-box.is-placing img { cursor: crosshair; }
|
||||
|
||||
@@ -14,9 +14,9 @@ function PersonAvatar({ person }) {
|
||||
: <span className="mp-avatar mp-avatar--initial" aria-hidden="true">{person?.firstName?.[0] || '?'}</span>
|
||||
}
|
||||
|
||||
// Points posés sur la photo : un rond par visage, le prénom au survol ou au focus. En identification, le point de
|
||||
// la personne choisie est mis en avant.
|
||||
export function MediaPeopleMarkers({ people, getPerson, activeId = null, onOpenPerson }) {
|
||||
// Ronds posés sur les visages, cachés tant qu'on ne les demande pas (survol de la photo, photo touchée, prénom survolé
|
||||
// dessous : `hotId`). En identification, ils restent visibles et le rond de la personne choisie est mis en avant.
|
||||
export function MediaPeopleMarkers({ people, getPerson, activeId = null, hotId = null, onOpenPerson }) {
|
||||
return people.filter((tag) => tag.x != null && tag.y != null).map((tag) => {
|
||||
const person = getPerson(tag.personId)
|
||||
if (!person) return null
|
||||
@@ -25,7 +25,7 @@ export function MediaPeopleMarkers({ people, getPerson, activeId = null, onOpenP
|
||||
<button
|
||||
key={tag.personId}
|
||||
type="button"
|
||||
className={`mp-marker${activeId === tag.personId ? ' is-active' : ''}`}
|
||||
className={`mp-marker${activeId === tag.personId ? ' is-active' : ''}${hotId === tag.personId ? ' is-hot' : ''}`}
|
||||
style={{ left: `${tag.x * 100}%`, top: `${tag.y * 100}%` }}
|
||||
onClick={(event) => { event.stopPropagation(); onOpenPerson?.(tag.personId) }}
|
||||
aria-label={t('Voir la fiche de {name}', { name })}
|
||||
@@ -49,6 +49,7 @@ export function MediaPeopleList({
|
||||
onAdd,
|
||||
onRemove,
|
||||
onOpenPerson,
|
||||
onHover,
|
||||
}) {
|
||||
const [query, setQuery] = useState('')
|
||||
const taken = useMemo(() => new Set(people.map((tag) => tag.personId)), [people])
|
||||
@@ -69,7 +70,16 @@ export function MediaPeopleList({
|
||||
<div className="mp-list" aria-label={t('Sur la photo')}>
|
||||
<span className="mp-list-label">{t('Sur la photo')}</span>
|
||||
{known.map(({ person }) => (
|
||||
<button key={person.id} type="button" className="mp-chip" onClick={() => onOpenPerson?.(person.id)}>
|
||||
<button
|
||||
key={person.id}
|
||||
type="button"
|
||||
className="mp-chip"
|
||||
onClick={() => onOpenPerson?.(person.id)}
|
||||
onPointerEnter={() => onHover?.(person.id)}
|
||||
onPointerLeave={() => onHover?.(null)}
|
||||
onFocus={() => onHover?.(person.id)}
|
||||
onBlur={() => onHover?.(null)}
|
||||
>
|
||||
<PersonAvatar person={person} />
|
||||
<span>{firstNameLabel(person)}</span>
|
||||
</button>
|
||||
|
||||
@@ -43,6 +43,10 @@ const MediaViewer = ({
|
||||
const [tagging, setTagging] = useState(false)
|
||||
const [draftPeople, setDraftPeople] = useState([])
|
||||
const [activeTagId, setActiveTagId] = useState(null)
|
||||
// Visages identifiés : rien sur la photo par défaut (Patch, 27/10). Toucher la photo montre ou cache les prénoms,
|
||||
// survoler un prénom sous la photo entoure son visage.
|
||||
const [showNames, setShowNames] = useState(false)
|
||||
const [hotPersonId, setHotPersonId] = useState(null)
|
||||
const [savingPeople, setSavingPeople] = useState(false)
|
||||
const [peopleError, setPeopleError] = useState('')
|
||||
const [peopleNotice, setPeopleNotice] = useState('')
|
||||
@@ -310,12 +314,16 @@ const MediaViewer = ({
|
||||
// La boîte épouse l'image : les points (x, y en proportion) tombent pile sur les visages
|
||||
return (
|
||||
<div className="media-photo-frame">
|
||||
<div className={`mv-photo-box${tagging ? ' is-tagging' : ''}${activeTagId ? ' is-placing' : ''}`} onClick={placeActiveTag}>
|
||||
<div
|
||||
className={`mv-photo-box${tagging ? ' is-tagging' : ''}${activeTagId ? ' is-placing' : ''}${showNames ? ' show-names' : ''}`}
|
||||
onClick={(event) => (tagging ? placeActiveTag(event) : setShowNames((shown) => !shown))}
|
||||
>
|
||||
<img src={mediaUrl} alt={media?.label || t('Photo')} />
|
||||
<MediaPeopleMarkers
|
||||
people={shownPeople}
|
||||
getPerson={getPerson}
|
||||
activeId={activeTagId}
|
||||
hotId={tagging ? null : hotPersonId}
|
||||
onOpenPerson={tagging ? (id) => setActiveTagId(id) : onOpenPerson}
|
||||
/>
|
||||
</div>
|
||||
@@ -666,7 +674,7 @@ const MediaViewer = ({
|
||||
</div>
|
||||
) : (
|
||||
<div className="mv-people">
|
||||
<MediaPeopleList people={savedPeople} getPerson={getPerson} onOpenPerson={onOpenPerson} />
|
||||
<MediaPeopleList people={savedPeople} getPerson={getPerson} onOpenPerson={onOpenPerson} onHover={setHotPersonId} />
|
||||
{canTag && onSavePeople && (
|
||||
<button type="button" className="pz-btn pz-btn--soft pz-btn--sm mv-people-btn" onClick={startTagging}>
|
||||
<UserPlus size={15} strokeWidth={2} aria-hidden="true" />
|
||||
|
||||
Reference in new issue
Block a user