@jupytergis/schema 0.10.1 → 0.12.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.
@@ -302,6 +302,70 @@
302
302
  }
303
303
  }
304
304
  },
305
+ "StorySegmentLayer": {
306
+ "type": "object",
307
+ "additionalProperties": false,
308
+ "required": [
309
+ "zoom",
310
+ "extent",
311
+ "transition"
312
+ ],
313
+ "properties": {
314
+ "zoom": {
315
+ "type": "number",
316
+ "default": 0
317
+ },
318
+ "extent": {
319
+ "type": "array",
320
+ "default": null,
321
+ "items": {
322
+ "type": "number"
323
+ }
324
+ },
325
+ "content": {
326
+ "type": "object",
327
+ "additionalProperties": false,
328
+ "properties": {
329
+ "title": {
330
+ "type": "string"
331
+ },
332
+ "image": {
333
+ "type": "string",
334
+ "description": "Link to image for the story"
335
+ },
336
+ "markdown": {
337
+ "type": "string",
338
+ "description": "Markdown string representing the content of the story stop"
339
+ }
340
+ }
341
+ },
342
+ "transition": {
343
+ "type": "object",
344
+ "description": "Transition configuration between to this story segment",
345
+ "properties": {
346
+ "type": {
347
+ "type": "string",
348
+ "enum": [
349
+ "linear",
350
+ "immediate",
351
+ "smooth"
352
+ ],
353
+ "description": "Transition animation style",
354
+ "default": "linear"
355
+ },
356
+ "time": {
357
+ "type": "number",
358
+ "description": "The time in seconds for the transition",
359
+ "default": 1
360
+ }
361
+ },
362
+ "required": [
363
+ "type",
364
+ "time"
365
+ ]
366
+ }
367
+ }
368
+ },
305
369
  "VectorLayer": {
306
370
  "type": "object",
307
371
  "required": [
@@ -17,7 +17,8 @@ export type LayerType =
17
17
  | "WebGlLayer"
18
18
  | "ImageLayer"
19
19
  | "HeatmapLayer"
20
- | "StacLayer";
20
+ | "StacLayer"
21
+ | "StorySegmentLayer";
21
22
  /**
22
23
  * This interface was referenced by `IJGISContent`'s JSON-Schema
23
24
  * via the `definition` "sourceType".
@@ -43,6 +44,23 @@ export type IJGISLayerItem = string | IJGISLayerGroup;
43
44
  * via the `definition` "jGISLayerTree".
44
45
  */
45
46
  export type IJGISLayerTree = IJGISLayerItem[];
47
+ export type WhetherPresentationModeIsActive = boolean;
48
+ /**
49
+ * The title of the story map
50
+ */
51
+ export type Title = string;
52
+ /**
53
+ * The type of story map
54
+ */
55
+ export type StoryType = "guided" | "unguided";
56
+ /**
57
+ * The background color of the story map
58
+ */
59
+ export type PresentationBackgroundColor = string;
60
+ /**
61
+ * The text color of the story map
62
+ */
63
+ export type PresentationTextColor = string;
46
64
 
47
65
  export interface IJGISContent {
48
66
  schemaVersion?: string;
@@ -50,6 +68,7 @@ export interface IJGISContent {
50
68
  sources: IJGISSources;
51
69
  layerTree?: IJGISLayerTree;
52
70
  options?: IJGISOptions;
71
+ stories?: IJGISStoryMap;
53
72
  metadata?: {
54
73
  /**
55
74
  * This interface was referenced by `undefined`'s JSON-Schema definition
@@ -140,4 +159,20 @@ export interface IJGISOptions {
140
159
  extent?: number[];
141
160
  projection?: string;
142
161
  useExtent?: boolean;
162
+ storyMapPresentationMode?: WhetherPresentationModeIsActive;
163
+ }
164
+ /**
165
+ * This interface was referenced by `IJGISContent`'s JSON-Schema
166
+ * via the `definition` "jGISStoryMap".
167
+ */
168
+ export interface IJGISStoryMap {
169
+ title?: Title;
170
+ storyType?: StoryType;
171
+ /**
172
+ * Array of story segments for the story map
173
+ */
174
+ storySegments?: string[];
175
+ presentaionBgColor?: PresentationBackgroundColor;
176
+ presentaionTextColor?: PresentationTextColor;
177
+ [k: string]: any;
143
178
  }
@@ -0,0 +1,39 @@
1
+ /* tslint:disable */
2
+ /**
3
+ * This file was automatically generated by json-schema-to-typescript.
4
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
5
+ * and run json-schema-to-typescript to regenerate this file.
6
+ */
7
+
8
+ /**
9
+ * StorySegmentLayer
10
+ */
11
+ export interface IStorySegmentLayer {
12
+ zoom: number;
13
+ extent: number[];
14
+ content?: {
15
+ title?: string;
16
+ /**
17
+ * Link to image for the story
18
+ */
19
+ image?: string;
20
+ /**
21
+ * Markdown string representing the content of the story stop
22
+ */
23
+ markdown?: string;
24
+ };
25
+ /**
26
+ * Transition configuration between to this story segment
27
+ */
28
+ transition: {
29
+ /**
30
+ * Transition animation style
31
+ */
32
+ type: "linear" | "immediate" | "smooth";
33
+ /**
34
+ * The time in seconds for the transition
35
+ */
36
+ time: number;
37
+ [k: string]: any;
38
+ };
39
+ }
package/lib/doc.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { MapChange, YDocument } from '@jupyter/ydoc';
2
2
  import { JSONObject } from '@lumino/coreutils';
3
3
  import { ISignal } from '@lumino/signaling';
4
- import { IJGISLayer, IJGISLayerItem, IJGISLayerTree, IJGISLayers, IJGISOptions, IJGISSource, IJGISSources } from './_interface/project/jgis';
5
- import { IDict, IJGISLayerDocChange, IJGISLayerTreeDocChange, IJGISSourceDocChange, IJupyterGISDoc, IJupyterGISDocChange } from './interfaces';
4
+ import { IJGISLayer, IJGISLayerItem, IJGISLayerTree, IJGISLayers, IJGISOptions, IJGISSource, IJGISSources, IJGISStoryMap } from './_interface/project/jgis';
5
+ import { IDict, IJGISLayerDocChange, IJGISLayerTreeDocChange, IJGISSourceDocChange, IJGISStoryMapDocChange, IJGISStoryMaps, IJupyterGISDoc, IJupyterGISDocChange } from './interfaces';
6
6
  export declare class JupyterGISDoc extends YDocument<IJupyterGISDocChange> implements IJupyterGISDoc {
7
7
  constructor();
8
8
  getSource(): JSONObject;
@@ -13,6 +13,8 @@ export declare class JupyterGISDoc extends YDocument<IJupyterGISDocChange> imple
13
13
  set layers(layers: IJGISLayers);
14
14
  set sources(sources: IJGISSources);
15
15
  get sources(): IJGISSources;
16
+ set stories(stories: IJGISStoryMaps);
17
+ get stories(): IJGISStoryMaps;
16
18
  get layerTree(): IJGISLayerTree;
17
19
  set layerTree(layerTree: IJGISLayerTree);
18
20
  getLayer(id: string): IJGISLayer | undefined;
@@ -22,6 +24,7 @@ export declare class JupyterGISDoc extends YDocument<IJupyterGISDocChange> imple
22
24
  get layersChanged(): ISignal<IJupyterGISDoc, IJGISLayerDocChange>;
23
25
  get layerTreeChanged(): ISignal<IJupyterGISDoc, IJGISLayerTreeDocChange>;
24
26
  get sourcesChanged(): ISignal<IJupyterGISDoc, IJGISSourceDocChange>;
27
+ get storyMapsChanged(): ISignal<IJupyterGISDoc, IJGISStoryMapDocChange>;
25
28
  get optionsChanged(): ISignal<IJupyterGISDoc, MapChange>;
26
29
  layerExists(id: string): boolean;
27
30
  removeLayer(id: string): void;
@@ -35,6 +38,10 @@ export declare class JupyterGISDoc extends YDocument<IJupyterGISDocChange> imple
35
38
  removeSource(id: string): void;
36
39
  addSource(id: string, value: IJGISSource): void;
37
40
  updateSource(id: string, value: any): void;
41
+ removeStoryMap(id: string): void;
42
+ addStoryMap(id: string, value: IJGISStoryMap): void;
43
+ updateStoryMap(id: string, value: any): void;
44
+ getStoryMap(id: string): IJGISStoryMap | undefined;
38
45
  getOption(key: keyof IJGISOptions): IDict | undefined;
39
46
  setOption(key: keyof IJGISOptions, value: IDict): void;
40
47
  getMetadata(key: string): string | undefined;
@@ -52,16 +59,19 @@ export declare class JupyterGISDoc extends YDocument<IJupyterGISDocChange> imple
52
59
  private _layersObserver;
53
60
  private _layerTreeObserver;
54
61
  private _sourcesObserver;
62
+ private _storyMapsObserver;
55
63
  private _optionsObserver;
56
64
  private _metaObserver;
57
65
  private _layers;
58
66
  private _layerTree;
59
67
  private _sources;
68
+ private _stories;
60
69
  private _options;
61
70
  private _metadata;
62
71
  private _optionsChanged;
63
72
  private _layersChanged;
64
73
  private _layerTreeChanged;
65
74
  private _sourcesChanged;
75
+ private _storyMapsChanged;
66
76
  private _metadataChanged;
67
77
  }
package/lib/doc.js CHANGED
@@ -7,27 +7,47 @@ export class JupyterGISDoc extends YDocument {
7
7
  super();
8
8
  this.editable = true;
9
9
  this._optionsObserver = (event) => {
10
- this._optionsChanged.emit(event.keys);
10
+ const changes = new Map();
11
+ event.changes.keys.forEach((event, key) => {
12
+ changes.set(key, {
13
+ action: event.action,
14
+ oldValue: event.oldValue,
15
+ newValue: this._options.get(key),
16
+ });
17
+ });
18
+ this._optionsChanged.emit(changes);
11
19
  };
12
20
  this._metaObserver = (event) => {
13
- this._metadataChanged.emit(event.keys);
21
+ const changes = new Map();
22
+ event.changes.keys.forEach((event, key) => {
23
+ changes.set(key, {
24
+ action: event.action,
25
+ oldValue: event.oldValue,
26
+ newValue: this._metadata.get(key),
27
+ });
28
+ });
29
+ this._metadataChanged.emit(changes);
14
30
  };
15
31
  this._optionsChanged = new Signal(this);
16
32
  this._layersChanged = new Signal(this);
17
33
  this._layerTreeChanged = new Signal(this);
18
34
  this._sourcesChanged = new Signal(this);
35
+ this._storyMapsChanged = new Signal(this);
19
36
  this._metadataChanged = new Signal(this);
20
37
  this._options = this.ydoc.getMap('options');
21
38
  this._layers = this.ydoc.getMap('layers');
22
39
  this._layerTree = this.ydoc.getArray('layerTree');
23
40
  this._sources = this.ydoc.getMap('sources');
41
+ this._stories = this.ydoc.getMap('stories');
24
42
  this._metadata = this.ydoc.getMap('metadata');
25
43
  this.undoManager.addToScope(this._layers);
26
44
  this.undoManager.addToScope(this._sources);
45
+ this.undoManager.addToScope(this._stories);
27
46
  this.undoManager.addToScope(this._layerTree);
28
47
  this._layers.observeDeep(this._layersObserver.bind(this));
29
48
  this._layerTree.observe(this._layerTreeObserver.bind(this));
30
49
  this._sources.observeDeep(this._sourcesObserver.bind(this));
50
+ this._stories.observeDeep(this._storyMapsObserver.bind(this));
31
51
  this._options.observe(this._optionsObserver.bind(this));
32
52
  this._metadata.observe(this._metaObserver.bind(this));
33
53
  }
@@ -36,8 +56,9 @@ export class JupyterGISDoc extends YDocument {
36
56
  const layerTree = this._layerTree.toJSON();
37
57
  const options = this._options.toJSON();
38
58
  const sources = this._sources.toJSON();
59
+ const stories = this._stories.toJSON();
39
60
  const metadata = this._metadata.toJSON();
40
- return { layers, layerTree, sources, options, metadata };
61
+ return { layers, layerTree, sources, stories, options, metadata };
41
62
  }
42
63
  setSource(value) {
43
64
  if (!value) {
@@ -48,7 +69,7 @@ export class JupyterGISDoc extends YDocument {
48
69
  }
49
70
  value = value;
50
71
  this.transact(() => {
51
- var _a, _b, _c, _d, _e;
72
+ var _a, _b, _c, _d, _e, _f;
52
73
  const layers = (_a = value['layers']) !== null && _a !== void 0 ? _a : {};
53
74
  Object.entries(layers).forEach(([key, val]) => this._layers.set(key, val));
54
75
  const layerTree = (_b = value['layerTree']) !== null && _b !== void 0 ? _b : [];
@@ -59,7 +80,9 @@ export class JupyterGISDoc extends YDocument {
59
80
  Object.entries(options).forEach(([key, val]) => this._options.set(key, val));
60
81
  const sources = (_d = value['sources']) !== null && _d !== void 0 ? _d : {};
61
82
  Object.entries(sources).forEach(([key, val]) => this._sources.set(key, val));
62
- const metadata = (_e = value['metadata']) !== null && _e !== void 0 ? _e : {};
83
+ const stories = (_e = value['stories']) !== null && _e !== void 0 ? _e : {};
84
+ Object.entries(stories).forEach(([key, val]) => this._stories.set(key, val));
85
+ const metadata = (_f = value['metadata']) !== null && _f !== void 0 ? _f : {};
63
86
  Object.entries(metadata).forEach(([key, val]) => this._metadata.set(key, val));
64
87
  });
65
88
  }
@@ -89,6 +112,16 @@ export class JupyterGISDoc extends YDocument {
89
112
  get sources() {
90
113
  return JSONExt.deepCopy(this._sources.toJSON());
91
114
  }
115
+ set stories(stories) {
116
+ this.transact(() => {
117
+ for (const [key, value] of Object.entries(stories)) {
118
+ this._stories.set(key, value);
119
+ }
120
+ });
121
+ }
122
+ get stories() {
123
+ return JSONExt.deepCopy(this._stories.toJSON());
124
+ }
92
125
  get layerTree() {
93
126
  return JSONExt.deepCopy(this._layerTree.toJSON());
94
127
  }
@@ -129,6 +162,9 @@ export class JupyterGISDoc extends YDocument {
129
162
  get sourcesChanged() {
130
163
  return this._sourcesChanged;
131
164
  }
165
+ get storyMapsChanged() {
166
+ return this._storyMapsChanged;
167
+ }
132
168
  get optionsChanged() {
133
169
  return this._optionsChanged;
134
170
  }
@@ -201,6 +237,25 @@ export class JupyterGISDoc extends YDocument {
201
237
  updateSource(id, value) {
202
238
  this.transact(() => this._sources.set(id, value));
203
239
  }
240
+ removeStoryMap(id) {
241
+ this.transact(() => {
242
+ this._stories.delete(id);
243
+ });
244
+ }
245
+ addStoryMap(id, value) {
246
+ this.transact(() => {
247
+ this._stories.set(id, value);
248
+ });
249
+ }
250
+ updateStoryMap(id, value) {
251
+ this.transact(() => this._stories.set(id, value));
252
+ }
253
+ getStoryMap(id) {
254
+ if (!this._stories.has(id)) {
255
+ return undefined;
256
+ }
257
+ return JSONExt.deepCopy(this._stories.get(id));
258
+ }
204
259
  getOption(key) {
205
260
  const content = this._options.get(key);
206
261
  if (!content) {
@@ -293,4 +348,23 @@ export class JupyterGISDoc extends YDocument {
293
348
  this._sourcesChanged.emit({ sourceChange: changes });
294
349
  }
295
350
  }
351
+ _storyMapsObserver(events) {
352
+ const changes = [];
353
+ let needEmit = false;
354
+ events.forEach(event => {
355
+ event.keys.forEach((change, key) => {
356
+ if (!needEmit) {
357
+ needEmit = true;
358
+ }
359
+ changes.push({
360
+ id: key,
361
+ newValue: JSONExt.deepCopy(event.target.toJSON()[key]),
362
+ });
363
+ });
364
+ });
365
+ needEmit = changes.length === 0 ? true : needEmit;
366
+ if (needEmit) {
367
+ this._storyMapsChanged.emit({ storyMapChange: changes });
368
+ }
369
+ }
296
370
  }
@@ -9,10 +9,13 @@ import { JSONObject } from '@lumino/coreutils';
9
9
  import { ISignal, Signal } from '@lumino/signaling';
10
10
  import { SplitPanel } from '@lumino/widgets';
11
11
  import { FeatureLike } from 'ol/Feature';
12
- import { IJGISContent, IJGISLayer, IJGISLayerGroup, IJGISLayerItem, IJGISLayers, IJGISLayerTree, IJGISOptions, IJGISSource, IJGISSources, SourceType } from './_interface/project/jgis';
12
+ import { IJGISContent, IJGISLayer, IJGISLayerGroup, IJGISLayerItem, IJGISLayers, IJGISLayerTree, IJGISOptions, IJGISSource, IJGISSources, IJGISStoryMap, SourceType } from './_interface/project/jgis';
13
13
  import { IRasterSource } from './_interface/project/sources/rasterSource';
14
14
  import { Modes } from './types';
15
15
  export { IGeoJSONSource } from './_interface/project/sources/geoJsonSource';
16
+ export interface IJGISStoryMaps {
17
+ [k: string]: IJGISStoryMap;
18
+ }
16
19
  export type JgisCoordinates = {
17
20
  x: number;
18
21
  y: number;
@@ -40,6 +43,12 @@ export interface IJGISLayerDocChange {
40
43
  export interface IJGISLayerTreeDocChange {
41
44
  layerTreeChange?: Delta<IJGISLayerItem[]>;
42
45
  }
46
+ export interface IJGISStoryMapDocChange {
47
+ storyMapChange?: Array<{
48
+ id: string;
49
+ newValue: IJGISStoryMap | undefined;
50
+ }>;
51
+ }
43
52
  export interface IJGISSourceDocChange {
44
53
  sourceChange?: Array<{
45
54
  id: string;
@@ -84,6 +93,7 @@ export interface IJupyterGISDoc extends YDocument<IJupyterGISDocChange> {
84
93
  options: IJGISOptions;
85
94
  layers: IJGISLayers;
86
95
  sources: IJGISSources;
96
+ stories: IJGISStoryMaps;
87
97
  layerTree: IJGISLayerTree;
88
98
  metadata: any;
89
99
  readonly editable: boolean;
@@ -100,6 +110,10 @@ export interface IJupyterGISDoc extends YDocument<IJupyterGISDocChange> {
100
110
  removeSource(id: string): void;
101
111
  addSource(id: string, value: IJGISSource): void;
102
112
  updateSource(id: string, value: IJGISSource): void;
113
+ getStoryMap(id: string): IJGISStoryMap | undefined;
114
+ removeStoryMap(id: string): void;
115
+ addStoryMap(id: string, value: IJGISStoryMap): void;
116
+ updateStoryMap(id: string, value: IJGISStoryMap): void;
103
117
  addLayerTreeItem(index: number, item: IJGISLayerItem): void;
104
118
  updateLayerTreeItem(index: number, item: IJGISLayerItem): void;
105
119
  updateObjectParameters(id: string, value: IJGISLayer['parameters'] | IJGISSource['parameters']): void;
@@ -112,6 +126,7 @@ export interface IJupyterGISDoc extends YDocument<IJupyterGISDocChange> {
112
126
  optionsChanged: ISignal<IJupyterGISDoc, MapChange>;
113
127
  layersChanged: ISignal<IJupyterGISDoc, IJGISLayerDocChange>;
114
128
  sourcesChanged: ISignal<IJupyterGISDoc, IJGISSourceDocChange>;
129
+ storyMapsChanged: ISignal<IJupyterGISDoc, IJGISStoryMapDocChange>;
115
130
  layerTreeChanged: ISignal<IJupyterGISDoc, IJGISLayerTreeDocChange>;
116
131
  metadataChanged: ISignal<IJupyterGISDoc, MapChange>;
117
132
  }
@@ -151,6 +166,7 @@ export interface IJupyterGISModel extends DocumentRegistry.IModel {
151
166
  contentsManager: Contents.IManager | undefined;
152
167
  filePath: string;
153
168
  pathChanged: ISignal<IJupyterGISModel, string>;
169
+ stories: Map<string, IJGISStoryMap>;
154
170
  getFeaturesForCurrentTile: ({ sourceId, }: {
155
171
  sourceId: string;
156
172
  }) => FeatureLike[];
@@ -212,6 +228,15 @@ export interface IJupyterGISModel extends DocumentRegistry.IModel {
212
228
  addFeatureAsMs(id: string, selectedFeature: string): void;
213
229
  triggerLayerUpdate(layerId: string, layer: IJGISLayer): void;
214
230
  disposed: ISignal<any, void>;
231
+ getSelectedStory(): {
232
+ storyId: string;
233
+ story: IJGISStoryMap | undefined;
234
+ };
235
+ addStorySegment(): {
236
+ storySegmentId: string;
237
+ storyId: string;
238
+ } | null;
239
+ isSpectaMode(): boolean;
215
240
  }
216
241
  export interface IUserData {
217
242
  userId: number;
@@ -316,4 +341,6 @@ export interface IJupyterGISSettings {
316
341
  objectPropertiesDisabled?: boolean;
317
342
  annotationsDisabled?: boolean;
318
343
  identifyDisabled?: boolean;
344
+ storyMapsDisabled: boolean;
345
+ zoomButtonsEnabled?: boolean;
319
346
  }
package/lib/model.d.ts CHANGED
@@ -6,7 +6,7 @@ import { ISettingRegistry } from '@jupyterlab/settingregistry';
6
6
  import { PartialJSONObject } from '@lumino/coreutils';
7
7
  import { ISignal, Signal } from '@lumino/signaling';
8
8
  import { FeatureLike } from 'ol/Feature';
9
- import { IJGISContent, IJGISLayer, IJGISLayerGroup, IJGISLayerTree, IJGISLayers, IJGISOptions, IJGISSource, IJGISSources } from './_interface/project/jgis';
9
+ import { IJGISContent, IJGISLayer, IJGISLayerGroup, IJGISLayerTree, IJGISLayers, IJGISOptions, IJGISSource, IJGISSources, IJGISStoryMap } from './_interface/project/jgis';
10
10
  import { IAnnotationModel, IDict, IJGISLayerDocChange, IJGISLayerTreeDocChange, IJGISSourceDocChange, IJupyterGISClientState, IJupyterGISDoc, IJupyterGISModel, ISelection, IUserData, IViewPortState, JgisCoordinates, Pointer, IJupyterGISSettings, SelectionType } from './interfaces';
11
11
  import { Modes } from './types';
12
12
  export declare class JupyterGISModel implements IJupyterGISModel {
@@ -157,6 +157,29 @@ export declare class JupyterGISModel implements IJupyterGISModel {
157
157
  itemId: string;
158
158
  } | null>;
159
159
  getClientId(): number;
160
+ /**
161
+ * Check if the application is running in Specta mode.
162
+ * Specta mode is enabled when the URL contains 'specta' AND the model has stories.
163
+ *
164
+ * @returns True if running in Specta mode
165
+ */
166
+ isSpectaMode(): boolean;
167
+ /**
168
+ * Placeholder in case we eventually want to support multiple stories
169
+ * @returns First/only story
170
+ */
171
+ getSelectedStory(): {
172
+ storyId: string;
173
+ story: IJGISStoryMap | undefined;
174
+ };
175
+ /**
176
+ * Adds a story segment from the current map view
177
+ * @returns Object with storySegmentId and storyMapId, or null if no extent/zoom found
178
+ */
179
+ addStorySegment(): {
180
+ storySegmentId: string;
181
+ storyId: string;
182
+ } | null;
160
183
  /**
161
184
  * Add an item in the layer tree.
162
185
  *
@@ -179,7 +202,7 @@ export declare class JupyterGISModel implements IJupyterGISModel {
179
202
  removeLayerGroup(groupName: string): void;
180
203
  /**
181
204
  * Toggle a map interaction mode on or off.
182
- * Toggleing off sets the mode to 'panning'.
205
+ * Toggling off sets the mode to 'panning'.
183
206
  * @param mode The mode to be toggled
184
207
  */
185
208
  toggleMode(mode: Modes): void;
@@ -228,6 +251,7 @@ export declare class JupyterGISModel implements IJupyterGISModel {
228
251
  private _geolocation;
229
252
  private _geolocationChanged;
230
253
  private _tileFeatureCache;
254
+ stories: Map<string, IJGISStoryMap>;
231
255
  }
232
256
  export declare namespace JupyterGISModel {
233
257
  /**
package/lib/model.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { UUID } from '@lumino/coreutils';
1
2
  import { Signal } from '@lumino/signaling';
2
3
  import Ajv from 'ajv';
3
4
  import { JupyterGISDoc } from './doc';
@@ -13,6 +14,8 @@ const DEFAULT_SETTINGS = {
13
14
  objectPropertiesDisabled: false,
14
15
  annotationsDisabled: false,
15
16
  identifyDisabled: false,
17
+ storyMapsDisabled: false,
18
+ zoomButtonsEnabled: false,
16
19
  };
17
20
  export class JupyterGISModel {
18
21
  constructor(options) {
@@ -60,6 +63,7 @@ export class JupyterGISModel {
60
63
  this._editingChanged = new Signal(this);
61
64
  this._geolocationChanged = new Signal(this);
62
65
  this._tileFeatureCache = new Map();
66
+ this.stories = new Map();
63
67
  const { annotationModel, sharedModel, settingRegistry } = options;
64
68
  if (sharedModel) {
65
69
  this._sharedModel = sharedModel;
@@ -258,11 +262,12 @@ export class JupyterGISModel {
258
262
  console.warn(errorMsg);
259
263
  }
260
264
  this.sharedModel.transact(() => {
261
- var _a, _b, _c, _d, _e;
265
+ var _a, _b, _c, _d, _e, _f;
262
266
  this.sharedModel.sources = (_a = jsonData.sources) !== null && _a !== void 0 ? _a : {};
263
267
  this.sharedModel.layers = (_b = jsonData.layers) !== null && _b !== void 0 ? _b : {};
264
268
  this.sharedModel.layerTree = (_c = jsonData.layerTree) !== null && _c !== void 0 ? _c : [];
265
- this.sharedModel.options = (_d = jsonData.options) !== null && _d !== void 0 ? _d : {
269
+ this.sharedModel.stories = (_d = jsonData.stories) !== null && _d !== void 0 ? _d : {};
270
+ this.sharedModel.options = (_e = jsonData.options) !== null && _e !== void 0 ? _e : {
266
271
  latitude: 0,
267
272
  longitude: 0,
268
273
  zoom: 0,
@@ -270,7 +275,7 @@ export class JupyterGISModel {
270
275
  pitch: 0,
271
276
  projection: 'EPSG:3857',
272
277
  };
273
- this.sharedModel.metadata = (_e = jsonData.metadata) !== null && _e !== void 0 ? _e : {};
278
+ this.sharedModel.metadata = (_f = jsonData.metadata) !== null && _f !== void 0 ? _f : {};
274
279
  });
275
280
  this.dirty = true;
276
281
  }
@@ -409,7 +414,19 @@ export class JupyterGISModel {
409
414
  const source_id = (_a = layer === null || layer === void 0 ? void 0 : layer.parameters) === null || _a === void 0 ? void 0 : _a.source;
410
415
  this._removeLayerTreeLayer(this.getLayerTree(), layer_id);
411
416
  this.sharedModel.removeLayer(layer_id);
412
- this.sharedModel.removeSource(source_id);
417
+ if ((layer === null || layer === void 0 ? void 0 : layer.type) === 'StorySegmentLayer') {
418
+ // remove this layer id from story maps
419
+ Object.entries(this.sharedModel.stories).forEach(([storyMapId, storyMap]) => {
420
+ var _a;
421
+ if ((_a = storyMap.storySegments) === null || _a === void 0 ? void 0 : _a.includes(layer_id)) {
422
+ const updatedStorySegments = storyMap.storySegments.filter(id => id !== layer_id);
423
+ this.sharedModel.updateStoryMap(storyMapId, Object.assign(Object.assign({}, storyMap), { storySegments: updatedStorySegments }));
424
+ }
425
+ });
426
+ }
427
+ else {
428
+ this.sharedModel.removeSource(source_id);
429
+ }
413
430
  }
414
431
  setOptions(value) {
415
432
  this._sharedModel.options = value;
@@ -470,6 +487,78 @@ export class JupyterGISModel {
470
487
  getClientId() {
471
488
  return this.sharedModel.awareness.clientID;
472
489
  }
490
+ /**
491
+ * Check if the application is running in Specta mode.
492
+ * Specta mode is enabled when the URL contains 'specta' AND the model has stories.
493
+ *
494
+ * @returns True if running in Specta mode
495
+ */
496
+ isSpectaMode() {
497
+ var _a;
498
+ const hasStories = Object.keys(this.sharedModel.stories).length > 0;
499
+ const isSpecta = !!document.querySelector('meta[name="specta-config"]');
500
+ const guidedMode = ((_a = this.getSelectedStory().story) === null || _a === void 0 ? void 0 : _a.storyType) === 'guided';
501
+ return isSpecta && hasStories && guidedMode;
502
+ }
503
+ /**
504
+ * Placeholder in case we eventually want to support multiple stories
505
+ * @returns First/only story
506
+ */
507
+ getSelectedStory() {
508
+ const stories = this.sharedModel.stories;
509
+ const storyId = Object.keys(stories)[0];
510
+ return {
511
+ storyId: storyId,
512
+ story: this.sharedModel.getStoryMap(storyId),
513
+ };
514
+ }
515
+ /**
516
+ * Adds a story segment from the current map view
517
+ * @returns Object with storySegmentId and storyMapId, or null if no extent/zoom found
518
+ */
519
+ addStorySegment() {
520
+ var _a;
521
+ const { zoom, extent } = this.getOptions();
522
+ const { storyId } = this.getSelectedStory();
523
+ if (!zoom || !extent) {
524
+ console.warn('No extent or zoom found');
525
+ return null;
526
+ }
527
+ const newStorySegmentId = UUID.uuid4();
528
+ const layerParams = {
529
+ extent,
530
+ zoom,
531
+ transition: { type: 'linear', time: 1 },
532
+ };
533
+ const layerModel = {
534
+ type: 'StorySegmentLayer',
535
+ visible: true,
536
+ name: 'Story Segment',
537
+ parameters: layerParams,
538
+ };
539
+ this.addLayer(newStorySegmentId, layerModel);
540
+ // if story doesn't exist then add one
541
+ if (!storyId) {
542
+ const storyId = UUID.uuid4();
543
+ const title = 'New Story';
544
+ const storyType = 'guided';
545
+ const storySegments = [newStorySegmentId];
546
+ const storyMap = { title, storyType, storySegments };
547
+ this.sharedModel.addStoryMap(storyId, storyMap);
548
+ return { storySegmentId: newStorySegmentId, storyId };
549
+ }
550
+ else {
551
+ // else need to update story
552
+ const { story } = this.getSelectedStory();
553
+ if (!story) {
554
+ console.warn('No story found, something went wrong');
555
+ return null;
556
+ }
557
+ const newStory = Object.assign(Object.assign({}, story), { storySegments: [...((_a = story.storySegments) !== null && _a !== void 0 ? _a : []), newStorySegmentId] });
558
+ this.sharedModel.updateStoryMap(storyId, newStory);
559
+ return { storySegmentId: newStorySegmentId, storyId };
560
+ }
561
+ }
473
562
  /**
474
563
  * Add an item in the layer tree.
475
564
  *
@@ -612,7 +701,7 @@ export class JupyterGISModel {
612
701
  }
613
702
  /**
614
703
  * Toggle a map interaction mode on or off.
615
- * Toggleing off sets the mode to 'panning'.
704
+ * Toggling off sets the mode to 'panning'.
616
705
  * @param mode The mode to be toggled
617
706
  */
618
707
  toggleMode(mode) {
@@ -20,6 +20,9 @@
20
20
  "options": {
21
21
  "$ref": "#/definitions/jGISOptions"
22
22
  },
23
+ "stories": {
24
+ "$ref": "#/definitions/jGISStoryMap"
25
+ },
23
26
  "metadata": {
24
27
  "type": "object",
25
28
  "patternProperties": {
@@ -41,7 +44,8 @@
41
44
  "WebGlLayer",
42
45
  "ImageLayer",
43
46
  "HeatmapLayer",
44
- "StacLayer"
47
+ "StacLayer",
48
+ "StorySegmentLayer"
45
49
  ]
46
50
  },
47
51
  "sourceType": {
@@ -136,6 +140,42 @@
136
140
  }
137
141
  ]
138
142
  },
143
+ "jGISStoryMap": {
144
+ "title": "IJGISStoryMap",
145
+ "type": "object",
146
+ "additionalProperties": true,
147
+ "properties": {
148
+ "title": {
149
+ "type": "string",
150
+ "title": "Title",
151
+ "description": "The title of the story map"
152
+ },
153
+ "storyType": {
154
+ "type": "string",
155
+ "title": "Story Type",
156
+ "enum": ["guided", "unguided"],
157
+ "description": "The type of story map"
158
+ },
159
+ "storySegments": {
160
+ "type": "array",
161
+ "default": [],
162
+ "description": "Array of story segments for the story map",
163
+ "items": {
164
+ "type": "string"
165
+ }
166
+ },
167
+ "presentaionBgColor": {
168
+ "type": "string",
169
+ "title": "Presentation Background Color",
170
+ "description": "The background color of the story map"
171
+ },
172
+ "presentaionTextColor": {
173
+ "type": "string",
174
+ "title": "Presentation Text Color",
175
+ "description": "The text color of the story map"
176
+ }
177
+ }
178
+ },
139
179
  "jGISLayers": {
140
180
  "title": "IJGISLayers",
141
181
  "type": "object",
@@ -201,6 +241,11 @@
201
241
  "useExtent": {
202
242
  "type": "boolean",
203
243
  "default": false
244
+ },
245
+ "storyMapPresentationMode": {
246
+ "type": "boolean",
247
+ "title": "Whether presentation mode is active",
248
+ "default": false
204
249
  }
205
250
  }
206
251
  },
@@ -0,0 +1,55 @@
1
+ {
2
+ "type": "object",
3
+ "description": "StorySegmentLayer",
4
+ "title": "IStorySegmentLayer",
5
+ "additionalProperties": false,
6
+ "required": ["zoom", "extent", "transition"],
7
+ "properties": {
8
+ "zoom": {
9
+ "type": "number",
10
+ "default": 0
11
+ },
12
+ "extent": {
13
+ "type": "array",
14
+ "default": null,
15
+ "items": {
16
+ "type": "number"
17
+ }
18
+ },
19
+ "content": {
20
+ "type": "object",
21
+ "additionalProperties": false,
22
+ "properties": {
23
+ "title": {
24
+ "type": "string"
25
+ },
26
+ "image": {
27
+ "type": "string",
28
+ "description": "Link to image for the story"
29
+ },
30
+ "markdown": {
31
+ "type": "string",
32
+ "description": "Markdown string representing the content of the story stop"
33
+ }
34
+ }
35
+ },
36
+ "transition": {
37
+ "type": "object",
38
+ "description": "Transition configuration between to this story segment",
39
+ "properties": {
40
+ "type": {
41
+ "type": "string",
42
+ "enum": ["linear", "immediate", "smooth"],
43
+ "description": "Transition animation style",
44
+ "default": "linear"
45
+ },
46
+ "time": {
47
+ "type": "number",
48
+ "description": "The time in seconds for the transition",
49
+ "default": 1
50
+ }
51
+ },
52
+ "required": ["type", "time"]
53
+ }
54
+ }
55
+ }
package/lib/types.d.ts CHANGED
@@ -11,6 +11,7 @@ export * from './_interface/project/sources/geoParquetSource';
11
11
  export * from './_interface/project/sources/markerSource';
12
12
  export * from './_interface/project/layers/heatmapLayer';
13
13
  export * from './_interface/project/layers/hillshadeLayer';
14
+ export * from './_interface/project/layers/storySegmentLayer';
14
15
  export * from './_interface/project/layers/rasterLayer';
15
16
  export * from './_interface/project/layers/vectorLayer';
16
17
  export * from './_interface/project/layers/imageLayer';
package/lib/types.js CHANGED
@@ -13,6 +13,7 @@ export * from './_interface/project/sources/markerSource';
13
13
  // Layers
14
14
  export * from './_interface/project/layers/heatmapLayer';
15
15
  export * from './_interface/project/layers/hillshadeLayer';
16
+ export * from './_interface/project/layers/storySegmentLayer';
16
17
  export * from './_interface/project/layers/rasterLayer';
17
18
  export * from './_interface/project/layers/vectorLayer';
18
19
  export * from './_interface/project/layers/imageLayer';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jupytergis/schema",
3
- "version": "0.10.1",
3
+ "version": "0.12.0",
4
4
  "description": "A JupyterGIS schema package.",
5
5
  "keywords": [
6
6
  "jupytergis"