@jupytergis/base 0.13.2 → 0.14.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.
Files changed (116) hide show
  1. package/lib/commands/BaseCommandIDs.d.ts +14 -13
  2. package/lib/commands/BaseCommandIDs.js +14 -14
  3. package/lib/commands/index.js +528 -130
  4. package/lib/commands/operationCommands.d.ts +22 -0
  5. package/lib/commands/operationCommands.js +305 -0
  6. package/lib/constants.js +11 -9
  7. package/lib/dialogs/ProcessingFormDialog.d.ts +1 -1
  8. package/lib/dialogs/ProcessingFormDialog.js +2 -2
  9. package/lib/dialogs/layerBrowserDialog.d.ts +2 -0
  10. package/lib/dialogs/layerBrowserDialog.js +12 -5
  11. package/lib/dialogs/layerCreationFormDialog.d.ts +7 -0
  12. package/lib/dialogs/layerCreationFormDialog.js +10 -2
  13. package/lib/dialogs/symbology/components/color_ramp/ColorRampControls.d.ts +2 -1
  14. package/lib/dialogs/symbology/components/color_ramp/ColorRampControls.js +21 -19
  15. package/lib/dialogs/symbology/components/color_ramp/ColorRampSelector.d.ts +3 -1
  16. package/lib/dialogs/symbology/components/color_ramp/ColorRampSelector.js +13 -5
  17. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +6 -4
  18. package/lib/dialogs/symbology/vector_layer/types/Categorized.js +7 -11
  19. package/lib/dialogs/symbology/vector_layer/types/Graduated.js +7 -10
  20. package/lib/dialogs/symbology/vector_layer/types/Heatmap.js +7 -8
  21. package/lib/formbuilder/creationform.d.ts +8 -8
  22. package/lib/formbuilder/creationform.js +130 -85
  23. package/lib/formbuilder/editform.d.ts +1 -7
  24. package/lib/formbuilder/editform.js +64 -52
  25. package/lib/formbuilder/formselectors.d.ts +5 -4
  26. package/lib/formbuilder/index.d.ts +1 -1
  27. package/lib/formbuilder/index.js +1 -1
  28. package/lib/formbuilder/objectform/SchemaForm.d.ts +36 -0
  29. package/lib/formbuilder/objectform/SchemaForm.js +77 -0
  30. package/lib/formbuilder/objectform/StoryEditorForm.d.ts +3 -9
  31. package/lib/formbuilder/objectform/StoryEditorForm.js +20 -14
  32. package/lib/formbuilder/objectform/components/LayerSelect.js +3 -8
  33. package/lib/formbuilder/objectform/components/SegmentFormSymbology.js +23 -10
  34. package/lib/formbuilder/objectform/components/SourcePropertiesField.d.ts +7 -0
  35. package/lib/formbuilder/objectform/components/SourcePropertiesField.js +29 -0
  36. package/lib/formbuilder/objectform/components/StorySegmentReset.js +1 -1
  37. package/lib/formbuilder/objectform/fileselectorwidget.js +1 -1
  38. package/lib/formbuilder/objectform/layer/heatmapLayerForm.d.ts +3 -12
  39. package/lib/formbuilder/objectform/layer/heatmapLayerForm.js +87 -55
  40. package/lib/formbuilder/objectform/layer/hillshadeLayerForm.d.ts +3 -8
  41. package/lib/formbuilder/objectform/layer/hillshadeLayerForm.js +36 -10
  42. package/lib/formbuilder/objectform/layer/layerform.d.ts +7 -9
  43. package/lib/formbuilder/objectform/layer/layerform.js +33 -20
  44. package/lib/formbuilder/objectform/layer/storySegmentLayerForm.d.ts +3 -5
  45. package/lib/formbuilder/objectform/layer/storySegmentLayerForm.js +73 -29
  46. package/lib/formbuilder/objectform/layer/vectorlayerform.d.ts +3 -14
  47. package/lib/formbuilder/objectform/layer/vectorlayerform.js +36 -29
  48. package/lib/formbuilder/objectform/layer/webGlLayerForm.d.ts +3 -10
  49. package/lib/formbuilder/objectform/layer/webGlLayerForm.js +37 -13
  50. package/lib/formbuilder/objectform/process/dissolveProcessForm.d.ts +8 -18
  51. package/lib/formbuilder/objectform/process/dissolveProcessForm.js +68 -56
  52. package/lib/formbuilder/objectform/processingForm.d.ts +12 -0
  53. package/lib/formbuilder/objectform/processingForm.js +33 -0
  54. package/lib/formbuilder/objectform/schemaUtils.d.ts +16 -0
  55. package/lib/formbuilder/objectform/schemaUtils.js +59 -0
  56. package/lib/formbuilder/objectform/source/geojsonsource.d.ts +3 -19
  57. package/lib/formbuilder/objectform/source/geojsonsource.js +94 -53
  58. package/lib/formbuilder/objectform/source/geotiffsource.d.ts +3 -20
  59. package/lib/formbuilder/objectform/source/geotiffsource.js +73 -74
  60. package/lib/formbuilder/objectform/source/pathbasedsource.d.ts +3 -19
  61. package/lib/formbuilder/objectform/source/pathbasedsource.js +76 -75
  62. package/lib/formbuilder/objectform/source/sourceform.d.ts +7 -8
  63. package/lib/formbuilder/objectform/source/sourceform.js +28 -11
  64. package/lib/formbuilder/objectform/source/tilesourceform.d.ts +3 -7
  65. package/lib/formbuilder/objectform/source/tilesourceform.js +63 -53
  66. package/lib/formbuilder/objectform/useSchemaFormState.d.ts +48 -0
  67. package/lib/formbuilder/objectform/useSchemaFormState.js +35 -0
  68. package/lib/index.d.ts +0 -1
  69. package/lib/index.js +0 -1
  70. package/lib/keybindings.json +10 -10
  71. package/lib/mainview/mainView.d.ts +11 -7
  72. package/lib/mainview/mainView.js +63 -36
  73. package/lib/mainview/mainviewmodel.js +2 -2
  74. package/lib/menus.js +8 -8
  75. package/lib/panelview/components/layers.js +5 -2
  76. package/lib/panelview/objectproperties.js +2 -2
  77. package/lib/panelview/rightpanel.js +17 -2
  78. package/lib/panelview/story-maps/SpectaPanel.d.ts +15 -0
  79. package/lib/panelview/story-maps/SpectaPanel.js +35 -0
  80. package/lib/panelview/story-maps/StoryViewerPanel.d.ts +24 -9
  81. package/lib/panelview/story-maps/StoryViewerPanel.js +22 -268
  82. package/lib/panelview/story-maps/{PreviewModeSwitch.js → components/PreviewModeSwitch.js} +1 -1
  83. package/lib/panelview/story-maps/components/SpectaDesktopView.d.ts +21 -0
  84. package/lib/panelview/story-maps/components/SpectaDesktopView.js +49 -0
  85. package/lib/panelview/story-maps/components/SpectaMobileView.d.ts +17 -0
  86. package/lib/panelview/story-maps/{MobileSpectaPanel.js → components/SpectaMobileView.js} +8 -22
  87. package/lib/panelview/story-maps/{StoryNavBar.d.ts → components/StoryNavBar.d.ts} +1 -1
  88. package/lib/panelview/story-maps/{StoryNavBar.js → components/StoryNavBar.js} +2 -4
  89. package/lib/panelview/story-maps/hooks/useStoryMap.d.ts +39 -0
  90. package/lib/panelview/story-maps/hooks/useStoryMap.js +252 -0
  91. package/lib/processing/index.d.ts +2 -2
  92. package/lib/processing/index.js +62 -35
  93. package/lib/processing/processingCommands.d.ts +1 -1
  94. package/lib/processing/processingCommands.js +26 -6
  95. package/lib/shared/components/Collapsible.d.ts +6 -0
  96. package/lib/shared/components/Collapsible.js +26 -0
  97. package/lib/statusbar/SpectaPresentationProgressBar.d.ts +7 -0
  98. package/lib/statusbar/SpectaPresentationProgressBar.js +40 -0
  99. package/lib/toolbar/widget.js +4 -2
  100. package/lib/tools.d.ts +6 -0
  101. package/lib/tools.js +9 -0
  102. package/lib/types.d.ts +29 -2
  103. package/lib/widget.js +14 -2
  104. package/package.json +2 -4
  105. package/style/base.css +23 -1
  106. package/style/dialog.css +5 -0
  107. package/style/leftPanel.css +14 -37
  108. package/style/shared/button.css +0 -10
  109. package/style/shared/switch.css +8 -7
  110. package/style/spectaProgressBar.css +144 -0
  111. package/style/storyPanel.css +33 -0
  112. package/style/symbologyDialog.css +2 -2
  113. package/lib/formbuilder/objectform/baseform.d.ts +0 -91
  114. package/lib/formbuilder/objectform/baseform.js +0 -231
  115. package/lib/panelview/story-maps/MobileSpectaPanel.d.ts +0 -7
  116. /package/lib/panelview/story-maps/{PreviewModeSwitch.d.ts → components/PreviewModeSwitch.d.ts} +0 -0
