@gramps-connect/story 0.5.0 → 0.6.1

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.
@@ -79,18 +79,19 @@ export declare function pruneMedia(doc: StoryDoc, files: Map<string, Uint8Array>
79
79
  /** "Josef Meyer" -> "Josef Meyer.story.zip", minus characters some file
80
80
  * systems refuse. */
81
81
  export declare function storyFileName(title: string): string;
82
- /** Undo and redo over whole documents. Typing in one field makes one step,
83
- * not one per keystroke: a change with the same `key` within COALESCE_MS
84
- * of the last replaces it. */
85
- export interface History {
86
- past: StoryDoc[];
87
- present: StoryDoc;
88
- future: StoryDoc[];
82
+ /** Undo and redo over whole documents (a StoryDoc here; any value that's
83
+ * replaced, never changed in place, works). Typing in one field makes one
84
+ * step, not one per keystroke: a change with the same `key` within
85
+ * COALESCE_MS of the last replaces it. */
86
+ export interface History<T = StoryDoc> {
87
+ past: T[];
88
+ present: T;
89
+ future: T[];
89
90
  lastKey?: string;
90
91
  lastAt?: number;
91
92
  }
92
93
  export declare const COALESCE_MS = 1500;
93
- export declare function startHistory(doc: StoryDoc): History;
94
- export declare function record(history: History, next: StoryDoc, key?: string, now?: number): History;
95
- export declare function undo(history: History): History;
96
- export declare function redo(history: History): History;
94
+ export declare function startHistory<T = StoryDoc>(doc: T): History<T>;
95
+ export declare function record<T>(history: History<T>, next: T, key?: string, now?: number): History<T>;
96
+ export declare function undo<T>(history: History<T>): History<T>;
97
+ export declare function redo<T>(history: History<T>): History<T>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gramps-connect/story",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "Read, write, validate and present .story.zip files: portable, versioned family-history stories.",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",
@@ -54,6 +54,9 @@ const COLOR_SWATCHES = [
54
54
  "#2a78d6", "#d64545", "#3aa657", "#e0a325", "#8654c9", "#2aa7a0", "#e0678a", "#555555",
55
55
  ];
56
56
 
57
+ /** A region's fill with no opacity of its own, as the maps and the story
58
+ * player draw it; also a new region's starting opacity. */
59
+ const UNSET_OPACITY = 0.25;
57
60
  const EMPTY_LABEL_PREVIEW: FeatureCollection = { type: "FeatureCollection", features: [] };
58
61
 
59
62
  /** An image overlay as the host gives and gets it: `image` is the host's
@@ -356,9 +359,14 @@ export const MapEditor = forwardRef<MapEditorHandle, MapEditorProps>(function Ma
356
359
  // both a newly-finished region's own opacity and, while one is selected,
357
360
  // what changing this edits in place. 0.25 matches Gramps Connect's map
358
361
  // view default, so an untouched region renders the same there.
359
- const [opacity, setOpacity] = useState(0.25);
362
+ const [opacity, setOpacity] = useState(UNSET_OPACITY);
360
363
  const opacityRef = useRef(opacity);
361
364
  opacityRef.current = opacity;
365
+ // How a loaded shape with no color of its own is drawn: as the maps draw
366
+ // it (their mark color), not in whatever the toolbar's color is now --
367
+ // and it's saved still without one.
368
+ const unsetColorRef = useRef(defaultColor ?? defaultColors(dark).mark);
369
+ unsetColorRef.current = defaultColor ?? defaultColors(dark).mark;
362
370
  // The selected point/region's own `name` (a point's label text, or a
363
371
  // region's purely-descriptive name), mirroring `color`'s own pattern --
364
372
  // NOT derived from getSnapshot() on every render, since
@@ -483,12 +491,12 @@ export const MapEditor = forwardRef<MapEditorHandle, MapEditorProps>(function Ma
483
491
  // and terra-draw calls a styling function fresh on every render, not
484
492
  // just at construction time.
485
493
  const readColor = (feature: GeoJSONStoreFeatures): HexColor =>
486
- (feature.properties.color as HexColor | undefined) ?? (colorRef.current as HexColor);
494
+ (feature.properties.color as HexColor | undefined) ?? (unsetColorRef.current as HexColor);
487
495
  // Same pattern as readColor, for a region's own `opacity` property --
488
496
  // only polygon/rectangle modes take a `fillOpacity` style at all (point/
489
497
  // line have nothing to fill).
490
498
  const readOpacity = (feature: GeoJSONStoreFeatures): number =>
491
- (feature.properties.opacity as number | undefined) ?? opacityRef.current;
499
+ (feature.properties.opacity as number | undefined) ?? UNSET_OPACITY;
492
500
 
493
501
  const draw = new TerraDraw({
494
502
  adapter: new TerraDrawMapLibreGLAdapter({ map }),
@@ -585,8 +593,9 @@ export const MapEditor = forwardRef<MapEditorHandle, MapEditorProps>(function Ma
585
593
  const feature = id ? draw.getSnapshot().find((f) => String(f.id) === id) : undefined;
586
594
  setLabelText((feature?.properties?.name as string | undefined) ?? "");
587
595
  if (feature) {
588
- setColor((feature.properties?.color as string | undefined) ?? colorRef.current);
589
- setOpacity((feature.properties?.opacity as number | undefined) ?? 0.25);
596
+ // An uncolored shape shows the color it's drawn in.
597
+ setColor((feature.properties?.color as string | undefined) ?? unsetColorRef.current);
598
+ setOpacity((feature.properties?.opacity as number | undefined) ?? UNSET_OPACITY);
590
599
  // Mirrors the overlay mousedown handler's own deselectFeature call
591
600
  // -- an image overlay and a drawn shape are mutually exclusive
592
601
  // selections, so a shape becoming selected clears any overlay
@@ -1227,14 +1236,17 @@ export const MapEditor = forwardRef<MapEditorHandle, MapEditorProps>(function Ma
1227
1236
  const name = (f.properties?.name as string | undefined)?.trim();
1228
1237
  // Opacity only for a region (Polygon) -- meaningless on a point/
1229
1238
  // line, which have nothing to fill (see readOpacity/opacityRef).
1239
+ // Color and opacity only as the shape has them: a drawn shape got
1240
+ // both when finished, and one loaded without them stays that way.
1230
1241
  const regionOpacity = f.geometry.type === "Polygon"
1231
- ? (f.properties?.opacity as number | undefined) ?? opacityRef.current
1242
+ ? f.properties?.opacity as number | undefined
1232
1243
  : undefined;
1244
+ const color = f.properties?.color as string | undefined;
1233
1245
  const sourceKey = f.properties?.sourceKey;
1234
1246
  return {
1235
1247
  ...(typeof sourceKey === "string" ? { sourceKey } : {}),
1236
1248
  geometry: f.geometry as EditableGeometry,
1237
- color: (f.properties?.color as string | undefined) ?? colorRef.current,
1249
+ ...(color !== undefined ? { color } : {}),
1238
1250
  ...(name ? { name } : {}),
1239
1251
  ...(regionOpacity !== undefined ? { opacity: regionOpacity } : {}),
1240
1252
  };
@@ -228,13 +228,14 @@ export function storyFileName(title: string): string {
228
228
  return `${safe || "story"}.story.zip`;
229
229
  }
230
230
 
231
- /** Undo and redo over whole documents. Typing in one field makes one step,
232
- * not one per keystroke: a change with the same `key` within COALESCE_MS
233
- * of the last replaces it. */
234
- export interface History {
235
- past: StoryDoc[];
236
- present: StoryDoc;
237
- future: StoryDoc[];
231
+ /** Undo and redo over whole documents (a StoryDoc here; any value that's
232
+ * replaced, never changed in place, works). Typing in one field makes one
233
+ * step, not one per keystroke: a change with the same `key` within
234
+ * COALESCE_MS of the last replaces it. */
235
+ export interface History<T = StoryDoc> {
236
+ past: T[];
237
+ present: T;
238
+ future: T[];
238
239
  lastKey?: string;
239
240
  lastAt?: number;
240
241
  }
@@ -242,24 +243,24 @@ export interface History {
242
243
  export const COALESCE_MS = 1500;
243
244
  const MAX_STEPS = 200;
244
245
 
245
- export function startHistory(doc: StoryDoc): History {
246
+ export function startHistory<T = StoryDoc>(doc: T): History<T> {
246
247
  return { past: [], present: doc, future: [] };
247
248
  }
248
249
 
249
- export function record(history: History, next: StoryDoc, key?: string, now = Date.now()): History {
250
+ export function record<T>(history: History<T>, next: T, key?: string, now = Date.now()): History<T> {
250
251
  if (next === history.present) return history;
251
252
  const coalesce = key !== undefined && key === history.lastKey && history.lastAt !== undefined && now - history.lastAt < COALESCE_MS;
252
253
  const past = coalesce ? history.past : [...history.past, history.present].slice(-MAX_STEPS);
253
254
  return { past, present: next, future: [], lastKey: key, lastAt: now };
254
255
  }
255
256
 
256
- export function undo(history: History): History {
257
+ export function undo<T>(history: History<T>): History<T> {
257
258
  const previous = history.past[history.past.length - 1];
258
259
  if (!previous) return history;
259
260
  return { past: history.past.slice(0, -1), present: previous, future: [history.present, ...history.future] };
260
261
  }
261
262
 
262
- export function redo(history: History): History {
263
+ export function redo<T>(history: History<T>): History<T> {
263
264
  const [next, ...future] = history.future;
264
265
  if (!next) return history;
265
266
  return { past: [...history.past, history.present], present: next, future };