@gramps-connect/story 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -13
- package/dist/editor.js +490 -119
- package/dist/editor.js.map +4 -4
- package/dist/embed.js +10 -10
- package/dist/player.js +63 -8
- package/dist/player.js.map +2 -2
- package/dist/types/editor/PhotosField.d.ts +16 -0
- package/dist/types/editor/StoryEditor.d.ts +17 -12
- package/dist/types/editor/index.d.ts +3 -2
- package/dist/types/editor/model.d.ts +26 -1
- package/dist/types/editor/strings.d.ts +12 -0
- package/dist/types/player/strings.d.ts +2 -0
- package/package.json +1 -1
- package/src/editor/PhotosField.tsx +134 -0
- package/src/editor/StoryEditor.tsx +119 -29
- package/src/editor/index.ts +5 -3
- package/src/editor/model.ts +63 -1
- package/src/editor/strings.ts +24 -0
- package/src/player/StoryPlayer.tsx +57 -5
- package/src/player/strings.ts +7 -0
|
@@ -114,15 +114,49 @@ function SlidePhoto({ mediaKey, item, caption, credit, resolveMedia, colors }: {
|
|
|
114
114
|
);
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
/** A small button showing one of a slide's photos. */
|
|
118
|
+
function PhotoThumb({ mediaKey, item, label, selected, onSelect, resolveMedia, colors }: {
|
|
119
|
+
mediaKey: string;
|
|
120
|
+
item: MediaItem;
|
|
121
|
+
label: string;
|
|
122
|
+
selected: boolean;
|
|
123
|
+
onSelect: () => void;
|
|
124
|
+
resolveMedia: StoryPlayerProps["resolveMedia"];
|
|
125
|
+
colors: PlayerColors;
|
|
126
|
+
}) {
|
|
127
|
+
const [src, setSrc] = useState<string | null>(null);
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
let cancelled = false;
|
|
130
|
+
resolveMedia(mediaKey, item).then((url) => { if (!cancelled) setSrc(url); }, () => {});
|
|
131
|
+
return () => { cancelled = true; };
|
|
132
|
+
}, [mediaKey, item, resolveMedia]);
|
|
133
|
+
return (
|
|
134
|
+
<button
|
|
135
|
+
type="button" aria-label={label} title={label} aria-pressed={selected} onClick={onSelect}
|
|
136
|
+
style={{
|
|
137
|
+
width: 44, height: 44, padding: 0, borderRadius: 4, overflow: "hidden", cursor: "pointer",
|
|
138
|
+
border: `2px solid ${selected ? colors.mark : "transparent"}`, background: colors.border,
|
|
139
|
+
opacity: selected ? 1 : 0.7,
|
|
140
|
+
}}
|
|
141
|
+
>
|
|
142
|
+
{src && <img src={src} alt="" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />}
|
|
143
|
+
</button>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function SlideContent({ slide, story, resolveMedia, colors, strings }: {
|
|
118
148
|
slide: Slide | undefined;
|
|
119
149
|
story: StoryDoc;
|
|
120
150
|
resolveMedia: StoryPlayerProps["resolveMedia"];
|
|
121
151
|
colors: PlayerColors;
|
|
152
|
+
strings: PlayerStrings;
|
|
122
153
|
}) {
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
|
|
154
|
+
// Every photo whose media exists; with several, thumbnails pick which
|
|
155
|
+
// one is shown. Keyed by slide in the caller, so each slide starts on
|
|
156
|
+
// its first photo.
|
|
157
|
+
const photos = (slide?.media ?? []).filter((m) => Object.prototype.hasOwnProperty.call(story.media, m.ref));
|
|
158
|
+
const [shown, setShown] = useState(0);
|
|
159
|
+
const photo = photos[Math.min(shown, photos.length - 1)];
|
|
126
160
|
const eyebrow = [slide?.date?.text, slide?.place?.name].filter(Boolean).join(" · ");
|
|
127
161
|
return (
|
|
128
162
|
<>
|
|
@@ -136,6 +170,22 @@ function SlideContent({ slide, story, resolveMedia, colors }: {
|
|
|
136
170
|
colors={colors}
|
|
137
171
|
/>
|
|
138
172
|
)}
|
|
173
|
+
{photos.length > 1 && (
|
|
174
|
+
<div style={{ display: "flex", gap: 6, flexWrap: "wrap", justifyContent: "center" }}>
|
|
175
|
+
{photos.map((m, i) => (
|
|
176
|
+
<PhotoThumb
|
|
177
|
+
key={`${i}:${m.ref}`}
|
|
178
|
+
mediaKey={m.ref}
|
|
179
|
+
item={story.media[m.ref]}
|
|
180
|
+
label={strings.photoOf(i + 1, photos.length)}
|
|
181
|
+
selected={photo === m}
|
|
182
|
+
onSelect={() => setShown(i)}
|
|
183
|
+
resolveMedia={resolveMedia}
|
|
184
|
+
colors={colors}
|
|
185
|
+
/>
|
|
186
|
+
))}
|
|
187
|
+
</div>
|
|
188
|
+
)}
|
|
139
189
|
<div style={{ display: "flex", flexDirection: "column", gap: 4, alignItems: "center", maxWidth: 640, textAlign: "center" }}>
|
|
140
190
|
{eyebrow && <div dir="auto" style={{ fontSize: 14, color: colors.muted }}>{eyebrow}</div>}
|
|
141
191
|
{slide?.title && <div dir="auto" style={{ fontSize: 20, fontWeight: 700, color: colors.text }}>{slide.title}</div>}
|
|
@@ -326,7 +376,9 @@ export function StoryPlayer({
|
|
|
326
376
|
return item ? resolveMedia(key, item) : Promise.resolve(null);
|
|
327
377
|
}, [story, resolveMedia]);
|
|
328
378
|
|
|
329
|
-
const content =
|
|
379
|
+
const content = (
|
|
380
|
+
<SlideContent key={slide?.id ?? index} slide={slide} story={story} resolveMedia={resolveMedia} colors={colors} strings={strings} />
|
|
381
|
+
);
|
|
330
382
|
const timeline = showTimeline && (
|
|
331
383
|
<TimelineStrip points={datedSlides} currentIndex={index} onSelect={setIndex} colors={colors} />
|
|
332
384
|
);
|
package/src/player/strings.ts
CHANGED
|
@@ -15,6 +15,8 @@ export interface PlayerStrings {
|
|
|
15
15
|
historicalMapYear: (year: number) => string;
|
|
16
16
|
noDateStandardMap: string;
|
|
17
17
|
historicalYear: string;
|
|
18
|
+
/** A photo's thumbnail button, on a slide with several. */
|
|
19
|
+
photoOf: (n: number, total: number) => string;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
export const ENGLISH: PlayerStrings = {
|
|
@@ -28,6 +30,7 @@ export const ENGLISH: PlayerStrings = {
|
|
|
28
30
|
historicalMapYear: (year) => `Historical map, ${year}`,
|
|
29
31
|
noDateStandardMap: "No date here — standard map",
|
|
30
32
|
historicalYear: "Historical map year",
|
|
33
|
+
photoOf: (n, total) => `Photo ${n} of ${total}`,
|
|
31
34
|
};
|
|
32
35
|
|
|
33
36
|
const TABLE: Record<string, PlayerStrings> = {
|
|
@@ -43,6 +46,7 @@ const TABLE: Record<string, PlayerStrings> = {
|
|
|
43
46
|
historicalMapYear: (year) => `Historische Karte, ${year}`,
|
|
44
47
|
noDateStandardMap: "Kein Datum hier — Standardkarte",
|
|
45
48
|
historicalYear: "Jahr der historischen Karte",
|
|
49
|
+
photoOf: (n, total) => `Foto ${n} von ${total}`,
|
|
46
50
|
},
|
|
47
51
|
es: {
|
|
48
52
|
previous: "Anterior",
|
|
@@ -55,6 +59,7 @@ const TABLE: Record<string, PlayerStrings> = {
|
|
|
55
59
|
historicalMapYear: (year) => `Mapa histórico, ${year}`,
|
|
56
60
|
noDateStandardMap: "Sin fecha aquí — mapa estándar",
|
|
57
61
|
historicalYear: "Año del mapa histórico",
|
|
62
|
+
photoOf: (n, total) => `Foto ${n} de ${total}`,
|
|
58
63
|
},
|
|
59
64
|
fr: {
|
|
60
65
|
previous: "Précédent",
|
|
@@ -67,6 +72,7 @@ const TABLE: Record<string, PlayerStrings> = {
|
|
|
67
72
|
historicalMapYear: (year) => `Carte historique, ${year}`,
|
|
68
73
|
noDateStandardMap: "Pas de date ici — carte standard",
|
|
69
74
|
historicalYear: "Année de la carte historique",
|
|
75
|
+
photoOf: (n, total) => `Photo ${n} sur ${total}`,
|
|
70
76
|
},
|
|
71
77
|
nl: {
|
|
72
78
|
previous: "Vorige",
|
|
@@ -79,6 +85,7 @@ const TABLE: Record<string, PlayerStrings> = {
|
|
|
79
85
|
historicalMapYear: (year) => `Historische kaart, ${year}`,
|
|
80
86
|
noDateStandardMap: "Geen datum hier — standaardkaart",
|
|
81
87
|
historicalYear: "Jaar van de historische kaart",
|
|
88
|
+
photoOf: (n, total) => `Foto ${n} van ${total}`,
|
|
82
89
|
},
|
|
83
90
|
};
|
|
84
91
|
|