@@ -1,19 +1,3 @@
1
- import { IDict } from '@jupytergis/schema';
2
- import { IChangeEvent, ISubmitEvent } from '@rjsf/core';
3
- import { ISourceFormProps, SourcePropertiesForm } from './sourceform';
4
- /**
5
- * The form to modify a PathBasedSource source.
6
- */
7
- export declare class PathBasedSourcePropertiesForm extends SourcePropertiesForm {
8
- constructor(props: ISourceFormProps);
9
- protected processSchema(data: IDict<any> | undefined, schema: IDict, uiSchema: IDict): void;
10
- protected onFormBlur(id: string, value: any): void;
11
- protected onFormChange(e: IChangeEvent): void;
12
- protected onFormSubmit(e: ISubmitEvent<any>): void;
13
- /**
14
- * Validate the path, to avoid invalid path.
15
- *
16
- * @param path - the path to validate.
17
- */
18
- protected _validatePath(path: string): Promise<void>;
19
- }
1
+ import React from 'react';
2
+ import type { ISourceFormProps } from './sourceform';
3
+ export declare function PathBasedSourcePropertiesForm(props: ISourceFormProps): React.ReactElement | null;
@@ -1,70 +1,29 @@
1
1
  import { showErrorMessage } from '@jupyterlab/apputils';
