@gramps-connect/story 0.1.2 → 0.2.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/dist/player.js CHANGED
@@ -83,6 +83,19 @@ function placeCenter(place) {
83
83
  const bounds = placeBounds(place);
84
84
  return bounds ? [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2] : null;
85
85
  }
86
+ function storyPlaces(places) {
87
+ const bounds = [Infinity, Infinity, -Infinity, -Infinity];
88
+ const points = /* @__PURE__ */ new Map();
89
+ for (const place of places) {
90
+ const center = placeCenter(place);
91
+ if (!center) continue;
92
+ points.set(`${center[0].toFixed(5)},${center[1].toFixed(5)}`, center);
93
+ visit(center, bounds);
94
+ const box = placeBounds(place);
95
+ if (box) visit([[box[0], box[1]], [box[2], box[3]]], bounds);
96
+ }
97
+ return points.size ? { points: [...points.values()], bounds } : null;
98
+ }
86
99
 
87
100
  // src/player/MapBackground.tsx
88
101
  import { useEffect, useRef, useState } from "react";
@@ -149,8 +162,17 @@ var LINE_LAYER = "story-shape-line";
149
162
  var POINT_LAYER = "story-shape-points";
150
163
  var LABEL_LAYER = "story-shape-labels";
151
164
  var OVERLAY_PREFIX = "story-overlay-";
165
+ var PLACES_SOURCE = "story-places";
166
+ var PLACES_LAYER = "story-places-dots";
167
+ var OVERVIEW_PADDING = 60;
152
168
  var EMPTY = { type: "FeatureCollection", features: [] };
