@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,62 +1,74 @@
1
- import { BaseForm, } from "../baseform"; // Ensure BaseForm imports states
2
- import { loadFile } from "../../../tools";
3
- export class DissolveForm extends BaseForm {
4
- constructor(options) {
5
- var _a;
6
- super(options);
7
- this.features = [];
8
- this.model = options.model;
9
- // Ensure initial state matches IBaseFormStates
10
- this.state = {
11
- schema: (_a = options.schema) !== null && _a !== void 0 ? _a : {}, // Ensure schema is never undefined
12
- };
13
- this.onFormChange = this.handleFormChange.bind(this);
14
- this.fetchFieldNames(options.sourceData.inputLayer);
1
+ import React, { useEffect, useMemo, useRef, useState } from 'react';
2
+ import { deepCopy, loadFile } from "../../../tools";
3
+ import { SchemaForm } from '../SchemaForm';
4
+ import { processBaseSchema, removeFormEntry } from '../schemaUtils';
5
+ import { useSchemaFormState } from '../useSchemaFormState';
6
+ async function fetchFieldNames(model, layerId) {
7
+ var _a, _b, _c;
8
+ if (!layerId) {
9
+ return [];
15
10
  }
16
- async fetchFieldNames(layerId) {
17
- var _a, _b;
18
- const layer = this.model.getLayer(layerId);
19
- if (!((_a = layer === null || layer === void 0 ? void 0 : layer.parameters) === null || _a === void 0 ? void 0 : _a.source)) {
20
- return;
21
- }
22
- const source = this.model.getSource(layer.parameters.source);
23
- if (!source || source.type !== 'GeoJSONSource') {
24
- return;
25
- }
26
- const sourceData = source.parameters;
27
- if (!(sourceData === null || sourceData === void 0 ? void 0 : sourceData.path)) {
11
+ const layer = model.getLayer(layerId);
12
+ if (!((_a = layer === null || layer === void 0 ? void 0 : layer.parameters) === null || _a === void 0 ? void 0 : _a.source)) {
13
+ return [];
14
+ }
15
+ const source = model.getSource(layer.parameters.source);
16
+ if (!source || source.type !== 'GeoJSONSource') {
17
+ return [];
18
+ }
19
+ const sourceData = source.parameters;
20
+ if (!(sourceData === null || sourceData === void 0 ? void 0 : sourceData.path)) {
21
+ return [];
22
+ }
23
+ const jsonData = await loadFile({
24
+ filepath: sourceData.path,
25
+ type: 'GeoJSONSource',
26
+ model,
27
+ });
28
+ if (!((_b = jsonData === null || jsonData === void 0 ? void 0 : jsonData.features) === null || _b === void 0 ? void 0 : _b.length)) {
29
+ return [];
30
+ }
31
+ return Object.keys((_c = jsonData.features[0].properties) !== null && _c !== void 0 ? _c : {});
32
+ }
33
+ export function DissolveForm(props) {
34
+ const { schema: schemaProp, sourceData, syncData, model, filePath, formContext, ok, } = props;
35
+ const { formData, formContextValue, hasSchema, handleChangeBase, handleSubmitBase, } = useSchemaFormState({ sourceData, schemaProp, model, syncData });
36
+ const [features, setFeatures] = useState([]);
37
+ const submitButtonRef = useRef(null);
38
+ useEffect(() => {
39
+ fetchFieldNames(model, formData === null || formData === void 0 ? void 0 : formData.inputLayer)
40
+ .then(setFeatures)
41
+ .catch(() => setFeatures([]));
42
+ }, [model, formData === null || formData === void 0 ? void 0 : formData.inputLayer]);
43
+ useEffect(() => {
44
+ if (!ok) {
28
45
  return;
29
46
  }
30
- try {
31
- const jsonData = await loadFile({
32
- filepath: sourceData.path,
33
- type: 'GeoJSONSource',
34
- model: this.model,
35
- });
36
- if (!((_b = jsonData === null || jsonData === void 0 ? void 0 : jsonData.features) === null || _b === void 0 ? void 0 : _b.length)) {
37
- return;
38
- }
39
- this.features = Object.keys(jsonData.features[0].properties);
40
- this.updateSchema();
41
- }
42
- catch (error) {
43
- console.error('Error loading GeoJSON:', error);
44
- }
45
- }
46
- handleFormChange(e) {
47
- super.onFormChange(e);
48
- if (e.formData.inputLayer) {
49
- this.fetchFieldNames(e.formData.inputLayer);
47
+ const handler = () => {
48
+ var _a;
49
+ (_a = submitButtonRef.current) === null || _a === void 0 ? void 0 : _a.click();
50
+ };
51
+ ok.connect(handler);
52
+ return () => {
53
+ ok.disconnect(handler);
54
+ };
55
+ }, [ok]);
56
+ const schema = useMemo(() => {
57
+ const schemaCopy = deepCopy(schemaProp !== null && schemaProp !== void 0 ? schemaProp : {});
58
+ if (schemaCopy.properties &&
59
+ schemaCopy.properties.dissolveField) {
60
+ schemaCopy.properties.dissolveField = Object.assign(Object.assign({}, schemaCopy.properties.dissolveField), { enum: [...features] });
50
61
  }
62
+ return schemaCopy;
63
+ }, [schemaProp, features]);
64
+ const uiSchema = useMemo(() => {
65
+ const builtUiSchema = {};
66
+ const dataCopy = deepCopy(formData);
67
+ processBaseSchema(dataCopy, schema, builtUiSchema, formContext, removeFormEntry);
68
+ return builtUiSchema;
69
+ }, [schema, formData, formContext]);
70
+ if (!hasSchema) {
71
+ return null;
51
72
  }
52
- updateSchema() {
53
- this.setState((prevState) => {
54
- var _a, _b, _c;
55
- return ({
56
- schema: Object.assign(Object.assign({}, prevState.schema), { properties: Object.assign(Object.assign({}, (_a = prevState.schema) === null || _a === void 0 ? void 0 : _a.properties), { dissolveField: Object.assign(Object.assign({}, (_c = (_b = prevState.schema) === null || _b === void 0 ? void 0 : _b.properties) === null || _c === void 0 ? void 0 : _c.dissolveField), { enum: [...this.features] }) }) }),
57
- });
58
- }, () => {
59
- this.forceUpdate();
60
- });
61
- }
73
+ return (React.createElement(SchemaForm, { schema: schema, formData: formData, onChange: handleChangeBase, onSubmit: handleSubmitBase, formContext: formContextValue, filePath: filePath, uiSchema: uiSchema, submitButtonRef: submitButtonRef }));
62
74
  }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Used by ProcessingFormDialog when processingType is not 'Dissolve'.
3
+ */
4
+ import { Dialog } from '@jupyterlab/apputils';
5
+ import { Signal } from '@lumino/signaling';
6
+ import React from 'react';
7
+ import type { IBaseFormProps } from "../../types";
8
+ export interface IProcessingFormWrapperProps extends IBaseFormProps {
9
+ /** Signal emitted by the dialog when OK is clicked; form submits when this fires. */
10
+ ok?: Signal<Dialog<any>, number>;
11
+ }
12
+ export declare function DefaultProcessingForm(props: IProcessingFormWrapperProps): React.ReactElement | null;
@@ -0,0 +1,33 @@
1
+ import React, { useEffect, useMemo, useRef } 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 DefaultProcessingForm(props) {
7
+ const { schema: schemaProp, sourceData, syncData, model, filePath, formContext, ok, } = props;
8
+ const { formData, schema, formContextValue, hasSchema, handleChangeBase, handleSubmitBase, } = useSchemaFormState({ sourceData, schemaProp, model, syncData });
9
+ const submitButtonRef = useRef(null);
10
+ useEffect(() => {
11
+ if (!ok) {
12
+ return;
13
+ }
14
+ const handler = () => {
15
+ var _a;
16
+ (_a = submitButtonRef.current) === null || _a === void 0 ? void 0 : _a.click();
17
+ };
18
+ ok.connect(handler);
19
+ return () => {
20
+ ok.disconnect(handler);
21
+ };
22
+ }, [ok]);
23
+ const uiSchema = useMemo(() => {
24
+ const builtUiSchema = {};
25
+ const dataCopy = deepCopy(formData);
26
+ processBaseSchema(dataCopy, schema, builtUiSchema, formContext, removeFormEntry);
27
+ return builtUiSchema;
28
+ }, [schema, formData, formContext]);
29
+ if (!hasSchema) {
30
+ return null;
31
+ }
32
+ return (React.createElement(SchemaForm, { schema: schema, formData: formData, onChange: handleChangeBase, onSubmit: handleSubmitBase, formContext: formContextValue, filePath: filePath, uiSchema: uiSchema, submitButtonRef: submitButtonRef }));
33
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Helpers for adapting JSON Schema and uiSchema before passing them to SchemaForm.
3
+ * Used by layer, source, and other object forms to hide fields, set array options,
4
+ * and apply read-only behaviour based on form context (create vs update).
5
+ */
6
+ import { RJSFSchema, UiSchema } from '@rjsf/utils';
7
+ import { IDict } from "../../types";
8
+ /**
9
+ * Remove a property from form data, schema, and uiSchema.
10
+ */
11
+ export declare function removeFormEntry(entry: string, data: IDict | undefined, schema: RJSFSchema, uiSchema: UiSchema): void;
12
+ /**
13
+ * Apply base processSchema: array options, opacity field, readOnly handling.
14
+ * Mutates schema, uiSchema, and optionally data (for readOnly removal in update).
15
+ */
16
+ export declare function processBaseSchema(data: IDict | undefined, schema: RJSFSchema, uiSchema: UiSchema, formContext: 'create' | 'update', removeEntry: typeof removeFormEntry): void;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Remove a property from form data, schema, and uiSchema.
3
+ */
4
+ export function removeFormEntry(entry, data, schema, uiSchema) {
5
+ if (data) {
6
+ delete data[entry];
7
+ }
8
+ if (schema.properties) {
9
+ delete schema.properties[entry];
10
+ }
11
+ delete uiSchema[entry];
12
+ if (schema.required && schema.required.includes(entry)) {
13
+ schema.required.splice(schema.required.indexOf(entry), 1);
14
+ }
15
+ }
16
+ /**
17
+ * Apply base processSchema: array options, opacity field, readOnly handling.
18
+ * Mutates schema, uiSchema, and optionally data (for readOnly removal in update).
19
+ */
20
+ export function processBaseSchema(data, schema, uiSchema, formContext, removeEntry) {
21
+ var _a, _b, _c, _d;
22
+ if (!schema['properties']) {
23
+ return;
24
+ }
25
+ const props = schema.properties;
26
+ for (const [k, v] of Object.entries(props)) {
27
+ uiSchema[k] = (_a = uiSchema[k]) !== null && _a !== void 0 ? _a : {};
28
+ if (v && typeof v === 'object' && v['type'] === 'array') {
29
+ uiSchema[k]['ui:options'] = Object.assign({ orderable: false, removable: false, addable: false }, ((_b = uiSchema[k]['ui:options']) !== null && _b !== void 0 ? _b : {}));
30
+ const items = v['items'];
31
+ if (items &&
32
+ typeof items === 'object' &&
33
+ items['type'] === 'array') {
34
+ uiSchema[k].items = Object.assign({ 'ui:options': {
35
+ orderable: false,
36
+ removable: false,
37
+ addable: false,
38
+ } }, ((_c = uiSchema[k].items) !== null && _c !== void 0 ? _c : {}));
39
+ }
40
+ }
41
+ if (v &&
42
+ typeof v === 'object' &&
43
+ v['type'] === 'object' &&
44
+ v.properties) {
45
+ processBaseSchema(data === null || data === void 0 ? void 0 : data[k], v, (_d = uiSchema[k]) !== null && _d !== void 0 ? _d : {}, formContext, removeEntry);
46
+ }
47
+ if (k === 'opacity') {
48
+ uiSchema[k]['ui:field'] = 'opacity';
49
+ }
50
+ if (v && typeof v === 'object' && v['readOnly']) {
51
+ if (formContext === 'create') {
52
+ delete v['readOnly'];
53
+ }
54
+ if (formContext === 'update') {
55
+ removeEntry(k, data, schema, uiSchema);
56
+ }
57
+ }
58
+ }
59
+ }
@@ -1,19 +1,3 @@
1
- import { IDict } from '@jupytergis/schema';
2
- import { ISubmitEvent } from '@rjsf/core';
3
- import { PathBasedSourcePropertiesForm } from './pathbasedsource';
4
- import { ISourceFormProps } from './sourceform';
5
- /**
6
- * The form to modify a GeoJSON source.
7
- */
8
- export declare class GeoJSONSourcePropertiesForm extends PathBasedSourcePropertiesForm {
9
- private _validate;
10
- constructor(props: ISourceFormProps);
11
- protected processSchema(data: IDict<any> | undefined, schema: IDict, uiSchema: IDict): void;
12
- /**
13
- * Validate the path, to avoid invalid path or invalid GeoJSON.
14
- *
15
- * @param path - the path to validate.
16
- */
17
- protected _validatePath(path: string | undefined): Promise<void>;
18
- protected onFormSubmit(e: ISubmitEvent<any>): void;
19
- }
1
+ import React from 'react';
2
+ import type { ISourceFormProps } from './sourceform';
3
+ export declare function GeoJSONSourcePropertiesForm(props: ISourceFormProps): React.ReactElement | null;
@@ -1,81 +1,122 @@
1
1
  import * as geojson from '@jupytergis/schema/src/schema/geojson.json';
2
2
  import { showErrorMessage } from '@jupyterlab/apputils';
3
3
  import { Ajv } from 'ajv';
4
- import { loadFile } from "../../../tools";
5
- import { PathBasedSourcePropertiesForm } from './pathbasedsource';
6
- /**
7
- * The form to modify a GeoJSON source.
8
- */
9
- export class GeoJSONSourcePropertiesForm extends PathBasedSourcePropertiesForm {
10
- constructor(props) {
11
- var _a, _b;
12
- super(props);
4
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
5
+ import { deepCopy, loadFile } from "../../../tools";
6
+ import { SchemaForm } from '../SchemaForm';
7
+ import { FileSelectorWidget } from '../fileselectorwidget';
8
+ import { processBaseSchema, removeFormEntry } from '../schemaUtils';
9
+ import { useSchemaFormState } from '../useSchemaFormState';
10
+ export function GeoJSONSourcePropertiesForm(props) {
11
+ const { schema: schemaProp, sourceData, syncData, model, filePath, formContext, dialogOptions, formErrorSignal, formSchemaRegistry, cancel, } = props;
12
+ const { formData, schema, formContextValue, hasSchema, handleChangeBase, handleSubmitBase, } = useSchemaFormState({
13
+ sourceData,
14
+ schemaProp,
15
+ model,
16
+ syncData,
17
+ cancel,
18
+ onAfterChange: dialogOptions
19
+ ? (data) => {
20
+ dialogOptions.sourceData = Object.assign({}, data);
21
+ }
22
+ : undefined,
23
+ });
24
+ const [extraErrors, setExtraErrors] = useState({});
25
+ const validateGeoJSON = useMemo(() => {
13
26
  const ajv = new Ajv();
14
- this._validate = ajv.compile(geojson);
15
- this._validatePath((_b = (_a = props.sourceData) === null || _a === void 0 ? void 0 : _a.path) !== null && _b !== void 0 ? _b : '');
16
- }
17
- processSchema(data, schema, uiSchema) {
18
- this.removeFormEntry('data', data, schema, uiSchema);
19
- if (this.props.formContext === 'create') {
20
- schema.properties.path.description =
21
- 'The local path to a GeoJSON file. (If no path/url is provided, an empty GeoJSON is created.)';
22
- }
23
- super.processSchema(data, schema, uiSchema);
24
- }
25
- /**
26
- * Validate the path, to avoid invalid path or invalid GeoJSON.
27
- *
28
- * @param path - the path to validate.
29
- */
30
- async _validatePath(path) {
27
+ return ajv.compile(geojson);
28
+ }, []);
29
+ const validatePath = useCallback(async (path) => {
31
30
  var _a;
32
- const extraErrors = this.state.extraErrors;
33
- let error = '';
31
+ const nextErrors = {};
34
32
  let valid = true;
35
- if (path) {
33
+ let error = '';
34
+ if (!path || !path.trim()) {
35
+ valid = false;
36
+ error = 'Path is required';
37
+ }
38
+ else {
36
39
  try {
37
40
  const geoJSONData = await loadFile({
38
41
  filepath: path,
39
- type: this.props.sourceType,
40
- model: this.props.model,
42
+ type: 'GeoJSONSource',
43
+ model,
41
44
  });
42
- valid = this._validate(geoJSONData);
45
+ valid = validateGeoJSON(geoJSONData);
43
46
  if (!valid) {
44
47
  error = `"${path}" is not a valid GeoJSON file`;
45
48
  }
46
49
  }
47
50
  catch (e) {
51
+ valid = false;
48
52
  error = `"${path}" is not a valid GeoJSON file: ${e}`;
49
53
  }
50
54
  }
51
55
  if (!valid) {
52
- extraErrors.path = {
53
- __errors: [error],
54
- };
55
- (_a = this._validate.errors) === null || _a === void 0 ? void 0 : _a.reverse().forEach(error => {
56
- extraErrors.path.__errors.push(error.message);
57
- });
58
- this.setState(old => (Object.assign(Object.assign({}, old), { extraErrors })));
56
+ nextErrors.path = { __errors: [error] };
57
+ if ((_a = validateGeoJSON.errors) === null || _a === void 0 ? void 0 : _a.length) {
58
+ validateGeoJSON.errors.reverse().forEach(err => {
59
+ var _a;
60
+ nextErrors.path.__errors.push((_a = err.message) !== null && _a !== void 0 ? _a : '');
61
+ });
62
+ }
59
63
  }
60
64
  else {
61
- this.setState(old => (Object.assign(Object.assign({}, old), { extraErrors: Object.assign(Object.assign({}, extraErrors), { path: { __errors: [] } }) })));
65
+ nextErrors.path = { __errors: [] };
62
66
  }
63
- if (this.props.formErrorSignal) {
64
- this.props.formErrorSignal.emit(!valid);
67
+ setExtraErrors(nextErrors);
68
+ formErrorSignal === null || formErrorSignal === void 0 ? void 0 : formErrorSignal.emit(!valid);
69
+ }, [model, validateGeoJSON, formErrorSignal]);
70
+ const uiSchema = useMemo(() => {
71
+ var _a, _b;
72
+ const builtUiSchema = {};
73
+ const dataCopy = deepCopy(formData);
74
+ removeFormEntry('data', dataCopy, schema, builtUiSchema);
75
+ if (formContext === 'create' && ((_a = schema.properties) === null || _a === void 0 ? void 0 : _a.path)) {
76
+ schema.properties.path.description =
77
+ 'The local path to a GeoJSON file. (If no path/url is provided, an empty GeoJSON is created.)';
65
78
  }
66
- }
67
- onFormSubmit(e) {
68
- var _a, _b, _c;
69
- 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) {
70
- showErrorMessage('Invalid file', this.state.extraErrors.path.__errors[0]);
79
+ processBaseSchema(dataCopy, schema, builtUiSchema, formContext, removeFormEntry);
80
+ const docManager = formSchemaRegistry === null || formSchemaRegistry === void 0 ? void 0 : formSchemaRegistry.getDocManager();
81
+ if (((_b = schema.properties) === null || _b === void 0 ? void 0 : _b.path) && docManager) {
82
+ builtUiSchema.path = {
83
+ 'ui:widget': FileSelectorWidget,
84
+ 'ui:options': {
85
+ docManager,
86
+ formOptions: props,
87
+ },
88
+ };
89
+ }
90
+ return builtUiSchema;
91
+ }, [schema, formData, formContext, formSchemaRegistry, props]);
92
+ const handleChange = useCallback((data) => {
93
+ handleChangeBase(data);
94
+ if (data.path !== undefined) {
95
+ validatePath(data.path);
96
+ }
97
+ }, [handleChangeBase, validatePath]);
98
+ const handleSubmit = useCallback((data) => {
99
+ var _a, _b;
100
+ 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) {
101
+ showErrorMessage('Invalid file', extraErrors.path.__errors[0]);
71
102
  return;
72
103
  }
73
- if (!e.formData.path) {
74
- e.formData.data = {
75
- type: 'FeatureCollection',
76
- features: [],
77
- };
104
+ let submitted = Object.assign({}, data);
105
+ if (!submitted.path) {
106
+ submitted = Object.assign(Object.assign({}, submitted), { data: {
107
+ type: 'FeatureCollection',
108
+ features: [],
109
+ } });
110
+ }
111
+ handleSubmitBase(submitted);
112
+ }, [extraErrors, handleSubmitBase]);
113
+ useEffect(() => {
114
+ if (formData === null || formData === void 0 ? void 0 : formData.path) {
115
+ validatePath(formData === null || formData === void 0 ? void 0 : formData.path);
78
116
  }
79
- super.onFormSubmit(e);
117
+ }, []);
118
+ if (!hasSchema) {
119
+ return null;
80
120
  }
121
+ return (React.createElement(SchemaForm, { schema: schema, formData: formData, onChange: handleChange, onSubmit: handleSubmit, formContext: formContextValue, filePath: filePath, uiSchema: uiSchema, extraErrors: extraErrors, formErrorSignal: formErrorSignal }));
81
122
  }
@@ -1,20 +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 GeoTiff source.
6
- */
7
- export declare class GeoTiffSourcePropertiesForm extends SourcePropertiesForm {
8
- private _isSubmitted;
9
- constructor(props: ISourceFormProps);
10
- protected processSchema(data: IDict<any> | undefined, schema: IDict, uiSchema: IDict): void;
11
- protected onFormChange(e: IChangeEvent): void;
12
- protected onFormBlur(id: string, value: any): void;
13
- protected onFormSubmit(e: ISubmitEvent<any>): Promise<void>;
14
- /**
15
- * Validate the URLs, ensuring that there is at least one object with required fields.
16
- *
17
- * @param urls - the URLs array to validate.
18
- */
19
- private _validateUrls;
20
- }
1
+ import React from 'react';
2
+ import type { ISourceFormProps } from './sourceform';
3
+ export declare function GeoTiffSourcePropertiesForm(props: ISourceFormProps): React.ReactElement | null;
@@ -1,80 +1,36 @@
1
1
  import { showErrorMessage } from '@jupyterlab/apputils';
2
- import { FileSelectorWidget } from "../fileselectorwidget";
3
- import { getMimeType } from "../../../tools";
4
- import { SourcePropertiesForm } from './sourceform';
5
- /**
6
- * The form to modify a GeoTiff source.
7
- */
8
- export class GeoTiffSourcePropertiesForm extends SourcePropertiesForm {
9
- constructor(props) {
10
- var _a, _b;
11
- super(props);
12
- this._isSubmitted = false;
13
- this._validateUrls((_b = (_a = props.sourceData) === null || _a === void 0 ? void 0 : _a.urls) !== null && _b !== void 0 ? _b : []);
14
- }
15
- processSchema(data, schema, uiSchema) {
16
- var _a;
17
- super.processSchema(data, schema, uiSchema);
18
- if (!schema.properties || !data) {
19
- return;
20
- }
21
- // Customize the widget for urls
22
- if (schema.properties && schema.properties.urls) {
23
- const docManager = (_a = this.props.formChangedSignal) === null || _a === void 0 ? void 0 : _a.sender.props.formSchemaRegistry.getDocManager();
24
- uiSchema.urls = Object.assign(Object.assign({}, uiSchema.urls), { items: Object.assign(Object.assign({}, uiSchema.urls.items), { url: {
25
- 'ui:widget': FileSelectorWidget,
26
- 'ui:options': {
27
- docManager,
28
- formOptions: this.props,
29
- },
30
- } }) });
31
- }
32
- // This is not user-editable
33
- delete schema.properties.valid;
34
- }
35
- onFormChange(e) {
36
- var _a;
37
- super.onFormChange(e);
38
- if ((_a = e.formData) === null || _a === void 0 ? void 0 : _a.urls) {
39
- this._validateUrls(e.formData.urls);
40
- }
41
- }
42
- onFormBlur(id, value) {
43
- // Is there a better way to spot the url text entry?
44
- if (!id.endsWith('_urls')) {
45
- return;
46
- }
47
- this._validateUrls(value);
48
- }
49
- async onFormSubmit(e) {
50
- this._isSubmitted = true;
51
- // validate urls.url only when submitting for better performance
52
- const { valid, errors } = await this._validateUrls(e.formData.urls);
53
- if (!valid) {
54
- if (errors.length > 0) {
55
- showErrorMessage('Invalid URLs', errors[0]);
2
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
3
+ import { deepCopy, getMimeType } 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 GeoTiffSourcePropertiesForm(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);
56
19
  }
57
- return;
58
- }
59
- super.onFormSubmit(e);
60
- }
61
- /**
62
- * Validate the URLs, ensuring that there is at least one object with required fields.
63
- *
64
- * @param urls - the URLs array to validate.
65
- */
66
- async _validateUrls(urls) {
67
- const extraErrors = this.state.extraErrors;
20
+ : undefined,
21
+ });
22
+ const [extraErrors, setExtraErrors] = useState({});
23
+ const validateUrls = useCallback(async (urls, isSubmit) => {
68
24
  const errors = [];
69
25
  let valid = true;
70
26
  if (urls && urls.length > 0) {
71
27
  for (let i = 0; i < urls.length; i++) {
72
28
  const { url, min, max } = urls[i];
73
- if (this._isSubmitted) {
29
+ if (isSubmit) {
74
30
  const mimeType = getMimeType(url);
75
31
  if (!mimeType || !mimeType.startsWith('image/tiff')) {
76
32
  valid = false;
77
- errors.push(`"${url}" is not a valid ${this.props.sourceType} file.`);
33
+ errors.push(`"${url}" is not a valid ${sourceType} file.`);
78
34
  }
79
35
  }
80
36
  else {
@@ -103,15 +59,58 @@ export class GeoTiffSourcePropertiesForm extends SourcePropertiesForm {
103
59
  errors.push('At least one valid URL with min/max values is required.');
104
60
  valid = false;
105
61
  }
106
- if (!valid) {
107
- this.setState(old => (Object.assign(Object.assign({}, old), { extraErrors: Object.assign(Object.assign({}, extraErrors), { urls: { __errors: errors } }) })));
62
+ const nextErrors = valid
63
+ ? { urls: { __errors: [] } }
64
+ : { urls: { __errors: errors } };
65
+ setExtraErrors(nextErrors);
66
+ formErrorSignal === null || formErrorSignal === void 0 ? void 0 : formErrorSignal.emit(!valid);
67
+ return { valid, errors };
68
+ }, [sourceType, formErrorSignal]);
69
+ const schema = useMemo(() => {
70
+ const schemaCopy = deepCopy(schemaProp !== null && schemaProp !== void 0 ? schemaProp : {});
71
+ if (schemaCopy.properties) {
72
+ delete schemaCopy.properties.valid;
108
73
  }
109
- else {
110
- this.setState(old => (Object.assign(Object.assign({}, old), { extraErrors: Object.assign(Object.assign({}, extraErrors), { urls: { __errors: [] } }) })));
74
+ return schemaCopy;
75
+ }, [schemaProp]);
76
+ const uiSchema = useMemo(() => {
77
+ var _a, _b;
78
+ const builtUiSchema = {};
79
+ const dataCopy = deepCopy(formData);
80
+ processBaseSchema(dataCopy, schema, builtUiSchema, formContext, removeFormEntry);
81
+ const docManager = formSchemaRegistry === null || formSchemaRegistry === void 0 ? void 0 : formSchemaRegistry.getDocManager();
82
+ if (((_a = schema.properties) === null || _a === void 0 ? void 0 : _a.urls) && docManager) {
83
+ builtUiSchema.urls = Object.assign(Object.assign({}, builtUiSchema.urls), { items: Object.assign(Object.assign({}, (_b = builtUiSchema.urls) === null || _b === void 0 ? void 0 : _b.items), { url: {
84
+ 'ui:widget': FileSelectorWidget,
85
+ 'ui:options': {
86
+ docManager,
87
+ formOptions: props,
88
+ },
89
+ } }) });
111
90
  }
112
- if (this.props.formErrorSignal) {
113
- this.props.formErrorSignal.emit(!valid);
91
+ return builtUiSchema;
92
+ }, [schema, formData, formContext, formSchemaRegistry, props]);
93
+ const handleChange = useCallback((data) => {
94
+ handleChangeBase(data);
95
+ if (data.urls) {
96
+ validateUrls(data.urls, false);
114
97
  }
115
- return { valid, errors };
98
+ }, [handleChangeBase, validateUrls]);
99
+ const handleSubmit = useCallback(async (data) => {
100
+ const { valid, errors } = await validateUrls(data.urls, true);
101
+ if (!valid) {
102
+ if (errors.length > 0) {
103
+ showErrorMessage('Invalid URLs', errors[0]);
104
+ }
105
+ return;
106
+ }
107
+ handleSubmitBase(data);
108
+ }, [validateUrls, handleSubmitBase]);
109
+ useEffect(() => {
110
+ validateUrls(formData === null || formData === void 0 ? void 0 : formData.urls, false);
111
+ }, []);
112
+ if (!hasSchema) {
113
+ return null;
116
114
  }
115
+ return (React.createElement(SchemaForm, { schema: schema, formData: formData, onChange: handleChange, onSubmit: handleSubmit, formContext: formContextValue, filePath: filePath, uiSchema: uiSchema, extraErrors: extraErrors, formErrorSignal: formErrorSignal }));
117
116
  }