2
- import { FileSelectorWidget } from "../fileselectorwidget";
3
- import { loadFile } from "../../../tools";
4
- import { SourcePropertiesForm } from './sourceform';
5
- /**
6
- * The form to modify a PathBasedSource source.
7
- */
8
- export class PathBasedSourcePropertiesForm extends SourcePropertiesForm {
9
- constructor(props) {
10
- var _a, _b;
11
- super(props);
12
- if (this.props.sourceType !== 'GeoJSONSource') {
13
- this._validatePath((_b = (_a = props.sourceData) === null || _a === void 0 ? void 0 : _a.path) !== null && _b !== void 0 ? _b : '');
14
- }
15
- }
16
- processSchema(data, schema, uiSchema) {
17
- var _a;
18
- super.processSchema(data, schema, uiSchema);
19
- if (!schema.properties || !data) {
20
- return;
21
- }
22
- // Customize the widget for path field
23
- if (schema.properties && schema.properties.path) {
24
- const docManager = (_a = this.props.formChangedSignal) === null || _a === void 0 ? void 0 : _a.sender.props.formSchemaRegistry.getDocManager();
25
- uiSchema.path = {
26
- 'ui:widget': FileSelectorWidget,
27
- 'ui:options': {
28
- docManager,
29
- formOptions: this.props,
30
- },
31
- };
32
- }
33
- // This is not user-editable
34
- delete schema.properties.valid;
35
- }
36
- onFormBlur(id, value) {
37
- // Is there a better way to spot the path text entry?
38
- if (!id.endsWith('_path')) {
39
- return;
40
- }
41
- this._validatePath(value);
42
- }
43
- // we need to use `onFormChange` instead of `onFormBlur` because it's no longer a text field
44
- onFormChange(e) {
45
- var _a;
46
- super.onFormChange(e);
47
- if (((_a = e.formData) === null || _a === void 0 ? void 0 : _a.path) !== undefined) {
48
- this._validatePath(e.formData.path);
49
- }
50
- }
51
- onFormSubmit(e) {
52
- var _a, _b, _c;
53
- if (((_c = (_b = (_a = this.state.extraErrors) === null || _a === void 0 ? void 0 : _a.path) === null || _b === void 0 ? void 0 : _b.__errors) === null || _c === void 0 ? void 0 : _c.length) >= 1) {
54
- showErrorMessage('Invalid file', this.state.extraErrors.path.__errors[0]);
55
- return;
56
- }
57
- super.onFormSubmit(e);
58
- }
59
- /**
60
- * Validate the path, to avoid invalid path.
61
- *
62
- * @param path - the path to validate.
63
- */
64
- async _validatePath(path) {
65
- const extraErrors = this.state.extraErrors;
66
- let error = '';
2
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
3
+ import { deepCopy, loadFile } from "../../../tools";
4
+ import { SchemaForm } from '../SchemaForm';
5
+ import { FileSelectorWidget } from '../fileselectorwidget';
6
+ import { processBaseSchema, removeFormEntry } from '../schemaUtils';
7
+ import { useSchemaFormState } from '../useSchemaFormState';
8
+ export function PathBasedSourcePropertiesForm(props) {
9
+ const { schema: schemaProp, sourceData, syncData, model, filePath, formContext, sourceType, dialogOptions, formErrorSignal, formSchemaRegistry, cancel, } = props;
10
+ const { formData, formContextValue, hasSchema, handleChangeBase, handleSubmitBase, } = useSchemaFormState({
11
+ sourceData,
12
+ schemaProp,
13
+ model,
14
+ syncData,
15
+ cancel,
16
+ onAfterChange: dialogOptions
17
+ ? (data) => {
18
+ dialogOptions.sourceData = Object.assign({}, data);
19
+ }
20
+ : undefined,
21
+ });
22
+ const [extraErrors, setExtraErrors] = useState({});
23
+ const validatePath = useCallback(async (path) => {
24
+ const nextErrors = {};
67
25
  let valid = true;
26
+ let error = '';
68
27
  if (!path) {
69
28
  valid = false;
70
29
  error = 'Path is required';
@@ -73,26 +32,68 @@ export class PathBasedSourcePropertiesForm extends SourcePropertiesForm {
73
32
  try {
74
33
  await loadFile({
75
34
  filepath: path,
76
- type: this.props.sourceType,
77
- model: this.props.model,
35
+ type: sourceType,
36
+ model,
78
37
  });
79
38
  }
80
39
  catch (e) {
81
40
  valid = false;
82
- error = `"${path}" is not a valid ${this.props.sourceType} file.`;
41
+ error = `"${path}" is not a valid ${sourceType} file.`;
83
42
  }
84
43
  }
85
44
  if (!valid) {
86
- extraErrors.path = {
87
- __errors: [error],
88
- };
89
- this.setState(old => (Object.assign(Object.assign({}, old), { extraErrors })));
45
+ nextErrors.path = { __errors: [error] };
90
46
  }
91
47
  else {
92
- this.setState(old => (Object.assign(Object.assign({}, old), { extraErrors: Object.assign(Object.assign({}, extraErrors), { path: { __errors: [] } }) })));
48
+ nextErrors.path = { __errors: [] };
93
49
  }
94
- if (this.props.formErrorSignal) {
95
- this.props.formErrorSignal.emit(!valid);
50
+ setExtraErrors(nextErrors);
51
+ formErrorSignal === null || formErrorSignal === void 0 ? void 0 : formErrorSignal.emit(!valid);
52
+ }, [model, sourceType, formErrorSignal]);
53
+ const schema = useMemo(() => {
54
+ const schemaCopy = deepCopy(schemaProp !== null && schemaProp !== void 0 ? schemaProp : {});
55
+ if (schemaCopy.properties) {
56
+ delete schemaCopy.properties.valid;
96
57
  }
58
+ return schemaCopy;
59
+ }, [schemaProp]);
60
+ const uiSchema = useMemo(() => {
61
+ var _a;
62
+ const builtUiSchema = {};
63
+ const dataCopy = deepCopy(formData);
64
+ processBaseSchema(dataCopy, schema, builtUiSchema, formContext, removeFormEntry);
65
+ const docManager = formSchemaRegistry === null || formSchemaRegistry === void 0 ? void 0 : formSchemaRegistry.getDocManager();
66
+ if (((_a = schema.properties) === null || _a === void 0 ? void 0 : _a.path) && docManager) {
67
+ builtUiSchema.path = {
68
+ 'ui:widget': FileSelectorWidget,
69
+ 'ui:options': {
70
+ docManager,
71
+ formOptions: props,
72
+ },
73
+ };
74
+ }
75
+ return builtUiSchema;
76
+ }, [schema, formData, formContext, formSchemaRegistry, props]);
77
+ const handleChange = useCallback((data) => {
78
+ handleChangeBase(data);
79
+ if (data.path !== undefined) {
80
+ validatePath(data.path);
81
+ }
82
+ }, [handleChangeBase, validatePath]);
83
+ const handleSubmit = useCallback((data) => {
84
+ var _a, _b;
85
+ if (((_b = (_a = extraErrors === null || extraErrors === void 0 ? void 0 : extraErrors.path) === null || _a === void 0 ? void 0 : _a.__errors) === null || _b === void 0 ? void 0 : _b.length) >= 1) {
86
+ showErrorMessage('Invalid file', extraErrors.path.__errors[0]);
87
+ return;
88
+ }
89
+ handleSubmitBase(data);
90
+ }, [extraErrors, handleSubmitBase]);
91
+ useEffect(() => {
92
+ var _a;
93
+ validatePath((_a = formData === null || formData === void 0 ? void 0 : formData.path) !== null && _a !== void 0 ? _a : '');
94
+ }, []);
95
+ if (!hasSchema) {
96
+ return null;
97
97
  }
98
+ return (React.createElement(SchemaForm, { schema: schema, formData: formData, onChange: handleChange, onSubmit: handleSubmit, formContext: formContextValue, filePath: filePath, uiSchema: uiSchema, extraErrors: extraErrors, formErrorSignal: formErrorSignal }));
98
99
  }
@@ -1,7 +1,11 @@
1
+ /**
2
+ * Base (default) source form and props.
3
+ * Used for RasterSource and any source type without a dedicated form.
4
+ */
1
5
  import { IDict, SourceType } from '@jupytergis/schema';
2
6
  import { Signal } from '@lumino/signaling';
3
- import { IChangeEvent } from '@rjsf/core';
4
- import { BaseForm, IBaseFormProps } from "../baseform";
7
+ import React from 'react';
8
+ import type { IBaseFormProps } from "../../../types";
5
9
  export interface ISourceFormProps extends IBaseFormProps {
6
10
  /**
7
11
  * The source type for this form.
@@ -16,9 +20,4 @@ export interface ISourceFormProps extends IBaseFormProps {
16
20
  */
17
21
  dialogOptions?: any;
18
22
  }
19
- export declare class SourcePropertiesForm extends BaseForm {
20
- props: ISourceFormProps;
21
- protected sourceFormChangedSignal: Signal<any, IDict<any>> | undefined;
22
- constructor(props: ISourceFormProps);
23
- protected onFormChange(e: IChangeEvent): void;
24
- }
23
+ export declare function SourcePropertiesForm(props: ISourceFormProps): React.ReactElement | null;
@@ -1,13 +1,30 @@
1
- import { BaseForm, } from "../baseform";
2
- export class SourcePropertiesForm extends BaseForm {
3
- constructor(props) {
4
- super(props);
5
- this.sourceFormChangedSignal = props.sourceFormChangedSignal;
6
- }
7
- onFormChange(e) {
8
- super.onFormChange(e);
9
- if (this.props.dialogOptions) {
10
- this.props.dialogOptions.sourceData = Object.assign({}, e.formData);
11
- }
1
+ import React, { useMemo } from 'react';
2
+ import { deepCopy } from "../../../tools";
3
+ import { SchemaForm } from '../SchemaForm';
4
+ import { processBaseSchema, removeFormEntry } from '../schemaUtils';
5
+ import { useSchemaFormState } from '../useSchemaFormState';
6
+ export function SourcePropertiesForm(props) {
7
+ const { schema: schemaProp, sourceData, syncData, model, filePath, formContext, dialogOptions, cancel, formErrorSignal, } = props;
8
+ const { formData, schema, formContextValue, hasSchema, handleChangeBase, handleSubmitBase, } = useSchemaFormState({
9
+ sourceData,
10
+ schemaProp,
11
+ model,
12
+ syncData,
13
+ cancel,
14
+ onAfterChange: dialogOptions
15
+ ? (data) => {
16
+ dialogOptions.sourceData = Object.assign({}, data);
17
+ }
18
+ : undefined,
19
+ });
20
+ const uiSchema = useMemo(() => {
21
+ const builtUiSchema = {};
22
+ const dataCopy = deepCopy(formData);
23
+ processBaseSchema(dataCopy, schema, builtUiSchema, formContext, removeFormEntry);
24
+ return builtUiSchema;
25
+ }, [schema, formData, formContext]);
26
+ if (!hasSchema) {
27
+ return null;
12
28
  }
29
+ return (React.createElement(SchemaForm, { schema: schema, formData: formData, onChange: handleChangeBase, onSubmit: handleSubmitBase, formContext: formContextValue, filePath: filePath, uiSchema: uiSchema, formErrorSignal: formErrorSignal }));
13
30
  }
@@ -1,7 +1,3 @@
1
- import { IDict } from '@jupytergis/schema';
2
- import { SourcePropertiesForm } from './sourceform';
3
- export declare class TileSourcePropertiesForm extends SourcePropertiesForm {
4
- private _urlParameters;
5
- protected processSchema(data: IDict<any> | undefined, schema: IDict, uiSchema: IDict): void;
6
- protected onFormBlur(id: string, value: any): void;
7
- }
1
+ import React from 'react';
2
+ import type { ISourceFormProps } from './sourceform';
3
+ export declare function TileSourcePropertiesForm(props: ISourceFormProps): React.ReactElement | null;
@@ -1,60 +1,70 @@
1
- import { SourcePropertiesForm } from './sourceform';
2
- export class TileSourcePropertiesForm extends SourcePropertiesForm {
3
- constructor() {
4
- super(...arguments);
5
- this._urlParameters = [];
1
+ import React, { useMemo } from 'react';
2
+ import { deepCopy } from "../../../tools";
3
+ import { SchemaForm } from '../SchemaForm';
4
+ import { processBaseSchema, removeFormEntry } from '../schemaUtils';
5
+ import { useSchemaFormState } from '../useSchemaFormState';
6
+ function getUrlParameters(url) {
7
+ if (!url || typeof url !== 'string') {
8
+ return [];
6
9
  }
7
- processSchema(data, schema, uiSchema) {
8
- super.processSchema(data, schema, uiSchema);
9
- if (!schema.properties || !data) {
10
- return;
10
+ const regex = /\{([^}]+)\}/g;
11
+ const matches = [];
12
+ let match;
13
+ while ((match = regex.exec(url)) !== null) {
14
+ if (['max_zoom', 'min_zoom', 'x', 'y', 'z'].includes(match[1])) {
15
+ continue;
11
16
  }
12
- // Grep all url-parameters from the url
13
- const regex = /\{([^}]+)\}/g;
14
- const matches = [];
15
- let match;
16
- while ((match = regex.exec(data.url)) !== null) {
17
- if (['max_zoom', 'min_zoom', 'x', 'y', 'z'].includes(match[1])) {
18
- continue;
17
+ matches.push(match[1]);
18
+ }
19
+ return matches;
20
+ }
21
+ export function TileSourcePropertiesForm(props) {
22
+ const { schema: schemaProp, sourceData, syncData, model, filePath, formContext, dialogOptions, cancel, formErrorSignal, } = props;
23
+ const { formData, schema, formContextValue, hasSchema, handleChangeBase, handleSubmitBase, } = useSchemaFormState({
24
+ sourceData,
25
+ schemaProp,
26
+ model,
27
+ syncData,
28
+ cancel,
29
+ onAfterChange: dialogOptions
30
+ ? (data) => {
31
+ dialogOptions.sourceData = Object.assign({}, data);
19
32
  }
20
- matches.push(match[1]);
21
- }
22
- this._urlParameters = matches;
23
- if (matches.length === 0) {
24
- this.removeFormEntry('urlParameters', data, schema, uiSchema);
25
- return;
26
- }
27
- // Dynamically inject url parameters schema based of the url
28
- const propertiesSchema = {};
29
- schema.properties.urlParameters = {
30
- type: 'object',
31
- required: this._urlParameters,
32
- properties: propertiesSchema,
33
- };
34
- for (const parameterName of this._urlParameters) {
35
- switch (parameterName) {
36
- // Special case for "time" where a date picker widget is nicer
37
- case 'time':
38
- propertiesSchema[parameterName] = {
39
- type: 'string',
40
- format: 'date',
41
- };
42
- break;
43
- default:
44
- propertiesSchema[parameterName] = {
45
- type: 'string',
46
- };
47
- break;
33
+ : undefined,
34
+ });
35
+ const uiSchema = useMemo(() => {
36
+ const builtUiSchema = {};
37
+ const dataCopy = deepCopy(formData);
38
+ processBaseSchema(dataCopy, schema, builtUiSchema, formContext, removeFormEntry);
39
+ if (schema.properties && (formData === null || formData === void 0 ? void 0 : formData.url)) {
40
+ const urlParams = getUrlParameters(formData.url);
41
+ if (urlParams.length === 0) {
42
+ removeFormEntry('urlParameters', dataCopy, schema, builtUiSchema);
43
+ }
44
+ else {
45
+ const propertiesSchema = {};
46
+ schema.properties.urlParameters = {
47
+ type: 'object',
48
+ required: urlParams,
49
+ properties: propertiesSchema,
50
+ };
51
+ for (const parameterName of urlParams) {
52
+ if (parameterName === 'time') {
53
+ propertiesSchema[parameterName] = {
54
+ type: 'string',
55
+ format: 'date',
56
+ };
57
+ }
58
+ else {
59
+ propertiesSchema[parameterName] = { type: 'string' };
60
+ }
61
+ }
48
62
  }
49
63
  }
64
+ return builtUiSchema;
65
+ }, [schema, formData, formContext]);
66
+ if (!hasSchema) {
67
+ return null;
50
68
  }
51
- onFormBlur(id, value) {
52
- super.onFormBlur(id, value);
53
- // Is there a better way to spot the url text entry?
54
- if (!id.endsWith('_url')) {
55
- return;
56
- }
57
- // Force a rerender on url change, as it probably changes the schema
58
- this.forceUpdate();
59
- }
69
+ return (React.createElement(SchemaForm, { schema: schema, formData: formData, onChange: handleChangeBase, onSubmit: handleSubmitBase, formContext: formContextValue, filePath: filePath, uiSchema: uiSchema, formErrorSignal: formErrorSignal }));
60
70
  }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Shared state for schema-based forms: form data synced from sourceData,
3
+ * copied schema, form context, and optional base change/submit handlers.
4
+ * Forms use handleChangeBase/handleSubmitBase directly or wrap them for
5
+ * validation and extra logic.
6
+ */
7
+ import { IJupyterGISModel } from '@jupytergis/schema';
8
+ import { RJSFSchema } from '@rjsf/utils';
9
+ import { type Dispatch, type SetStateAction } from 'react';
10
+ import { IDict } from "../../types";
11
+ export interface IUseSchemaFormStateProps {
12
+ /** External data to sync into the form (e.g. layer/source parameters). */
13
+ sourceData: IDict | undefined;
14
+ /** JSON Schema for the form (will be deep-copied). */
15
+ schemaProp: IDict | undefined;
16
+ /** Current JGIS model. */
17
+ model: IJupyterGISModel;
18
+ /** Called when form data changes (enables handleChangeBase). */
19
+ syncData?: (data: IDict) => void;
20
+ /** Called when form is submitted (enables handleSubmitBase). */
21
+ cancel?: () => void;
22
+ /** Optional side effect after change (e.g. update dialogOptions.layerData). */
23
+ onAfterChange?: (data: IDict) => void;
24
+ }
25
+ export interface IUseSchemaFormStateResult {
26
+ /** Current form data (controlled state). */
27
+ formData: IDict;
28
+ /** Set form data (e.g. from onChange). */
29
+ setFormData: Dispatch<SetStateAction<IDict>>;
30
+ /** Schema to pass to SchemaForm (deep copy of schemaProp). */
31
+ schema: RJSFSchema;
32
+ /** Form context value { model, formData } for SchemaForm. */
33
+ formContextValue: {
34
+ model: IJupyterGISModel;
35
+ formData: IDict;
36
+ };
37
+ /** Whether the form has a schema (false => form may render null). */
38
+ hasSchema: boolean;
39
+ /** Base change handler: setFormData, syncData, onAfterChange. Use or wrap. */
40
+ handleChangeBase: (data: IDict) => void;
41
+ /** Base submit handler: syncData(data), cancel?.(). Use or wrap. */
42
+ handleSubmitBase: (data: IDict) => void;
43
+ }
44
+ /**
45
+ * Manages form data state, schema copy, and form context for object forms.
46
+ * Callers supply uiSchema, handleChange, handleSubmit, and any validation.
47
+ */
48
+ export declare function useSchemaFormState(props: IUseSchemaFormStateProps): IUseSchemaFormStateResult;
@@ -0,0 +1,35 @@
1
+ import { useCallback, useEffect, useMemo, useState, } from 'react';
2
+ import { deepCopy } from "../../tools";
3
+ /**
4
+ * Manages form data state, schema copy, and form context for object forms.
5
+ * Callers supply uiSchema, handleChange, handleSubmit, and any validation.
6
+ */
7
+ export function useSchemaFormState(props) {
8
+ const { sourceData, schemaProp, model, syncData, cancel, onAfterChange } = props;
9
+ const [formData, setFormData] = useState(() => deepCopy(sourceData !== null && sourceData !== void 0 ? sourceData : {}));
10
+ useEffect(() => {
11
+ if (sourceData !== undefined) {
12
+ setFormData(deepCopy(sourceData !== null && sourceData !== void 0 ? sourceData : {}));
13
+ }
14
+ }, [sourceData]);
15
+ const schema = useMemo(() => deepCopy(schemaProp !== null && schemaProp !== void 0 ? schemaProp : {}), [schemaProp]);
16
+ const formContextValue = useMemo(() => ({ model, formData }), [model, formData]);
17
+ const handleChangeBase = useCallback((data) => {
18
+ setFormData(data);
19
+ syncData === null || syncData === void 0 ? void 0 : syncData(data);
20
+ onAfterChange === null || onAfterChange === void 0 ? void 0 : onAfterChange(data);
21
+ }, [syncData, onAfterChange]);
22
+ const handleSubmitBase = useCallback((data) => {
23
+ syncData === null || syncData === void 0 ? void 0 : syncData(data);
24
+ cancel === null || cancel === void 0 ? void 0 : cancel();
25
+ }, [syncData, cancel]);
26
+ return {
27
+ formData,
28
+ setFormData,
29
+ schema,
30
+ formContextValue,
31
+ hasSchema: schemaProp !== null,
32
+ handleChangeBase,
33
+ handleSubmitBase,
34
+ };
35
+ }
package/lib/index.d.ts CHANGED
@@ -2,7 +2,6 @@ export * from './annotations';
2
2
  export * from './commands/index';
3
3
  export * from './constants';
4
4
  export * from './dialogs/layerCreationFormDialog';
5
- export * from './formbuilder/objectform/baseform';
6
5
  export * from './icons';
7
6
  export * from './mainview';
8
7
  export * from './menus';
package/lib/index.js CHANGED
@@ -2,7 +2,6 @@ export * from './annotations';
2
2
  export * from './commands/index';
3
3
  export * from './constants';
4
4
  export * from './dialogs/layerCreationFormDialog';
5
- export * from './formbuilder/objectform/baseform';
6
5
  export * from './icons';
7
6
  export * from './mainview';
8
7
  export * from './menus';
@@ -1,4 +1,14 @@
1
1
  [
2
+ {
3
+ "command": "jupytergis:storyPrev",
4
+ "keys": ["ArrowLeft"],
5
+ "selector": ".data-jgis-keybinding"
6
+ },
7
+ {
8
+ "command": "jupytergis:storyNext",
9
+ "keys": ["ArrowRight"],
10
+ "selector": ".data-jgis-keybinding"
11
+ },
2
12
  {
3
13
  "command": "jupytergis:undo",
4
14
  "keys": ["Accel Z"],
@@ -19,16 +29,6 @@
19
29
  "keys": ["Accel I"],
20
30
  "selector": ".data-jgis-keybinding"
21
31
  },
22
- {
23
- "command": "jupytergis:removeSource",
24
- "keys": ["Delete"],
25
- "selector": ".data-jgis-keybinding .jp-gis-source.jp-gis-sourceUnused"
26
- },
27
- {
28
- "command": "jupytergis:renameSource",
29
- "keys": ["F2"],
30
- "selector": ".data-jgis-keybinding .jp-gis-source"
31
- },
32
32
  {
33
33
  "command": "jupytergis:removeSelected",
34
34
  "keys": ["Delete"],
@@ -5,13 +5,13 @@ import { Layer } from 'ol/layer';
5
5
  import * as React from 'react';
6
6
  import { ClientPointer } from './CollaboratorPointers';
7
7
  import { MainViewModel } from './mainviewmodel';
8
- interface IProps {
8
+ interface IMainViewProps {
9
9
  viewModel: MainViewModel;
10
10
  state?: IStateDB;
11
11
  formSchemaRegistry?: IJGISFormSchemaRegistry;
12
12
  annotationModel?: IAnnotationModel;
13
13
  /** True when viewport matches (max-width: 768px). Injected by MainViewWithMediaQuery. */
14
- isMobile?: boolean;
14
+ isMobile: boolean;
15
15
  }
16
16
  interface IStates {
17
17
  id: string;
@@ -35,11 +35,12 @@ interface IStates {
35
35
  filterStates: IDict<IJGISFilterItem | undefined>;
36
36
  jgisSettings: IJupyterGISSettings;
37
37
  isSpectaPresentation: boolean;
38
+ initialLayersReady: boolean;
38
39
  }
39
- export declare class MainView extends React.Component<IProps, IStates> {
40
- constructor(props: IProps);
40
+ export declare class MainView extends React.Component<IMainViewProps, IStates> {
41
+ constructor(props: IMainViewProps);
41
42
  componentDidMount(): Promise<void>;
42
- componentDidUpdate(prevProps: IProps, prevState: IStates): void;
43
+ componentDidUpdate(prevProps: IMainViewProps, prevState: IStates): void;
43
44
  componentWillUnmount(): void;
44
45
  generateMap(center: number[], zoom: number): Promise<void>;
45
46
  updateCenter: () => void;
@@ -182,7 +183,6 @@ export declare class MainView extends React.Component<IProps, IStates> {
182
183
  private _removeAllInteractions;
183
184
  private _setupStoryScrollListener;
184
185
  private _cleanupStoryScrollListener;
185
- private _updateSpectaPresentationColors;
186
186
  private _onSharedMetadataChanged;
187
187
  private _computeAnnotationPosition;
188
188
  private _updateAnnotation;
@@ -198,6 +198,8 @@ export declare class MainView extends React.Component<IProps, IStates> {
198
198
  private _handleGeolocationChanged;
199
199
  private _handleThemeChange;
200
200
  private _handleWindowResize;
201
+ private _handleSpectaTouchStart;
202
+ private _handleSpectaTouchEnd;
201
203
  render(): JSX.Element;
202
204
  private _clickCoords;
203
205
  private _commands;
@@ -227,7 +229,9 @@ export declare class MainView extends React.Component<IProps, IStates> {
227
229
  private _storyScrollHandler;
228
230
  private _clearStoryScrollGuard;
229
231
  private _pendingStoryScrollRafId;
232
+ private _initialLayersCount;
233
+ private _spectaTouchStartX;
230
234
  }
231
235
  /** Thin wrapper that injects isMobile from useMediaQuery so MainView can use it in JSX. */
232
- declare function MainViewWithMediaQuery(props: IProps): React.JSX.Element;
236
+ declare function MainViewWithMediaQuery(props: Omit<IMainViewProps, 'isMobile'>): React.JSX.Element;
233
237
  export { MainViewWithMediaQuery };