153
- function moveCamera(map, place, duration) {
169
+ function moveCamera(map, slide, duration) {
170
+ if (slide.overview) {
171
+ const [west, south, east, north] = slide.overview.bounds;
172
+ map.fitBounds([[west, south], [east, north]], { maxZoom: 9, padding: OVERVIEW_PADDING, duration });
173
+ return;
174
+ }
175
+ const place = slide.place;
154
176
  const bounds = placeBounds(place);
155
177
  if (bounds) {
156
178
  map.fitBounds([[bounds[0], bounds[1]], [bounds[2], bounds[3]]], { maxZoom: 17, duration });
@@ -159,11 +181,20 @@ function moveCamera(map, place, duration) {
159
181
  const center = placeCenter(place);
160
182
  if (center) map.flyTo({ center, zoom: 9, essential: true, duration });
161
183
  }
162
- function placeMarker(marker, center) {
184
+ function placeMarker(marker, slide) {
163
185
  if (!marker) return;
186
+ const center = placeCenter(slide.place);
164
187
  if (center) marker.setLngLat(center);
165
- marker.getElement().style.opacity = center ? "1" : "0.4";
188
+ marker.setOpacity(slide.overview ? 0 : center ? 1 : 0.4);
166
189
  }
190
+ var placeDots = (overview) => ({
191
+ type: "FeatureCollection",
192
+ features: (overview?.points ?? []).map((coordinates) => ({
193
+ type: "Feature",
194
+ properties: {},
195
+ geometry: { type: "Point", coordinates }
196
+ }))
197
+ });
167
198
  function MapBackground({ initialCenter, current, dark, colors, panelFraction, ohmYear, resolveMedia }) {
168
199
  const [containerEl, setContainerEl] = useState(null);
169
200
  const mapRef = useRef(null);
@@ -194,10 +225,9 @@ function MapBackground({ initialCenter, current, dark, colors, panelFraction, oh
194
225
  mapRef.current = map;
195
226
  map.on("style.load", () => setReady(true));
196
227
  map.setPadding({ top: 0, bottom: 0, left: 0, right: containerEl.clientWidth * panelFraction });
197
- const start = currentRef.current.place;
198
- const startCenter = placeCenter(start);
199
- map.jumpTo({ center: startCenter ?? initialCenter });
200
- if (startCenter) moveCamera(map, start, 0);
228
+ const start = currentRef.current;
229
+ map.jumpTo({ center: placeCenter(start.place) ?? initialCenter });
230
+ moveCamera(map, start, 0);
201
231
  map.on("load", () => {
202
232
  const c = colorsRef.current;
203
233
  const el = document.createElement("div");
@@ -206,7 +236,7 @@ function MapBackground({ initialCenter, current, dark, colors, panelFraction, oh
206
236
  el.style.transition = "opacity 300ms";
207
237
  el.innerHTML = `<svg viewBox="0 0 24 24" width="26" height="26"><path d="${PIN_PATH_D}" fill="${c.mark}" stroke="${c.surface}" stroke-width="1.5" /></svg>`;
208
238
  markerRef.current = new maplibregl.Marker({ element: el, anchor: "bottom" }).setLngLat(initialCenter).addTo(map);
209
- placeMarker(markerRef.current, placeCenter(currentRef.current.place));
239
+ placeMarker(markerRef.current, currentRef.current);
210
240
  });
211
241
  return () => {
212
242
  markerRef.current?.remove();
@@ -273,6 +303,13 @@ function MapBackground({ initialCenter, current, dark, colors, panelFraction, oh
273
303
  "text-halo-width": 1.5
274
304
  }
275
305
  });
306
+ map.addSource(PLACES_SOURCE, { type: "geojson", data: EMPTY });
307
+ map.addLayer({
308
+ id: PLACES_LAYER,
309
+ type: "circle",
310
+ source: PLACES_SOURCE,
311
+ paint: { "circle-radius": 6, "circle-color": c.mark, "circle-stroke-width": 2, "circle-stroke-color": c.surface }
312
+ });
276
313
  }, [ready]);
277
314
  const place = current.place;
278
315
  useEffect(() => {
@@ -280,13 +317,14 @@ function MapBackground({ initialCenter, current, dark, colors, panelFraction, oh
280
317
  if (!map || !ready) return;
281
318
  const source = map.getSource(SHAPE_SOURCE);
282
319
  source?.setData(place?.geometry ?? EMPTY);
283
- }, [ready, current.key, place]);
320
+ const dots = map.getSource(PLACES_SOURCE);
321
+ dots?.setData(placeDots(current.overview));
322
+ }, [ready, current.key, place, current.overview]);
284
323
  useEffect(() => {
285
324
  const map = mapRef.current;
286
325
  if (!map) return;
287
- const center = placeCenter(place);
288
- placeMarker(markerRef.current, center);
289
- if (center) moveCamera(map, place, 900);
326
+ placeMarker(markerRef.current, current);
327
+ moveCamera(map, current, 900);
290
328
  }, [current.key]);
291
329
  const overlayKey = JSON.stringify(place?.overlays ?? []);
292
330
  useEffect(() => {
@@ -719,6 +757,7 @@ function StoryPlayer({
719
757
  onSlideChange,
720
758
  renderMapModeControl,
721
759
  brand,
760
+ actions,
722
761
  initialSlide = 0
723
762
  }) {
724
763
  const colors = useMemo(() => ({ ...defaultColors(dark), ...colorOverrides }), [dark, colorOverrides]);
@@ -802,7 +841,13 @@ function StoryPlayer({
802
841
  else goPrev();
803
842
  }
804
843
  };
805
- const mapSlide = useMemo(() => ({ key: slide?.id ?? String(index), place: slide?.place }), [slide, index]);
844
+ const places = useMemo(() => storyPlaces(slides.map((s) => s.place)), [slides]);
845
+ const mapSlide = useMemo(() => ({
846
+ key: slide?.id ?? String(index),
847
+ place: slide?.place,
848
+ // The opening card, with no place of its own, shows where the story goes.
849
+ overview: index === 0 && !placeCenter(slide?.place) ? places : null
850
+ }), [slide, index, places]);
806
851
  const firstCenter = useMemo(() => {
807
852
  if (story.presentation?.basemap === "none") return null;
808
853
  for (const s of slides) {
@@ -894,6 +939,22 @@ function StoryPlayer({
894
939
  children: /* @__PURE__ */ jsx4("div", { style: { pointerEvents: "auto" }, children: renderMapModeControl ? renderMapModeControl(modeControlProps) : /* @__PURE__ */ jsx4(MapModeControl, { ...modeControlProps, strings, colors }) })
895
940
  }
896
941
  ),
942
+ actions && /* @__PURE__ */ jsx4(
943
+ "div",
944
+ {
945
+ style: {
946
+ position: "absolute",
947
+ top: 0,
948
+ right: onClose ? 60 : 12,
949
+ height: BAR_HEIGHT,
950
+ zIndex: 3,
951
+ display: "flex",
952
+ alignItems: "center",
953
+ gap: 8
954
+ },
955
+ children: actions
956
+ }
957
+ ),
897
958
  onClose && /* @__PURE__ */ jsx4(
898
959
  RoundButton,
899
960
  {