@gramps-connect/story 0.4.0 → 0.5.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 +13 -4
- package/dist/editor.js +1710 -121
- package/dist/editor.js.map +4 -4
- package/dist/embed.js +1 -1
- package/dist/map-edit.js +361 -0
- package/dist/map-edit.js.map +7 -0
- package/dist/player.js +3 -1
- package/dist/player.js.map +2 -2
- package/dist/types/editor/MapEditor.d.ts +89 -0
- package/dist/types/editor/PlaceField.d.ts +4 -1
- package/dist/types/editor/PlaceMapDialog.d.ts +18 -0
- package/dist/types/editor/StoryEditor.d.ts +4 -1
- package/dist/types/editor/index.d.ts +4 -0
- package/dist/types/editor/mapEditorStrings.d.ts +50 -0
- package/dist/types/editor/placeShapes.d.ts +13 -0
- package/dist/types/editor/strings.d.ts +13 -0
- package/dist/types/mapEdit/geometry.d.ts +91 -0
- package/dist/types/mapEdit/index.d.ts +3 -0
- package/dist/types/mapEdit/mapFile.d.ts +77 -0
- package/dist/types/mapEdit/model.d.ts +55 -0
- package/package.json +18 -2
- package/src/editor/MapEditor.tsx +1545 -0
- package/src/editor/PlaceField.tsx +12 -2
- package/src/editor/PlaceMapDialog.tsx +228 -0
- package/src/editor/StoryEditor.tsx +24 -1
- package/src/editor/index.ts +6 -0
- package/src/editor/mapEditorStrings.ts +109 -0
- package/src/editor/placeShapes.ts +63 -0
- package/src/editor/strings.ts +27 -1
- package/src/mapEdit/geometry.ts +211 -0
- package/src/mapEdit/index.ts +15 -0
- package/src/mapEdit/mapFile.ts +199 -0
- package/src/mapEdit/model.ts +250 -0
- package/src/player/MapBackground.tsx +3 -1
|
@@ -3,6 +3,7 @@ import type { StoryDoc } from "../types";
|
|
|
3
3
|
import type { PlayerStrings } from "../player/strings";
|
|
4
4
|
import { type DateTextParser } from "./dates";
|
|
5
5
|
import { type NewImage } from "./model";
|
|
6
|
+
import type { MapEditorStrings } from "./mapEditorStrings";
|
|
6
7
|
import { type PlaceSearch } from "./placeSearch";
|
|
7
8
|
import { type EditorStrings } from "./strings";
|
|
8
9
|
export interface StoryEditorProps {
|
|
@@ -31,7 +32,9 @@ export interface StoryEditorProps {
|
|
|
31
32
|
/** Finding a place by name. By default Wikidata, and only when the
|
|
32
33
|
* person asks; null leaves the search out. */
|
|
33
34
|
placeSearch?: PlaceSearch | null;
|
|
35
|
+
/** The map editor's words (Edit map), over its English. */
|
|
36
|
+
mapStrings?: Partial<MapEditorStrings>;
|
|
34
37
|
}
|
|
35
38
|
/** The default prepareImage. */
|
|
36
39
|
export declare function prepareImage(file: Blob): Promise<NewImage>;
|
|
37
|
-
export declare function StoryEditor({ initialStory, initialFiles, onChange, prepareImage: prepare, toolbar, brand, dark, strings: stringOverrides, playerStrings, parseDateText, placeSearch, }: StoryEditorProps): import("react").JSX.Element;
|
|
40
|
+
export declare function StoryEditor({ initialStory, initialFiles, onChange, prepareImage: prepare, toolbar, brand, dark, strings: stringOverrides, playerStrings, parseDateText, placeSearch, mapStrings, }: StoryEditorProps): import("react").JSX.Element;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export { prepareImage, StoryEditor, type StoryEditorProps } from "./StoryEditor";
|
|
2
|
+
export { MapEditor, MapEditorHelp, type MapEditorHandle, type MapEditorOverlay, type MapEditorProps, type MapEditorResult, type MapEditorStatus, } from "./MapEditor";
|
|
3
|
+
export { MAP_EDITOR_ENGLISH, type MapEditorStrings } from "./mapEditorStrings";
|
|
2
4
|
export { PhotosField, type PhotosFieldProps } from "./PhotosField";
|
|
3
5
|
export { PlaceField, type PlaceFieldProps, type PlaceUpdate } from "./PlaceField";
|
|
6
|
+
export { PlaceMapDialog, type PlaceMapDialogProps } from "./PlaceMapDialog";
|
|
7
|
+
export { editorToPlace, placeFeatures, placeToEditor } from "./placeShapes";
|
|
4
8
|
export { searchWikidataPlaces, wikidataPoint, WIKIDATA_PLACE_SEARCH, type PlaceSearch, type PlaceSearchResult } from "./placeSearch";
|
|
5
9
|
export { StoryTextField, type StoryTextFieldProps } from "./StoryTextField";
|
|
6
10
|
export { SlideList, type SlideListProps } from "./SlideList";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface MapEditorStrings {
|
|
2
|
+
point: string;
|
|
3
|
+
line: string;
|
|
4
|
+
polygon: string;
|
|
5
|
+
rectangle: string;
|
|
6
|
+
select: string;
|
|
7
|
+
image: string;
|
|
8
|
+
editOptions: string;
|
|
9
|
+
labelText: string;
|
|
10
|
+
regionName: string;
|
|
11
|
+
fillOpacity: string;
|
|
12
|
+
scale: string;
|
|
13
|
+
transform: string;
|
|
14
|
+
overlayName: string;
|
|
15
|
+
opacity: string;
|
|
16
|
+
stackingOrder: string;
|
|
17
|
+
removeImage: string;
|
|
18
|
+
resizeImage: string;
|
|
19
|
+
rotateImage: string;
|
|
20
|
+
warpCorner: string;
|
|
21
|
+
basemapFailed: string;
|
|
22
|
+
imageFailed: string;
|
|
23
|
+
lockedOne: string;
|
|
24
|
+
lockedMany: (count: number) => string;
|
|
25
|
+
lockedExplain: string;
|
|
26
|
+
helpTitle: string;
|
|
27
|
+
helpDrawing: string;
|
|
28
|
+
helpPoint: string;
|
|
29
|
+
helpLine: string;
|
|
30
|
+
helpEnterFinishes: string;
|
|
31
|
+
helpEscCancels: string;
|
|
32
|
+
helpRectangle: string;
|
|
33
|
+
helpImage: string;
|
|
34
|
+
helpImages: string;
|
|
35
|
+
helpBelow: string;
|
|
36
|
+
helpEditing: string;
|
|
37
|
+
helpSelect: string;
|
|
38
|
+
helpVertex: string;
|
|
39
|
+
helpColor: string;
|
|
40
|
+
helpDelete: string;
|
|
41
|
+
helpOr: string;
|
|
42
|
+
helpRotate: string;
|
|
43
|
+
helpRotateHandle: string;
|
|
44
|
+
helpImageMove: string;
|
|
45
|
+
helpImageResize: string;
|
|
46
|
+
helpImageRotate: string;
|
|
47
|
+
helpImageTransform: string;
|
|
48
|
+
helpImageRemove: string;
|
|
49
|
+
}
|
|
50
|
+
export declare const MAP_EDITOR_ENGLISH: MapEditorStrings;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Feature } from "geojson";
|
|
2
|
+
import type { Place, StoryDate } from "../types";
|
|
3
|
+
import type { MapEditorOverlay, MapEditorResult } from "./MapEditor";
|
|
4
|
+
/** The features in a place's `geometry`, whichever form it takes. */
|
|
5
|
+
export declare function placeFeatures(place: Place | undefined): Feature[];
|
|
6
|
+
/** What the map editor opens with for a place. */
|
|
7
|
+
export declare function placeToEditor(place: Place | undefined): {
|
|
8
|
+
features: Feature[];
|
|
9
|
+
overlays: MapEditorOverlay[];
|
|
10
|
+
};
|
|
11
|
+
/** The place with what the map editor ended with. `dates` holds each
|
|
12
|
+
* overlay's date by media key, as edited beside the editor. */
|
|
13
|
+
export declare function editorToPlace(place: Place | undefined, result: MapEditorResult, dates: Map<string, StoryDate | undefined>): Place | undefined;
|
|
@@ -44,6 +44,19 @@ export interface EditorStrings {
|
|
|
44
44
|
placeShape: string;
|
|
45
45
|
placeOverlays: (count: number) => string;
|
|
46
46
|
placeKeeps: (what: string) => string;
|
|
47
|
+
editMap: string;
|
|
48
|
+
editMapHint: string;
|
|
49
|
+
mapDialogTitle: (placeName: string | undefined) => string;
|
|
50
|
+
mapHelp: string;
|
|
51
|
+
importShapes: string;
|
|
52
|
+
importShapesHint: string;
|
|
53
|
+
importFailed: (fileName: string) => string;
|
|
54
|
+
importNothing: (fileName: string) => string;
|
|
55
|
+
overlayAlready: string;
|
|
56
|
+
overlayDate: string;
|
|
57
|
+
overlayDateHint: string;
|
|
58
|
+
cancel: string;
|
|
59
|
+
done: string;
|
|
47
60
|
photos: string;
|
|
48
61
|
photosHint: string;
|
|
49
62
|
addPhoto: string;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Feature } from "geojson";
|
|
2
|
+
/** An overlay's 4 world corners, maplibre's own ImageSource `coordinates`
|
|
3
|
+
* order (top-left, top-right, bottom-right, bottom-left) and type. */
|
|
4
|
+
export type OverlayCorners = [[number, number], [number, number], [number, number], [number, number]];
|
|
5
|
+
/** Rotates one point by `rotationDeg` around `center` (positive =
|
|
6
|
+
* counterclockwise, matching KML's own <LatLonBox><rotation> convention)
|
|
7
|
+
* -- done in Mercator-projected space (see mercatorFromLngLat), not raw
|
|
8
|
+
* [lng,lat], so a rectangle stays a rectangle (right angles preserved)
|
|
9
|
+
* after rotating, regardless of latitude. A naive rotation directly on
|
|
10
|
+
* [lng,lat] -- this function's original implementation -- shears instead,
|
|
11
|
+
* since a degree of longitude and a degree of latitude aren't the same
|
|
12
|
+
* ground (or screen) distance away from the equator (found live: a square
|
|
13
|
+
* image's corners visibly stopped being 90 degrees once rotated away from
|
|
14
|
+
* 0). Gramps Connect's own image overlays are never meant to be opened in an
|
|
15
|
+
* external viewer (their picture is an app-internal Media reference), so diverging here
|
|
16
|
+
* from what Google Earth's own <LatLonBox><rotation> would render is an
|
|
17
|
+
* acceptable trade for Gramps Connect always rendering its own files correctly.
|
|
18
|
+
* Pass a negative `rotationDeg` to go the other way (world -> local). */
|
|
19
|
+
export declare function rotatePoint(point: [number, number], center: [number, number], rotationDeg: number): [number, number];
|
|
20
|
+
/** Scales the distance from `center` to `point` by `scale`, in the same
|
|
21
|
+
* Mercator-projected space rotatePoint rotates in -- so scaling a
|
|
22
|
+
* rectangle (or an already-warped quad) uniformly from its own center
|
|
23
|
+
* preserves its exact shape/aspect ratio regardless of latitude, the same
|
|
24
|
+
* way rotatePoint preserves angles. MapItemEditorDialog.tsx's resize
|
|
25
|
+
* handle applies this to all 4 corners at once (see mountOverlay). */
|
|
26
|
+
export declare function scalePoint(point: [number, number], center: [number, number], scale: number): [number, number];
|
|
27
|
+
/** The distance between `a` and `b` in the same Mercator-projected space
|
|
28
|
+
* scalePoint scales in -- not a real-world distance (Mercator distorts
|
|
29
|
+
* those away from the equator), just a consistent unit for computing a
|
|
30
|
+
* resize handle's before/after scale ratio, which only ever needs to be
|
|
31
|
+
* internally consistent with scalePoint's own projection, not physically
|
|
32
|
+
* meaningful on its own. */
|
|
33
|
+
export declare function mercatorDistance(a: [number, number], b: [number, number]): number;
|
|
34
|
+
/** The angle (degrees, positive = counterclockwise, 0 = due east) from
|
|
35
|
+
* `from` to `to` -- measured in the same Mercator-projected, north-up
|
|
36
|
+
* plane rotatePoint itself rotates in, so a caller tracking "how far has
|
|
37
|
+
* the user dragged this handle around the center" gets an angle that
|
|
38
|
+
* matches how rotatePoint will then actually render it (unlike a plain
|
|
39
|
+
* atan2 on raw [lng,lat], which suffers the same latitude-dependent skew
|
|
40
|
+
* rotatePoint's own doc comment describes). */
|
|
41
|
+
export declare function mercatorBearing(from: [number, number], to: [number, number]): number;
|
|
42
|
+
/** An axis-aligned box plus a rotation about its own center's 4 world
|
|
43
|
+
* corners -- KML's own <LatLonBox><rotation> model. Only ever needed to
|
|
44
|
+
* interpret an *old* overlay saved before Gramps Connect always wrote
|
|
45
|
+
* explicit corners (see its fetchAllKmlImageOverlays); the map editor
|
|
46
|
+
* itself only ever deals with corners directly, never this box shape. */
|
|
47
|
+
export declare function cornersFromBox(box: {
|
|
48
|
+
north: number;
|
|
49
|
+
south: number;
|
|
50
|
+
east: number;
|
|
51
|
+
west: number;
|
|
52
|
+
rotation: number;
|
|
53
|
+
}): OverlayCorners;
|
|
54
|
+
/** Every coordinate's bounding box across a set of KML features, as
|
|
55
|
+
* [west, south, east, north] -- maplibre's own LngLatBoundsLike corner
|
|
56
|
+
* order, so a caller can hand this straight to fitBounds/cameraForBounds to
|
|
57
|
+
* frame a field boundary or a route at whatever zoom actually fits it,
|
|
58
|
+
* rather than guessing one. Null when the features carry no coordinates at
|
|
59
|
+
* all (an empty file, or every fetch above failed). */
|
|
60
|
+
export declare function kmlBounds(features: Feature[]): [west: number, south: number, east: number, north: number] | null;
|
|
61
|
+
/** The center of kmlBounds -- a plain bbox center rather than an
|
|
62
|
+
* area-weighted centroid, which is all a "roughly where does this file sit"
|
|
63
|
+
* marker position needs. */
|
|
64
|
+
export declare function kmlCenter(features: Feature[]): [lat: number, long: number] | null;
|
|
65
|
+
/** Same bbox as kmlBounds, but over a set of image overlays' corners instead
|
|
66
|
+
* of vector features -- overlays (GroundOverlay/region placemarks) carry no
|
|
67
|
+
* Point/LineString/Polygon geometry for kmlBounds to find anything in, since
|
|
68
|
+
* fetchAllKmlFeatures excludes them entirely. Lets a caller fit the camera to
|
|
69
|
+
* an overlay-only KML attachment the same way it would a vector one. */
|
|
70
|
+
export declare function kmlOverlayBounds(overlays: {
|
|
71
|
+
corners: OverlayCorners;
|
|
72
|
+
}[]): [west: number, south: number, east: number, north: number] | null;
|
|
73
|
+
/** Combines two nullable bboxes (as returned by kmlBounds/kmlOverlayBounds)
|
|
74
|
+
* into the box that encloses both -- a place's KML attachment(s) can carry
|
|
75
|
+
* vector shapes and image overlays side by side, and framing the camera to
|
|
76
|
+
* just one kind would clip whichever the caller didn't ask for. Either side
|
|
77
|
+
* missing (no shapes, or no overlays) just passes the other through; both
|
|
78
|
+
* missing is null, same as either input alone would be. */
|
|
79
|
+
export declare function unionBounds(a: [west: number, south: number, east: number, north: number] | null, b: [west: number, south: number, east: number, north: number] | null): [west: number, south: number, east: number, north: number] | null;
|
|
80
|
+
/** Same bbox-center guess as kmlCenter, but over a set of image overlays'
|
|
81
|
+
* corners instead of vector features -- useVisualData.ts's fallback for a
|
|
82
|
+
* PendingKmlPlace whose only KML attachment is a GroundOverlay (an
|
|
83
|
+
* overlay-only file: no Point/LineString/Polygon placemark for kmlCenter to
|
|
84
|
+
* find anything in, since fetchAllKmlFeatures excludes GroundOverlay
|
|
85
|
+
* placemarks entirely). Without this, a place with only an image overlay
|
|
86
|
+
* attached and no lat/long of its own never gets a derived position and so
|
|
87
|
+
* never renders on the map until someone sets its coordinates by hand
|
|
88
|
+
* (found live). */
|
|
89
|
+
export declare function kmlOverlayCenter(overlays: {
|
|
90
|
+
corners: OverlayCorners;
|
|
91
|
+
}[]): [lat: number, long: number] | null;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { addToSession, assembleForSaving, lockPart, prepareForEditing, type EditableGeometry, type EditablePart, type EditMode, type EditSession, type SavedPart, } from "./model";
|
|
2
|
+
export { IMAGE_OVERLAY_PROPERTY, isKmlLine, isKmlRegion, lineCoordsOf, mapItemToGeoJson, MEDIA_HANDLE_PREFIX, overlaySourceOfText, parseMapFile, polygonRingsOf, SOURCE_PROPERTY, type ImageOverlay, } from "./mapFile";
|
|
3
|
+
export { cornersFromBox, kmlBounds, kmlCenter, kmlOverlayBounds, kmlOverlayCenter, mercatorBearing, mercatorDistance, rotatePoint, scalePoint, unionBounds, type OverlayCorners, } from "./geometry";
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Feature, Position } from "geojson";
|
|
2
|
+
import type { OverlayCorners } from "./geometry";
|
|
3
|
+
/** An image overlay's picture, as a Media handle behind this marker (not a
|
|
4
|
+
* real URL: an app-internal reference, since a resolvable link would need
|
|
5
|
+
* an expiring auth token baked in) -- in a KML GroundOverlay's href, and
|
|
6
|
+
* what asGroundOverlay makes of a GeoJSON one's `image`. */
|
|
7
|
+
export declare const MEDIA_HANDLE_PREFIX = "media-handle:";
|
|
8
|
+
/** A map file's text, as GeoJSON features. The format is read off the
|
|
9
|
+
* content (GeoJSON is JSON; GPX and KML differ by root element) rather
|
|
10
|
+
* than the media object's stored mime, which a file attached by another
|
|
11
|
+
* client may not have set to anything Gramps Connect recognizes. Throws on
|
|
12
|
+
* unparseable text; features with no geometry are dropped -- a KML
|
|
13
|
+
* Placemark without Point/LineString/Polygon (see @tmcw/togeojson's
|
|
14
|
+
* KMLOptions.skipNullGeometry), or a GeoJSON Feature with `geometry: null`,
|
|
15
|
+
* is of no use to a caller here, so dropped rather than threaded through
|
|
16
|
+
* as `Feature<Geometry | null>` everywhere downstream. */
|
|
17
|
+
export declare function parseMapFile(text: string): Feature[];
|
|
18
|
+
/** An overlay file's SOURCE_PROPERTY, if it's GeoJSON that has one. */
|
|
19
|
+
export declare function overlaySourceOfText(text: string): string | undefined;
|
|
20
|
+
/** Every Polygon ring inside `feature` -- itself (a plain Polygon), or each
|
|
21
|
+
* Polygon sub-geometry of a GeometryCollection. A country-level Wikidata/
|
|
22
|
+
* Commons outline (e.g. the United States) is often a disjoint shape --
|
|
23
|
+
* islands/territories a single Polygon ring can't express -- unlike a
|
|
24
|
+
* state's simpler single-ring Polygon. A KML file stores one as a
|
|
25
|
+
* <MultiGeometry> of several <Polygon>s, and @tmcw/togeojson reads *any* multi-child
|
|
26
|
+
* Placemark geometry back as a GeometryCollection (never reconstituting
|
|
27
|
+
* MultiPolygon) -- so that's the shape that actually reaches here, not
|
|
28
|
+
* "MultiPolygon" itself (confirmed live, 2026-09-09, round-tripping a
|
|
29
|
+
* synthetic MultiPolygon through both libraries). A non-Polygon sub-geometry
|
|
30
|
+
* (there shouldn't be one -- Gramps Connect never writes anything else into a
|
|
31
|
+
* region's own Placemark) is simply skipped rather than failing the whole
|
|
32
|
+
* feature. `[]` for anything else (a Point, a bare LineString route, ...),
|
|
33
|
+
* meaning "not a region" to every caller below. */
|
|
34
|
+
export declare function polygonRingsOf(feature: Feature): Position[][];
|
|
35
|
+
/** True for a feature fetchAllKmlRegions/MapCanvas.tsx's own shapes effect
|
|
36
|
+
* both treat as one drawable/controllable region -- see polygonRingsOf.
|
|
37
|
+
* Exported so MapCanvas.tsx's key/hidden/opacity stamping can use the exact
|
|
38
|
+
* same test fetchAllKmlRegions does: the two must never drift, or a row's
|
|
39
|
+
* key stops matching the feature it's meant to control -- found live as a
|
|
40
|
+
* place whose outline drew on the map (MapLibre's GeoJSON source splits a
|
|
41
|
+
* GeometryCollection into one sub-feature per geometry before rendering,
|
|
42
|
+
* each individually typed "Polygon") but never showed up in
|
|
43
|
+
* OverlayLayersPanel.tsx's list. */
|
|
44
|
+
export declare function isKmlRegion(feature: Feature): boolean;
|
|
45
|
+
/** Every vertex of `feature` if it's a line: a LineString, a MultiLineString
|
|
46
|
+
* (a GPX track with several segments) or a GeometryCollection of only
|
|
47
|
+
* LineStrings (how @tmcw/togeojson reads a KML MultiGeometry -- see
|
|
48
|
+
* polygonRingsOf). `[]` for anything else, meaning "not a line". */
|
|
49
|
+
export declare function lineCoordsOf(feature: Feature): Position[];
|
|
50
|
+
/** True for a feature fetchAllKmlLines lists and MapCanvas.tsx's shapes
|
|
51
|
+
* effect lets the panel hide/dim -- shared for the same reason as
|
|
52
|
+
* isKmlRegion: the two must never drift apart. A region is never a line. */
|
|
53
|
+
export declare function isKmlLine(feature: Feature): boolean;
|
|
54
|
+
/** An image overlay, as MapItemEditorDialog.tsx tracks one -- always 4
|
|
55
|
+
* explicit world corners; see kmlMedia.ts's KmlImageOverlay, which this
|
|
56
|
+
* mirrors on the write side. */
|
|
57
|
+
export interface ImageOverlay {
|
|
58
|
+
handle: string;
|
|
59
|
+
corners: OverlayCorners;
|
|
60
|
+
name?: string;
|
|
61
|
+
opacity?: number;
|
|
62
|
+
order?: number;
|
|
63
|
+
}
|
|
64
|
+
/** The property marking a feature as an image overlay rather than a shape:
|
|
65
|
+
* its Polygon is the image's 4 corners (top-left, top-right, bottom-right,
|
|
66
|
+
* bottom-left, closed), and `image` is the picture's Media handle. This
|
|
67
|
+
* app's own convention -- plain GeoJSON has no image overlays -- read back
|
|
68
|
+
* by parseMapFile. */
|
|
69
|
+
export declare const IMAGE_OVERLAY_PROPERTY = "gramps:image-overlay";
|
|
70
|
+
/** Records which Media object an overlay was made from (mapOverlays.ts) --
|
|
71
|
+
* a "foreign member" of the FeatureCollection, which GeoJSON allows. */
|
|
72
|
+
export declare const SOURCE_PROPERTY = "gramps:source";
|
|
73
|
+
/** Every shape plus every image overlay, as one GeoJSON FeatureCollection.
|
|
74
|
+
* Overlays go last: kmlMedia.ts's region/line row indexes count every
|
|
75
|
+
* feature in the file but MapCanvas.tsx's leave overlays out, and the two
|
|
76
|
+
* only agree while no overlay comes before a shape. */
|
|
77
|
+
export declare function mapItemToGeoJson(features: Feature[], overlays?: ImageOverlay[], source?: string): string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Feature, LineString, Point, Polygon, Position } from "geojson";
|
|
2
|
+
export type EditableGeometry = Point | LineString | Polygon;
|
|
3
|
+
export type EditMode = "point" | "linestring" | "polygon";
|
|
4
|
+
/** One single-geometry part, ready for terra-draw. `sourceKey` rides on the
|
|
5
|
+
* terra-draw feature as a property so save can find what was set aside. */
|
|
6
|
+
export interface EditablePart {
|
|
7
|
+
sourceKey: string;
|
|
8
|
+
mode: EditMode;
|
|
9
|
+
geometry: EditableGeometry;
|
|
10
|
+
name?: string;
|
|
11
|
+
color?: string;
|
|
12
|
+
opacity?: number;
|
|
13
|
+
}
|
|
14
|
+
interface PartSource {
|
|
15
|
+
group: string;
|
|
16
|
+
/** Position within its group, so rejoined parts keep their order. */
|
|
17
|
+
index: number;
|
|
18
|
+
extra: Record<string, unknown>;
|
|
19
|
+
/** A track part's per-point times, and the (2D, rounded) points they
|
|
20
|
+
* belong to -- kept only while the saved points still match. */
|
|
21
|
+
times?: string[];
|
|
22
|
+
coords?: Position[];
|
|
23
|
+
}
|
|
24
|
+
export interface EditSession {
|
|
25
|
+
parts: EditablePart[];
|
|
26
|
+
sources: Map<string, PartSource>;
|
|
27
|
+
/** Parts terra-draw wouldn't accept -- see lockPart. */
|
|
28
|
+
locked: Map<string, EditablePart>;
|
|
29
|
+
}
|
|
30
|
+
/** Splits a file's features (e.g. parseMapFile's) into
|
|
31
|
+
* editable single parts. A polygon with holes is locked from the start:
|
|
32
|
+
* terra-draw's polygon editing only knows an outer ring. */
|
|
33
|
+
export declare function prepareForEditing(features: Feature[], firstIndex?: number): EditSession;
|
|
34
|
+
/** Adds more features (a file imported while editing) after the session's
|
|
35
|
+
* own, so they save after them too. Returns the session of just the new
|
|
36
|
+
* ones, whose parts the canvas still has to take. */
|
|
37
|
+
export declare function addToSession(session: EditSession, features: Feature[]): EditSession;
|
|
38
|
+
/** Marks a part terra-draw refused to load (addFeatures' validation): it's
|
|
39
|
+
* no longer editable, and assembleForSaving writes it back unchanged. */
|
|
40
|
+
export declare function lockPart(session: EditSession, sourceKey: string): void;
|
|
41
|
+
/** A part as saved from the canvas: its (possibly edited) geometry and the
|
|
42
|
+
* editor's own properties, plus the sourceKey it was loaded with -- absent
|
|
43
|
+
* for a shape drawn new in this session. */
|
|
44
|
+
export interface SavedPart {
|
|
45
|
+
sourceKey?: string;
|
|
46
|
+
geometry: EditableGeometry;
|
|
47
|
+
name?: string;
|
|
48
|
+
color?: string;
|
|
49
|
+
opacity?: number;
|
|
50
|
+
}
|
|
51
|
+
/** Everything to save: the canvas's parts with their set-aside data put
|
|
52
|
+
* back, multi-part shapes rejoined, and locked parts as they were loaded.
|
|
53
|
+
* Features come out in their original file order, new shapes last. */
|
|
54
|
+
export declare function assembleForSaving(session: EditSession, saved: SavedPart[]): Feature[];
|
|
55
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gramps-connect/story",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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",
|
|
@@ -36,6 +36,11 @@
|
|
|
36
36
|
"types": "./dist/types/editor/index.d.ts",
|
|
37
37
|
"default": "./dist/editor.js"
|
|
38
38
|
},
|
|
39
|
+
"./map-edit": {
|
|
40
|
+
"source": "./src/mapEdit/index.ts",
|
|
41
|
+
"types": "./dist/types/mapEdit/index.d.ts",
|
|
42
|
+
"default": "./dist/map-edit.js"
|
|
43
|
+
},
|
|
39
44
|
"./embed": "./dist/embed.js",
|
|
40
45
|
"./schema/1.0": "./spec/1.0/story.schema.json",
|
|
41
46
|
"./package.json": "./package.json"
|
|
@@ -65,6 +70,7 @@
|
|
|
65
70
|
"dependencies": {
|
|
66
71
|
"@gramps-connect/gramps-date": "^0.1.0",
|
|
67
72
|
"@openhistoricalmap/maplibre-gl-dates": "^1.4.0",
|
|
73
|
+
"@tmcw/togeojson": "^7.1.2",
|
|
68
74
|
"fflate": "^0.8.3"
|
|
69
75
|
},
|
|
70
76
|
"devDependencies": {
|
|
@@ -80,6 +86,8 @@
|
|
|
80
86
|
"playwright-core": "^1.64.1",
|
|
81
87
|
"react": "^18.3.1",
|
|
82
88
|
"react-dom": "^18.3.1",
|
|
89
|
+
"terra-draw": "^1.32.3",
|
|
90
|
+
"terra-draw-maplibre-gl-adapter": "^1.4.1",
|
|
83
91
|
"tsx": "^4.23.5",
|
|
84
92
|
"typescript": "^7.0.2"
|
|
85
93
|
},
|
|
@@ -88,7 +96,9 @@
|
|
|
88
96
|
"@mantine/hooks": "^8",
|
|
89
97
|
"maplibre-gl": ">=5",
|
|
90
98
|
"react": "^18.2 || ^19",
|
|
91
|
-
"react-dom": "^18.2 || ^19"
|
|
99
|
+
"react-dom": "^18.2 || ^19",
|
|
100
|
+
"terra-draw": "^1.32.3",
|
|
101
|
+
"terra-draw-maplibre-gl-adapter": "^1.4.1"
|
|
92
102
|
},
|
|
93
103
|
"peerDependenciesMeta": {
|
|
94
104
|
"@mantine/core": {
|
|
@@ -105,6 +115,12 @@
|
|
|
105
115
|
},
|
|
106
116
|
"react-dom": {
|
|
107
117
|
"optional": true
|
|
118
|
+
},
|
|
119
|
+
"terra-draw": {
|
|
120
|
+
"optional": true
|
|
121
|
+
},
|
|
122
|
+
"terra-draw-maplibre-gl-adapter": {
|
|
123
|
+
"optional": true
|
|
108
124
|
}
|
|
109
125
|
}
|
|
110
126
|
}
|